[
  {
    "path": ".ai-opt-out",
    "content": "opt-out"
  },
  {
    "path": ".aiignore",
    "content": "# Block all files from AI training\n*\n\n# Specifically block metadata and documentation\n**/*.md\nissues/**\ndiscussions/**\nCONTRIBUTING.md"
  },
  {
    "path": ".gitattributes",
    "content": "* ai-training=false\n* linguist-generated=true"
  },
  {
    "path": ".github/workflows/ubuntu-latest-python-specific.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python\n\nname: Python linting and typing\n\non:\n  push:\n    branches: [ \"dev\" ]\n  pull_request:\n    branches: [ \"dev\" ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.13\"]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Rust\n      uses: actions-rust-lang/setup-rust-toolchain@v1\n\n    - name: Set up uv\n      uses: astral-sh/setup-uv@v5\n      with:\n        enable-cache: true\n        python-version: ${{ matrix.python-version }}\n\n    - name: Python Ruff linting\n      run: |\n        uv run --group lint ruff check\n\n    - name: Python Ruff formatting\n      run: |\n        uv run --group lint ruff format --check\n\n    - name: Python static typing\n      run: |\n        uv run --group typing mypy --config-file pyproject.toml"
  },
  {
    "path": ".github/workflows/ubuntu-latest-rust-specific.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python\n\nname: Ruff linting and tests\n\non:\n  push:\n    branches: [ \"dev\" ]\n  pull_request:\n    branches: [ \"dev\" ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.13\"]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Rust\n      uses: actions-rust-lang/setup-rust-toolchain@v1\n\n    - name: Rust linting checks\n      run: |\n        cargo fmt --check\n\n    - name: Rust library tests\n      run: |\n        cargo test --lib\n\n    - name: Rust doc tests\n      run: |\n        cargo test --doc"
  },
  {
    "path": ".github/workflows/ubuntu-latest.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python\n\nname: Ubuntu Pytest\n\non:\n  push:\n    branches: [ \"dev\" ]\n  pull_request:\n    branches: [ \"dev\" ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [ \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\" ]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Rust\n      uses: actions-rust-lang/setup-rust-toolchain@v1\n\n    - name: Set up uv\n      uses: astral-sh/setup-uv@v5\n      with:\n        enable-cache: true\n        python-version: ${{ matrix.python-version }}\n\n    #    - name: Test with pytest and display Coverage\n    #      env:\n    #        RATESLIB_LICENCE: ${{ secrets.RATESLIB_LICENCE }}\n    #      run: |\n    #        uv run --group test coverage run -m --source=rateslib pytest\n    #        uv run coverage report -m\n\n    - name: Test with pytest\n      env:\n        RATESLIB_LICENCE: ${{ secrets.RATESLIB_LICENCE }}\n      run: |\n        uv run --group test pytest\n"
  },
  {
    "path": ".github/workflows/ubuntu-minimum.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python\n\nname: Ubuntu minimum support\n\non:\n  push:\n    branches: [ \"dev\" ]\n  pull_request:\n    branches: [ \"dev\" ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.10\"]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Rust\n      uses: actions-rust-lang/setup-rust-toolchain@v1\n\n    - name: Set up uv\n      uses: astral-sh/setup-uv@v5\n      with:\n        enable-cache: true\n        python-version: ${{ matrix.python-version }}\n\n    - name: Pytest minimum dependencies\n      # --resolution=lowest-direct picks the oldest allowed versions\n      # --group test ensures your test dependencies (like pytest) are included\n      env:\n        RATESLIB_LICENCE: ${{ secrets.RATESLIB_LICENCE }}\n        RATESLIB_DEVELOPMENT: False\n      run: uv run --resolution=lowest-direct --group test pytest"
  },
  {
    "path": ".github/workflows/windows-latest.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python\n\nname: Windows Pytest\n\non:\n  push:\n    branches: [ \"dev\" ]\n  pull_request:\n    branches: [ \"dev\" ]\n\njobs:\n  build:\n\n    runs-on: windows-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n    env:\n      MPLBACKEND: Agg    # https://github.com/orgs/community/discussions/26434\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Rust\n      uses: actions-rust-lang/setup-rust-toolchain@v1\n\n    - name: Set up uv\n      uses: astral-sh/setup-uv@v5\n      with:\n        enable-cache: true\n        python-version: ${{ matrix.python-version }}\n\n    - name: Test with pytest\n      env:\n        RATESLIB_LICENCE: ${{ secrets.RATESLIB_LICENCE }}\n      run: |\n        uv run --group test pytest"
  },
  {
    "path": ".github/workflows/windows-minimum.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python\n\nname: Windows minimum\n\non:\n  push:\n    branches: [ \"dev\" ]\n  pull_request:\n    branches: [ \"dev\" ]\n\njobs:\n  build:\n\n    runs-on: windows-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.10\"]\n    env:\n      MPLBACKEND: Agg    # https://github.com/orgs/community/discussions/26434\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Rust\n      uses: actions-rust-lang/setup-rust-toolchain@v1\n\n    - name: Set up uv\n      uses: astral-sh/setup-uv@v5\n      with:\n        enable-cache: true\n        python-version: ${{ matrix.python-version }}\n\n    - name: Pytest minimum dependencies\n      # --resolution=lowest-direct picks the oldest allowed versions\n      # --group test ensures your test dependencies (like pytest) are included\n      env:\n        RATESLIB_LICENCE: ${{ secrets.RATESLIB_LICENCE }}\n        RATESLIB_DEVELOPMENT: False\n      run: uv run --resolution=lowest-direct --group test pytest"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Rust extensions\nsrc/bin\n\nconfig.toml\n\n# Distribution / packaging\n.Python\nlocal_resources/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\n.asv/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\n.pypirc\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.dual_log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv*/\nvenv11/\nvenv311/\nvenv312/\nvenv11+/\nvenv9/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# scractch files\nscratch.py\nscratch1.py\nscratch2.py\nscratch3.py\nscratch4.py\nscratch5.py\nscratch6.py\n\n.idea/\n/*.ipynb\nCargo.lock\n.devcontainer\n.cargo/\n"
  },
  {
    "path": "COMMERCIAL_LICENCE",
    "content": "Commercial Subscription Licence Agreement\n=========================================\n\nThis Commercial Subscription Licence Agreement (“Agreement”) is entered into between:\n\nLicensor:\nSiffrorna Technology Limited\n42 Town Street, Sutton, Retford, DN22 8PT, UK\n\nand\n\nLicensee:\nThe individual or legal entity accepting this Agreement.\n\n1. Grant of Licence\n-------------------\n\n  Subject to payment of applicable fees and compliance with this Agreement, the Licensor grants\n  the Licensee a non-exclusive, non-transferable, non-sublicensable licence to use the software\n  identified below (the “Software”) during the Subscription Term.\n\n  The Licensee may install and use the Software only within the scope of the subscription\n  purchased. Unless otherwise agreed in writing as per any purchase order or licence key file,\n  each subscription permits use by a single internal user or a single designated system environment.\n\n  This licence permits the Licensee to:\n\n  - use the Software for internal commercial and internal professional purposes only\n  - deploy the Software in internal production environments\n  - integrate the Software with internal systems solely for the Licensee’s internal\n    business purposes\n  - modify the Software and create derivative works for internal use\n  - distribute the Software as part of an internal product or service\n\n  Except as expressly permitted, all rights are reserved by the Licensor.\n\n  The Licensor shall have no responsibility for any modified versions of the Software\n  created by the Licensee.\n\n2. Subscription Term\n--------------------\n\n  This Agreement is effective for the duration of the active subscription (“Subscription Term”).\n\n  Upon expiration or termination of the Subscription Term, all rights granted under this\n  Agreement automatically terminate unless renewed in writing.\n\n3. Fees and Payment\n-------------------\n\n  Use of the Software under this Agreement requires payment of the applicable subscription fees,\n  as agreed separately or displayed at the time of purchase.\n\n  Failure to pay fees when due constitutes a material breach of this Agreement.\n\n4. Ownership\n------------\n\n  The Software is licensed, not sold.\n\n  All right, title, and interest in and to the Software, including all intellectual property\n  rights, remain with the Licensor.\n\n5. Restrictions\n---------------\n\n  The Licensee may not:\n\n  - remove or obscure copyright or licence notices\n  - misrepresent ownership of the Software\n  - use the Software in violation of applicable laws or regulations\n\n6. Termination\n--------------\n\n  This Agreement may be terminated:\n\n  - automatically upon expiration of the Subscription Term\n  - immediately by the Licensor in the event of material breach\n  - by the Licensee by ceasing use and not renewing the subscription\n\n  Upon termination, the Licensee must cease use of the Software and delete all copies, except\n  where continued use is expressly permitted in writing.\n\n7. Disclaimer of Warranty\n-------------------------\n\n  THE SOFTWARE IS PROVIDED “AS IS” AND “AS AVAILABLE”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED.\n\n  TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSOR DISCLAIMS ALL WARRANTIES, INCLUDING BUT\n  NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT,\n  AND ANY WARRANTIES ARISING FROM COURSE OF DEALING OR USAGE OF TRADE.\n\n  THE LICENSOR DOES NOT WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE, THAT DEFECTS WILL BE\n  CORRECTED, OR THAT ANY RESULTS, OUTPUTS, CALCULATIONS, OR ANALYTICAL RESULTS GENERATED BY\n  THE SOFTWARE WILL BE ACCURATE, COMPLETE, OR SUITABLE FOR ANY PARTICULAR PURPOSE.\n\n8. No Investment Advice and Independent Judgment\n------------------------------------------------\n\n  The Software and any outputs, calculations, models, analytics or data generated by the\n  Software are provided solely for informational and analytical purposes. They do not constitute\n  investment advice, financial advice, trading advice, or a recommendation to buy, sell, or\n  hold any security, financial instrument, or investment.\n\n  The Licensee acknowledges that it is solely responsible for evaluating the accuracy,\n  completeness, and usefulness of the Software and any outputs generated by it. The Licensee\n  must exercise its own independent judgment when making investment, trading, or\n  financial decisions and must not rely solely on the Software.\n\n  The Licensee acknowledges that the Software is a tool designed to assist analysis and\n  that all investment, trading, and financial decisions remain the sole responsibility\n  of the Licensee.\n\n9. Data and Input Responsibility\n--------------------------------\n\n  The Licensee is solely responsible for all data, assumptions, parameters,\n  configurations, and other inputs used with the Software. The Licensor shall not be\n  responsible for any errors or losses arising from inaccurate or incomplete inputs\n  supplied by the Licensee or any third party.\n\n10. Intended Use\n----------------\n\n  The Software is provided as a general analytical tool. The Licensee is responsible for\n  determining whether the Software is appropriate for its intended use. The Licensor shall\n  not be liable for any losses arising from use of the Software in applications or\n  contexts for which it was not designed.\n\n11. Limitation of Liability\n--------------------------\n\n  TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSOR SHALL NOT BE LIABLE FOR ANY INDIRECT,\n  INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING BUT NOT LIMITED TO\n  LOSS OF PROFITS, TRADING LOSSES, LOSS OF BUSINESS OPPORTUNITY, LOSS OF DATA,\n  OR BUSINESS INTERRUPTION, ARISING OUT OF OR IN CONNECTION WITH THIS AGREEMENT OR THE\n  USE OF THE SOFTWARE.\n\n  THE TOTAL AGGREGATE LIABILITY OF THE LICENSOR ARISING OUT OF OR IN CONNECTION WITH THIS\n  AGREEMENT OR THE SOFTWARE, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE), OR\n  OTHERWISE, SHALL NOT EXCEED THE TOTAL FEES PAID BY THE LICENSEE FOR THE SOFTWARE\n  DURING THE TWELVE (12) MONTHS PRECEDING THE EVENT GIVING RISE TO THE CLAIM.\n\n12. Governing Law\n----------------\n\n  This Agreement shall be governed by and construed in accordance with the laws of England\n  and Wales, excluding its conflict of law principles.\n\n13. Severability\n----------------\n\n  If any provision of this Agreement is held to be invalid or unenforceable, the remaining\n  provisions shall remain in full force and effect.\n\n14. Entire Agreement\n--------------------\n\n  This Agreement constitutes the entire agreement between the parties regarding the Software and\n  supersedes all prior or contemporaneous agreements or understandings relating to its\n  subject matter.\n\n15. Language\n------------\n\n  This Agreement is written in English. Any translations are provided for convenience only.\n  In the event of any conflict, the English version shall prevail.\n\nEND OF AGREEMENT"
  },
  {
    "path": "COMMERCIAL_LICENCE_ADDENDUM1",
    "content": "Continuity of Licence Addendum\n==============================\n(Commercial Subscription Licence)\n\nThis Continuity of Licence Addendum (“Addendum”) forms part of the Commercial Subscription\nLicence Agreement (“Agreement”) between the Licensor and the Licensee.\n\n1. Continuity Event\n-------------------\n\n  A Continuity Event occurs if any of the following circumstances arise:\n\n  a) the Licensor enters liquidation, dissolution, or bankruptcy proceedings and ceases to carry\n     on business;\n  b) the Licensor permanently ceases operations and is no longer offering commercial licences for\n     the Software; or\n  c) where the Licensor is a sole proprietor or single-employee entity, the death or permanent\n     incapacity of that individual results in the Licensor being unable to continue licensing or\n     supporting the Software.\n\n2. Effect of Continuity Event\n-----------------------------\n\n  Upon the occurrence of a Continuity Event:\n\n  - any valid and paid-up Commercial Subscription Licence held by the Licensee shall\n    automatically convert into a perpetual, non-exclusive, royalty-free licence to use\n    the Software; and\n  - the Licensee may continue to exercise the rights granted under the Commercial Subscription\n    Licence as they existed immediately prior to the Continuity Event.\n\n3. Scope of Continued Rights\n----------------------------\n\n  Following a Continuity Event, the Licensee may:\n\n  - continue to use the Software for commercial and internal business purposes;\n  - deploy the Software in production environments;\n  - maintain, modify, and create derivative works of the Software for its own internal use;\n  - continue distributing the Software internally solely as part of its existing products\n    or services.\n\n  The Licensee may not:\n\n  - resell, sublicense, or otherwise make the Software available on a standalone basis; or\n  - represent itself as the owner of the Software or its intellectual property.\n\n4. No Obligation to Provide Support\n-----------------------------------\n\n  Nothing in this Addendum obligates the Licensor\n  (or any successor or estate) to provide maintenance, updates, support, or\n  warranties following a Continuity Event.\n\n5. Survival\n-----------\n\n  This Addendum shall survive termination or expiration of the Agreement and shall take\n  effect only upon the occurrence of a Continuity Event.\n\n6. No Early Trigger\n-------------------\n\n  The occurrence of a Continuity Event shall not be deemed to have occurred solely due to:\n\n  - a temporary suspension of business;\n  - a delay in responding to communications;\n  - a change in ownership or corporate structure where licensing continues; or\n  - the discontinuation of a particular product version while the Licensor continues to operate.\n\n7. Governing Law\n----------------\n\n  This Addendum shall be governed by and construed in accordance with the laws of\n  England and Wales, excluding its conflict of law principles.\n\nEND OF ADDENDUM"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"rateslib\"\nversion = \"2.7.1\"\nedition = \"2021\"\nexclude = [\n    \".github/*\",\n    \"benches/*\",\n    \"benchmarks/*\",\n    \"notebooks/*\",\n    \"docs/*\",\n    \"robots.txt\",\n]\n\n[lib]\nname = \"rateslib\"\npath = \"rust/lib.rs\"\ncrate-type = [\"cdylib\", \"rlib\"]  # \"lib\" alone works but this is more explicit\n\n[[bin]]\nname = \"main\"\npath = \"rust/main.rs\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n#pyo3 = { version = \"0.20.3\", features = [\"abi3-py39\", \"extension-module\"] }\nserde = { version = \"1.0\", features = [\"derive\", \"rc\"] }\nchrono = { version = \"0.4\", features = [\"serde\"] }\nindexmap = { version = \"2.7\", features = [\"serde\"] }\nndarray = { version = \"0.17\", features = [\"serde\"] }\ninternment = {  version = \"0.8\", features = [\"serde\"] }\npyo3 = \"0.28\"\nnum-traits = \"0.2\"\nauto_ops = \"0.3\"\nnumpy = \"0.28\"\nitertools = \"0.14\"\nstatrs = \"0.18\"\nbincode = { version = \"2.0\", features = [\"serde\"] }\nserde_json = \"1.0\"\n\n[features]\n# multiple-pymethods = [\"pyo3/multiple-pymethods\"]\nabi3-py310 = [\"pyo3/abi3-py310\"]\npyo3-chrono = [\"pyo3/chrono\"]\npyo3-indexmap = [\"pyo3/indexmap\"]\ndefault = [\"abi3-py310\", \"pyo3-chrono\", \"pyo3-indexmap\"]\n# 'extension-module' has been added to 'features' of [tool.maturin] in pyproject.toml\n#extension-module = [\"pyo3/extension-module\"]\n#default = [\"extension-module\", \"abi3-py39\", \"chrono\"]\n\n# -------------  When building commment the below out.\n\n#[dev-dependencies]\n#criterion = { version = \"0.4\", features = [\"html_reports\"] }\n\n#[[bench]]\n#name = \"my_benchmark\"\n#harness = false"
  },
  {
    "path": "LICENCE",
    "content": "LICENCE\n=======\nDual Licensing – Source-Available Non-Commercial Licence\nand Commercial Subscription Licence\n\nCopyright © 2023 Siffrorna Technology Limited\nAll rights reserved.\n\nLicence Acceptance\n------------------\n\nBy downloading, installing, copying, accessing, or otherwise using this software, you\nacknowledge that you have read, understood, and agree to be bound by the terms of one of the\nlicences below.\n\nThis software is not open source.\n\nDual-Licensing Overview\n-----------------------\n\nThis software is offered under two alternative licences:\n\n  1. Non-Commercial Source-Available Licence (free, default)\n  2. Commercial Subscription Licence (paid, required for business use)\n\nYou may use this software only if you comply with the terms of one of these licences.\n\n1. Non-Commercial Source-Available Licence\n------------------------------------------\n\n(Personal and Educational Use Only)\n\n  1.1 Grant of Rights\n\n    Subject to the restrictions below, permission is granted to view, download, and run the\n    software solely for non-commercial purposes, including:\n\n    - personal use\n    - academic or educational use\n\n    This licence does not grant any right to distribute, modify, or commercially exploit the software.\n\n  1.2 Restrictions\n\n    You may not, directly or indirectly:\n\n    - Install or use the software for any purpose in a commercial environment\n    - Sell, license, sublicense, rent, lease, or monetize the software\n    - Distribute or redistribute the software, in source or binary form\n    - Modify, adapt, translate, or create derivative works\n    - Incorporate the software into any other software, library, service, or product\n    - Use the software to provide services to third parties\n    - Circumvent, remove, or obscure copyright or licence notices\n\n    For the purposes of this licence, “commercial” means any use primarily intended for or\n    directed toward commercial advantage, monetary compensation, or business operations,\n    whether direct or indirect.\n\n  1.3 Ownership\n\n    The software is licensed, not sold.\n    All right, title, and interest in and to the software remain with the copyright holder.\n\n  1.4 Termination\n\n    Any violation of this licence automatically terminates the rights granted herein.\n    Upon termination, you must immediately cease all use of the software and delete all copies\n    in your possession or control.\n\n2. Commercial Subscription Licence\n----------------------------------\n(Required for Business or Revenue-Generating Use)\n\n  Any use of the software in a commercial, professional, or revenue-generating context requires\n  a valid Commercial Subscription Licence.\n\n  A Commercial Subscription Licence may permit, subject to separate written terms:\n\n  - Commercial and internal business use\n  - Deployment in production environments\n  - Integration with proprietary or open-source systems\n  - Modification and derivative works\n  - Distribution as part of a product or service\n\n  Commercial licences are offered under separate written terms and are typically provided on\n  a subscription basis.\n\nTo obtain a Commercial Subscription Licence, visit https://rateslib.com/licence\n\n3. No Implied Rights\n--------------------\n\n  Except as expressly granted in writing, no rights are granted under this licence, whether by\n  implication, estoppel, or otherwise.\n\n  No patent, trademark, or other intellectual property rights are granted under the\n  Non-Commercial Source-Available Licence.\n\n4. Restriction on Machine Learning, AI Training and Generative AI\n-----------------------------------------------------------------\n\n  The Source Code and all related assets in this repository may not be used, directly or indirectly,\n  for the purpose of training, developing, or improving any artificial intelligence,\n  machine learning model, or large language model. This includes, but is not limited to,\n  using the Source Code for data mining, scraping, or as part of a training dataset for generative\n  AI tools. Any such use is an unauthorized reproduction and a violation of this license.\n\n5. Disclaimer of Warranty\n-------------------------\n\n  THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n  BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,\n  AND NON-INFRINGEMENT.\n\n6. Limitation of Liability\n--------------------------\n\n  TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR\n  ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM OR IN CONNECTION WITH THE USE OF THE SOFTWARE.\n\n7. Governing Law\n----------------\n\n  This licence and any dispute arising out of or in connection with it shall be governed by and\n  construed in accordance with the laws of England and Wales, excluding its conflict of\n  law principles.\n\n  Nothing in this section limits the copyright holder’s right to seek injunctive or equitable\n  relief in any jurisdiction.\n\n8. Severability\n---------------\n\n  If any provision of this licence is held to be invalid, illegal, or unenforceable by a court of\n  competent jurisdiction, the remaining provisions shall remain in full force and effect.\n\n9. No Waiver\n------------\n\n  Failure to enforce any provision of this licence shall not constitute a waiver of future\n  enforcement of that or any other provision.\n\n10. Language\n-----------\n\n  This licence is written in English. Any translations are provided for convenience only.\n  In the event of any inconsistency or dispute, the English version shall prevail.\n\nEND OF TERMS"
  },
  {
    "path": "README.md",
    "content": "<div style=\"text-align: center; padding: 2em 0 2em\">\n    <img src=\"https://rateslib.readthedocs.io/en/latest/_static/rateslib_logo_big2.png\" alt=\"rateslib\">\n</div>\n\n<div style=\"text-align: center\">\n  <img src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frateslib.com%2Fpy%2Fen%2Flatest%2F_static%2Fbadges.json&query=%24.python&label=Python&color=blue\" alt=\"Python\">\n  <img src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frateslib.com%2Fpy%2Fen%2Flatest%2F_static%2Fbadges.json&query=%24.pypi&label=PyPi&color=blue\" alt=\"PyPi\">\n  <img src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frateslib.com%2Fpy%2Fen%2Flatest%2F_static%2Fbadges.json&query=%24.conda&label=Conda&color=blue\" alt=\"Conda\">\n  <img src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frateslib.com%2Fpy%2Fen%2Flatest%2F_static%2Fbadges.json&query=%24.licence&label=Licence&color=red\" alt=\"Licence\">\n  <img src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frateslib.com%2Fpy%2Fen%2Flatest%2F_static%2Fbadges.json&query=%24.status&label=Status&color=green\" alt=\"Status\">\n  <img src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frateslib.com%2Fpy%2Fen%2Flatest%2F_static%2Fbadges.json&query=%24.coverage&label=Coverage&color=green\" alt=\"Coverage\">\n  <img src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frateslib.com%2Fpy%2Fen%2Flatest%2F_static%2Fbadges.json&query=%24.style&label=Code%20Style&color=black\" alt=\"Code Style\">\n</div>\n\n# Rateslib\n\n``Rateslib`` is a state-of-the-art **fixed income library** designed for Python.\nIts purpose is to provide advanced, flexible and efficient fixed income analysis\nwith a high level, well documented API.\n\nThe techniques and object interaction within *rateslib* were inspired by\nthe requirements of multi-disciplined fixed income teams working, both cooperatively\nand independently, within global investment banks.\n\n\nLicence\n=======\n\nThis library is released under specific Dual Licensing Terms - Source-Available Non-Commercial Licence\nand Commercial Subscription Licence. See [latest licence](https://rateslib.com/py/en/latest/i_licence.html)\n\nThis project is source-available, **not** open source. Commercial use requires a paid licence.\n\nGet Started\n===========\n\nRead the documentation at \n[rateslib.com/py](https://rateslib.com/py/)\n\n\n\n\n\n"
  },
  {
    "path": "docs/source/z_ir_vol_time_to_expiry.rst",
    "content": ".. _cook-ir-vol-time-doc:\n\n.. ipython:: python\n   :suppress:\n\n   from rateslib.curves import *\n   from rateslib.instruments import *\n   from rateslib.solver import Solver\n   from rateslib import calendars\n   from itertools import product\n   from rateslib.volatility import IRSabrCube, IRSplineCube, IRSplineSmile, IRSabrSmile\n   import matplotlib.pyplot as plt\n   from datetime import datetime as dt\n   import numpy as np\n   from pandas import DataFrame, option_context, Series\n   import pandas as pd\n\nIR Volatility Time To Expiry Remapping\n**********************************************************************\n\nThis page presents examples for working with time to expiry for IR volatility products.\n\n  **Key Points**\n\n  - Every *time to expiry* is an Act365 calendar day measure unless remapped.\n  - At each ``expiry`` on any *Cube* *time to expiry* is Act365 calendar day measure assuming the associated\n    volatility is calibrated to market.\n  - Any intermediate *time to expiry* between the chosen ``expiries`` on a *Cube* can be remapped.\n\nIntroduction\n-------------\n\nEvery *IR volatility* pricing object has an ``eval_date`` as part of its ``meta`` parameters.\nThis allows any :class:`~rateslib.volatility._BaseIRSmile` to make a natural measure of time to expiry\nusing the equation:\n\n.. math::\n\n   t = \\frac{days(expiry - eval date)}{365}\n\nWhen a :class:`~rateslib.volatility._BaseIRSmile` yields a volatility value for a specific strike,\nthat volatility value is assumed to be associated with that *time to expiry* that\nthat :class:`~rateslib.volatility._BaseIRSmile` calculates.\n\nMost of the time a user will not need to be aware of that. Lets create a basic swaption and analyse\ndifferent pricing models. For example:\n\n.. ipython:: python\n\n   curve = Curve({dt(2001, 1, 1): 1.0, dt(2004, 1, 1): 0.90}, convention=\"act360\", calendar=\"nyc\")\n   iro = IRSCall(expiry=dt(2002, 1, 1), tenor=\"1y\", irs_series=\"usd_irs\", strike=3.30)\n\n.. tabs::\n\n   .. group-tab:: IRSplineSmile\n\n      .. ipython:: python\n\n         irss = IRSplineSmile(\n             eval_date=dt(2001, 1, 1),\n             expiry=dt(2002, 1, 1),\n             tenor=\"1y\",\n             nodes={-25.0: 52, 0: 50, 25: 53},\n             k=4,\n             irs_series=\"usd_irs\"\n         )\n         print(irss.get_from_strike(k=2.4, f=2.1))\n         print(iro.rate(curves=curve, vol=irss, metric=\"percentnotional\"))\n\n   .. group-tab:: IRSabrSmile\n\n      .. ipython:: python\n\n         irss = IRSabrSmile(\n             eval_date=dt(2001, 1, 1),\n             expiry=dt(2002, 1, 1),\n             tenor=\"1y\",\n             nodes={\"alpha\": 0.35, \"rho\": -0.05, \"nu\": 0.65},\n             beta=0.5,\n             irs_series=\"usd_irs\",\n         )\n         print(irss.get_from_strike(k=2.4, f=2.1))\n         print(iro.rate(curves=curve, vol=irss, metric=\"percentnotional\"))\n\nThe pair of values here :math:`(t, \\sigma)` are used in pricing models such as the Black76 or Bachelier model directly.\n\nTime Scaling\n--------------\n\nIt is possible, however, to apply a scaling parameter to the calendar day measure to arrive at a different\n*time to expiry*. Doing so yields a pair :math:`(\\hat{t}, \\hat{\\sigma})`\n\n.. math::\n\n   \\hat{t} = \\xi t\n\n.. tabs::\n\n   .. group-tab:: IRSplineSmile\n\n      .. ipython:: python\n\n         irss = IRSplineSmile(\n             eval_date=dt(2001, 1, 1),\n             expiry=dt(2002, 1, 1),\n             tenor=\"1y\",\n             nodes={-25.0: 52, 0: 50, 25: 53},\n             k=4,\n             irs_series=\"usd_irs\",\n             time_scalar=0.98,\n         )\n         print(irss.get_from_strike(k=2.4, f=2.1))\n         print(iro.rate(curves=curve, vol=irss, metric=\"percentnotional\"))\n\n   .. group-tab:: IRSabrSmile\n\n      .. ipython:: python\n\n         irss = IRSabrSmile(\n             eval_date=dt(2001, 1, 1),\n             expiry=dt(2002, 1, 1),\n             tenor=\"1y\",\n             nodes={\"alpha\": 0.35, \"rho\": -0.05, \"nu\": 0.65},\n             beta=0.5,\n             irs_series=\"usd_irs\",\n             time_scalar=0.98,\n         )\n         print(irss.get_from_strike(k=2.4, f=2.1))\n         print(iro.rate(curves=curve, vol=irss, metric=\"percentnotional\"))\n\nWorking with a Cube\n----------------------\n\nTypically the *time scalar* is not a quantity one will add to a *Smile* directly.\nInstead it exists to allow *Cubes* to handle time interpolation.\nThe ``weights`` argument on a *Cube* can apportion volatility to specific dates in between\nchosen ``expiries``. It is **assumed** that on every given expiry the time scalar equals one\nand the *Cube* is calibrated to market *Instruments*.\n\n.. tabs::\n\n   .. tab:: Calendar Days\n\n      .. ipython:: python\n\n         irsc1 = IRSabrCube(\n             eval_date=dt(2001, 1, 1),\n             expiries=[dt(2001, 2, 1), dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 7, 1)],\n             tenors=[\"1y\"],\n             alpha=0.35,\n             beta=0.5,\n             rho=-0.05,\n             nu=0.45,\n             irs_series=\"usd_irs\",\n         )\n\n   .. tab:: Business Days\n\n      .. ipython:: python\n\n         nyc = calendars.get(\"nyc\")\n         weights = Series(  # set the weight of non-business days to zero\n             index=[_ for _ in nyc.cal_date_range(dt(2001, 1, 1), dt(2001, 8, 1)) if nyc.is_non_bus_day(_)],\n             data=0.0\n         )\n         irsc2 = IRSabrCube(\n             eval_date=dt(2001, 1, 1),\n             expiries=[dt(2001, 2, 1), dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 7, 1)],\n             tenors=[\"1y\"],\n             alpha=0.35,\n             beta=0.5,\n             rho=-0.05,\n             nu=0.45,\n             irs_series=\"usd_irs\",\n             weights=weights,\n         )\n\n   .. tab:: Semi-Business Days\n\n      .. ipython:: python\n\n         weights2 = Series(  # set the weight of non-business days to 0.5\n             index=[_ for _ in nyc.cal_date_range(dt(2001, 1, 1), dt(2001, 8, 1)) if nyc.is_non_bus_day(_)],\n             data=0.5\n         )\n         irsc3 = IRSabrCube(\n             eval_date=dt(2001, 1, 1),\n             expiries=[dt(2001, 2, 1), dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 7, 1)],\n             tenors=[\"1y\"],\n             alpha=0.35,\n             beta=0.5,\n             rho=-0.05,\n             nu=0.45,\n             irs_series=\"usd_irs\",\n             weights=weights2,\n         )\n\nPrices of Options\n-------------------\n\nWith the different models above we plot the prices of ATM Payer Swaptions. In fact these graphs show the\ndifferences in prices of percent of notional for an option of every expiry date. After the end of the ``weights``\n*Series* the prices converge as both models fall back to calendar day type.\n\n.. ipython:: python\n\n   x, y, y2 = [], [], []\n   for expiry in nyc.cal_date_range(dt(2001, 1, 5), dt(2001, 9, 1)):\n       iro = IRSCall(\n           expiry=expiry,\n           tenor=\"1y\",\n           strike=\"atm\",\n           irs_series=\"usd_irs\",\n       )\n       x.append(expiry)\n       y.append(iro.rate(curves=curve, vol=irsc1, metric=\"percentnotional\") - iro.rate(curves=curve, vol=irsc2, metric=\"percentnotional\"))\n       y2.append(iro.rate(curves=curve, vol=irsc1, metric=\"percentnotional\") - iro.rate(curves=curve, vol=irsc3, metric=\"percentnotional\"))\n\n\n.. plot::\n\n   from rateslib import dt, Curve, IRSabrCube, calendars, IRSCall\n   from pandas import Series\n\n   curve = Curve({dt(2001, 1, 1): 1.0, dt(2004, 1, 1): 0.90}, convention=\"act360\", calendar=\"nyc\")\n   irsc1 = IRSabrCube(\n       eval_date=dt(2001, 1, 1),\n       expiries=[dt(2001, 2, 1), dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 7, 1)],\n       tenors=[\"1y\"],\n       alpha=0.35,\n       beta=0.5,\n       rho=-0.05,\n       nu=0.45,\n       irs_series=\"usd_irs\",\n   )\n   nyc = calendars.get(\"nyc\")\n   weights = Series(  # set the weight of non-business days to zero\n       index=[_ for _ in nyc.cal_date_range(dt(2001, 1, 1), dt(2001, 8, 1)) if nyc.is_non_bus_day(_)],\n       data=0.0\n   )\n   weights2 = Series(  # set the weight of non-business days to 0.5\n       index=[_ for _ in nyc.cal_date_range(dt(2001, 1, 1), dt(2001, 8, 1)) if nyc.is_non_bus_day(_)],\n       data=0.5\n   )\n   irsc2 = IRSabrCube(\n       eval_date=dt(2001, 1, 1),\n       expiries=[dt(2001, 2, 1), dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 7, 1)],\n       tenors=[\"1y\"],\n       alpha=0.35,\n       beta=0.5,\n       rho=-0.05,\n       nu=0.45,\n       irs_series=\"usd_irs\",\n       weights=weights,\n   )\n   irsc3 = IRSabrCube(\n       eval_date=dt(2001, 1, 1),\n       expiries=[dt(2001, 2, 1), dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 7, 1)],\n       tenors=[\"1y\"],\n       alpha=0.35,\n       beta=0.5,\n       rho=-0.05,\n       nu=0.45,\n       irs_series=\"usd_irs\",\n       weights=weights2,\n   )\n   x, y, y2 = [], [], []\n   for expiry in nyc.cal_date_range(dt(2001, 1, 5), dt(2001, 9, 1)):\n       iro = IRSCall(\n           expiry=expiry,\n           tenor=\"1y\",\n           strike=\"atm\",\n           irs_series=\"usd_irs\",\n       )\n       x.append(expiry)\n       y.append(iro.rate(curves=curve, vol=irsc1, metric=\"percentnotional\") - iro.rate(curves=curve, vol=irsc2, metric=\"percentnotional\"))\n       y2.append(iro.rate(curves=curve, vol=irsc1, metric=\"percentnotional\") - iro.rate(curves=curve, vol=irsc3, metric=\"percentnotional\"))\n\n   from matplotlib import pyplot as plt\n   fig, ax = plt.subplots(1,1)\n   ax.plot(x,y)\n   ax.plot(x,y2)\n   ax.scatter([dt(2001, 2, 1), dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 7, 1)], [0, 0, 0, 0], s=25, c='r')\n   plt.show()\n   plt.close()"
  },
  {
    "path": "notebooks/coding/ch5_fx.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.fx import FXRates, FXForwards\\n\",\n    \"from rateslib.dual import Dual\\n\",\n    \"from rateslib.curves import Curve\\n\",\n    \"from datetime import datetime as dt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Chapter 5 - FX Rates\\n\",\n    \"\\n\",\n    \"### Unsuitable initialisation\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"FXRates({\\\"usdeur\\\": 1.0, \\\"noksek\\\":1.0})\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"FXRates({\\\"usdeur\\\": 1.0, \\\"gbpusd\\\":1.0, \\\"gbpeur\\\": 1.0})\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"FXRates({\\\"usdeur\\\": 1.0, \\\"eurusd\\\":1.0, \\\"noksek\\\": 1.0})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## FX Rates Array\\n\",\n    \"\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr = FXRates({\\\"usdeur\\\": 2.0, \\\"usdgbp\\\": 2.5})\\n\",\n    \"fxr.rates_table()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr.rate(\\\"eurgbp\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Representation via Dual\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr = FXRates({\\\"usdnok\\\": 8.0})\\n\",\n    \"fxr.convert(1000000, \\\"nok\\\", \\\"usd\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Equivalence of Cash Positions and Base Value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr.currencies\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"base_value = fxr.convert_positions([0, 1000000], \\\"usd\\\")\\n\",\n    \"base_value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"positions = fxr.positions(base_value, \\\"usd\\\")\\n\",\n    \"positions\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Introduce a third currency\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr = FXRates({\\\"usdeur\\\": 0.9, \\\"eurnok\\\": 8.888889})\\n\",\n    \"fxr.currencies\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"base_value = fxr.convert_positions([0, 0, 1000000], \\\"usd\\\")\\n\",\n    \"base_value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr.positions(base_value, \\\"usd\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"base_value = Dual(125000, \\\"fx_usdnok\\\", [-15625])\\n\",\n    \"positions = fxr.positions(base_value, \\\"usd\\\")\\n\",\n    \"positions\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr.convert_positions(positions, \\\"usd\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Re-expression in Majors\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr_crosses = FXRates({\\\"eurusd\\\": 1.0, \\\"gbpjpy\\\": 100, \\\"eurjpy\\\": 100})\\n\",\n    \"fxr_crosses.convert(1, \\\"usd\\\", \\\"jpy\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr_majors = fxr_crosses.restate([\\\"eurusd\\\", \\\"usdjpy\\\", \\\"gbpusd\\\"])\\n\",\n    \"fxr_majors.convert(1, \\\"usd\\\", \\\"jpy\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## FX Forwards\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fx_rates = FXRates({\\\"usdeur\\\": 0.9, \\\"eurnok\\\": 8.888889}, dt(2022, 1, 3))\\n\",\n    \"fx_curves = {\\n\",\n    \"    \\\"usdusd\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.96}),\\n\",\n    \"    \\\"eureur\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}),\\n\",\n    \"    \\\"eurusd\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.991}),\\n\",\n    \"    \\\"noknok\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}),\\n\",\n    \"    \\\"nokeur\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.978}),\\n\",\n    \"}\\n\",\n    \"fxf = FXForwards(fx_rates, fx_curves)\\n\",\n    \"fxf.rate(\\\"usdnok\\\", dt(2022, 8, 15))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Equivalence of Delta Risk\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fx_rates = FXRates({\\\"usdeur\\\": 0.9, \\\"eurnok\\\": 8.888889}, dt(2022, 1, 3))\\n\",\n    \"start, end = dt(2022, 1, 1), dt(2023, 1,1)\\n\",\n    \"fx_curves = {\\n\",\n    \"    \\\"usdusd\\\": Curve({start: 1.0, end: 0.96}, id=\\\"uu\\\", ad=1),\\n\",\n    \"    \\\"eureur\\\": Curve({start: 1.0, end: 0.99}, id=\\\"ee\\\", ad=1),\\n\",\n    \"    \\\"eurusd\\\": Curve({start: 1.0, end: 0.991}, id=\\\"eu\\\", ad=1),\\n\",\n    \"    \\\"noknok\\\": Curve({start: 1.0, end: 0.98}, id=\\\"nn\\\", ad=1),\\n\",\n    \"    \\\"nokeur\\\": Curve({start: 1.0, end: 0.978}, id=\\\"ne\\\", ad=1),\\n\",\n    \"}\\n\",\n    \"fxf = FXForwards(fx_rates, fx_curves)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"discounted_nok = fx_curves[\\\"nokeur\\\"][dt(2022, 8, 15)] * 1000\\n\",\n    \"base_value = discounted_nok * fxf.rate(\\\"nokusd\\\", dt(2022, 1, 1))\\n\",\n    \"base_value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"forward_eur = fxf.rate(\\\"nokeur\\\", dt(2022, 8, 15)) * 1000\\n\",\n    \"discounted_eur = forward_eur * fx_curves[\\\"eureur\\\"][dt(2022, 8, 15)]\\n\",\n    \"base_value = discounted_eur * fxf.rate(\\\"eurusd\\\", dt(2022, 1, 1))\\n\",\n    \"base_value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"base_value.gradient([\\\"uu1\\\", \\\"ee1\\\", \\\"eu1\\\", \\\"nn1\\\", \\\"ne1\\\", \\\"fx_usdeur\\\", \\\"fx_eurnok\\\"])\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Combining Settlement Dates\\n\",\n    \"\\n\",\n    \"### Separable system\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr1 = FXRates({\\\"eurusd\\\": 1.05}, settlement=dt(2022, 1, 3))\\n\",\n    \"fxr2 = FXRates({\\\"usdcad\\\": 1.1}, settlement=dt(2022, 1, 2))\\n\",\n    \"fxf = FXForwards(\\n\",\n    \"    fx_rates=[fxr1, fxr2],\\n\",\n    \"    fx_curves={\\n\",\n    \"        \\\"usdusd\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"eureur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"usdeur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadusd\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"    }\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxf.rate(\\\"eurcad\\\", dt(2022, 2, 1))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Acyclic Dependent Systems\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxf = FXForwards(\\n\",\n    \"    fx_rates=[fxr1, fxr2],\\n\",\n    \"    fx_curves={\\n\",\n    \"        \\\"usdusd\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"eureur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"usdeur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadeur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"    }\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxf.rate(\\\"eurcad\\\", dt(2022, 2, 1))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Cyclic Dependent Systems Fail\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr1 = FXRates({\\\"eurusd\\\": 1.05, \\\"gbpusd\\\": 1.25}, settlement=dt(2022, 1, 3))\\n\",\n    \"fxf = FXForwards(\\n\",\n    \"    fx_rates=[fxr1, fxr2],\\n\",\n    \"    fx_curves={\\n\",\n    \"        \\\"usdusd\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"eureur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"usdeur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadeur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"gbpcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"gbpgbp\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"    }\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"But cyclic systems can be restructured\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr1 = FXRates({\\\"eurusd\\\": 1.05}, settlement=dt(2022, 1, 3))\\n\",\n    \"fxr3 = FXRates({\\\"gbpusd\\\": 1.25}, settlement=dt(2022, 1, 3))\\n\",\n    \"fxf = FXForwards(\\n\",\n    \"    fx_rates=[fxr1, fxr2, fxr3],\\n\",\n    \"    fx_curves={\\n\",\n    \"        \\\"usdusd\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"eureur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"usdeur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadeur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"gbpcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"gbpgbp\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"    }\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxf.rate(\\\"eurcad\\\", dt(2022, 2, 1))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Unsolvable System\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr1 = FXRates({\\\"eurusd\\\": 1.05, \\\"gbpusd\\\": 1.25}, settlement=dt(2022, 1, 3))\\n\",\n    \"fxr3 = FXRates({\\\"gbpjpy\\\": 100}, settlement=dt(2022, 1, 4))\\n\",\n    \"FXForwards(\\n\",\n    \"    fx_rates=[fxr1, fxr2, fxr3],\\n\",\n    \"    fx_curves={\\n\",\n    \"        \\\"usdusd\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"eureur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"gbpgbp\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"usdjpy\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"eurcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"eurjpy\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"gbpcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"    }\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Dual Representation\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div>\\n\",\n       \"<style scoped>\\n\",\n       \"    .dataframe tbody tr th:only-of-type {\\n\",\n       \"        vertical-align: middle;\\n\",\n       \"    }\\n\",\n       \"\\n\",\n       \"    .dataframe tbody tr th {\\n\",\n       \"        vertical-align: top;\\n\",\n       \"    }\\n\",\n       \"\\n\",\n       \"    .dataframe thead th {\\n\",\n       \"        text-align: right;\\n\",\n       \"    }\\n\",\n       \"</style>\\n\",\n       \"<table border=\\\"1\\\" class=\\\"dataframe\\\">\\n\",\n       \"  <thead>\\n\",\n       \"    <tr style=\\\"text-align: right;\\\">\\n\",\n       \"      <th></th>\\n\",\n       \"      <th>2022-01-01</th>\\n\",\n       \"      <th>2022-01-02</th>\\n\",\n       \"      <th>2022-01-03</th>\\n\",\n       \"    </tr>\\n\",\n       \"  </thead>\\n\",\n       \"  <tbody>\\n\",\n       \"    <tr>\\n\",\n       \"      <th>cad</th>\\n\",\n       \"      <td>0.0</td>\\n\",\n       \"      <td>181500.0</td>\\n\",\n       \"      <td>0.0</td>\\n\",\n       \"    </tr>\\n\",\n       \"    <tr>\\n\",\n       \"      <th>eur</th>\\n\",\n       \"      <td>0.0</td>\\n\",\n       \"      <td>0.0</td>\\n\",\n       \"      <td>-100000.0</td>\\n\",\n       \"    </tr>\\n\",\n       \"    <tr>\\n\",\n       \"      <th>usd</th>\\n\",\n       \"      <td>100000.0</td>\\n\",\n       \"      <td>-165000.0</td>\\n\",\n       \"      <td>105000.0</td>\\n\",\n       \"    </tr>\\n\",\n       \"  </tbody>\\n\",\n       \"</table>\\n\",\n       \"</div>\"\n      ],\n      \"text/plain\": [\n       \"     2022-01-01  2022-01-02  2022-01-03\\n\",\n       \"cad         0.0    181500.0         0.0\\n\",\n       \"eur         0.0         0.0   -100000.0\\n\",\n       \"usd    100000.0   -165000.0    105000.0\"\n      ]\n     },\n     \"execution_count\": 2,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"fxr1 = FXRates({\\\"eurusd\\\": 1.05}, settlement=dt(2022, 1, 3))\\n\",\n    \"fxr2 = FXRates({\\\"usdcad\\\": 1.1}, settlement=dt(2022, 1, 2))\\n\",\n    \"fxf = FXForwards(\\n\",\n    \"    fx_rates=[fxr1, fxr2],\\n\",\n    \"    fx_curves={\\n\",\n    \"        \\\"usdusd\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"eureur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadcad\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"usdeur\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"        \\\"cadusd\\\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\\n\",\n    \"    }\\n\",\n    \")\\n\",\n    \"pv = Dual(100000, [\\\"fx_eurusd\\\", \\\"fx_usdcad\\\"], [-100000, -150000])\\n\",\n    \"fxf.positions(pv, base=\\\"usd\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"cad    181500.0\\n\",\n       \"eur   -100000.0\\n\",\n       \"usd     40000.0\\n\",\n       \"dtype: float64\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"fxf.positions(pv, base=\\\"usd\\\", aggregate=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<Dual: 100,000.000000, ['fx_eurusd', 'fx_usdcad'], [-100000. -150000.]>\"\n      ]\n     },\n     \"execution_count\": 18,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"fxf.convert_positions(fxf.positions(pv, base=\\\"usd\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "notebooks/coding/curves.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f8825706-c252-40d7-8075-b438f5756093\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Curves\\n\",\n    \"\\n\",\n    \"### CompositeCurve example\\n\",\n    \"\\n\",\n    \"The first section here regards efficient operations and compositing two curves.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"c88c3ce0-72f1-4182-a6c0-36209ccc9954\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import dt\\n\",\n    \"from rateslib.curves import Curve, LineCurve, CompositeCurve\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"9cbc5699-fa68-46cd-8e75-3752d078977c\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"3.75\"\n      ]\n     },\n     \"execution_count\": 2,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"line_curve1 = LineCurve({dt(2022, 1, 1): 2.0, dt(2022, 1, 3): 4.0}, id=\\\"C1_\\\")\\n\",\n    \"line_curve2 = LineCurve({dt(2022, 1, 1): 0.5, dt(2022, 1, 3): 1.0}, id=\\\"C2_\\\")\\n\",\n    \"composite_curve = CompositeCurve(curves=(line_curve1, line_curve2))\\n\",\n    \"composite_curve.rate(dt(2022, 1, 2))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"fd5f49ac-ed99-4422-844a-13c657b823f1\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<Dual: 3.750000, ('C1_0', 'C1_1', 'C2_0', 'C2_1'), [0.5 0.5 0.5 0.5]>\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"line_curve1._set_ad_order(1)\\n\",\n    \"line_curve2._set_ad_order(1)\\n\",\n    \"composite_curve.rate(dt(2022, 1, 2))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8cb305f5-19a5-46b6-a9f2-82a2bd1f6592\",\n   \"metadata\": {},\n   \"source\": [\n    \"The code above demonstrates the summing of individual rates and of interoperability with Dual datatypes.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"b658689c-65f2-4aae-992a-7fbf61f5d2c4\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Error in approximated rates and execution time\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"973e0754-edfc-42ce-9d0c-d2272c69465f\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(array([ 3455,  3451, 22875, 21294, 48033,   892]),\\n\",\n       \" array([0.e+00, 5.e-07, 1.e-06, 5.e-06, 1.e-05, 5.e-05, 1.e+00]))\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"MIN, MAX, SAMPLES, DAYS, d = 0, 4, 100000, 3, 1.0/365\\n\",\n    \"c1 = np.random.rand(DAYS, SAMPLES) * (MAX - MIN) + MIN\\n\",\n    \"c2 = np.random.rand(DAYS, SAMPLES) * (MAX - MIN) + MIN\\n\",\n    \"r_true=((1 + d * (c1 + c2) / 100).prod(axis=0) - 1) * 100 / (d * DAYS)\\n\",\n    \"c1_bar = ((1 + d * c1 / 100).prod(axis=0)**(1/DAYS) - 1) * 100 / d\\n\",\n    \"c2_bar = ((1 + d * c2 / 100).prod(axis=0)**(1/DAYS) - 1) * 100 / d\\n\",\n    \"r_bar = ((1 + d * (c1_bar + c2_bar) / 100) ** DAYS - 1) * 100 / (d * DAYS)\\n\",\n    \"np.histogram(np.abs(r_true-r_bar), bins=[0, 5e-7, 1e-6, 5e-6, 1e-5, 5e-5, 1]) \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"c56f3b7d-07ce-4007-bf57-bda4bc2259cb\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"50.3 µs ± 1.22 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"composite_curve = CompositeCurve(\\n\",\n    \"    (\\n\",\n    \"        Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.95}, id=\\\"C1_\\\"),\\n\",\n    \"        Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.99}, id=\\\"C2_\\\"),\\n\",\n    \"    )\\n\",\n    \")\\n\",\n    \"%timeit composite_curve.rate(dt(2022, 6, 1), \\\"1y\\\", approximate=True)  \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"7e74017e-41f0-424a-bd17-aed0d168a8df\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"21.9 ms ± 890 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"%timeit composite_curve.rate(dt(2022, 6, 1), \\\"1y\\\", approximate=False)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5f2769bb-0f25-4d5e-996f-5684e1f18a26\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Curve operations: shift\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"64caaec4-072a-4dd5-a9ef-ac4b95852a7f\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"1.9926509362075961\\n\",\n      \"2.4926509362108717\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98})\\n\",\n    \"print(curve.rate(dt(2022, 2, 1), \\\"1d\\\"))\\n\",\n    \"print(curve.shift(50).rate(dt(2022, 2, 1), \\\"1d\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"id\": \"5a386ff1-1577-42b1-8d13-7d53dc509aa5\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"2.050958904109589\\n\",\n      \"2.550958904109589\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"line_curve = LineCurve({dt(2022, 1, 1): 2.0, dt(2023, 1, 1): 2.6})\\n\",\n    \"print(line_curve.rate(dt(2022, 2, 1), \\\"1d\\\"))\\n\",\n    \"print(line_curve.shift(50).rate(dt(2022, 2, 1), \\\"1d\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"44ebfa6c-72ee-473e-9299-e1727a8884b7\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Curve operations: translate\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"id\": \"bdf0aca9-39e0-406b-9d64-9bfcfc9ffed6\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"3.8711064912719806 3.8711064912719806\\n\",\n      \"3.8709012910311813 3.8709012910311813\\n\",\n      \"3.8706902731000525 3.870690273092059\\n\",\n      \"0.0 0.0\\n\",\n      \"0.0 0.0\\n\",\n      \"3.8971038951416404 3.9052558203165333\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"for interpolation in [\\n\",\n    \"    \\\"linear\\\", \\\"log_linear\\\", \\\"linear_index\\\", \\\"flat_forward\\\", \\\"flat_backward\\\", \\\"linear_zero_rate\\\"\\n\",\n    \"]:\\n\",\n    \"    curve = Curve(\\n\",\n    \"        nodes={dt(2022, 1, 1): 1.0, dt(2022, 2, 1):0.998, dt(2022, 3, 1): 0.995}, \\n\",\n    \"        interpolation=interpolation\\n\",\n    \"    )\\n\",\n    \"    curve_translated = curve.translate(dt(2022, 1, 15)) \\n\",\n    \"    print(\\n\",\n    \"        curve.rate(dt(2022, 2, 15), \\\"1d\\\"),\\n\",\n    \"        curve_translated.rate(dt(2022, 2, 15), \\\"1d\\\") \\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"327672ae-28af-4e15-bfe1-0b5a52cedcc8\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Curve operations: roll\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"id\": \"6e4e86f8-ff29-48bb-a7f7-9985aa2f0748\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"2.1111503451809455\\n\",\n      \"2.1111503451809455\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"curve = Curve(\\n\",\n    \"    nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98, dt(2024, 1, 1): 0.97},\\n\",\n    \"    t=[dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1),\\n\",\n    \"       dt(2023, 1, 1),\\n\",\n    \"       dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)]\\n\",\n    \")\\n\",\n    \"print(curve.rate(dt(2022, 6, 1), \\\"1d\\\"))\\n\",\n    \"print(curve.roll(\\\"30d\\\").rate(dt(2022, 7, 1), \\\"1d\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"id\": \"d4e5fc59-aa88-48ec-a7f6-e5d13b10b1f3\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"2.3082258965546494\\n\",\n      \"2.3082258965546494\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"line_curve = LineCurve(\\n\",\n    \"    nodes={dt(2022, 1, 1): 2.0, dt(2023, 1, 1): 2.6, dt(2024, 1, 1): 2.5},\\n\",\n    \"    t=[dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1),\\n\",\n    \"       dt(2023, 1, 1),\\n\",\n    \"       dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)]\\n\",\n    \")\\n\",\n    \"print(line_curve.rate(dt(2022, 6, 1)))\\n\",\n    \"print(line_curve.roll(\\\"-31d\\\").rate(dt(2022, 5, 1), \\\"1d\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ee9951a7-1eea-4255-9e5c-2a4818983598\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Operations on CompositeCurves\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"id\": \"8fff533b-4ee2-4405-b6e3-bdf4fe53aadf\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"3.0252576094156325\"\n      ]\n     },\n     \"execution_count\": 14,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"composite_curve.rate(dt(2022, 6, 1), \\\"1d\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"id\": \"53409cb5-9512-43f1-8e9c-cb1886ed1f6e\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"3.525257609418908\"\n      ]\n     },\n     \"execution_count\": 15,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"composite_curve.shift(50).rate(dt(2022, 6, 1), \\\"1d\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"id\": \"89680e78-5318-4b56-af1d-8be7dae90ca4\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"3.025257609407639\"\n      ]\n     },\n     \"execution_count\": 16,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"composite_curve.roll(\\\"30d\\\").rate(dt(2022, 7, 1), \\\"1d\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"id\": \"b23d6958-903a-4442-98a7-d5585fe4d56c\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"3.0252576094156325\"\n      ]\n     },\n     \"execution_count\": 17,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"composite_curve.translate(dt(2022, 5, 1)).rate(dt(2022, 6, 1), \\\"1d\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding/scheduling.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"88003b38-8369-4263-b409-fe548b1250cb\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import dt\\n\",\n    \"from rateslib.scheduling import _get_unadjusted_roll\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"ac36569f-fa13-49ae-829d-458104892eed\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"15\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_roll(ueffective=dt(2022, 3, 15), utermination=dt(2023, 3, 15), eom=False)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"e5181913-6ea2-4ccc-893f-fdf8621e4534\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"28\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_roll(ueffective=dt(2022, 2, 28), utermination=dt(2023, 2, 28), eom=False)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"8bb47315-3042-4c9a-a8eb-3766be6a2fe7\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'eom'\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_roll(ueffective=dt(2022, 2, 28), utermination=dt(2023, 2, 28), eom=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"c46d63ff-99c8-48fa-a03d-93e90e0b7b1c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 60,\n   \"id\": \"bf3b7df1-9cd1-4a0d-988a-d7d80e7b9b08\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.scheduling import _generate_regular_schedule_unadjusted\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 61,\n   \"id\": \"19802482-fe5d-4e21-aa7b-8b419fe6245e\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"[datetime.datetime(2023, 3, 15, 0, 0),\\n\",\n       \" datetime.datetime(2023, 4, 19, 0, 0),\\n\",\n       \" datetime.datetime(2023, 5, 17, 0, 0),\\n\",\n       \" datetime.datetime(2023, 6, 21, 0, 0),\\n\",\n       \" datetime.datetime(2023, 7, 19, 0, 0),\\n\",\n       \" datetime.datetime(2023, 8, 16, 0, 0),\\n\",\n       \" datetime.datetime(2023, 9, 20, 0, 0)]\"\n      ]\n     },\n     \"execution_count\": 61,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"dates = [\\n\",\n    \"    d for d in \\n\",\n    \"    _generate_regular_schedule_unadjusted(\\n\",\n    \"        ueffective=dt(2023, 3, 15),\\n\",\n    \"        utermination=dt(2023, 9, 20),\\n\",\n    \"        frequency=\\\"M\\\",\\n\",\n    \"        roll=\\\"imm\\\"\\n\",\n    \"    )\\n\",\n    \"]\\n\",\n    \"dates\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"3b0dbdd3-5daa-4933-8cc2-5d08605bf57f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"04b4befd-8430-4471-9b44-b367343268b0\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"995ebd97-495e-4736-a5a1-780b5d87b0d1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"4e3a0787-082b-45eb-a769-6347e90a5190\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.scheduling import _check_unadjusted_regular_swap\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"id\": \"d0d910d9-a974-4167-bfd3-f4a9c772de9a\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(False, 'Roll day could not be inferred from given dates.')\"\n      ]\n     },\n     \"execution_count\": 13,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_check_unadjusted_regular_swap(\\n\",\n    \"        ueffective=dt(2022, 3, 16), utermination=dt(2022, 9, 21),\\n\",\n    \"        frequency=\\\"M\\\", roll=None, eom=False\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"id\": \"67d9c5e4-84c7-4f0b-afa6-896c8eebf521\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(True,\\n\",\n       \" {'ueffective': datetime.datetime(2022, 2, 28, 0, 0),\\n\",\n       \"  'utermination': datetime.datetime(2023, 2, 28, 0, 0),\\n\",\n       \"  'frequency': 'M',\\n\",\n       \"  'roll': 'eom',\\n\",\n       \"  'eom': True})\"\n      ]\n     },\n     \"execution_count\": 12,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_check_unadjusted_regular_swap(\\n\",\n    \"        ueffective=dt(2022, 2, 28), utermination=dt(2023, 2, 28),\\n\",\n    \"        frequency=\\\"M\\\", eom=True, roll=None\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"id\": \"cc11e373-87c8-49d8-859c-06610031c44a\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.scheduling import _get_unadjusted_short_stub_date\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 24,\n   \"id\": \"d02c470c-727b-4878-9658-5f63e5d4bace\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"datetime.datetime(2022, 6, 30, 0, 0)\"\n      ]\n     },\n     \"execution_count\": 24,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_short_stub_date(\\n\",\n    \"        ueffective=dt(2022, 6, 15), utermination=dt(2023, 2, 28),\\n\",\n    \"        frequency=\\\"M\\\", eom=True, roll=None, stub_side=\\\"FRONT\\\"\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 25,\n   \"id\": \"de5285b8-50bf-4a84-af4e-a0113a63bf50\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"datetime.datetime(2022, 6, 28, 0, 0)\"\n      ]\n     },\n     \"execution_count\": 25,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_short_stub_date(\\n\",\n    \"        ueffective=dt(2022, 6, 15), utermination=dt(2023, 2, 28),\\n\",\n    \"        frequency=\\\"M\\\", eom=False, roll=None, stub_side=\\\"FRONT\\\"\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 26,\n   \"id\": \"0135ff22-784f-4c43-8162-2577073a1927\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"datetime.datetime(2022, 6, 29, 0, 0)\"\n      ]\n     },\n     \"execution_count\": 26,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_short_stub_date(\\n\",\n    \"        ueffective=dt(2022, 6, 15), utermination=dt(2023, 2, 28),\\n\",\n    \"        frequency=\\\"M\\\", eom=True, roll=29, stub_side=\\\"FRONT\\\"\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ab24b046-b86b-4945-8767-381c53d00a04\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 47,\n   \"id\": \"19505125-6eed-43da-a541-32aab0f845d6\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.scheduling import _get_unadjusted_stub_date\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 50,\n   \"id\": \"daff6659-f94a-4cd6-ab65-018fb20af567\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"datetime.datetime(2022, 7, 31, 0, 0)\"\n      ]\n     },\n     \"execution_count\": 50,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_stub_date(\\n\",\n    \"        ueffective=dt(2022, 6, 15), utermination=dt(2023, 2, 28),\\n\",\n    \"        frequency=\\\"M\\\", eom=True, roll=None, stub=\\\"LONGFRONT\\\"\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 51,\n   \"id\": \"94e4ff12-a80a-44d1-85cd-b7ea2435cb52\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"datetime.datetime(2022, 7, 28, 0, 0)\"\n      ]\n     },\n     \"execution_count\": 51,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_stub_date(\\n\",\n    \"        ueffective=dt(2022, 6, 15), utermination=dt(2023, 2, 28),\\n\",\n    \"        frequency=\\\"M\\\", eom=False, roll=None, stub=\\\"LONGFRONT\\\"\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 52,\n   \"id\": \"2f6ddb5e-6322-43f0-9d76-6a989c3617a7\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"datetime.datetime(2022, 7, 29, 0, 0)\"\n      ]\n     },\n     \"execution_count\": 52,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_get_unadjusted_stub_date(\\n\",\n    \"        ueffective=dt(2022, 6, 15), utermination=dt(2023, 2, 28),\\n\",\n    \"        frequency=\\\"M\\\", eom=True, roll=29, stub=\\\"LONGFRONT\\\"\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"519ff189-e0df-44ba-a8f2-05da66123906\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 56,\n   \"id\": \"8afde93d-6f6d-4656-a456-bc69900e53f0\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.calendars import get_calendar\\n\",\n    \"from rateslib.scheduling import _check_regular_swap\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 58,\n   \"id\": \"ee7487e8-2b6d-437b-954f-26eb2ae913c4\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(True,\\n\",\n       \" {'ueffective': datetime.datetime(2022, 6, 5, 0, 0),\\n\",\n       \"  'utermination': datetime.datetime(2022, 12, 5, 0, 0),\\n\",\n       \"  'frequency': 'Q',\\n\",\n       \"  'roll': 5,\\n\",\n       \"  'eom': False})\"\n      ]\n     },\n     \"execution_count\": 58,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"_check_regular_swap(\\n\",\n    \"    effective=dt(2022, 6, 6),\\n\",\n    \"    termination=dt(2022, 12,  5),\\n\",\n    \"    frequency=\\\"Q\\\",\\n\",\n    \"    modifier=\\\"MF\\\",\\n\",\n    \"    eom=False,\\n\",\n    \"    roll=None,\\n\",\n    \"    calendar=get_calendar(\\\"bus\\\"),\\n\",\n    \")\\n\",\n    \"    \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"3957a681-a10a-4925-8fe9-07d227a46786\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/AutomaticDifferentiation.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"207f47dd-6e8d-4a49-8d4c-c775b157f8cb\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import *\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"4a2c4aa5-99a9-4a63-8b8a-3dc9fe2785ae\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Definitions of dual numbers\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b5149cbc-1a29-4f20-a40b-3980866b6914\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"z_x = Dual2(0.0, [\\\"x\\\"], [], [])\\n\",\n    \"z_x\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"26ce32a9-9f08-4477-8fd7-f98fed699362\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"z_x * z_x\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"5e6b947f-1ea1-44d6-8c7b-a7d9445f5158\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"(z_x * z_x).dual2\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3364bb0a-7fe0-43fa-bda4-c67c7e6e4630\",\n   \"metadata\": {},\n   \"source\": [\n    \"# General functions of dual numbers\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e48f6a4d-fe10-4ea5-9f39-85c310423e76\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import math\\n\",\n    \"def dual_sin(x: float | Dual) -> float | Dual:\\n\",\n    \"    if isinstance(x, Dual):\\n\",\n    \"        return Dual(math.sin(x.real), x.vars, math.cos(x.real) * x.dual)\\n\",\n    \"    return math.sin(x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d3cd2d23-0538-41e3-b94d-401bb7d2d35c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x = Dual(2.1, [\\\"y\\\"], [])\\n\",\n    \"dual_sin(x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e5cda5f5-c336-4e1c-abb6-2bf84db40352\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Upcasting and dynamic variables\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"79196a21-0418-47a3-9d65-a6448b57df06\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"first_dual = Dual(11.0, [\\\"x\\\", \\\"y\\\"], [3, 8])\\n\",\n    \"second_dual = Dual(-3.0, [\\\"y\\\", \\\"z\\\"], [-2, 5])\\n\",\n    \"first_dual + second_dual + 2.65\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"93465777-50f0-4b1d-ad6d-00b054a52a37\",\n   \"metadata\": {},\n   \"source\": [\n    \"# First order derivatives and performance\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e66cf2c2-26a9-4b87-9600-265d43dd98ce\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def func(x, y, z):\\n\",\n    \"    return x**6 + dual_exp(x/y) + dual_log(z)\\n\",\n    \"\\n\",\n    \"x, y, z = 2.0, 1.0, 2.0\\n\",\n    \"func(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ff535dd2-92af-4141-8343-78025f101278\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit func(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"827b29f9-9314-40f3-a02d-44072643ee75\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x, y, z = Dual(2.0, [\\\"x\\\"], []), Dual(1.0, [\\\"y\\\"], []), Dual(2.0, [\\\"z\\\"], [])\\n\",\n    \"func(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"5e5d6fd4-ea9d-4cad-888c-935a6eec92c5\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit func(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"1a1fb920-993f-4f9f-a023-7b946051f31c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x = Dual(2.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [1.0, 0.0, 0.0])\\n\",\n    \"y = Dual(1.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [0.0, 1.0, 0.0])\\n\",\n    \"z = Dual(2.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [0.0, 0.0, 1.0])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"6dbce6a4-54b5-46dd-ae43-7011aa21703e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit func(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ceb92f32-8064-46e1-a3ce-54b888759744\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x = Dual(2.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [1.0, 0.0, 0.0])\\n\",\n    \"y = Dual.vars_from(x, 1.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [0.0, 1.0, 0.0])\\n\",\n    \"z = Dual.vars_from(x, 2.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [0.0, 0.0, 1.0])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"94cc0d63-5705-4848-9207-76d1885ba7d4\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit func(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"170edec4-7a07-43ed-812f-2681225fd9b0\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Numerical differentiation\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"a616687b-e18f-491a-867e-efa6a4352262\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def df_fwd_diff(f, x, y, z):\\n\",\n    \"    base = f(x, y, z)\\n\",\n    \"    dh = 1e-10\\n\",\n    \"    dx = f(x+dh, y, z) - base\\n\",\n    \"    dy = f(x, y+dh, z) - base\\n\",\n    \"    dz = f(x, y, z+dh) - base\\n\",\n    \"    return base, dx/dh, dy/dh, dz/dh\\n\",\n    \"\\n\",\n    \"%timeit df_fwd_diff(func, 2.0, 1.0, 2.0)    \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5841581d-1acf-4a97-b4c5-f5136ee9b4b1\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Functions with execution line delay\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"a47f8cea-1e05-4387-8e8f-f09030b39aa0\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import time\\n\",\n    \"def func_complex(x, y, z):\\n\",\n    \"    time.sleep(0.000025)\\n\",\n    \"    return x**6 + dual_exp(x/y) + dual_log(z)\\n\",\n    \"\\n\",\n    \"%timeit func_complex(2.0, 1.0, 2.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"16867e87-f124-45de-81c6-ddf251bc07c8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit func_complex(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e3273e1f-af3f-4b4f-bd37-93893cfd2055\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit df_fwd_diff(func_complex, 2.0, 1.0, 2.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3dd7f29a-5c85-4b51-8877-c198ca0c52f4\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Second order derivatives\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"65332b6c-e18d-4cd2-b4a7-434b4098a70f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x = Dual2(2.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [1.0, 0.0, 0.0], [])\\n\",\n    \"y = Dual2(1.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [0.0, 1.0, 0.0], [])\\n\",\n    \"z = Dual2(2.0, [\\\"x\\\", \\\"y\\\", \\\"z\\\"], [0.0, 0.0, 1.0], [])\\n\",\n    \"func(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"0ed2c033-9373-469c-a857-47aa3a32892c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"gradient(func(x, y, z), [\\\"x\\\", \\\"y\\\"], order=2)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"72618649-a785-40d0-b8ea-f297a1f50621\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit func(x, y, z)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"17ff6e34-0a43-4158-961a-5c3fe51d167b\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Exogenous Variables\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"959084c7-cc2a-4206-adaa-7d663a2c6a7e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x = Variable(1.5, [\\\"x\\\"])\\n\",\n    \"y = Variable(3.9, [\\\"y\\\"])\\n\",\n    \"x * y\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"3c30894e-04a8-429a-bbea-4d8d3fc2144c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"defaults._global_ad_order = 2\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"dcbe5c20-ce23-4546-a9c5-bf4fe3753f01\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x * y\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"a78d3ed5-96e5-4c87-8933-62245877a379\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"(x * y).dual2\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ec449dab-7251-4225-b2e0-eb5212f9095a\",\n   \"metadata\": {},\n   \"source\": [\n    \"# One Dimensional Newton-Raphson Algorithm\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"04dc714b-49d8-4f77-b199-da3bd10416f8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.dual import newton_1dim\\n\",\n    \"\\n\",\n    \"def f(g, s):\\n\",\n    \"    f0 = g**2 - s   # Function value\\n\",\n    \"    f1 = 2*g        # Analytical derivative is required\\n\",\n    \"    return f0, f1\\n\",\n    \"\\n\",\n    \"s = Dual(2.0, [\\\"s\\\"], [])\\n\",\n    \"newton_1dim(f, g0=1.0, args=(s,))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"9b6df9e8-f5ae-4d3d-ac86-2cee332bf1de\",\n   \"metadata\": {},\n   \"source\": [\n    \"# One Dimensional Inverse Function Theorem\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"289e0c45-7a40-4c24-b9f1-f1efe21b1966\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.dual import ift_1dim\\n\",\n    \"\\n\",\n    \"def s(g):\\n\",\n    \"    return dual_exp(g) + g**2\\n\",\n    \"\\n\",\n    \"s_tgt = Dual(2.0, [\\\"s\\\"], [])\\n\",\n    \"ift_1dim(s, s_tgt, h=\\\"modified_brent\\\", ini_h_args=(0.0, 2.0))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"82100c0b-63bc-4d19-bf62-848bb6cc91b3\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Normal functions\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"06f5fa1d-8084-46a2-b9a1-94db300ec28e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.dual import dual_norm_pdf, dual_norm_cdf, dual_inv_norm_cdf\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ed9396d9-88d6-4817-bd86-c6a93fc3222e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dual_norm_pdf(Variable(1.5, [\\\"u\\\"]))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"5e661276-6c82-4f99-9a18-0e1bc0a71c83\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dual_norm_cdf(Variable(1.5, [\\\"u\\\"]))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ced0f08c-0ebf-4274-82e9-fe81480ab0fa\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dual_inv_norm_cdf(Variable(0.933193, [\\\"v\\\"]))\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.13.0\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/Calendars.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2362d250-6e1f-43d3-a853-4e53db61ef19\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import *\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7c691656-ba4f-4278-8849-a75fc13b83f8\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Timings\\n\",\n    \"\\n\",\n    \"Get a calendar straight from a hash table.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"0d8d2453-d45d-44a1-9794-31f9315f2de4\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit get_calendar(\\\"ldn\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"09dcad09-c185-48c3-9773-818f3af0d5db\",\n   \"metadata\": {},\n   \"source\": [\n    \"Construct a ``Cal`` directly from a list of holidays and week mask.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"375dc5c5-afbb-43a4-bd45-abb989ce3057\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"cal = get_calendar(\\\"ldn\\\")\\n\",\n    \"holidays = cal.holidays\\n\",\n    \"%timeit Cal(holidays=holidays, week_mask=[5,6])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e17cff9e-1685-491c-8b68-d43a5bd6f6d4\",\n   \"metadata\": {},\n   \"source\": [\n    \"Get a ``NamedCal`` parsed and constructed in Python.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9096fea1-5cf6-4866-8f2d-2f549092be48\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit get_calendar(\\\"ldn,tgt\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"359e90af-641f-4753-a031-105a5fe0d54e\",\n   \"metadata\": {},\n   \"source\": [\n    \"Construct a ``UnionCal`` directly from multiple ``Cal``.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"07ef7035-3406-4f6f-91fe-5232599ee91c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"c1 = Cal(holidays=get_calendar(\\\"ldn\\\", named=False).holidays, week_mask=[5,6])\\n\",\n    \"c2 = Cal(holidays=get_calendar(\\\"tgt\\\", named=False).holidays, week_mask=[5,6])\\n\",\n    \"\\n\",\n    \"%timeit UnionCal([c1, c2])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ad0bd111-3c5d-4f93-a097-ecdf3cdb6090\",\n   \"metadata\": {},\n   \"source\": [\n    \"Add a new calendar to ``defaults.calendars`` and fetch that directly.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"3db567be-5110-41e5-a36e-c8a7c8fd9445\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"defaults.calendars[\\\"ldn,tgt\\\"] = get_calendar(\\\"ldn,tgt\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e1a030ff-3403-4b5d-a271-7154c62e5597\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit get_calendar(\\\"ldn,tgt\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"97dd17e2-5b84-4a9c-ba5d-017ba5815ee2\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Tenor Manipulations\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"c1afb68c-f364-47cb-8818-63736ec0a911\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"add_tenor(dt(2001, 9, 28), \\\"-6m\\\", modifier=\\\"MF\\\", calendar=\\\"LDN\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"993f5d28-6945-44ea-a83d-d7a66a80256f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"add_tenor(dt(2001, 9, 28), \\\"-6m\\\", modifier=\\\"MF\\\", calendar=\\\"LDN\\\", roll=31)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"8bcddb24-17ab-46ca-a8c1-77c6e4fe2ac8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"add_tenor(dt(2001, 9, 28), \\\"-6m\\\", modifier=\\\"MF\\\", calendar=\\\"LDN\\\", roll=29)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"32a400ca-6e1c-4f0d-880f-bf256e6ce776\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Associated Settlement Calendars\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"f38317db-2936-496e-a29e-3a7a5fcaa6b2\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"tgt_and_nyc = get_calendar(\\\"tgt,nyc\\\")\\n\",\n    \"tgt_and_nyc.add_bus_days(dt(2009, 11, 10), 2, True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"adc402b7-de57-4bc8-afb0-652a61c150d9\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"tgt_plus_nyc_settle = get_calendar(\\\"tgt|nyc\\\")\\n\",\n    \"tgt_plus_nyc_settle.add_bus_days(dt(2009, 11, 10), 2, True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"79f4e7e4-c5ed-4d1e-ad55-669838a1b2ff\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"tgt_plus_nyc_settle.add_bus_days(dt(2009, 11, 10), 1, settlement=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"90585e34-914d-4292-a6e8-9d146ba432de\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"tgt_plus_nyc_settle.add_bus_days(dt(2009, 11, 10), 1, settlement=False)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.0\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/Cookbook.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a731e2a1-7df1-4627-87a4-eece8b11f3ec\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Turns\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b19074cb-c470-4da9-8c03-5db7f134bd4d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import *\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"bba57742-a6f2-4c05-b221-6af17deab2bf\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve(\\n\",\n    \"    nodes={dt(2022, 12, 1): 1.0, dt(2023, 2, 1): 1.0}, \\n\",\n    \"    interpolation=\\\"log_linear\\\"\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e7d887d4-7ed4-477e-b64b-08707dd27d27\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve({\\n\",\n    \"    dt(2022, 12, 1): 1.0,\\n\",\n    \"    dt(2022, 12, 31): 1.0,\\n\",\n    \"    dt(2023, 1, 1): 1.0,\\n\",\n    \"    dt(2023, 2, 1): 1.0,\\n\",\n    \"}, interpolation=\\\"log_linear\\\")\\n\",\n    \"instruments = [\\n\",\n    \"    IRS(dt(2022, 12, 1), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"    Spread(\\n\",\n    \"        IRS(dt(2022, 12, 30), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"        IRS(dt(2022, 12, 31), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"    ),\\n\",\n    \"    Spread(\\n\",\n    \"        IRS(dt(2022, 12, 31), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"        IRS(dt(2023, 1, 1), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"    ), \\n\",\n    \"]\\n\",\n    \"solver = Solver(curves=[curve], instruments=instruments, s=[0.0, -0.5, 0.5])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ba791a79-f913-44ff-8631-c4c65b9a9a28\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"instruments = [\\n\",\n    \"    IRS(dt(2022, 12, 1), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"    Spread(\\n\",\n    \"        IRS(dt(2022, 12, 30), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"        IRS(dt(2022, 12, 31), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"    ),\\n\",\n    \"    IRS(dt(2023, 1, 1), \\\"1d\\\", \\\"A\\\", curves=curve),\\n\",\n    \"]\\n\",\n    \"solver = Solver(curves=[curve], instruments=instruments, s=[0.0, -50.0, 0.0])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"c662bd2b-118c-4016-9c24-d8141d5c3a2c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve.plot(\\\"1b\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"93a4f1b0-cacc-4e3c-933f-6305b21b3d0f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"linecurve = LineCurve({\\n\",\n    \"        dt(2022, 12, 1): 0.0,\\n\",\n    \"        dt(2022, 12, 31): -50.0,\\n\",\n    \"        dt(2023, 1, 1): 0.0,\\n\",\n    \"}, interpolation=\\\"flat_forward\\\")\\n\",\n    \"instruments = [\\n\",\n    \"    Value(dt(2022, 12, 1), curves=linecurve),\\n\",\n    \"    Value(dt(2022, 12, 31), curves=linecurve),\\n\",\n    \"    Value(dt(2023, 1, 1), curves=linecurve),\\n\",\n    \"]\\n\",\n    \"solver = Solver(curves=[linecurve], instruments=instruments, s=[0.0, -0.5, 0.0])\\n\",\n    \"linecurve.plot(\\\"1b\\\", right=dt(2023, 2, 1))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"596da5c8-04c9-4668-a8ec-755c788e5d77\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Injecting turns to spline curves\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2545ff72-0a2c-46e3-a1bb-ab0ef94f211d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"turn_curve = Curve({\\n\",\n    \"    dt(2022, 12, 1): 1.0,\\n\",\n    \"    dt(2022, 12, 31): 1.0,\\n\",\n    \"    dt(2023, 1, 1): 1.0,\\n\",\n    \"    dt(2023, 2, 1): 1.0,\\n\",\n    \"}, interpolation=\\\"log_linear\\\")\\n\",\n    \"cubic_curve = Curve({\\n\",\n    \"    dt(2022, 12, 1): 1.0,\\n\",\n    \"    dt(2022, 12, 21): 1.0,\\n\",\n    \"    dt(2023, 1, 11): 1.0,\\n\",\n    \"    dt(2023, 2, 1): 1.0,\\n\",\n    \"}, t = [\\n\",\n    \"    dt(2022, 12, 1), dt(2022, 12, 1), dt(2022, 12, 1), dt(2022, 12, 1),\\n\",\n    \"    dt(2022, 12, 21),\\n\",\n    \"    dt(2023, 1, 11),\\n\",\n    \"    dt(2023, 2, 1), dt(2023, 2, 1), dt(2023, 2, 1), dt(2023, 2, 1),\\n\",\n    \"])\\n\",\n    \"composite_curve = CompositeCurve([turn_curve, cubic_curve])\\n\",\n    \"instruments = [\\n\",\n    \"    IRS(dt(2022, 12, 1), \\\"1d\\\", \\\"A\\\", curves=turn_curve),\\n\",\n    \"    Spread(\\n\",\n    \"        IRS(dt(2022, 12, 30), \\\"1d\\\", \\\"A\\\", curves=turn_curve),\\n\",\n    \"        IRS(dt(2022, 12, 31), \\\"1d\\\", \\\"A\\\", curves=turn_curve),\\n\",\n    \"    ),\\n\",\n    \"    IRS(dt(2023, 1, 1), \\\"1d\\\", \\\"A\\\", curves=turn_curve),\\n\",\n    \"    IRS(dt(2022, 12, 1), \\\"20d\\\", \\\"A\\\", curves=composite_curve),\\n\",\n    \"    IRS(dt(2022, 12, 21), \\\"20d\\\", \\\"A\\\", curves=composite_curve),\\n\",\n    \"    IRS(dt(2023, 1, 11), \\\"18d\\\", \\\"A\\\", curves=composite_curve),\\n\",\n    \"]\\n\",\n    \"solver = Solver(\\n\",\n    \"    curves=[turn_curve, cubic_curve, composite_curve], \\n\",\n    \"    instruments=instruments, \\n\",\n    \"    s=[0.0, -50.0, 0.0, 2.01, 2.175, 2.35],\\n\",\n    \"    instrument_labels=[\\\"zero1\\\", \\\"turn\\\", \\\"zero2\\\", \\\"irs1\\\", \\\"irs2\\\", \\\"irs3\\\"],\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"f418e922-efc7-4eb6-a715-46a5b12ae319\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"composite_curve.plot(\\\"1b\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a95663da-9904-4d57-9990-ffb3f9509672\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Irrational turns on tenor curves\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e94c4fbc-7a59-46bc-9770-3c115c82c298\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"turn_curve = LineCurve({\\n\",\n    \"    dt(2022, 9, 15): 0.0,\\n\",\n    \"    dt(2022, 10, 1): -0.20,\\n\",\n    \"    dt(2023, 1, 1): 0.0,\\n\",\n    \"}, interpolation=\\\"flat_forward\\\")\\n\",\n    \"fading_turn_curve = LineCurve({\\n\",\n    \"    dt(2022, 9, 15): 0.0,\\n\",\n    \"    dt(2022, 9, 30): 0.0,\\n\",\n    \"    dt(2022, 10, 1): -0.20,\\n\",\n    \"    dt(2022, 12, 31): -0.04,\\n\",\n    \"    dt(2023, 1, 1): 0.0,\\n\",\n    \"    dt(2023, 3, 15): 0.0,\\n\",\n    \"}, interpolation=\\\"linear\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"322b92ab-e10f-47b0-8847-5ae6e0eb5768\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"line_curve = LineCurve({\\n\",\n    \"    dt(2022, 9, 15): 1.0,\\n\",\n    \"    dt(2022, 12, 15): 1.0,\\n\",\n    \"    dt(2023, 3, 15): 1.0,\\n\",\n    \"}, interpolation=\\\"linear\\\")\\n\",\n    \"composite_curve=CompositeCurve([fading_turn_curve, line_curve], id=\\\"cc\\\")\\n\",\n    \"instruments = [\\n\",\n    \"    Value(dt(2022, 9, 15), curves=fading_turn_curve),\\n\",\n    \"    Value(dt(2022, 9, 30), curves=fading_turn_curve),\\n\",\n    \"    Value(dt(2022, 10, 1), curves=fading_turn_curve),\\n\",\n    \"    Value(dt(2022, 12, 31), curves=fading_turn_curve),\\n\",\n    \"    Value(dt(2023, 1, 1), curves=fading_turn_curve),\\n\",\n    \"    Value(dt(2023, 3, 15), curves=fading_turn_curve),\\n\",\n    \"    Value(dt(2022, 9, 15), curves=composite_curve),\\n\",\n    \"    Value(dt(2022, 12, 15), curves=composite_curve),\\n\",\n    \"    Value(dt(2023, 3, 15), curves=composite_curve),\\n\",\n    \"]\\n\",\n    \"solver = Solver(\\n\",\n    \"    curves=[fading_turn_curve, line_curve, composite_curve], \\n\",\n    \"    instruments=instruments, \\n\",\n    \"    s=[0.0, 0.0, -0.2, -0.04, 0.0, 0.0, 3.5, 3.7, 4.05],\\n\",\n    \"    instrument_labels=[\\\"zero1\\\", \\\"zero2\\\", \\\"turnA\\\", \\\"turnB\\\", \\\"zero3\\\", \\\"zero4\\\", \\\"fra1\\\", \\\"fra2\\\", \\\"fra3\\\"],\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"de87d60c-351e-4e19-b380-a4d24b97b956\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"composite_curve.plot(\\\"1b\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5b810412-99f3-4192-8b67-a3beeee18fb4\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Analysing roll on trade strategies\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"74f4c34a-ff5d-4c34-9fa1-ad208e13d8cf\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve(\\n\",\n    \"    nodes={\\n\",\n    \"        dt(2024, 1, 1): 1.0,\\n\",\n    \"        dt(2025, 1, 1): 0.96,\\n\",\n    \"        dt(2026, 1, 1): 0.935,\\n\",\n    \"        dt(2027, 1, 1): 0.915,\\n\",\n    \"    },\\n\",\n    \"    convention=\\\"act360\\\",\\n\",\n    \"    t=[\\n\",\n    \"        dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1),\\n\",\n    \"        dt(2025, 1, 1), dt(2026, 1, 1),\\n\",\n    \"        dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1)\\n\",\n    \"    ],\\n\",\n    \")\\n\",\n    \"irs = IRS(\\n\",\n    \"    effective=dt(2024, 1, 1),\\n\",\n    \"    termination=\\\"18m\\\",\\n\",\n    \"    spec=\\\"usd_irs\\\",\\n\",\n    \")\\n\",\n    \"irs.rate(curve)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"179e8bac-63d9-4eef-a61d-25d284dd7a76\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"irs.rate(curve.roll(\\\"6w\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"066f8cf4-47e3-46cf-adff-8487abbf3e17\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Stepping underspecified Curves on central bank effective dates\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"118da85c-15bb-46ff-902a-c2cd40893073\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve(\\n\",\n    \"    nodes={\\n\",\n    \"        dt(2024, 1, 31): 1.00, dt(2024, 2, 2): 1.00, dt(2024, 3, 13): 1.00, \\n\",\n    \"        dt(2024, 4, 17): 1.0, dt(2024, 6, 12): 1.0, dt(2024, 7, 24): 1.0,\\n\",\n    \"        dt(2024, 9, 18): 1.0, dt(2024, 10, 23): 1.0, dt(2024, 12, 18): 1.0,\\n\",\n    \"        dt(2025, 1, 29): 1.0, dt(2025, 7, 31): 1.0,\\n\",\n    \"    },\\n\",\n    \"    convention=\\\"act360\\\", interpolation=\\\"log_linear\\\", calendar=\\\"tgt\\\", id=\\\"estr\\\",\\n\",\n    \")\\n\",\n    \"instruments = [\\n\",\n    \"    IRS(dt(2024, 1, 31), \\\"1b\\\", spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # O/N rate\\n\",\n    \"    IRS(dt(2024, 2, 2), dt(2024, 3, 13), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 3, 13), dt(2024, 4, 17), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 3, 20), dt(2024, 6, 19), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # IMM\\n\",\n    \"    IRS(dt(2024, 6, 19), dt(2024, 9, 18), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # IMM\\n\",\n    \"    IRS(dt(2024, 9, 18), dt(2024, 12, 18), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # IMM\\n\",\n    \"    IRS(dt(2024, 12, 18), dt(2025, 3, 19), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # IMM\\n\",\n    \"]\\n\",\n    \"pps = [  # policy periods\\n\",\n    \"    IRS(dt(2024, 2, 2), dt(2024, 3, 13), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 3, 13), dt(2024, 4, 17), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 4, 17), dt(2024, 6, 12), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 6, 12), dt(2024, 7, 24), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 7, 24), dt(2024, 9, 18), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 9, 18), dt(2024, 10, 2), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 10, 23), dt(2024, 12, 18), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2024, 12, 18), dt(2025, 1, 29), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"    IRS(dt(2025, 1, 29), dt(2025, 3, 15), spec=\\\"eur_irs\\\", curves=\\\"estr\\\"),  # MPC\\n\",\n    \"]\\n\",\n    \"curvature = [\\n\",\n    \"    Fly(pps[2], pps[3], pps[4]), \\n\",\n    \"    Fly(pps[4], pps[5], pps[6]), \\n\",\n    \"    Fly(pps[6], pps[7], pps[8]),\\n\",\n    \"]\\n\",\n    \"solver = Solver(\\n\",\n    \"    curves=[curve],\\n\",\n    \"    instruments=instruments+curvature,\\n\",\n    \"    weights=[1.0] * 7 + [1e-8] * 3,\\n\",\n    \"    s=[3.899, 3.904, 3.859, 3.692, 3.215, 2.725, 2.37] + [0.0] * 3,\\n\",\n    \"    instrument_labels=[\\n\",\n    \"        \\\"depo\\\", \\\"1r\\\", \\\"2r\\\", \\\"1f\\\", \\\"2f\\\", \\\"3f\\\", \\\"4f\\\", \\\"cv0\\\", \\\"cv1\\\", \\\"cv2\\\"\\n\",\n    \"    ],\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"bc6cdbaa-255c-4c33-b163-0d2a14a0f57a\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve.plot(\\\"1b\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/CurveSolving.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"95844b55-1388-4bed-ae0e-63dd3296d868\",\n   \"metadata\": {},\n   \"source\": [\n    \"### This chapter on Curve Solving has no actionable 'rateslib' code listing\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/Curves.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f8825706-c252-40d7-8075-b438f5756093\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Curves\\n\",\n    \"\\n\",\n    \"### CompositeCurve example\\n\",\n    \"\\n\",\n    \"The first section here regards efficient operations and compositing two curves.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"c88c3ce0-72f1-4182-a6c0-36209ccc9954\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import dt, defaults\\n\",\n    \"from rateslib.curves import Curve, LineCurve, CompositeCurve, MultiCsaCurve\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9cbc5699-fa68-46cd-8e75-3752d078977c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"line_curve1 = LineCurve({dt(2022, 1, 1): 2.0, dt(2022, 1, 3): 4.0}, id=\\\"C1_\\\")\\n\",\n    \"line_curve2 = LineCurve({dt(2022, 1, 1): 0.5, dt(2022, 1, 3): 1.0}, id=\\\"C2_\\\")\\n\",\n    \"composite_curve = CompositeCurve(curves=(line_curve1, line_curve2))\\n\",\n    \"composite_curve.rate(dt(2022, 1, 2))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"fd5f49ac-ed99-4422-844a-13c657b823f1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"line_curve1._set_ad_order(1)\\n\",\n    \"line_curve2._set_ad_order(1)\\n\",\n    \"composite_curve.rate(dt(2022, 1, 2))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8cb305f5-19a5-46b6-a9f2-82a2bd1f6592\",\n   \"metadata\": {},\n   \"source\": [\n    \"The code above demonstrates the summing of individual rates and of interoperability with Dual datatypes.\\n\",\n    \"\\n\",\n    \"Below measures rate lookup.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"cd18dd64-5f2c-47ed-8039-284be1c8fc33\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"defaults.curve_caching = False\\n\",\n    \"\\n\",\n    \"composite_curve = CompositeCurve(\\n\",\n    \"    (\\n\",\n    \"        Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.95}, id=\\\"C1_\\\"),\\n\",\n    \"        Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.99}, id=\\\"C2_\\\"),\\n\",\n    \"    )\\n\",\n    \")\\n\",\n    \"%timeit composite_curve.rate(dt(2022, 6, 1), \\\"1y\\\")  \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ef56982a-2ffc-45f5-9c2b-08517f22f026\",\n   \"metadata\": {},\n   \"source\": [\n    \"### MultiCsaCurve\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ec3bb527-c507-43bf-ad2e-e744ad40e351\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"c1 = Curve({dt(2022, 1, 1): 1.0, dt(2052, 1, 1): 0.5})\\n\",\n    \"c2 = Curve({dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 0.4, dt(2052, 1, 1):0.39}) \\n\",\n    \"mcc = MultiCsaCurve([c1, c2])\\n\",\n    \"\\n\",\n    \"%timeit c2[dt(2052, 1, 1)]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"78b70fb7-d04d-478c-8509-0e1f5c42573f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit mcc[dt(2052, 1, 1)]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"b658689c-65f2-4aae-992a-7fbf61f5d2c4\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Error in approximated rates and execution time\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"973e0754-edfc-42ce-9d0c-d2272c69465f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"MIN, MAX, SAMPLES, DAYS, d = 0, 4, 100000, 3, 1.0/365\\n\",\n    \"c1 = np.random.rand(DAYS, SAMPLES) * (MAX - MIN) + MIN\\n\",\n    \"c2 = np.random.rand(DAYS, SAMPLES) * (MAX - MIN) + MIN\\n\",\n    \"r_true=((1 + d * (c1 + c2) / 100).prod(axis=0) - 1) * 100 / (d * DAYS)\\n\",\n    \"c1_bar = ((1 + d * c1 / 100).prod(axis=0)**(1/DAYS) - 1) * 100 / d\\n\",\n    \"c2_bar = ((1 + d * c2 / 100).prod(axis=0)**(1/DAYS) - 1) * 100 / d\\n\",\n    \"r_bar = ((1 + d * (c1_bar + c2_bar) / 100) ** DAYS - 1) * 100 / (d * DAYS)\\n\",\n    \"np.histogram(np.abs(r_true-r_bar), bins=[0, 5e-7, 1e-6, 5e-6, 1e-5, 5e-5, 1]) \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5f2769bb-0f25-4d5e-996f-5684e1f18a26\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Curve operations: shift\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"64caaec4-072a-4dd5-a9ef-ac4b95852a7f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, convention=\\\"Act365F\\\", id=\\\"v\\\", ad=1)\\n\",\n    \"curve.rate(dt(2022, 6, 1), \\\"1b\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"90928588-dc74-4886-a044-8d6f69b9cfcf\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"shifted_curve = curve.shift(50)\\n\",\n    \"shifted_curve.rate(dt(2022, 6, 1), \\\"1b\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b488431e-7e87-4195-9cf0-82f10a3d9bd0\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"type(shifted_curve)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9803bfc3-52ce-4480-a972-a6aee2f9f100\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit curve.rate(dt(2022, 6, 1), \\\"1b\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d66ee277-43e7-4f5a-9d2f-2c72c6e76a15\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit shifted_curve.rate(dt(2022, 6, 1), \\\"1b\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"327672ae-28af-4e15-bfe1-0b5a52cedcc8\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Curve operations: roll\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"6e4e86f8-ff29-48bb-a7f7-9985aa2f0748\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve(\\n\",\n    \"    nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98, dt(2024, 1, 1): 0.97},\\n\",\n    \"    t=[dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1),\\n\",\n    \"       dt(2023, 1, 1),\\n\",\n    \"       dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)]\\n\",\n    \")\\n\",\n    \"print(curve.rate(dt(2022, 6, 1), \\\"1d\\\"))\\n\",\n    \"print(curve.roll(\\\"30d\\\").rate(dt(2022, 7, 1), \\\"1d\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d4e5fc59-aa88-48ec-a7f6-e5d13b10b1f3\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"line_curve = LineCurve(\\n\",\n    \"    nodes={dt(2022, 1, 1): 2.0, dt(2023, 1, 1): 2.6, dt(2024, 1, 1): 2.5},\\n\",\n    \"    t=[dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1),\\n\",\n    \"       dt(2023, 1, 1),\\n\",\n    \"       dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)]\\n\",\n    \")\\n\",\n    \"print(line_curve.rate(dt(2022, 6, 1)))\\n\",\n    \"print(line_curve.roll(\\\"-31d\\\").rate(dt(2022, 5, 1), \\\"1d\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"44ebfa6c-72ee-473e-9299-e1727a8884b7\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Curve operations: translate\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"bdf0aca9-39e0-406b-9d64-9bfcfc9ffed6\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"for interpolation in [\\n\",\n    \"    \\\"linear\\\", \\\"log_linear\\\", \\\"linear_index\\\", \\\"flat_forward\\\", \\\"flat_backward\\\", \\\"linear_zero_rate\\\"\\n\",\n    \"]:\\n\",\n    \"    curve = Curve(\\n\",\n    \"        nodes={dt(2022, 1, 1): 1.0, dt(2022, 2, 1):0.998, dt(2022, 3, 1): 0.995}, \\n\",\n    \"        interpolation=interpolation\\n\",\n    \"    )\\n\",\n    \"    curve_translated = curve.translate(dt(2022, 1, 15)) \\n\",\n    \"    print(\\n\",\n    \"        curve.rate(dt(2022, 2, 15), \\\"1d\\\"),\\n\",\n    \"        curve_translated.rate(dt(2022, 2, 15), \\\"1d\\\") \\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ee9951a7-1eea-4255-9e5c-2a4818983598\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Operations on CompositeCurves\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"8fff533b-4ee2-4405-b6e3-bdf4fe53aadf\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"composite_curve.rate(dt(2022, 6, 1), \\\"1d\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"53409cb5-9512-43f1-8e9c-cb1886ed1f6e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"composite_curve.shift(50).rate(dt(2022, 6, 1), \\\"1d\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"89680e78-5318-4b56-af1d-8be7dae90ca4\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"composite_curve.roll(\\\"30d\\\").rate(dt(2022, 7, 1), \\\"1d\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b23d6958-903a-4442-98a7-d5585fe4d56c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"composite_curve.translate(dt(2022, 5, 1)).rate(dt(2022, 6, 1), \\\"1d\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/FXRates.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"48397019-8e34-4802-9f82-eba040e083fd\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import FXRates, FXForwards, Dual, dt, Curve, gradient\\n\",\n    \"import numpy as np\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"26a71ddc-8f7a-4b70-a032-e80abfeded61\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Defined FXRates Systems - Errors\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9562c03a-5c29-4260-8470-392cc7ba21c1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"try:\\n\",\n    \"    FXRates(fx_rates={\\\"usdeur\\\": 0.9, \\\"noksek\\\": 1.10})\\n\",\n    \"except ValueError as e:\\n\",\n    \"    print(e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"0d5680fe-26fc-4b37-babe-9b6156195eac\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"try:\\n\",\n    \"    FXRates(fx_rates={\\\"usdeur\\\": 0.9, \\\"gbpusd\\\": 1.10, \\\"eurgbp\\\": 1.124})\\n\",\n    \"except ValueError as e:\\n\",\n    \"    print(e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"8e89f984-8ae4-490a-8dd1-dad6e72159bb\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"try:\\n\",\n    \"    FXRates ( fx_rates ={\\\" usdeur \\\": 0.90 , \\\" eurusd \\\": 1.11 , \\\" noksek \\\": 1.10})\\n\",\n    \"except ValueError as e:\\n\",\n    \"    print(e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"79afcdf3-34ca-440b-94c1-85d58ad8303c\",\n   \"metadata\": {},\n   \"source\": [\n    \"# FXRates Array\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9de92e9f-a7cd-4701-9900-510ba9d72cba\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr = FXRates({\\\"usdeur\\\": 2.0, \\\"usdgbp\\\": 2.5})\\n\",\n    \"from rateslib.dual.utils import _dual_float\\n\",\n    \"np.reshape([_dual_float(_) for _ in fxr.fx_array.ravel()], (3,3))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e4595d60-9093-410c-a256-49e5faab4bc1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr.rate(\\\"eurgbp\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7512e613-2166-4cdb-a48e-648ebb47fcfe\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Representation via Dual\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e9e9569b-2de9-49bb-8978-e6421721768f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"1e6  * (1/8.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"4d5d77d6-8b33-4b1b-8153-fa16531149f1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr = FXRates({\\\"usdnok\\\": 8.0})\\n\",\n    \"fxr.convert(1e6, \\\"nok\\\", \\\"usd\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9ba025b2-e121-4875-8d93-daebc796e967\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr._set_ad_order(2)\\n\",\n    \"fxr.convert(1e6, \\\"nok\\\", \\\"usd\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"40b16a07-dd1d-46e3-a3cc-413dd874fe6f\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Cash positions and base value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"88061cf6-1fca-4171-87bc-a1f1c1b20819\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr = FXRates({\\\"usdnok\\\": 8.0})\\n\",\n    \"fxr.currencies\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"f4fa9823-f17a-4c2d-b4fc-cb7ea8ce213f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# convert cash positions into an aggregated NOK value\\n\",\n    \"base_nok_value = fxr . convert_positions ([0 , 1000000] , \\\"nok\\\")\\n\",\n    \"base_nok_value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"8fec05c6-f5c2-4e39-957e-eacb87b6a323\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Convert cash positions into an aggregated USD value\\n\",\n    \"base_usd_value = fxr.convert_positions ([0 , 1000000] , \\\"usd\\\")\\n\",\n    \"base_usd_value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"058b7041-784d-44ab-8224-6b529e7d8a18\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Convert an aggregated USD value back to cash positions\\n\",\n    \"positions = fxr.positions(base_usd_value , \\\"usd\\\")\\n\",\n    \"positions\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"d6a071c6-9629-47f5-90cd-e12c9f1d363c\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Introducing additional currency exposures\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"a3929f34-f705-4bfa-baaf-e4fe39d10360\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr = FXRates ({\\\"usdeur\\\": 0.9 , \\\"eurnok \\\": 8.888889})\\n\",\n    \"fxr.currencies\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"132afa56-41cf-4832-a72f-f42e1bc2af69\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"base_value = fxr.convert_positions ([0 , 0, 1000000] , \\\"usd\\\")\\n\",\n    \"base_value\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d8ce0e36-9e05-444f-a4a9-5e5f7194cf0c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"positions = fxr.positions(base_value, \\\"usd\\\")\\n\",\n    \"positions\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"75a8e694-06de-49d5-ac54-fc6cfc766058\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"base_usd_value = Dual(125000 , [\\\"fx_usdnok\\\"], [-15625])\\n\",\n    \"positions = fxr.positions(base_usd_value, \\\"usd\\\")\\n\",\n    \"positions\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e310fc72-cd0b-4ec2-accd-fa8bc9d864bf\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr.convert_positions(positions, \\\"usd\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"fb72ccf5-49c2-49a8-80f5-f66e2da6c800\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Re-expression in Majors or Crosses\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e8c647e5-387b-4da2-962d-57b6b6ec6edd\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr_crosses = FXRates({\\\"eurusd\\\": 1.0 , \\\"gbpjpy\\\": 100 , \\\"eurjpy\\\": 100})\\n\",\n    \"fxr_crosses.convert(1, \\\"usd\\\", \\\"jpy\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"8d88c1c0-b5d4-4021-b9ca-36efd17e5710\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr_majors = fxr_crosses.restate ([\\\"eurusd\\\", \\\"usdjpy\\\", \\\"gbpusd\\\"])\\n\",\n    \"fxr_majors.convert(1, \\\"usd\\\", \\\"jpy\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8726169a-e1e2-4f75-a5c0-4dc83f37aa02\",\n   \"metadata\": {},\n   \"source\": [\n    \"# FXForwards\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"8256869c-9b79-4018-9793-65d9f067c464\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fx_rates = FXRates ({\\\"usdeur\\\": 0.9 , \\\"eurnok\\\": 8.888889} , dt(2022, 1, 3))\\n\",\n    \"fx_curves = {\\n\",\n    \"    # local currency curves first\\n\",\n    \"    \\\"usdusd\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.96}),\\n\",\n    \"    \\\"eureur\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}),\\n\",\n    \"    \\\"noknok\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}),\\n\",\n    \"    # cross - currency collateral curves next\\n\",\n    \"    \\\"eurusd\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.991}) ,\\n\",\n    \"    \\\"nokeur\\\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.978}) ,\\n\",\n    \"}\\n\",\n    \"fxf = FXForwards(fx_rates, fx_curves)\\n\",\n    \"fxf.rate(\\\"usdnok\\\", dt(2022, 8, 15))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"308b549d-6c64-4c50-bf85-71cbdb8e838d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxf.currencies\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"c86ef618-595e-4690-9849-70a86af32a02\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Paths are expressed by indexed currencies: 1 = \\\"EUR\\\"\\n\",\n    \"fxf._paths\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1c64ab97-058b-4e16-bfa0-6238d36c0a60\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Equivalence of Delta Risk\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"14ba7ac1-3597-455d-b638-16dc75b62155\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fx_rates = FXRates({\\\"usdeur\\\": 0.9, \\\"eurnok\\\": 8.888889}, dt(2022 , 1, 3))\\n\",\n    \"start, end = dt(2022, 1, 1), dt(2023, 1, 1)\\n\",\n    \"fx_curves = {\\n\",\n    \"    \\\"usdusd\\\": Curve({start: 1.0 , end: 0.96}, id=\\\"uu\\\", ad=1) ,\\n\",\n    \"    \\\"eureur\\\": Curve({start: 1.0 , end: 0.99}, id=\\\"ee\\\", ad=1) ,\\n\",\n    \"    \\\"eurusd\\\": Curve({start: 1.0 , end: 0.991}, id=\\\"eu\\\", ad=1) ,\\n\",\n    \"    \\\"noknok\\\": Curve({start: 1.0 , end: 0.98}, id=\\\"nn\\\", ad=1) ,\\n\",\n    \"    \\\"nokeur\\\": Curve({start: 1.0 , end: 0.978}, id=\\\"ne\\\", ad=1) ,\\n\",\n    \"}\\n\",\n    \"fxf = FXForwards(fx_rates, fx_curves)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"a307d877-f53e-4d1c-bdfd-3910dbcf044e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"discounted_nok = fx_curves[\\\"nokeur\\\"][dt(2022, 8, 15)] * 1000\\n\",\n    \"base_value_1 = discounted_nok * fxf.rate(\\\"nokusd\\\", dt(2022 , 1, 1))\\n\",\n    \"base_value_1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"feb226d6-fb62-4b50-b000-1dd7d986c5ad\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"gradient(base_value_1, [\\\"uu1\\\", \\\"ee1\\\", \\\"eu1\\\", \\\"nn1\\\", \\\"ne1\\\", \\\"fx_usdeur\\\", \\\"fx_eurnok\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ff513fb7-82ac-4ab7-a0b9-db13522b1052\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"forward_eur = fxf.rate(\\\"nokeur\\\", dt(2022, 8, 15)) * 1000\\n\",\n    \"discounted_eur = forward_eur * fx_curves[\\\"eureur\\\"][dt(2022, 8, 15)]\\n\",\n    \"base_value_2 = discounted_eur * fxf.rate(\\\"eurusd\\\", dt(2022, 1, 1))\\n\",\n    \"base_value_2\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"13543dab-b1c3-472f-a64d-641c8014de2b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"gradient(base_value_2, [\\\"uu1\\\", \\\"ee1\\\", \\\"eu1\\\", \\\"nn1\\\", \\\"ne1\\\", \\\"fx_usdeur\\\", \\\"fx_eurnok\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e68ee47b-b557-4ad0-bfa8-61660922841c\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Combining Settlement dates\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ce5bb6b0-aa86-41ba-bd60-8244ee9ded1a\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve ({ dt (2000 , 1, 1): 1.0 , dt (2001 , 1, 1): 0.99})\\n\",\n    \"fxr1 = FXRates ({\\\"eurusd\\\": 1.10 , \\\"gbpusd\\\": 1.30} , settlement =dt (2000 , 1, 1))\\n\",\n    \"fxr2 = FXRates ({\\\"usdcad\\\": 1.05} , settlement =dt (2000 , 1, 2))\\n\",\n    \"fxr3 = FXRates ({\\\"gbpjpy\\\": 100.0} , settlement =dt (2000 , 1, 3))\\n\",\n    \"try:\\n\",\n    \"    fxf = FXForwards (\\n\",\n    \"        fx_curves ={\\n\",\n    \"            \\\"usdusd\\\": curve, \\\"eureur\\\": curve, \\\"gbpgbp\\\": curve,\\n\",\n    \"            \\\"jpyjpy\\\": curve, \\\"cadcad\\\": curve, \\\"usdjpy\\\": curve,\\n\",\n    \"            \\\"eurjpy\\\": curve, \\\"eurcad\\\": curve, \\\"gbpcad\\\": curve,\\n\",\n    \"        },\\n\",\n    \"        fx_rates =[fxr1, fxr2, fxr3]\\n\",\n    \"    )\\n\",\n    \"except ValueError as e:\\n\",\n    \"    print(e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"19c3f55b-6e41-4fea-b057-5c71d1457f38\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Dual represenation\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2cc50ab7-f56f-4f2a-bacb-d7c98604a854\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"pv = Dual(100000 , [\\\"fx_eurusd\\\", \\\"fx_usdcad\\\"], [-100000 , 150000]) # base is USD\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"4eb92a49-fd50-4fb6-a23a-9111e5a6d5e1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr1 = FXRates ({\\\"eurusd\\\": 1.05} , settlement=dt(2022, 1, 3))\\n\",\n    \"fxr2 = FXRates ({\\\"usdcad\\\": 1.1} , settlement=dt(2022, 1, 2))\\n\",\n    \"fxf = FXForwards (\\n\",\n    \"    fx_rates =[fxr1, fxr2],\\n\",\n    \"    fx_curves ={\\n\",\n    \"        \\\"usdusd\\\": Curve ({dt(2022, 1, 1): 1.0 , dt(2022, 2, 1): 0.999}) ,\\n\",\n    \"        \\\"eureur\\\": Curve ({dt(2022, 1, 1): 1.0 , dt(2022, 2, 1): 0.999}) ,\\n\",\n    \"        \\\"cadcad\\\": Curve ({dt(2022, 1, 1): 1.0 , dt(2022, 2, 1): 0.999}) ,\\n\",\n    \"        \\\"usdeur\\\": Curve ({dt(2022, 1, 1): 1.0 , dt(2022, 2, 1): 0.999}) ,\\n\",\n    \"        \\\"cadusd\\\": Curve ({dt(2022, 1, 1): 1.0 , dt(2022, 2, 1): 0.999}) ,\\n\",\n    \"    }\\n\",\n    \")\\n\",\n    \"fxf.positions(pv, base=\\\"usd\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.0\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/FXVolatility.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"477eccd6-a966-41f8-b6db-954a2e3a09b0\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import *\\n\",\n    \"from pandas import Series\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ffb96542-04f8-46cb-865f-4ebc8681cb93\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Time Weighting for Volatility Surface\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"89f7bc83-a5e0-41be-8f6f-36cd9694a684\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxv = FXDeltaVolSurface( \\n\",\n    \"    eval_date=dt(2024, 7, 25),\\n\",\n    \"    expiries=[dt(2024, 7, 30), dt(2024, 8, 5)], \\n\",\n    \"    delta_indexes=[0.5],\\n\",\n    \"    node_values =[[10.0] , [10.0]] , \\n\",\n    \"    weights=Series(0.1, index=[\\n\",\n    \"        dt(2024, 7, 27), dt(2024, 7, 28), dt(2024, 8, 3), dt(2024, 8, 4)]\\n\",\n    \"    ),\\n\",\n    \"    delta_type=\\\"forward\\\", \\n\",\n    \")\\n\",\n    \"print(fxv.meta.weights[dt(2024, 7, 25):dt(2024, 8, 5)])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"67cdacf9-ba83-4433-aada-76b489ba78f0\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Sticky strike, sticky delta and Solver delta\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9d3e0eef-4831-4536-a7ee-789db442a18d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Define Curves\\n\",\n    \"usd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\\\"nyc\\\", id=\\\"usd\\\") \\n\",\n    \"eur = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\\\"tgt\\\", id=\\\"eur\\\") \\n\",\n    \"eurusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, id=\\\"eurusd\\\")\\n\",\n    \"\\n\",\n    \"# Create an FX Forward market with spot FX rate data\\n\",\n    \"spot = dt(2024, 5, 9)\\n\",\n    \"fxr = FXRates({\\\"eurusd\\\": 1.0760}, settlement=spot) \\n\",\n    \"fxf = FXForwards(\\n\",\n    \"    fx_rates=fxr, \\n\",\n    \"    fx_curves={\\\"eureur\\\": eur, \\\"usdusd\\\": usd, \\\"eurusd\\\": eurusd},\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"# Solve the Curves to market\\n\",\n    \"pre_solver = Solver(\\n\",\n    \"    curves=[eur, eurusd, usd], \\n\",\n    \"    instruments=[\\n\",\n    \"        IRS(spot, \\\"3W\\\", spec=\\\"eur_irs\\\", curves=\\\"eur\\\"),\\n\",\n    \"        IRS(spot, \\\"3W\\\", spec=\\\"usd_irs\\\", curves=\\\"usd\\\"),\\n\",\n    \"        FXSwap(spot, \\\"3W\\\", pair=\\\"eurusd\\\", curves=[None, \\\"eurusd\\\", None, \\\"usd\\\"]),\\n\",\n    \"    ],\\n\",\n    \"    s=[3.90, 5.32, 8.85], \\n\",\n    \"    fx=fxf,\\n\",\n    \"    id=\\\"fxf\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"98f143f4-adbc-40f0-a205-6291488cec5c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Define the Vol Smile\\n\",\n    \"smile = FXSabrSmile(\\n\",\n    \"    nodes={\\\"alpha\\\": 0.05, \\\"beta\\\": 1.0, \\\"rho\\\": 0.01, \\\"nu\\\": 0.03}, \\n\",\n    \"    eval_date=dt(2024, 5, 7),\\n\",\n    \"    expiry=dt(2024, 5, 28),\\n\",\n    \"    id=\\\"smile\\\",\\n\",\n    \"    pair=\\\"eurusd\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"4aa1bb20-522a-4c49-9ebf-88f11f214d5d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Collect FXOption arguments\\n\",\n    \"option_args = dict(\\n\",\n    \"    pair=\\\"eurusd\\\",\\n\",\n    \"    expiry=dt(2024, 5, 28), \\n\",\n    \"    calendar=\\\"tgt|fed\\\", \\n\",\n    \"    delta_type=\\\"spot\\\",\\n\",\n    \"    curves=[None, \\\"eurusd\\\", None, \\\"usd\\\"], \\n\",\n    \"    vol=\\\"smile\\\",\\n\",\n    \")\\n\",\n    \"# Calibrate the Smile to market option data\\n\",\n    \"solver = Solver( \\n\",\n    \"    pre_solvers=[pre_solver], \\n\",\n    \"    curves=[smile],\\n\",\n    \"    instruments=[\\n\",\n    \"        FXStraddle(strike=\\\"atm_delta\\\", **option_args),\\n\",\n    \"        FXRiskReversal(strike=(\\\"-25d\\\", \\\"25d\\\"), **option_args),\\n\",\n    \"        FXRiskReversal(strike=(\\\"-10d\\\", \\\"10d\\\"), **option_args),\\n\",\n    \"        FXBrokerFly(strike=((\\\"-25d\\\", \\\"25d\\\"), \\\"atm_delta\\\"), **option_args),\\n\",\n    \"        FXBrokerFly(strike=((\\\"-10d\\\", \\\"10d\\\"), \\\"atm_delta\\\"), **option_args),\\n\",\n    \"    ],\\n\",\n    \"    s=[5.493, -0.157, -0.289, 0.071, 0.238],\\n\",\n    \"    fx=fxf,\\n\",\n    \"    id=\\\"smile\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"6205d317-42ab-41fa-9fc2-a1dd6cbd4a37\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxc = FXCall(**option_args, notional=100e6, strike =1.07, premium=982144.59) # <-- mid-market premium giving zero NPV\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"5cfde62c-8f0f-4c09-abd7-251dc617300f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxc.delta(solver=solver).loc[(\\\"fx\\\", \\\"fx\\\", \\\"eurusd\\\")]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"5215023d-bb18-45fd-abc6-e9d77da2e99d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxc.gamma(solver=solver).loc[(\\\"usd\\\", \\\"usd\\\", \\\"fx\\\", \\\"fx\\\", \\\"eurusd\\\"), (\\\"fx\\\", \\\"fx\\\", \\\"eurusd\\\")]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"892be093-60d4-4054-a7e2-eb1da01a377b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxr.update({\\\"eurusd\\\": 1.0761})\\n\",\n    \"pre_solver.iterate()\\n\",\n    \"solver.iterate()\\n\",\n    \"fxc.npv(solver=solver)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2466685c-5b21-421e-a2db-3c78b7c46733\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Sticky delta\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"86a640c0-fb02-41ab-855b-d8dd745fb5a8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxc.analytic_greeks(solver=solver)[\\\"delta_sticky\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"7672a3fd-69ba-4e8e-907b-bd60a989a079\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxc.analytic_greeks(solver=solver)[\\\"delta\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"947fc271-0d9a-4050-9c1c-a0268b8625d0\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"option_args = dict(\\n\",\n    \"    pair=\\\"eurusd\\\",\\n\",\n    \"    expiry=dt(2024, 5, 28), \\n\",\n    \"    calendar=\\\"tgt|fed\\\", \\n\",\n    \"    delta_type=\\\"forward\\\",\\n\",\n    \"    curves=[None, \\\"eurusd\\\", None, \\\"usd\\\"], \\n\",\n    \"    vol=\\\"smile\\\",\\n\",\n    \")\\n\",\n    \"fxc = FXCall(**option_args, notional=100e6, strike =1.07, premium=982144.59) # <-- mid-market premium giving zero NPV\\n\",\n    \"fxc.analytic_greeks(solver=solver)[\\\"delta_sticky\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"51e3b16d-d6b3-4183-bda5-374859727d73\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fxc.analytic_greeks(solver=solver)[\\\"delta\\\"]\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/Instruments.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"07490ad9-c75c-403e-83d5-9f808360b49e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import FixedRateBond, dt, Bill, IndexFixedRateBond\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e8200863-fdbf-499f-82f0-88298eced48f\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Bond analogue methods\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"42cba7c3-f7e0-43df-8b3b-0d9a8c5f2367\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"bond = FixedRateBond (\\n\",\n    \"    effective=dt(2022, 1, 1) ,\\n\",\n    \"    termination=dt(2023, 1, 1) ,\\n\",\n    \"    fixed_rate=5.0,\\n\",\n    \"    spec =\\\"uk_gb\\\",\\n\",\n    \")\\n\",\n    \"bond.accrued(dt(2022, 4, 15))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"1725b831-3372-4100-a24e-2dc1a6b0b4d9\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"bond = FixedRateBond (\\n\",\n    \"    effective=dt(2022, 1, 1) ,\\n\",\n    \"    termination=dt(2023, 1, 1) ,\\n\",\n    \"    fixed_rate=5.0,\\n\",\n    \"    spec =\\\"ca_gb\\\",\\n\",\n    \")\\n\",\n    \"bond.accrued(dt(2022, 4, 15))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3cdcce1b-f71a-4104-82c8-f6cc60063409\",\n   \"metadata\": {},\n   \"source\": [\n    \"### YTM iteration\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"3c943458-8b8b-4a67-9ea6-f6e31dbc8e2b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"bond = FixedRateBond (\\n\",\n    \"    effective=dt(2000 , 1, 1) , termination =dt(2010 , 1, 1) ,\\n\",\n    \"    fixed_rate=2.5 , spec=\\\"us_gb\\\"\\n\",\n    \")\\n\",\n    \"bond.ytm(95.0, settlement=dt(2000, 7, 1))\\n\",\n    \"# ( -3.0000 , 2.0000 , 12.0000) - Initial interval requires 4 function evaluations\\n\",\n    \"# (2.0000 , 3.2858 , 12.0000) - Second interval requires 1 function evaluation\\n\",\n    \"# (2.0000 , 3.1063 , 3.2858) - Third interval requires 1 function evaluation\\n\",\n    \"# (3.1063 , 3.1120 , 3.2858) - Fourth interval requires 1 function evaluation\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7abe6651-eb34-466f-9b5f-745da0b8dcb2\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Bills\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"1c5d9f53-aea1-4307-836d-5338cff04346\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"bill = Bill(\\n\",\n    \"    effective=dt(2023, 5, 17),\\n\",\n    \"    termination=dt(2023, 9, 26),\\n\",\n    \"    spec=\\\"us_gbb\\\"\\n\",\n    \")\\n\",\n    \"bill.ytm(99.75, settlement=dt(2023 , 9, 7))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"5a24a850-47c8-41fd-b6a3-b5f7fd3806d7\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"bond = FixedRateBond (\\n\",\n    \"    effective=dt(2023, 3, 26),\\n\",\n    \"    termination=dt(2023, 9, 26),\\n\",\n    \"    fixed_rate=0.0,\\n\",\n    \"    spec=\\\"us_gb\\\",\\n\",\n    \")\\n\",\n    \"bond.ytm(99.75, settlement=dt(2023, 9, 7))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"778325e0-6d64-4215-8218-1484fce9e643\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Inflation Linked\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b4b0c470-fc27-47cd-a6ca-c8c839b12958\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ukt = FixedRateBond (\\n\",\n    \"    spec =\\\"uk_gb\\\",\\n\",\n    \"    effective =dt (2022 , 2, 1) ,\\n\",\n    \"    termination =\\\"2y\\\",\\n\",\n    \"    fixed_rate =2.5 ,\\n\",\n    \")\\n\",\n    \"ukt.price(ytm=3.0, settlement=dt(2023 , 10, 1))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"5afffec3-c117-44bf-b4e5-7a027391cde9\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ukti = IndexFixedRateBond (\\n\",\n    \"    spec=\\\"uk_gbi\\\",\\n\",\n    \"    effective=dt(2022, 2, 1) ,\\n\",\n    \"    termination=\\\"2y\\\",\\n\",\n    \"    fixed_rate=2.5,\\n\",\n    \"    index_base=100.0,\\n\",\n    \")\\n\",\n    \"ukti.price(ytm=3.0, settlement=dt(2023 , 10, 1))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"13397bf0-a8e8-413f-a4e4-d1af737fa97e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"e0e9a2c5-b929-4582-8ca5-40b38ca57562\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"456eef8f-0b15-4a51-a584-869c6d8c29d9\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.0\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/InterpolationAndSplines.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ac6cd685-e9ba-4813-ac98-c533012f10ed\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import *\\n\",\n    \"from rateslib.splines import evaluate\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e974b0e9-27b1-4df8-84e5-26805a44c22f\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Splines and AD\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"aa34c83c-2f5a-42d8-9493-a2d5c638abdd\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"pps = PPSplineDual(\\n\",\n    \"    k=3,\\n\",\n    \"    t=[0,0,0,4,4,4]\\n\",\n    \")\\n\",\n    \"pps.csolve(\\n\",\n    \"    tau=[1, 2, 3],\\n\",\n    \"    y=[\\n\",\n    \"        Dual(2.0, [\\\"y1\\\"], []),\\n\",\n    \"        Dual(1.0, [\\\"y2\\\"], []),\\n\",\n    \"        Dual(2.6, [\\\"y3\\\"], []),\\n\",\n    \"    ],\\n\",\n    \"    left_n=0,\\n\",\n    \"    right_n=0,\\n\",\n    \"    allow_lsq=False\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"f2626baa-e0f9-4161-98c4-c209636f9f34\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"pps.ppev_single(3.5)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"12b89c6b-7f90-4c17-b373-c0e88709f2e8\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Application to curves\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"c733ce29-72df-4807-825a-4a3268d0a133\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"spline = PPSplineF64(\\n\",\n    \"    k=4,\\n\",\n    \"    t=[_.timestamp() for _ in [\\n\",\n    \"        dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1),\\n\",\n    \"        dt(2023, 1, 1),\\n\",\n    \"        dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)\\n\",\n    \"    ]]\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"29c3f337-4cbc-46e3-8ff5-e8e4b6cfc5a9\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"spline.bsplmatrix(\\n\",\n    \"    tau=[_.timestamp() for _ in [\\n\",\n    \"        dt(2022, 1, 1), dt(2022, 1, 1), dt(2023, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)\\n\",\n    \"    ]],\\n\",\n    \"    left_n=2,\\n\",\n    \"    right_n=2\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"17c307c9-b798-47ee-910e-55cf57becc14\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"spline.csolve(\\n\",\n    \"    tau=[_.timestamp() for _ in [\\n\",\n    \"        dt(2022, 1, 1), dt(2022, 1, 1), dt(2023, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)\\n\",\n    \"    ]],\\n\",\n    \"    y=[0.0, 1.5, 1.85, 1.80, 0.0],\\n\",\n    \"    left_n=2,\\n\",\n    \"    right_n=2,\\n\",\n    \"    allow_lsq=False,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"aaa86c7a-d11d-45d6-815b-88c3220a55bb\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"spline.c\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"18c744d3-51a9-473b-99e3-1bb4c0b133a8\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Log-spline to DFs\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9b59245c-cf58-4a61-8603-ca751f0093cd\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from math import log, exp\\n\",\n    \"from datetime import timedelta\\n\",\n    \"\\n\",\n    \"log_spline = PPSplineF64(\\n\",\n    \"    k=4,\\n\",\n    \"    t=[_.timestamp() for _ in [\\n\",\n    \"        dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1),\\n\",\n    \"        dt(2023, 1, 1),\\n\",\n    \"        dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)\\n\",\n    \"    ]]\\n\",\n    \")\\n\",\n    \"log_spline.csolve(\\n\",\n    \"    tau=[_.timestamp() for _ in [\\n\",\n    \"        dt(2022,1,1), dt(2022,1,1), dt(2023,1,1), dt(2024,1,1), dt(2024,1,1)\\n\",\n    \"    ]], \\n\",\n    \"    y=[0, log(1.0), log(0.983), log(0.964), 0],\\n\",\n    \"    left_n=2,\\n\",\n    \"    right_n=2,\\n\",\n    \"    allow_lsq=False,\\n\",\n    \")\\n\",\n    \"log_spline.c\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"eb7d47f8-e4db-4815-bf0c-a1b2f6e27a15\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import matplotlib.pyplot as plt\\n\",\n    \"x = [_.timestamp() for _ in [\\n\",\n    \"    dt(2022, 1, 1) + timedelta(days=i) for i in range(720)]]\\n\",\n    \"fix, ax = plt.subplots(1,1)\\n\",\n    \"ax.plot(x, [exp(log_spline.ppev_single(_)) for _ in x])\\n\",\n    \"    \"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.0\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/Legs.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8ced5feb-616f-469c-8b1a-68bd7c9ef252\",\n   \"metadata\": {},\n   \"source\": [\n    \"### The chapter on Legs contains no code listings\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/Periods.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"49c1059d-3472-4797-a9a2-ae7efbc9ba1d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import Curve, FloatPeriod, dt, defaults\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ce44b389-41a3-4a48-8dad-9d9601eddc8e\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Expression of fixings risk in fixings table\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"7390a465-ddd6-424f-a417-98a4a6e5e310\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve ({dt(2022, 1, 1): 1.0 , dt(2025, 1, 1): 0.94},\\n\",\n    \"               id=\\\"euribor3m\\\", calendar=\\\"tgt\\\", convention=\\\"act360\\\"\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2d803cf3-ec6b-415f-97d9-d7d70fa511b3\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"imm_fp = FloatPeriod (\\n\",\n    \"    start=dt(2023, 3, 15),\\n\",\n    \"    end=dt(2023, 6, 21), # <--- IMM start and end dates\\n\",\n    \"    payment=dt(2023, 6, 21),\\n\",\n    \"    frequency=\\\"q\\\",\\n\",\n    \"    convention=\\\"act360\\\",\\n\",\n    \"    calendar=\\\"tgt\\\",\\n\",\n    \"    fixing_method=\\\"ibor\\\",\\n\",\n    \"    method_param=2,\\n\",\n    \"    notional=-1e6 # <-- Notional for period is -1mm\\n\",\n    \" )\\n\",\n    \"imm_fp.fixings_table(curve)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b6cbb243-dbc1-4d95-8a5d-9227e582a542\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve2 = Curve ({dt(2022, 1, 1): 1.0 , dt(2025, 1, 1): 0.94} ,\\n\",\n    \"                id=\\\"euribor1m\\\", calendar=\\\"tgt\\\", convention=\\\"act360\\\"\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"stub_fp = FloatPeriod (\\n\",\n    \"    start=dt(2022, 3, 14),\\n\",\n    \"    end=dt(2022, 5, 14), # <--- 2M stub tenor\\n\",\n    \"    payment =dt(2022, 5, 14),\\n\",\n    \"    frequency=\\\"q\\\",\\n\",\n    \"    convention=\\\"act360\\\",\\n\",\n    \"    calendar=\\\"tgt\\\",\\n\",\n    \"    fixing_method=\\\"ibor\\\",\\n\",\n    \"    method_param=2,\\n\",\n    \"    notional=-1e6 ,\\n\",\n    \"    stub=True,\\n\",\n    \")\\n\",\n    \"stub_fp.fixings_table({\\\"1m\\\": curve2 , \\\"3m\\\": curve}, disc_curve=curve2)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"784ed642-54b7-4864-89cc-4d4d7d5c4805\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"defaults.curve_caching = False\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ce13bfea-17a0-436c-aaab-ae9dfee8d2b8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"curve = Curve ({ dt(2022, 1, 4): 1.0, dt(2023, 1, 4): 0.98}, calendar=\\\"ldn\\\")\\n\",\n    \"float_period = FloatPeriod(start=dt(2022, 1, 4), end=dt(2023, 1, 4),\\n\",\n    \"                           payment=dt(2023, 1, 4) ,frequency =\\\"A\\\",\\n\",\n    \"                           fixing_method=\\\"rfr_lookback\\\", method_param=0)\\n\",\n    \"\\n\",\n    \"%timeit float_period.fixings_table(curve)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"46902d71-7080-48d5-83d0-5ef250329709\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%timeit float_period.fixings_table(curve, approximate=True)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.0\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "notebooks/coding_2/Scheduling.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"350b18e6-9448-4c28-9c45-444dabe50160\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib import *\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a454c714-2127-4a86-ad73-4f924210aee1\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Regular Unadjusted Schedules\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"a6a10173-9141-40f8-91f1-bb4c06b5a3be\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.scheduling import _generate_regular_schedule_unadjusted\\n\",\n    \"\\n\",\n    \"dates = list (_generate_regular_schedule_unadjusted (\\n\",\n    \"    ueffective=dt(2023 , 3, 15),\\n\",\n    \"    utermination=dt(2023 , 9, 20),\\n\",\n    \"    frequency=\\\"M\\\", \\n\",\n    \"    roll=\\\"imm\\\",\\n\",\n    \"))\\n\",\n    \"\\n\",\n    \"dates\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f8e77e1c-6af9-4885-aaea-1de5b77ccee5\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Stub and Roll Inference\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"75166f2f-e9bd-4de1-b78d-b43f0a931e7c\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Get a Roll\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d2905999-2165-480f-882c-a8b33e4aa105\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib . scheduling import _get_unadjusted_roll\\n\",\n    \"\\n\",\n    \"_get_unadjusted_roll (\\n\",\n    \"    ueffective =dt (2022 ,3 ,15) , utermination =dt (2023 ,3 ,15) , eom = True\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"7b582faa-05f3-40ea-9964-8677ed1dd250\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_get_unadjusted_roll (\\n\",\n    \"    ueffective =dt (2022 ,2 ,28) , utermination =dt (2023 ,2 ,28) , eom = False\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ba2b8488-8e88-4ca2-a24b-db73abf65ca9\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_get_unadjusted_roll (\\n\",\n    \"    ueffective =dt (2022 ,2 ,28) , utermination =dt (2023 ,2 ,28) , eom = True\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"b054fb9d-5a88-4d05-bd1b-2e0c7cc34e22\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Validate for a regular unadjusted swap\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"3860386d-a9ce-4afd-a1ce-b4c959b20f26\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib . scheduling import _check_unadjusted_regular_swap\\n\",\n    \"\\n\",\n    \"_check_unadjusted_regular_swap(\\n\",\n    \"    ueffective=dt(2022, 2, 28),\\n\",\n    \"    utermination=dt(2023, 2, 28),\\n\",\n    \"    frequency=\\\"M\\\",\\n\",\n    \"    eom=False,\\n\",\n    \"    roll=NoInput(0),\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2c83a2d2-fc06-44ae-bf81-680d15be987b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_check_unadjusted_regular_swap (\\n\",\n    \"    ueffective=dt (2022 , 2, 28) ,\\n\",\n    \"    utermination=dt (2023 , 2, 28) ,\\n\",\n    \"    frequency=\\\"M\\\",\\n\",\n    \"    eom=True,\\n\",\n    \"    roll=NoInput(0),\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"6b90f1c2-b9d9-4655-9936-999ffd613ddd\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_check_unadjusted_regular_swap (\\n\",\n    \"    ueffective=dt(2022 , 3, 16) ,\\n\",\n    \"    utermination=dt(2022 , 9, 21) ,\\n\",\n    \"    frequency=\\\"M\\\",\\n\",\n    \"    eom=False ,\\n\",\n    \"    roll=NoInput(0),\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d766c8b5-9102-4853-8b05-a23d76c0c893\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_check_unadjusted_regular_swap (\\n\",\n    \"    ueffective=dt(2022 , 3, 16) ,\\n\",\n    \"    utermination=dt(2022 , 9, 21) ,\\n\",\n    \"    frequency=\\\"M\\\",\\n\",\n    \"    eom=False ,\\n\",\n    \"    roll=\\\"imm\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"cace2c03-ce4c-46a3-a384-524752253ae3\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Get a stub\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"f7aac92e-1ab9-4f38-942c-10e37faa2ee1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.scheduling import _get_unadjusted_short_stub_date\\n\",\n    \"\\n\",\n    \"kws = dict (\\n\",\n    \"    ueffective =dt (2022 , 6, 15),\\n\",\n    \"    utermination =dt (2023 , 2, 28),  # <-- End of Fenruary\\n\",\n    \"    frequency =\\\"M\\\",\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"_get_unadjusted_short_stub_date (**kws , eom=False , roll=NoInput(0) ,stub_side=\\\"FRONT\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"ba070165-f691-432b-8819-76943a1f22a1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_get_unadjusted_short_stub_date(**kws, eom=True, roll=NoInput(0), stub_side=\\\"FRONT\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"711b2df6-dc4c-46da-b167-62bd861465a3\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_get_unadjusted_short_stub_date(**kws, eom=True, roll=29, stub_side=\\\"FRONT\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b89e7485-2667-424c-9288-9758151c0b22\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib . scheduling import _get_unadjusted_stub_date\\n\",\n    \"\\n\",\n    \"_get_unadjusted_stub_date(**kws, eom=False, roll=NoInput(0), stub=\\\"LONGFRONT\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d5e1af0b-855f-425d-8a50-0ce085e7612b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_get_unadjusted_stub_date(**kws, eom=True, roll=NoInput(0), stub=\\\"LONGFRONT\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b1514504-3a81-43b9-9ed5-d5f92bb28c55\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"_get_unadjusted_stub_date(**kws, eom=False, roll=29, stub=\\\"LONGFRONT\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"271c4d0d-ba1a-49fc-8ec6-eb355c46ed91\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Validate for a regular swap account for business days\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"983a9f5e-4500-4ffc-955f-25a7811ded3a\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from rateslib.scheduling import _check_regular_swap\\n\",\n    \"\\n\",\n    \"_check_regular_swap( \\n\",\n    \"    effective=dt(2022, 6, 6), \\n\",\n    \"    termination=dt(2022, 12, 5),\\n\",\n    \"    frequency=\\\"Q\\\",\\n\",\n    \"    eom=False,\\n\",\n    \"    roll=NoInput(0),\\n\",\n    \"    modifier =\\\"MF\\\",\\n\",\n    \"    calendar=get_calendar(\\\"bus\\\"),\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"18abf86d-81f8-4fd1-bb4c-5a8e0d06bb8c\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Schedule Building\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"562b95aa-550c-4654-bb4a-010ea7f39875\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sch = Schedule (\\n\",\n    \"    effective =\\\"1Y\\\",\\n\",\n    \"    termination =\\\"1Y\\\",\\n\",\n    \"    frequency =\\\"S\\\",\\n\",\n    \"    calendar =\\\"tgt\\\",\\n\",\n    \"    payment_lag =1,\\n\",\n    \"    eval_date=dt (2023 , 8, 17) ,\\n\",\n    \"    eval_mode=\\\"swaps_align\\\", \\n\",\n    \")\\n\",\n    \"print(sch)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"72ab63a2-fe4a-49bd-8baa-7ed046ad2b4f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sch = Schedule (\\n\",\n    \"    effective =\\\"1Y\\\",\\n\",\n    \"    termination =\\\"1Y\\\",\\n\",\n    \"    frequency =\\\"S\\\",\\n\",\n    \"    calendar =\\\"tgt\\\",\\n\",\n    \"    payment_lag =1,\\n\",\n    \"    eval_date =dt (2023 , 8, 17) ,\\n\",\n    \"    eval_mode=\\\"swaptions_align\\\", \\n\",\n    \")\\n\",\n    \"print(sch)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "pyproject.toml",
    "content": "# pyproject.toml\n\n[build-system]\nrequires = [\"maturin>=1.0,<2.0\"]\nbuild-backend = \"maturin\"\n\n[tool.maturin]\nmodule-name = \"rateslib.rs\"\npython-source = \"python\"\nbindings = \"pyo3\"\ncompatibility = \"linux\"\nfeatures = [\"pyo3/extension-module\"]\n# rustc --print target-list\n# https://doc.rust-lang.org/rustc/platform-support.html\n\n[project]\nname = \"rateslib\"\nversion = \"2.7.1\"\ndescription = \"A fixed income library for trading interest rates\"\nreadme = \"README.md\"\nauthors = [{ name = \"J H M Darbyshire\"}]\nlicense-files = [\"LICEN[CS]E\", \"COMMERCIAL_LICENCE\", \"COMMERCIAL_LICENCE_ADDENDUM1\"]\nkeywords = [\"interest rate\", \"derivatives\", \"swaps\", \"bonds\", \"fixed income\"]\ndependencies = [\n    \"numpy>=1.21.5,<3.0\",\n    \"matplotlib>=3.5.1,<4.0\",\n    \"pandas>=1.4.1,<4.0\",\n]\nrequires-python = \">=3.10\"\nclassifiers = [\n    \"Programming Language :: Rust\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n    \"Programming Language :: Python :: Implementation :: PyPy\",\n]\n\n[dependency-groups]\ntest = [\n    # \"pandas>=3.0,<4.0\",\n    \"pytest>=9.0,<10.0\",\n    \"pytest-env>=1.0,<2.0\",\n    \"coverage>=7.6.1,<8.0\",\n]\nlint = [\n    \"ruff>=0.6.3,<1.0\",\n]\ntyping = [\n    \"mypy>=1.13,<1.20\",\n    \"pandas-stubs>2.0,<4.0\",\n]\ndocs = [\n    \"sphinx>=9.0,<10.0; python_version >= '3.11'\",\n    \"sphinx-automodapi>=0.16.0,<1.0\",\n    \"sphinxcontrib-googleanalytics>=0.4,<1.0\",\n    \"sphinx-tabs>=3.4,<4.0\",\n    \"pydata-sphinx-theme>=0.15.4,<1.0\",\n    \"nbsphinx>=0.9.5,<1.0\",\n]\ngui = [\n    \"jupyterlab>=4.0,<5.0\",\n    \"pickleshare>=0.7.5,<1.0\",\n]\n\n[tool.pytest.ini_options]\n# pythonpath = [\".\", \"python/rateslib\"]\nminversion = \"8.0\"\naddopts = [\n   \"--ignore-glob=*_ignore.py\",\n] # use -s to show print capture, use -q for quiet, use -v for verbose\ntestpaths = [\n    \"python/tests\",\n]\nfilterwarnings = [\n    \"ignore::DeprecationWarning\",\n    \"ignore::PendingDeprecationWarning\"\n]\n\n[tool.pytest_env]\nMPLBACKEND = \"Agg\"\n\n[tool.setuptools]\npackages = [\"rateslib\"]\n\n[project.urls]\nHomepage = \"https://github.com/attack68/rateslib\"\n\n[tool.ruff]\nexclude = [\n    \".git\",\n    \".github\",\n    \"docs\",\n    \"notebooks\",\n    \"target\",\n    \"venv9\",\n    \"venv11\",\n    \"scratch*.py\",\n    \"__pycache__\",\n    \"docs/source/conf.py\",\n    \"old\",\n    \"build\",\n    \"dist\",\n    \"bench\",\n    \"benchmarks\",\n]\n# Same as Black.\nline-length = 100\nindent-width = 4\n# Assume Python 3.12\ntarget-version = \"py310\"\n\n[tool.ruff.format]\nquote-style = \"double\"\nindent-style = \"space\"\ndocstring-code-format = false\n\n[tool.ruff.lint]\nselect = [\n    # \"ANN\",  # flake8-annotations  -- Superceded by the use of mypy\n    # \"COM\",  # flake8-commas  -- conflicts with ruff format\n    \"E\",  # pycodestyle\n    \"W\",\n    \"F\",  # Pyflakes\n    \"UP\",  # pyupgrade\n    \"B\",  # flake8-bugbear\n    \"SIM\",  # flake8-simplify\n    \"C4\",  # flake8-comprehensions\n    \"S\",  # flake8-bandit\n    \"PIE\",  # flake8-pie\n    \"A\",  # flake8-builtins\n    \"Q\",  # flake8-quotes\n    \"PT\",  # flake8-pytest-style\n    \"C90\",  # mccabe complexity  -- Requires work\n    \"I\",  # isort\n    \"N\",  # pep8 naming\n    # \"RUF\",  # -- Requires work\n    # \"D\", Pydocs -- requires work\n]\nignore = [\n    \"A005\",  # json and typing module name shadowing is allowed\n    \"PT011\", \"PT030\", \"PT031\", # -- Requires work inputting match statements\n    \"PIE790\",  # unnecessary pass\n    \"C408\",  # unnecessary dict call\n    \"N806\", \"N815\", \"N803\", \"N802\",\n    \"SIM116\",  # use a dict instead of successive ifs: off due to performance degradation.\n    \"SIM108\",  # ternary operators: off due to code coverage degradation.\n    \"B008\",  # function calls in argument defaults, e.g. NoInput(0)\n    # \"B006\",  # mutable data structures for argument defaults, e.g. []\n    \"B904\",  # raising within except clauses\n    \"B028\",  # no explicit stack level\n    \"E702\",  # semi-colons for multiple line statements\n]\n\n[tool.ruff.lint.per-file-ignores]\n\"__init__.py\" = [\"E402\", \"N801\"]\n\"local_types.py\" = [\"E501\", \"E402\"]\n\"python/tests/*\" = [\"F401\", \"B\", \"N\", \"S\", \"ANN\", \"D\"]\n\"rust/*\" = [\"D\"]\n\n[tool.ruff.lint.mccabe]\n# Flag errors (`C901`) whenever the complexity level exceeds 5.\nmax-complexity = 14\n\n[tool.mypy]\nfiles = [\"python/\"]\nexclude = [\n    \"python/tests\",\n    # \"/periods/ir_volatility.py\",\n]\nstrict = true\n#packages = [\n#    \"rateslib\"\n#]\n\n[tool.coverage.run]\nomit = [\n    \"/local_types.py\",\n    # \"python/tests/*\"\n]\n"
  },
  {
    "path": "python/rateslib/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n__docformat__ = \"restructuredtext\"\n\n# Let users know if they're missing any of our hard dependencies\n_hard_dependencies = (\"pandas\", \"matplotlib\", \"numpy\")\n_missing_dependencies: list[str] = []\n\nfor _dependency in _hard_dependencies:\n    try:\n        __import__(_dependency)\n    except ImportError as _e:  # pragma: no cover\n        raise ImportError(f\"`rateslib` requires installation of {_dependency}: {_e}\")\n\nfrom rateslib.verify import VERSION, Licence\n\n__version__ = VERSION\nlicence = Licence()\n\nfrom datetime import datetime as dt\n\nfrom rateslib.data.loader import Fixings\nfrom rateslib.default import Defaults\nfrom rateslib.rs import CalendarManager\n\ndefaults = Defaults()\nfixings = Fixings()\ncalendars = CalendarManager()\n\nfrom contextlib import ContextDecorator\n\n\nclass default_context(ContextDecorator):\n    \"\"\"\n    Context manager to temporarily set options in the `with` statement context.\n\n    You need to invoke as ``option_context(pat, val, [(pat, val), ...])``.\n\n    Examples\n    --------\n    >>> with option_context('convention', \"act360\", 'frequency', \"S\"):\n    ...     pass\n    \"\"\"\n\n    def __init__(self, *args) -> None:  # type: ignore[no-untyped-def]\n        if len(args) % 2 != 0 or len(args) < 2:\n            raise ValueError(\"Need to invoke as option_context(pat, val, [(pat, val), ...]).\")\n\n        self.ops = list(zip(args[::2], args[1::2], strict=False))\n\n    def __enter__(self) -> None:\n        self.undo = [(pat, getattr(defaults, pat, None)) for pat, _ in self.ops]\n\n        for pat, val in self.ops:\n            setattr(defaults, pat, val)\n\n    def __exit__(self, *args) -> None:  # type: ignore[no-untyped-def]\n        if self.undo:\n            for pat, val in self.undo:\n                setattr(defaults, pat, val)\n\n\nfrom rateslib.curves import (\n    CompositeCurve,\n    Curve,\n    LineCurve,\n    MultiCsaCurve,\n    ProxyCurve,\n    index_left,\n    index_value,\n)\nfrom rateslib.curves.academic import (\n    NelsonSiegelCurve,\n    NelsonSiegelSvenssonCurve,\n    SmithWilsonCurve,\n)\nfrom rateslib.data.fixings import (\n    FloatRateIndex,\n    FloatRateSeries,\n    FXFixing,\n    FXIndex,\n    IBORFixing,\n    IBORStubFixing,\n    IndexFixing,\n    RFRFixing,\n)\nfrom rateslib.dual import ADOrder, Dual, Dual2, Variable, dual_exp, dual_log, dual_solve, gradient\nfrom rateslib.enums import FloatFixingMethod, NoInput\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.instruments import (\n    CDS,\n    FRA,\n    IIRS,\n    IRS,\n    NDF,\n    NDXCS,\n    SBS,\n    XCS,\n    ZCIS,\n    ZCS,\n    Bill,\n    BillCalcMode,\n    BondCalcMode,\n    BondFuture,\n    Fee,\n    FixedRateBond,\n    FloatRateNote,\n    Fly,\n    FXBrokerFly,\n    FXCall,\n    FXForward,\n    FXPut,\n    FXRiskReversal,\n    FXStraddle,\n    FXStrangle,\n    FXSwap,\n    FXVolValue,\n    IndexFixedRateBond,\n    IRSCall,\n    IRSPut,\n    IRSRiskReversal,\n    IRSStraddle,\n    IRSStrangle,\n    IRVolValue,\n    Loan,\n    Portfolio,\n    Spread,\n    STIRFuture,\n    Value,\n    YoYIS,\n)\nfrom rateslib.legs import (\n    Amortization,\n    CreditPremiumLeg,\n    CreditProtectionLeg,\n    CustomLeg,\n    FixedLeg,\n    FloatLeg,\n    ZeroFixedLeg,\n    ZeroFloatLeg,\n)\nfrom rateslib.periods import (\n    Cashflow,\n    CreditPremiumPeriod,\n    CreditProtectionPeriod,\n    FixedPeriod,\n    FloatPeriod,\n    FXCallPeriod,\n    FXPutPeriod,\n    IRSCallPeriod,\n    IRSPutPeriod,\n    ZeroFixedPeriod,\n    ZeroFloatPeriod,\n)\nfrom rateslib.scheduling import (\n    Adjuster,\n    Cal,\n    Convention,\n    Frequency,\n    Imm,\n    NamedCal,\n    RollDay,\n    Schedule,\n    StubInference,\n    UnionCal,\n    add_tenor,\n    dcf,\n    get_calendar,\n    get_imm,\n    next_imm,\n)\nfrom rateslib.serialization import from_json\nfrom rateslib.solver import Solver\nfrom rateslib.splines import (\n    PPSplineDual,\n    PPSplineDual2,\n    PPSplineF64,\n    bspldnev_single,\n    bsplev_single,\n)\nfrom rateslib.volatility import (\n    FXDeltaVolSmile,\n    FXDeltaVolSurface,\n    FXSabrSmile,\n    FXSabrSurface,\n    IRSabrCube,\n    IRSabrSmile,\n    IRSplineCube,\n    IRSplineSmile,\n)\n\n# module level doc-string\n__doc__ = \"\"\"\nRatesLib - An efficient and interconnected fixed income library for Python\n==========================================================================\n\n**rateslib** is a Python package providing fast, flexible, and accurate\nfixed income instrument configuration and calculation.\nIt aims to be the fundamental high-level building block for practical analysis of\nfixed income securities, derivatives, FX representation and curve construction\nin Python.\n\"\"\"  # noqa: A001\n\n__all__ = [\n    \"dt\",\n    \"defaults\",\n    \"fixings\",\n    \"calendars\",\n    \"licence\",\n    \"from_json\",\n    # enums.py\n    \"NoInput\",\n    \"FloatFixingMethod\",\n    # dual.py\n    \"ADOrder\",\n    \"Dual\",\n    \"Dual2\",\n    \"Variable\",\n    \"dual_log\",\n    \"dual_exp\",\n    \"dual_solve\",\n    \"gradient\",\n    # splines.py\n    \"bsplev_single\",\n    \"bspldnev_single\",\n    \"PPSplineF64\",\n    \"PPSplineDual\",\n    \"PPSplineDual2\",\n    # scheduling.py\n    \"get_calendar\",\n    \"get_imm\",\n    \"next_imm\",\n    \"add_tenor\",\n    \"dcf\",\n    \"Cal\",\n    \"UnionCal\",\n    \"NamedCal\",\n    \"Schedule\",\n    \"Frequency\",\n    \"RollDay\",\n    \"Adjuster\",\n    \"StubInference\",\n    \"Convention\",\n    \"Imm\",\n    # curves.py\n    \"Curve\",\n    \"LineCurve\",\n    \"MultiCsaCurve\",\n    \"CompositeCurve\",\n    \"ProxyCurve\",\n    \"index_left\",\n    \"index_value\",\n    # academic curves\n    \"NelsonSiegelCurve\",\n    \"NelsonSiegelSvenssonCurve\",\n    \"SmithWilsonCurve\",\n    # fixings.py\n    \"FXFixing\",\n    \"IBORFixing\",\n    \"IBORStubFixing\",\n    \"IndexFixing\",\n    \"RFRFixing\",\n    \"FXIndex\",\n    \"FloatRateIndex\",\n    \"FloatRateSeries\",\n    # volatility/fx\n    \"FXDeltaVolSmile\",\n    \"FXDeltaVolSurface\",\n    \"FXSabrSmile\",\n    \"FXSabrSurface\",\n    # volatility/ir\n    \"IRSabrSmile\",\n    \"IRSabrCube\",\n    \"IRSplineSmile\",\n    \"IRSplineCube\",\n    # solver.py\n    \"Solver\",\n    # fx.py\n    \"FXRates\",\n    \"FXForwards\",\n    # periods.py,\n    \"FixedPeriod\",\n    \"FloatPeriod\",\n    \"ZeroFixedPeriod\",\n    \"ZeroFloatPeriod\",\n    \"Cashflow\",\n    \"FXCallPeriod\",\n    \"FXPutPeriod\",\n    \"IRSCallPeriod\",\n    \"IRSPutPeriod\",\n    \"CreditPremiumPeriod\",\n    \"CreditProtectionPeriod\",\n    # legs.py\n    \"Amortization\",\n    \"FixedLeg\",\n    \"FloatLeg\",\n    \"ZeroFloatLeg\",\n    \"ZeroFixedLeg\",\n    \"CustomLeg\",\n    \"CreditPremiumLeg\",\n    \"CreditProtectionLeg\",\n    # instruments.py\n    \"FixedRateBond\",\n    \"IndexFixedRateBond\",\n    \"FloatRateNote\",\n    \"BondFuture\",\n    \"BondCalcMode\",\n    \"CDS\",\n    \"FRA\",\n    \"Value\",\n    \"FXVolValue\",\n    \"IRVolValue\",\n    \"Bill\",\n    \"Fee\",\n    \"Loan\",\n    \"BillCalcMode\",\n    \"IRS\",\n    \"NDF\",\n    \"STIRFuture\",\n    \"IIRS\",\n    \"ZCS\",\n    \"ZCIS\",\n    \"YoYIS\",\n    \"SBS\",\n    \"FXSwap\",\n    \"FXForward\",\n    \"XCS\",\n    \"NDXCS\",\n    \"Spread\",\n    \"Fly\",\n    \"Portfolio\",\n    \"FXCall\",\n    \"FXPut\",\n    \"FXRiskReversal\",\n    \"FXStraddle\",\n    \"FXStrangle\",\n    \"FXBrokerFly\",\n    \"IRSCall\",\n    \"IRSPut\",\n    \"IRSRiskReversal\",\n    \"IRSStraddle\",\n    \"IRSStrangle\",\n]\n"
  },
  {
    "path": "python/rateslib/_spec_loader.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport os\nfrom typing import TYPE_CHECKING\n\nimport pandas as pd\nfrom packaging import version\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n    )\n\nDEVELOPMENT = os.environ.get(\"RATESLIB_DEVELOPMENT\", \"False\")\n\n# This is output from a development version and hard coded before a release for performance.\nINSTRUMENT_SPECS: dict[str, dict[str, Any]] = {\n    \"test\": {\n        \"frequency\": \"m\",\n        \"stub\": \"longfront\",\n        \"eom\": False,\n        \"modifier\": \"p\",\n        \"calendar\": \"nyc,tgt,ldn\",\n        \"payment_lag\": 4,\n        \"currency\": \"tes\",\n        \"convention\": \"yearsmonths\",\n        \"leg2_frequency\": \"m\",\n        \"leg2_stub\": \"longback\",\n        \"leg2_roll\": 1,\n        \"leg2_eom\": False,\n        \"leg2_modifier\": \"mp\",\n        \"leg2_calendar\": \"nyc,tgt,ldn\",\n        \"leg2_payment_lag\": 3,\n        \"leg2_convention\": \"one\",\n    },\n    \"eurusd_call\": {\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt|fed\",\n        \"payment_lag\": 2,\n        \"pair\": \"eurusd\",\n        \"delivery_lag\": 2,\n    },\n    \"us_ig_cds\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"roll\": 20,\n        \"eom\": False,\n        \"modifier\": \"fex\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"act360\",\n        \"fixed_rate\": 1.0,\n    },\n    \"inr_ndirs\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"mum\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"act365f\",\n        \"pair\": \"usdinr\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"inrusd_ndxcs\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"mum|fed\",\n        \"payment_lag\": 2,\n        \"currency\": \"usd\",\n        \"convention\": \"act365f\",\n        \"fixed\": True,\n        \"pair\": \"usdinr\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"mxn_irs\": {\n        \"frequency\": \"28d\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"f\",\n        \"calendar\": \"mex\",\n        \"payment_lag\": 2,\n        \"currency\": \"mxn\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"usd_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 2,\n        \"currency\": \"usd\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"usd_irs_lt_2y\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 2,\n        \"currency\": \"usd\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"gbp_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"ldn\",\n        \"payment_lag\": 0,\n        \"currency\": \"gbp\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"eur_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 1,\n        \"currency\": \"eur\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"sek_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"stk\",\n        \"payment_lag\": 1,\n        \"currency\": \"sek\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"nok_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"osl\",\n        \"payment_lag\": 2,\n        \"currency\": \"nok\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"chf_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"zur\",\n        \"payment_lag\": 2,\n        \"currency\": \"chf\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"cad_irs\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tro\",\n        \"payment_lag\": 1,\n        \"currency\": \"cad\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"cad_irs_le_1y\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tro\",\n        \"payment_lag\": 1,\n        \"currency\": \"cad\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"jpy_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tyo\",\n        \"payment_lag\": 2,\n        \"currency\": \"jpy\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"nzd_irs3\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"wlg\",\n        \"payment_lag\": 0,\n        \"currency\": \"nzd\",\n        \"convention\": \"act365f\",\n        \"leg2_frequency\": \"q\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"nzd_irs6\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"wlg\",\n        \"payment_lag\": 0,\n        \"currency\": \"nzd\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"nzd_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"wlg\",\n        \"payment_lag\": 2,\n        \"currency\": \"nzd\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"aud_irs6\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"syd\",\n        \"payment_lag\": 0,\n        \"currency\": \"aud\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"aud_irs3\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"syd\",\n        \"payment_lag\": 0,\n        \"currency\": \"aud\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"aud_irs3_gt_3y\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"syd\",\n        \"payment_lag\": 0,\n        \"currency\": \"aud\",\n        \"convention\": \"act365f\",\n        \"leg2_frequency\": \"q\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"aud_irs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"syd\",\n        \"payment_lag\": 2,\n        \"currency\": \"aud\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"eur_irs6\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"30e360\",\n        \"leg2_frequency\": \"s\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"eur_irs3\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"30e360\",\n        \"leg2_frequency\": \"q\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"eur_irs1\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"30e360\",\n        \"leg2_frequency\": \"m\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"sek_irs3\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"stk\",\n        \"payment_lag\": 0,\n        \"currency\": \"sek\",\n        \"convention\": \"30e360\",\n        \"leg2_frequency\": \"q\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"nok_irs3\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"osl\",\n        \"payment_lag\": 0,\n        \"currency\": \"nok\",\n        \"convention\": \"30e360\",\n        \"leg2_frequency\": \"q\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"nok_irs6\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"osl\",\n        \"payment_lag\": 0,\n        \"currency\": \"nok\",\n        \"convention\": \"30e360\",\n        \"leg2_frequency\": \"s\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"eurusd_xcs\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt,nyc\",\n        \"payment_lag\": 2,\n        \"currency\": \"eur\",\n        \"convention\": \"act360\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"rfr_payment_delay\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"eurusd\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"gbpusd_xcs\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"ldn,nyc\",\n        \"payment_lag\": 2,\n        \"currency\": \"gbp\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"rfr_payment_delay\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"gbpusd\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"eurgbp_xcs\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt,ldn\",\n        \"payment_lag\": 2,\n        \"currency\": \"eur\",\n        \"convention\": \"act360\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"rfr_payment_delay\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"eurgbp\",\n        \"leg2_convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"gbpeur_xcs\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt,ldn\",\n        \"payment_lag\": 2,\n        \"currency\": \"gbp\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"rfr_payment_delay\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"eurgbp\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"jpyusd_xcs\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc,tyo\",\n        \"payment_lag\": 2,\n        \"currency\": \"jpy\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"rfr_payment_delay\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"usdjpy\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"audusd_xcs3\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc,syd\",\n        \"payment_lag\": 2,\n        \"currency\": \"aud\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(0)\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"audusd\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"audusd_xcs\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc,syd\",\n        \"payment_lag\": 2,\n        \"currency\": \"aud\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"rfr_payment_delay\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"audusd\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"nzdusd_xcs3\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc,wlg\",\n        \"payment_lag\": 2,\n        \"currency\": \"nzd\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(0)\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"nzdusd\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"nzdaud_xcs3\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc,wlg,syd\",\n        \"payment_lag\": 2,\n        \"currency\": \"nzd\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(0)\",\n        \"payment_lag_exchange\": 0,\n        \"fixed\": False,\n        \"pair\": \"audnzd\",\n        \"leg2_convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n        \"leg2_fixed\": False,\n        \"leg2_mtm\": True,\n    },\n    \"eur_zcis\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"1+\",\n        \"leg2_index_method\": \"monthly\",\n        \"leg2_index_lag\": 3,\n    },\n    \"gbp_zcis\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"ldn\",\n        \"payment_lag\": 0,\n        \"currency\": \"gbp\",\n        \"convention\": \"1+\",\n        \"leg2_index_method\": \"monthly\",\n        \"leg2_index_lag\": 2,\n    },\n    \"usd_zcis\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"1+\",\n        \"leg2_index_method\": \"daily\",\n        \"leg2_index_lag\": 3,\n    },\n    \"gbp_zcs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"mf\",\n        \"calendar\": \"ldn\",\n        \"payment_lag\": 0,\n        \"currency\": \"gbp\",\n        \"convention\": \"act365f\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n    },\n    \"sek_iirs\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"stk\",\n        \"payment_lag\": 0,\n        \"currency\": \"sek\",\n        \"convention\": \"actacticma\",\n        \"index_method\": \"daily\",\n        \"index_lag\": 3,\n        \"leg2_frequency\": \"q\",\n        \"leg2_convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"eur_sbs36\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"act360\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(2)\",\n        \"leg2_frequency\": \"s\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"nok_sbs36\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"osl\",\n        \"payment_lag\": 0,\n        \"currency\": \"nok\",\n        \"convention\": \"act360\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(2)\",\n        \"leg2_frequency\": \"s\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"aud_sbs36\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"syd\",\n        \"payment_lag\": 0,\n        \"currency\": \"aud\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(0)\",\n        \"leg2_frequency\": \"s\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"aud_sbs31\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"syd\",\n        \"payment_lag\": 0,\n        \"currency\": \"aud\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(0)\",\n        \"leg2_frequency\": \"m\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"nzd_sbs36\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"wlg\",\n        \"payment_lag\": 0,\n        \"currency\": \"nzd\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(0)\",\n        \"leg2_frequency\": \"s\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"nzd_sbs31\": {\n        \"frequency\": \"q\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"wlg\",\n        \"payment_lag\": 0,\n        \"currency\": \"nzd\",\n        \"convention\": \"act365f\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"ibor(0)\",\n        \"leg2_frequency\": \"m\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(0)\",\n    },\n    \"us_gb\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"none\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"us_gb\",\n    },\n    \"us_gbi\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"none\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"index_method\": \"daily\",\n        \"index_lag\": 3,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"us_gb\",\n    },\n    \"us_corp\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"none\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"30u360\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"us_corp\",\n    },\n    \"us_muni\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"none\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"30u360\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"us_muni\",\n    },\n    \"us_gb_tsy\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": True,\n        \"modifier\": \"none\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"us_gb_tsy\",\n    },\n    \"uk_gb\": {\n        \"frequency\": \"s\",\n        \"stub\": \"longfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"ldn\",\n        \"payment_lag\": 0,\n        \"currency\": \"gbp\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-7b\",\n        \"calc_mode\": \"uk_gb\",\n    },\n    \"au_gb\": {\n        \"frequency\": \"s\",\n        \"stub\": \"longfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"syd\",\n        \"payment_lag\": 0,\n        \"currency\": \"aud\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 2,\n        \"ex_div\": \"-8d\",\n        \"calc_mode\": \"au_gb\",\n    },\n    \"nz_gb\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"wlg\",\n        \"payment_lag\": 0,\n        \"currency\": \"nzd\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-8b\",\n        \"calc_mode\": \"nz_gb\",\n    },\n    \"cn_gb\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"bjs\",\n        \"payment_lag\": 0,\n        \"currency\": \"cny\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"cn_gb\",\n    },\n    \"de_gb\": {\n        \"frequency\": \"a\",\n        \"stub\": \"longfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 2,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"de_gb\",\n    },\n    \"fr_gb\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 2,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"fr_gb\",\n    },\n    \"nl_gb\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 2,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"nl_gb\",\n    },\n    \"it_gb\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 2,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"it_gb\",\n    },\n    \"ch_gb\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"zur\",\n        \"payment_lag\": 0,\n        \"currency\": \"chf\",\n        \"convention\": \"30e360\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"ch_gb\",\n    },\n    \"se_gb\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"stk\",\n        \"payment_lag\": 0,\n        \"currency\": \"sek\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 2,\n        \"ex_div\": \"-5b\",\n        \"calc_mode\": \"se_gb\",\n    },\n    \"no_gb\": {\n        \"frequency\": \"a\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"osl\",\n        \"payment_lag\": 0,\n        \"currency\": \"nok\",\n        \"convention\": \"actacticma_stub365f\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"no_gb\",\n    },\n    \"ca_gb\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"tro\",\n        \"payment_lag\": 0,\n        \"currency\": \"cad\",\n        \"convention\": \"actacticma_stub365f\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"ca_gb\",\n    },\n    \"ca_gbi\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"tro\",\n        \"payment_lag\": 0,\n        \"currency\": \"cad\",\n        \"convention\": \"actacticma_stub365f\",\n        \"payment_lag_exchange\": 0,\n        \"index_method\": \"daily\",\n        \"index_lag\": 3,\n        \"settle\": 1,\n        \"ex_div\": \"-1b\",\n        \"calc_mode\": \"ca_gb\",\n    },\n    \"us_gbb\": {\n        \"eom\": True,\n        \"modifier\": \"none\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"act360\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"0b\",\n        \"calc_mode\": \"us_gbb\",\n    },\n    \"se_gbb\": {\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"stk\",\n        \"payment_lag\": 0,\n        \"currency\": \"sek\",\n        \"convention\": \"act360\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 2,\n        \"ex_div\": \"0b\",\n        \"calc_mode\": \"se_gbb\",\n    },\n    \"no_gbb\": {\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"osl\",\n        \"payment_lag\": 0,\n        \"currency\": \"nok\",\n        \"convention\": \"act365f\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 2,\n        \"ex_div\": \"0b\",\n        \"calc_mode\": \"no_gbb\",\n    },\n    \"uk_gbb\": {\n        \"eom\": True,\n        \"modifier\": \"none\",\n        \"calendar\": \"ldn\",\n        \"payment_lag\": 0,\n        \"currency\": \"gbp\",\n        \"convention\": \"act365f\",\n        \"payment_lag_exchange\": 0,\n        \"settle\": 1,\n        \"ex_div\": \"0b\",\n        \"calc_mode\": \"uk_gbb\",\n    },\n    \"uk_gbi\": {\n        \"frequency\": \"s\",\n        \"stub\": \"shortfront\",\n        \"eom\": False,\n        \"modifier\": \"none\",\n        \"calendar\": \"ldn\",\n        \"payment_lag\": 0,\n        \"currency\": \"gbp\",\n        \"convention\": \"actacticma\",\n        \"payment_lag_exchange\": 0,\n        \"index_method\": \"daily\",\n        \"index_lag\": 3,\n        \"settle\": 1,\n        \"ex_div\": \"-7b\",\n        \"calc_mode\": \"uk_gb\",\n    },\n    \"sek_fra3\": {\n        \"termination\": \"3m\",\n        \"frequency\": \"q\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"stk\",\n        \"payment_lag\": 0,\n        \"currency\": \"sek\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"eur_fra3\": {\n        \"termination\": \"3m\",\n        \"frequency\": \"q\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"eur_fra6\": {\n        \"termination\": \"6m\",\n        \"frequency\": \"s\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"eur_fra1\": {\n        \"termination\": \"1m\",\n        \"frequency\": \"m\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"nok_fra3\": {\n        \"termination\": \"3m\",\n        \"frequency\": \"q\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"osl\",\n        \"payment_lag\": 0,\n        \"currency\": \"nok\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"nok_fra6\": {\n        \"termination\": \"6m\",\n        \"frequency\": \"s\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"osl\",\n        \"payment_lag\": 0,\n        \"currency\": \"nok\",\n        \"convention\": \"act360\",\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n    },\n    \"usd_frn5\": {\n        \"frequency\": \"q\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"act360\",\n        \"spread_compound_method\": \"none_simple\",\n        \"fixing_method\": \"rfr_observation_shift(5)\",\n        \"settle\": 1,\n        \"ex_div\": \"1b\",\n    },\n    \"usd_stir\": {\n        \"frequency\": \"q\",\n        \"roll\": \"imm\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"actacticma\",\n        \"nominal\": 1000000.0,\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixing_series\": \"usd_rfr\",\n    },\n    \"usd_stir1\": {\n        \"frequency\": \"m\",\n        \"roll\": \"som\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"nyc\",\n        \"payment_lag\": 0,\n        \"currency\": \"usd\",\n        \"convention\": \"actacticma\",\n        \"nominal\": 5000400.0,\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay_avg\",\n        \"leg2_fixing_series\": \"usd_rfr\",\n    },\n    \"eur_stir\": {\n        \"frequency\": \"q\",\n        \"roll\": \"imm\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"actacticma\",\n        \"nominal\": 1000000.0,\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixing_series\": \"eur_rfr\",\n    },\n    \"eur_stir1\": {\n        \"frequency\": \"m\",\n        \"roll\": \"som\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"actacticma\",\n        \"nominal\": 3000000.0,\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay_avg\",\n        \"leg2_fixing_series\": \"eur_rfr\",\n    },\n    \"eur_stir3\": {\n        \"frequency\": \"q\",\n        \"roll\": \"imm\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"tgt\",\n        \"payment_lag\": 0,\n        \"currency\": \"eur\",\n        \"convention\": \"actacticma\",\n        \"nominal\": 1000000.0,\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"ibor(2)\",\n        \"leg2_fixing_series\": \"eur_ibor\",\n    },\n    \"gbp_stir\": {\n        \"frequency\": \"q\",\n        \"roll\": \"imm\",\n        \"eom\": False,\n        \"modifier\": \"mf\",\n        \"calendar\": \"ldn\",\n        \"payment_lag\": 0,\n        \"currency\": \"gbp\",\n        \"convention\": \"actacticma\",\n        \"nominal\": 1000000.0,\n        \"leg2_spread_compound_method\": \"none_simple\",\n        \"leg2_fixing_method\": \"rfr_payment_delay\",\n        \"leg2_fixing_series\": \"gbp_rfr\",\n    },\n    \"uk_gb_2y\": {\n        \"calendar\": \"ldn\",\n        \"currency\": \"gbp\",\n        \"calc_mode\": \"ice_gbp\",\n        \"nominal\": 100000.0,\n        \"coupon\": 3.0,\n    },\n    \"uk_gb_5y\": {\n        \"calendar\": \"ldn\",\n        \"currency\": \"gbp\",\n        \"calc_mode\": \"ice_gbp\",\n        \"nominal\": 100000.0,\n        \"coupon\": 4.0,\n    },\n    \"uk_gb_10y\": {\n        \"calendar\": \"ldn\",\n        \"currency\": \"gbp\",\n        \"calc_mode\": \"ice_gbp\",\n        \"nominal\": 100000.0,\n        \"coupon\": 4.0,\n    },\n    \"uk_gb_30y\": {\n        \"calendar\": \"ldn\",\n        \"currency\": \"gbp\",\n        \"calc_mode\": \"ice_gbp\",\n        \"nominal\": 100000.0,\n        \"coupon\": 4.0,\n    },\n    \"us_gb_2y\": {\n        \"calendar\": \"fed\",\n        \"currency\": \"usd\",\n        \"calc_mode\": \"ust_short\",\n        \"nominal\": 200000.0,\n        \"coupon\": 6.0,\n    },\n    \"us_gb_3y\": {\n        \"calendar\": \"fed\",\n        \"currency\": \"usd\",\n        \"calc_mode\": \"ust_short\",\n        \"nominal\": 200000.0,\n        \"coupon\": 6.0,\n    },\n    \"us_gb_5y\": {\n        \"calendar\": \"fed\",\n        \"currency\": \"usd\",\n        \"calc_mode\": \"ust_short\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"us_gb_10y\": {\n        \"calendar\": \"fed\",\n        \"currency\": \"usd\",\n        \"calc_mode\": \"ust_long\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"us_gb_30y\": {\n        \"calendar\": \"fed\",\n        \"currency\": \"usd\",\n        \"calc_mode\": \"ust_long\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"de_gb_2y\": {\n        \"calendar\": \"tgt\",\n        \"currency\": \"eur\",\n        \"calc_mode\": \"eurex_eur\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"de_gb_5y\": {\n        \"calendar\": \"tgt\",\n        \"currency\": \"eur\",\n        \"calc_mode\": \"eurex_eur\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"de_gb_10y\": {\n        \"calendar\": \"tgt\",\n        \"currency\": \"eur\",\n        \"calc_mode\": \"eurex_eur\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"de_gb_30y\": {\n        \"calendar\": \"tgt\",\n        \"currency\": \"eur\",\n        \"calc_mode\": \"eurex_eur\",\n        \"nominal\": 100000.0,\n        \"coupon\": 4.0,\n    },\n    \"fr_gb_5y\": {\n        \"calendar\": \"tgt\",\n        \"currency\": \"eur\",\n        \"calc_mode\": \"eurex_eur\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"fr_gb_10y\": {\n        \"calendar\": \"tgt\",\n        \"currency\": \"eur\",\n        \"calc_mode\": \"eurex_eur\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"sp_gb_10y\": {\n        \"calendar\": \"tgt\",\n        \"currency\": \"eur\",\n        \"calc_mode\": \"eurex_eur\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n    \"ch_gb_10y\": {\n        \"calendar\": \"zur\",\n        \"currency\": \"chf\",\n        \"calc_mode\": \"eurex_chf\",\n        \"nominal\": 100000.0,\n        \"coupon\": 6.0,\n    },\n}\n\nif DEVELOPMENT == \"True\":\n    # DEVELOPMENT mode is used to load and create instrument specs from a CSV file.\n    # This is loaded by default and slower to parse than directly creating a dict\n    # So when packaging output the INSTRUMENT_SPEC dict and paste into the non-development\n    # section.\n\n    if version.parse(pd.__version__) < version.parse(\"3.0.0\"):\n        raise RuntimeError(\n            \"Development of instrument `spec` loading from CSV should be handled by pandas >= 3.0.\"\n            \"To avoid development mode set DEVELOPMENT=False.\"\n        )\n\n    path = \"data/__instrument_spec.csv\"\n    abspath = os.path.dirname(os.path.abspath(__file__))\n    target = os.path.join(abspath, path)\n    df2 = pd.read_csv(target, header=[0, 1, 2, 3], index_col=[0])\n\n    for column in df2.columns:\n        df2[column] = df2[column].astype(column[3])  # type: ignore[call-overload]\n    df2_legs = df2.loc[:, (slice(None), [\"leg1\", \"leg2\"])]\n\n    INSTRUMENT_SPECS = {}\n    for spec in df2_legs.index:\n        leg1 = df2_legs.loc[spec].dropna().droplevel([0, 3]).loc[\"leg1\"].to_dict()\n        try:\n            leg2 = df2_legs.loc[spec].dropna().droplevel([0, 3]).loc[\"leg2\"].to_dict()\n        except KeyError:\n            leg2 = {}\n        INSTRUMENT_SPECS.update({spec: {**leg1, **{f\"leg2_{k}\": v for k, v in leg2.items()}}})\n\n    # extra dtype conversion mappings for keys\n    def _map_str_float_int(v: Any) -> Any:\n        try:\n            return int(float(v))\n        except (ValueError, TypeError):\n            return v\n\n    _maps = {\n        \"roll\": _map_str_float_int,\n        \"leg2_roll\": _map_str_float_int,\n    }\n\n    for _, v in INSTRUMENT_SPECS.items():\n        for k2, v2 in v.items():\n            if k2 in _maps:\n                v[k2] = _maps[k2](v2)\n"
  },
  {
    "path": "python/rateslib/curves/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.curves.curves import (\n    CompositeCurve,\n    CreditImpliedCurve,\n    Curve,\n    LineCurve,\n    MultiCsaCurve,\n    ProxyCurve,\n    RolledCurve,\n    ShiftedCurve,\n    TranslatedCurve,\n    _BaseCurve,\n    _WithMutability,\n    _WithOperations,\n    index_value,\n)\nfrom rateslib.curves.interpolation import index_left\nfrom rateslib.curves.utils import (\n    _CurveInterpolator,\n    _CurveMeta,\n    _CurveNodes,\n    _CurveSpline,\n    _CurveType,\n    _ProxyCurveInterpolator,\n    average_rate,\n)\n\n__all__ = (\n    \"Curve\",\n    \"LineCurve\",\n    \"CompositeCurve\",\n    \"MultiCsaCurve\",\n    \"ProxyCurve\",\n    \"CreditImpliedCurve\",\n    \"RolledCurve\",\n    \"ShiftedCurve\",\n    \"TranslatedCurve\",\n    \"_BaseCurve\",\n    \"_WithOperations\",\n    \"_WithMutability\",\n    \"average_rate\",\n    \"index_left\",\n    \"index_value\",\n    \"_CurveMeta\",\n    \"_CurveType\",\n    \"_CurveSpline\",\n    \"_CurveInterpolator\",\n    \"_CurveNodes\",\n    \"_ProxyCurveInterpolator\",\n)\n"
  },
  {
    "path": "python/rateslib/curves/_parsers.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport warnings\nfrom collections.abc import Sequence\nfrom typing import TYPE_CHECKING, TypeVar\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.curves import MultiCsaCurve, ProxyCurve\nfrom rateslib.curves.utils import _CurveType\nfrom rateslib.enums.generics import Err, NoInput, Ok\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurveInput,\n        CurveInput_,\n        CurveOption,\n        CurveOption_,\n        CurveOrId,\n        Curves_,\n        Curves_DiscTuple,\n        Curves_Tuple,\n        Result,\n        Solver,\n        _BaseCurve,\n        _BaseCurve_,\n    )\n\n\ndef _map_curve_or_id_from_solver_(curve: CurveOrId, solver: Solver) -> _BaseCurve:\n    \"\"\"\n    Maps a \"Curve | str\" to a \"Curve\" via a Solver mapping.\n\n    If a Curve, runs a check against whether that Curve is associated with the given Solver,\n    and perform an action based on `defaults.curve_not_in_solver`\n    \"\"\"\n    if isinstance(curve, str):\n        return solver._get_pre_curve(curve)\n    elif type(curve) is ProxyCurve or type(curve) is MultiCsaCurve:\n        # TODO: (mid) consider also adding CompositeCurves as exceptions under the same rule\n        # Proxy curves and MultiCsaCurves can exist outside of Solvers but be constructed\n        # directly from an FXForwards object tied to a Solver using only a Solver's\n        # dependent curves and AD variables.\n        return curve\n    else:\n        try:\n            # it is a safeguard to load curves from solvers when a solver is\n            # provided and multiple curves might have the same id\n            __: _BaseCurve = solver._get_pre_curve(curve.id)\n            if id(__) != id(curve):  # Python id() is a memory id, not a string label id.\n                raise ValueError(\n                    \"A curve has been supplied, as part of ``curves``, which has the same \"\n                    f\"`id` ('{curve.id}'),\\nas one of the curves available as part of the \"\n                    \"Solver's collection but is not the same object.\\n\"\n                    \"This is ambiguous and cannot price.\\n\"\n                    \"Either refactor the arguments as follows:\\n\"\n                    \"1) remove the conflicting curve: [curves=[..], solver=<Solver>] -> \"\n                    \"[curves=None, solver=<Solver>]\\n\"\n                    \"2) change the `id` of the supplied curve and ensure the rateslib.defaults \"\n                    \"option 'curve_not_in_solver' is set to 'ignore'.\\n\"\n                    \"   This will remove the ability to accurately price risk metrics.\",\n                )\n            return __\n        except AttributeError:\n            raise AttributeError(\n                \"`curve` has no attribute `id`, likely it not a valid object, got: \"\n                f\"{curve}.\\nSince a solver is provided have you missed labelling the `curves` \"\n                f\"of the instrument or supplying `curves` directly?\",\n            )\n        except KeyError:\n            if defaults.curve_not_in_solver == \"ignore\":\n                return curve\n            elif defaults.curve_not_in_solver == \"warn\":\n                warnings.warn(\"`curve` not found in `solver`.\", UserWarning)\n                return curve\n            else:\n                raise ValueError(\"`curve` must be in `solver`.\")\n\n\ndef _map_curve_from_solver_(curve: CurveInput, solver: Solver) -> CurveOption:\n    \"\"\"\n    Maps a \"Curve | str | dict[str, Curve | str]\" to a \"Curve | dict[str, Curve]\" via a Solver.\n\n    If curve input involves strings get objects directly from solver curves mapping.\n\n    This is the explicit variety which does not handle NoInput.\n    \"\"\"\n    if isinstance(curve, dict):\n        mapped_dict: dict[str, _BaseCurve] = {\n            k: _map_curve_or_id_from_solver_(v, solver) for k, v in curve.items()\n        }\n        return mapped_dict\n    else:\n        return _map_curve_or_id_from_solver_(curve, solver)\n\n\ndef _map_curve_from_solver(curve: CurveInput_, solver: Solver) -> CurveOption_:\n    \"\"\"\n    Maps a \"Curve | str | dict[str, Curve | str] | NoInput\" to a\n    \"Curve | dict[str, Curve] | NoInput\" via a Solver.\n\n    This is the inexplicit variety which handles NoInput.\n    \"\"\"\n    if isinstance(curve, NoInput) or curve is None:\n        return NoInput(0)\n    else:\n        return _map_curve_from_solver_(curve, solver)\n\n\ndef _validate_curve_not_str(curve: CurveOrId) -> _BaseCurve:\n    if isinstance(curve, str):\n        raise ValueError(\"`curves` must contain Curve, not str, if `solver` not given.\")\n    return curve\n\n\ndef _validate_no_str_in_curve_input(curve: CurveInput_) -> CurveOption_:\n    \"\"\"\n    If a Solver is not available then raise an Exception if a CurveInput contains string Id.\n    \"\"\"\n    if isinstance(curve, dict):\n        return {k: _validate_curve_not_str(v) for k, v in curve.items()}\n    elif isinstance(curve, NoInput) or curve is None:\n        return NoInput(0)\n    else:\n        return _validate_curve_not_str(curve)\n\n\ndef _get_curves_maybe_from_solver(\n    curves_attr: Curves_,\n    solver: Solver | NoInput,\n    curves: Curves_,\n) -> Curves_DiscTuple:\n    \"\"\"\n    Attempt to resolve curves as a variety of input types to a 4-tuple consisting of:\n    (leg1 forecasting, leg1 discounting, leg2 forecasting, leg2 discounting)\n\n    Parameters\n    ----------\n    curves_attr : Curves\n        This is an external set of Curves which is used as a substitute for pricing. These might\n        be taken from an Instrument at initialisation, for example.\n    solver: Solver\n        Solver containing the Curves mapping\n    curves: Curves\n        A possible override option to allow curves to be specified directly, even if they exist\n        as an attribute on the Instrument.\n\n    Returns\n    -------\n    4-Tuple of Curve, dict[str, Curve], NoInput\n    \"\"\"\n    if isinstance(curves, NoInput) and isinstance(curves_attr, NoInput):\n        # no data is available so consistently return a 4-tuple of no data\n        return (NoInput(0), NoInput(0), NoInput(0), NoInput(0))\n    elif isinstance(curves, NoInput):\n        # set the `curves` input as that which is set as attribute at instrument init.\n        curves = curves_attr\n\n    # refactor curves into a list\n    if isinstance(curves, str) or not isinstance(curves, Sequence):  # Sequence can be str!\n        # convert isolated value input to list\n        curves_as_list: list[\n            _BaseCurve\n            | dict[str, str | _BaseCurve]\n            | dict[str, str]\n            | dict[str, _BaseCurve]\n            | NoInput\n            | str\n        ] = [curves]\n    else:\n        curves_as_list = list(curves)\n\n    # parse curves_as_list\n    if isinstance(solver, NoInput):\n        curves_parsed: tuple[CurveOption_, ...] = tuple(\n            _validate_no_str_in_curve_input(curve) for curve in curves_as_list\n        )\n    else:\n        try:\n            curves_parsed = tuple(_map_curve_from_solver(curve, solver) for curve in curves_as_list)\n        except KeyError as e:\n            raise ValueError(\n                \"`curves` must contain str curve `id` s existing in `solver` \"\n                \"(or its associated `pre_solvers`).\\n\"\n                f\"The sought id was: '{e.args[0]}'.\\n\"\n                f\"The available ids are {list(solver.pre_curves.keys())}.\",\n            )\n\n    curves_tuple = _make_4_tuple_of_curve(curves_parsed)\n    return _validate_disc_curves_are_not_dict(curves_tuple)\n\n\ndef _make_4_tuple_of_curve(curves: tuple[CurveOption_, ...]) -> Curves_Tuple:\n    \"\"\"Convert user sequence input to a 4-Tuple.\"\"\"\n    n = len(curves)\n    if n == 1:\n        curves *= 4\n    elif n == 2:\n        curves *= 2\n    elif n == 3:\n        curves += (curves[1],)\n    elif n > 4:\n        raise ValueError(\"Can only supply a maximum of 4 `curves`.\")\n    return curves  # type: ignore[return-value]\n\n\ndef _validate_curve_is_not_dict(curve: CurveOption_) -> _BaseCurve_:\n    if isinstance(curve, dict):\n        raise ValueError(\"`disc_curve` cannot be supplied as, or inferred from, a dict of Curves.\")\n    return curve\n\n\ndef _validate_disc_curves_are_not_dict(curves_tuple: Curves_Tuple) -> Curves_DiscTuple:\n    return (\n        curves_tuple[0],\n        _validate_curve_is_not_dict(curves_tuple[1]),\n        curves_tuple[2],\n        _validate_curve_is_not_dict(curves_tuple[3]),\n    )\n\n\ndef _validate_curve_not_no_input(curve: _BaseCurve_) -> _BaseCurve:\n    if isinstance(curve, NoInput):\n        raise ValueError(\"`curve` must be supplied. Got NoInput or None.\")\n    return curve\n\n\nT = TypeVar(\"T\")\n\n\ndef _validate_obj_not_no_input(obj: T | NoInput, name: str) -> T:\n    if isinstance(obj, NoInput):\n        raise ValueError(f\"`{name}` must be supplied. Got NoInput or None.\")\n    return obj\n\n\ndef _disc_maybe_from_curve(curve: CurveOption_, disc_curve: _BaseCurve_) -> _BaseCurve_:\n    \"\"\"Return a discount curve, pointed as the `curve` if not provided and if suitable Type.\"\"\"\n    if isinstance(disc_curve, NoInput):\n        if isinstance(curve, dict):\n            raise ValueError(\"`disc_curve` cannot be inferred from a dictionary of curves.\")\n        elif isinstance(curve, NoInput):\n            return NoInput(0)\n        elif curve._base_type == _CurveType.values:\n            raise ValueError(\"`disc_curve` cannot be inferred from a non-DF based curve.\")\n        _: _BaseCurve | NoInput = curve\n    else:\n        _ = disc_curve\n    return _\n\n\ndef _disc_required_maybe_from_curve(curve: CurveOption_, disc_curve: CurveOption_) -> _BaseCurve:\n    \"\"\"Return a discount curve, pointed as the `curve` if not provided and if suitable Type.\"\"\"\n    if isinstance(disc_curve, dict):\n        raise NotImplementedError(\"`disc_curve` cannot currently be inferred from a dict.\")\n    _: _BaseCurve_ = _disc_maybe_from_curve(curve, disc_curve)\n    if isinstance(_, NoInput):\n        raise TypeError(\n            \"`curves` have not been supplied correctly. \"\n            \"A `disc_curve` is required to perform function.\"\n        )\n    return _\n\n\ndef _try_disc_required_maybe_from_curve(\n    curve: CurveOption_, disc_curve: CurveOption_\n) -> Result[_BaseCurve]:\n    \"\"\"Return a discount curve, pointed as the `curve` if not provided and if suitable Type.\"\"\"\n    if isinstance(disc_curve, dict):\n        return Err(NotImplementedError(err.NI_NO_DISC_FROM_DICT))\n    if isinstance(disc_curve, NoInput):\n        if isinstance(curve, dict):\n            return Err(NotImplementedError(err.NI_NO_DISC_FROM_DICT))\n        elif isinstance(curve, NoInput):\n            return Err(ValueError(err.VE_NEEDS_DISC_CURVE))\n        elif curve._base_type == _CurveType.values:\n            return Err(ValueError(err.VE_NO_DISC_FROM_VALUES))\n        return Ok(curve)\n    if disc_curve._base_type == _CurveType.values:\n        return Err(ValueError(err.VE_NO_DISC_FROM_VALUES))\n    return Ok(disc_curve)\n\n\ndef _maybe_set_ad_order(\n    curve: CurveOption_, order: int | dict[str, int | None] | None\n) -> int | dict[str, int | None] | None:\n    \"\"\"method is used internally to set AD order and then later revert the curve to its original\"\"\"\n    if isinstance(curve, NoInput) or order is None:\n        return None  # do nothing\n    else:\n        if isinstance(curve, dict):\n            # method will return a dict of orders if a dict of curves is provided as input\n            if isinstance(order, dict):\n                return {\n                    k: _maybe_set_ad_order(v, order[k])  # type: ignore[misc]\n                    for k, v in curve.items()\n                }\n            else:\n                return {\n                    k: _maybe_set_ad_order(v, order)  # type: ignore[misc]\n                    for k, v in curve.items()\n                }\n        else:\n            try:\n                original_order = curve.ad\n                curve._set_ad_order(order)  # type: ignore[arg-type]\n            except AttributeError:\n                # Curve has no method (possibly a custom curve and not a subclass of _BaseCurve)\n                return None\n            return original_order\n"
  },
  {
    "path": "python/rateslib/curves/academic/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.curves.academic.ns import NelsonSiegelCurve\nfrom rateslib.curves.academic.nss import NelsonSiegelSvenssonCurve\nfrom rateslib.curves.academic.sw import SmithWilsonCurve\n\n__all__ = [\"NelsonSiegelCurve\", \"NelsonSiegelSvenssonCurve\", \"SmithWilsonCurve\"]\n"
  },
  {
    "path": "python/rateslib/curves/academic/ns.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\nfrom uuid import uuid4\n\nimport numpy as np\n\nfrom rateslib import defaults\nfrom rateslib.curves import _BaseCurve, _CurveMeta, _CurveNodes, _CurveType, _WithMutability\nfrom rateslib.dual import Dual, Dual2, dual_exp, set_order_convert\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.mutability import _clear_cache_post, _new_state_post\nfrom rateslib.scheduling import Convention, dcf, get_calendar\nfrom rateslib.scheduling.convention import _get_convention\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalInput,\n        DualTypes,\n        Variable,\n        datetime,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass NelsonSiegelCurve(_WithMutability, _BaseCurve):\n    r\"\"\"\n    A Nelson-Siegel curve defined by discount factors.\n\n    The continuously compounded rate to maturity, :math:`r(T)`, is given by the following\n    equation of **four** parameters, :math:`[\\beta_0, \\beta_1, \\beta_2, \\lambda]`\n\n    .. math::\n\n       r(T) = \\begin{bmatrix} \\beta_0 & \\beta_1 & \\beta_2 \\end{bmatrix} \\begin{bmatrix} 1 \\\\ \\lambda (1- e^{-T/ \\lambda}) / T \\\\ \\lambda (1- e^{-T/ \\lambda})/ T - e^{-T/ \\lambda} \\end{bmatrix}\n\n    The **discount factors** on that curve equaling:\n\n    .. math::\n\n       v(T) = e^{-T r(T)}\n\n    *T* is determined as the day count fraction between the start of the curve and the maturity\n    under the given the ``convention`` and ``calendar``.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    dates: 2-tuple of datetime, :red:`required`\n        The dates defining the eval date and final date of the *Curve*.\n    parameters: 4-tuple of Dual, Dual2, Variable, float, :red:`required`\n        The parameters associated with the *Curve*. In order these are\n        :math:`[\\beta_0, \\beta_1, \\beta_2, \\lambda]`.\n    id : str, :green:`optional (set randomly)`\n        The unique identifier to distinguish between curves in a multicurve framework.\n    convention : Convention, str, :green:`optional (set as ActActISDA)`\n        The convention of the curve for determining rates. Please see\n        :meth:`dcf()<rateslib.scheduling.dcf>` for all available options.\n    modifier : str, :green:`optional (set by 'defaults')`\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"}, for determining rates when input as\n        a tenor, e.g. \"3M\".\n    calendar : calendar, str, :green:`optional (set as 'all')`\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data. Used for determining rates.\n    ad : int in {0, 1, 2}, :green:`optional`\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n    index_base: float, :green:`optional`\n        The initial index value at the initial node date of the curve. Used for\n        forecasting future index values.\n    index_lag : int, :green:`optional (set by 'defaults')`\n        Number of months of by which the index lags the date. For example if the initial\n        curve node date is 1st Sep 2021 based on the inflation index published\n        17th June 2023 then the lag is 3 months. Best practice is to use 0 months.\n    collateral : str, :green:`optional (set as None)`\n        A currency identifier to denote the collateral currency against which the discount factors\n        for this *Curve* are measured.\n    credit_discretization : int, :green:`optional (set by 'defaults')`\n        A parameter for numerically solving the integral for credit protection legs and default\n        events. Expressed in calendar days. Only used by *Curves* functioning as *hazard Curves*.\n    credit_recovery_rate : Variable | float, :green:`optional (set by 'defaults')`\n        A parameter used in pricing credit protection legs and default events.\n\n    \"\"\"  # noqa: E501\n\n    # ABC properties\n\n    _ini_solve = 0\n    _base_type = _CurveType.dfs\n    _id = None  # type: ignore[assignment]\n    _meta = None  # type: ignore[assignment]\n    _nodes = None  # type: ignore[assignment]\n    _ad = None  # type: ignore[assignment]\n    _interpolator = None  # type: ignore[assignment]\n    _n = 4\n\n    @_new_state_post\n    def __init__(\n        self,\n        dates: tuple[datetime, datetime],\n        parameters: tuple[DualTypes, DualTypes, DualTypes, DualTypes],\n        id: str_ = NoInput(0),  # noqa: A002\n        *,\n        convention: Convention | str | NoInput = NoInput(0),\n        modifier: str | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        ad: int = 0,\n        index_base: Variable | float_ = NoInput(0),\n        index_lag: int | NoInput = NoInput(0),\n        collateral: str_ = NoInput(0),\n        credit_discretization: int_ = NoInput(0),\n        credit_recovery_rate: Variable | float_ = NoInput(0),\n    ):\n        self._nodes = _CurveNodes({dates[0]: 0.0, dates[1]: 0.0})\n        self._params = parameters\n        self._meta = _CurveMeta(\n            _calendar=get_calendar(calendar),\n            _convention=_get_convention(_drb(Convention.ActActISDA, convention)),\n            _modifier=_drb(defaults.modifier, modifier).upper(),\n            _index_base=index_base,\n            _index_lag=_drb(defaults.index_lag_curve, index_lag),\n            _collateral=_drb(None, collateral),\n            _credit_discretization=_drb(\n                defaults.cds_protection_discretization, credit_discretization\n            ),\n            _credit_recovery_rate=_drb(defaults.cds_recovery_rate, credit_recovery_rate),\n        )\n\n        self._id = _drb(uuid4().hex[:5], id)  # 1 in a million clash\n        self._set_ad_order(order=ad)  # will also clear and initialise the cache\n\n    @property\n    def params(self) -> tuple[DualTypes, DualTypes, DualTypes, DualTypes]:\n        r\"\"\"\n        The parameters associated with the *Curve*.\n        In order these are :math:`[\\beta_0, \\beta_1, \\beta_2, \\lambda]`.\n        \"\"\"\n        return self._params\n\n    def __getitem__(self, date: datetime) -> DualTypes:\n        if defaults.curve_caching and date in self._cache:\n            return self._cache[date]\n\n        if date < self.nodes.initial:\n            return 0.0\n        elif date == self.nodes.initial:\n            return 1.0\n        b0, b1, b2, l0 = self._params\n        T = dcf(\n            self.nodes.initial, date, convention=self.meta.convention, calendar=self.meta.calendar\n        )\n        a1 = l0 * (1 - dual_exp(-T / l0)) / T\n        a2 = a1 - dual_exp(-T / l0)\n        r = b0 + a1 * b1 + a2 * b2\n\n        return self._cached_value(date, dual_exp(-T * r))\n\n    # Solver mutability methods\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[Any]]:\n        return np.array(self._params)\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        return tuple(f\"{self._id}{i}\" for i in range(self._ini_solve, self._n))\n\n    @_new_state_post\n    @_clear_cache_post\n    def _set_node_vector(self, vector: list[DualTypes], ad: int) -> None:\n        if ad == 0:\n            self._params = tuple(_dual_float(_) for _ in vector)  # type: ignore[assignment]\n        elif ad == 1:\n            self._params = tuple(  # type: ignore[assignment]\n                Dual(_dual_float(_), [f\"{self._id}{i}\"], []) for i, _ in enumerate(vector)\n            )\n        else:  # ad == 2\n            self._params = tuple(  # type: ignore[assignment]\n                Dual2(_dual_float(_), [f\"{self._id}{i}\"], [], []) for i, _ in enumerate(vector)\n            )\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        if self.ad == order:\n            return None\n        elif order not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = order\n        self._params = tuple(  # type: ignore[assignment]\n            set_order_convert(_, order, [f\"{self._id}{i}\"]) for i, _ in enumerate(self.params)\n        )\n"
  },
  {
    "path": "python/rateslib/curves/academic/nss.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\nfrom uuid import uuid4\n\nimport numpy as np\n\nfrom rateslib import defaults\nfrom rateslib.curves import _BaseCurve, _CurveMeta, _CurveNodes, _CurveType, _WithMutability\nfrom rateslib.dual import Dual, Dual2, dual_exp, set_order_convert\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.mutability import _clear_cache_post, _new_state_post\nfrom rateslib.scheduling import Convention, dcf, get_calendar\nfrom rateslib.scheduling.convention import _get_convention\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalInput,\n        DualTypes,\n        Variable,\n        datetime,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass NelsonSiegelSvenssonCurve(_WithMutability, _BaseCurve):\n    r\"\"\"\n    A Nelson-Siegel-Svensson curve defined by discount factors.\n\n    The continuously compounded rate to maturity, :math:`r(T)`, is given by the following\n    equation of **six** parameters, :math:`[\\beta_0, \\beta_1, \\beta_2, \\lambda_0, \\beta_3, \\lambda_1]`\n\n    .. math::\n\n       r(T) = \\begin{bmatrix} \\beta_0 & \\beta_1 & \\beta_2 & \\beta_3 \\end{bmatrix} \\begin{bmatrix} 1 \\\\ \\lambda_0 (1- e^{-T/ \\lambda_0}) / T \\\\ \\lambda_0 (1- e^{-T/ \\lambda_0})/ T - e^{-T/ \\lambda_0} \\\\ \\lambda_1 (1- e^{-T/ \\lambda_1})/ T - e^{-T/ \\lambda_1} \\end{bmatrix}\n\n    The **discount factors** on that curve equaling:\n\n    .. math::\n\n       v(T) = e^{-T r(T)}\n\n    *T* is determined as the day count fraction between the start of the curve and the maturity\n    under the given the ``convention`` and ``calendar``.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    dates: 2-tuple of datetime, :red:`required`\n        The dates defining the eval date and final date of the *Curve*.\n    parameters: 6-tuple of Dual, Dual2, Variable, float, :red:`required`\n        The parameters associated with the *Curve*. In order these are\n        :math:`[\\beta_0, \\beta_1, \\beta_2, \\lambda_0, \\beta_3, \\lambda_1]`.\n    id : str, :green:`optional (set randomly)`\n        The unique identifier to distinguish between curves in a multicurve framework.\n    convention : Convention, str, :green:`optional (set as ActActISDA)`\n        The convention of the curve for determining rates. Please see\n        :meth:`dcf()<rateslib.scheduling.dcf>` for all available options.\n    modifier : str, :green:`optional (set by 'defaults')`\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"}, for determining rates when input as\n        a tenor, e.g. \"3M\".\n    calendar : calendar, str, :green:`optional (set as 'all')`\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data. Used for determining rates.\n    ad : int in {0, 1, 2}, :green:`optional`\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n    index_base: float, :green:`optional`\n        The initial index value at the initial node date of the curve. Used for\n        forecasting future index values.\n    index_lag : int, :green:`optional (set by 'defaults')`\n        Number of months of by which the index lags the date. For example if the initial\n        curve node date is 1st Sep 2021 based on the inflation index published\n        17th June 2023 then the lag is 3 months. Best practice is to use 0 months.\n    collateral : str, :green:`optional (set as None)`\n        A currency identifier to denote the collateral currency against which the discount factors\n        for this *Curve* are measured.\n    credit_discretization : int, :green:`optional (set by 'defaults')`\n        A parameter for numerically solving the integral for credit protection legs and default\n        events. Expressed in calendar days. Only used by *Curves* functioning as *hazard Curves*.\n    credit_recovery_rate : Variable | float, :green:`optional (set by 'defaults')`\n        A parameter used in pricing credit protection legs and default events.\n\n    \"\"\"  # noqa: E501\n\n    # ABC properties\n\n    _ini_solve = 0\n    _base_type = _CurveType.dfs\n    _id = None  # type: ignore[assignment]\n    _meta = None  # type: ignore[assignment]\n    _nodes = None  # type: ignore[assignment]\n    _ad = None  # type: ignore[assignment]\n    _interpolator = None  # type: ignore[assignment]\n    _n = 6\n\n    @_new_state_post\n    def __init__(\n        self,\n        dates: tuple[datetime, datetime],\n        parameters: tuple[DualTypes, DualTypes, DualTypes, DualTypes, DualTypes, DualTypes],\n        id: str_ = NoInput(0),  # noqa: A002\n        *,\n        convention: Convention | str | NoInput = NoInput(0),\n        modifier: str | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        ad: int = 0,\n        index_base: Variable | float_ = NoInput(0),\n        index_lag: int | NoInput = NoInput(0),\n        collateral: str_ = NoInput(0),\n        credit_discretization: int_ = NoInput(0),\n        credit_recovery_rate: Variable | float_ = NoInput(0),\n    ):\n        self._nodes = _CurveNodes({dates[0]: 0.0, dates[1]: 0.0})\n        self._params = parameters\n        self._meta = _CurveMeta(\n            _calendar=get_calendar(calendar),\n            _convention=_get_convention(_drb(Convention.ActActISDA, convention)),\n            _modifier=_drb(defaults.modifier, modifier).upper(),\n            _index_base=index_base,\n            _index_lag=_drb(defaults.index_lag_curve, index_lag),\n            _collateral=_drb(None, collateral),\n            _credit_discretization=_drb(\n                defaults.cds_protection_discretization, credit_discretization\n            ),\n            _credit_recovery_rate=_drb(defaults.cds_recovery_rate, credit_recovery_rate),\n        )\n\n        self._id = _drb(uuid4().hex[:5], id)  # 1 in a million clash\n        self._set_ad_order(order=ad)  # will also clear and initialise the cache\n\n    @property\n    def params(self) -> tuple[DualTypes, DualTypes, DualTypes, DualTypes, DualTypes, DualTypes]:\n        r\"\"\"\n        The parameters associated with the *Curve*.\n        In order these are :math:`[\\beta_0, \\beta_1, \\beta_2, \\lambda_0, \\beta_3, \\lambda_1]`.\n        \"\"\"\n        return self._params\n\n    def __getitem__(self, date: datetime) -> DualTypes:\n        if defaults.curve_caching and date in self._cache:\n            return self._cache[date]\n\n        if date < self.nodes.initial:\n            return 0.0\n        elif date == self.nodes.initial:\n            return 1.0\n        b0, b1, b2, l0, b3, l1 = self._params\n        T = dcf(\n            self.nodes.initial, date, convention=self.meta.convention, calendar=self.meta.calendar\n        )\n        a1 = l0 * (1 - dual_exp(-T / l0)) / T\n        a2 = a1 - dual_exp(-T / l0)\n        x1 = l1 * (1 - dual_exp(-T / l1)) / T\n        x2 = x1 - dual_exp(-T / l1)\n        r = b0 + a1 * b1 + a2 * b2 + x2 * b3\n\n        return self._cached_value(date, dual_exp(-T * r))\n\n    # Solver mutability methods\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[Any]]:\n        return np.array(self._params)\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        return tuple(f\"{self._id}{i}\" for i in range(self._ini_solve, self._n))\n\n    @_new_state_post\n    @_clear_cache_post\n    def _set_node_vector(self, vector: list[DualTypes], ad: int) -> None:\n        if ad == 0:\n            self._params = tuple(_dual_float(_) for _ in vector)  # type: ignore[assignment]\n        elif ad == 1:\n            self._params = tuple(  # type: ignore[assignment]\n                Dual(_dual_float(_), [f\"{self._id}{i}\"], []) for i, _ in enumerate(vector)\n            )\n        else:  # ad == 2\n            self._params = tuple(  # type: ignore[assignment]\n                Dual2(_dual_float(_), [f\"{self._id}{i}\"], [], []) for i, _ in enumerate(vector)\n            )\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        if self.ad == order:\n            return None\n        elif order not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = order\n        self._params = tuple(  # type: ignore[assignment]\n            set_order_convert(_, order, [f\"{self._id}{i}\"]) for i, _ in enumerate(self.params)\n        )\n"
  },
  {
    "path": "python/rateslib/curves/academic/sw.py",
    "content": "#############################################################\n# COPYRIGHT 2022 Siffrorna Technology Limited\n# This code may not be copied, modified, used or distributed\n# except with the express permission and licence to\n# do so, provided by the copyright holder.\n# See: https://rateslib.com/py/en/latest/i_licence.html\n#############################################################\n\nfrom __future__ import annotations\n\nfrom datetime import timezone\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\nfrom uuid import uuid4\n\nimport numpy as np\n\nfrom rateslib import defaults\nfrom rateslib.curves import (\n    _BaseCurve,\n    _CurveMeta,\n    _CurveNodes,\n    _CurveType,\n    _WithMutability,\n)\nfrom rateslib.dual import dual_exp, dual_log\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.mutability import _new_state_post\nfrom rateslib.scheduling import Convention, get_calendar\nfrom rateslib.scheduling.convention import _get_convention\n\nif TYPE_CHECKING:\n    from numpy import float64 as Nf64  # noqa: N812\n    from numpy import object_ as Nobject  # noqa: N812\n    from numpy.typing import NDArray\n\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        DualTypes,\n        Variable,\n        datetime,\n        float_,\n        int_,\n        str_,\n    )\n\n\nUTC = timezone.utc\n\n\nclass _NullInterpolator:\n    def _csolve(self, curve_type: _CurveType, nodes: _CurveNodes, ad: int) -> None:\n        pass\n\n\ndef _dual_sinh(x: DualTypes) -> DualTypes:\n    return (dual_exp(x) - dual_exp(-x)) * 0.5\n\n\nclass SmithWilsonCurve(_WithMutability, _BaseCurve):\n    r\"\"\"\n    A Smith-Wilson style *Curve* defined by discount factors.\n\n    The discount factors on this curve are defined by:\n\n    .. math::\n\n       v(t) = e^{-wt} + \\mathbf{W}[t, \\mathbf{u}] \\mathbf{\\hat{b}}\n\n    where,\n\n    .. math::\n\n       W(t, u) &= e^{-w(t+u)} \\left ( \\alpha \\min(t, u) - e^{\\alpha max(t, u)} sinh(\\alpha min(t, u)) \\right )  \\\\\n       w &= \\ln ( 1 + UFR)\n\n    and :math:`\\alpha` and :math:`UFR` are parameters controlling convergence to some rate in the\n    long term, and :math:`\\mathbf{\\hat{b}}` are calibration parameters. All 'time' quantities are\n    derived under an effective '*Act/365.25*' day count convention.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    nodes: dict[datetime, float]\n        The parameters of the *Curve*. The value associated with the *initial node date* is\n        treated as :math:`\\alpha`. All subsequent key-value pairs define the (Mx1) vectors\n        :math:`\\mathbf{u}` and :math:`\\mathbf{\\hat{b}}` respectively.\n    ufr: float, :red:`required`\n        The rates that is denoted by the *'ultimate forward rate'*.\n    solve_alpha: bool, :green:`optional (set as False)`\n        Define whether :math:`\\alpha` is to be treated as a parameter in the solver process\n        simultaneously with :math:`\\mathbf{\\hat{b}}`.\n    id : str, :green:`optional (set randomly)`\n        The unique identifier to distinguish between curves in a multicurve framework.\n    convention : Convention, str, :green:`optional (set as Act365_25)`\n        The convention of the curve for determining rates. Please see\n        :meth:`dcf()<rateslib.scheduling.dcf>` for all available options.\n    modifier : str, :green:`optional (set by 'defaults')`\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"}, for determining rates when input as\n        a tenor, e.g. \"3M\".\n    calendar : calendar, str, :green:`optional (set as 'all')`\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data. Used for determining rates.\n    ad : int in {0, 1, 2}, :green:`optional`\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n    index_base: float, :green:`optional`\n        The initial index value at the initial node date of the curve. Used for\n        forecasting future index values.\n    index_lag : int, :green:`optional (set by 'defaults')`\n        Number of months of by which the index lags the date. For example if the initial\n        curve node date is 1st Sep 2021 based on the inflation index published\n        17th June 2023 then the lag is 3 months. Best practice is to use 0 months.\n    collateral : str, :green:`optional (set as None)`\n        A currency identifier to denote the collateral currency against which the discount factors\n        for this *Curve* are measured.\n    credit_discretization : int, :green:`optional (set by 'defaults')`\n        A parameter for numerically solving the integral for credit protection legs and default\n        events. Expressed in calendar days. Only used by *Curves* functioning as *hazard Curves*.\n    credit_recovery_rate : Variable | float, :green:`optional (set by 'defaults')`\n        A parameter used in pricing credit protection legs and default events.\n\n    Notes\n    -----\n\n    **EIOPA's Approach**\n\n    The Smith-Wilson *Curve* as `defined by EIOPA <https://www.eiopa.europa.eu/system/files/2022-09/eiopa-bos-22-409-technical-documentation.pdf>`__\n    is a *Curve* designed with the following properties:\n\n    - A matrix-type formulation to solve calibration parameters using linear algebra.\n    - An *'ultra-forward-rate (UFR)'* and convergence parameter :math:`\\alpha` to control\n      the curve beyond points at which there might be priced market instruments.\n\n    The official version of the Smith-Wilson discount factor function is:\n\n    .. math::\n\n       v(t) = e^{-wt} + \\mathbf{W}[t, \\mathbf{u}]\\mathbf{C} \\mathbf{b}\n\n    In this equation a set of *N* bonds (likely coupon bearing) are selected from the market and\n    the vector :math:`\\mathbf{u}`, of length *M*, contains ordered times to each cashflow of any\n    bond. The *(MxN)* matrix :math:`\\mathbf{C}_{i,j}` structures individual cashflows attributable\n    to each bond, *j*, at cashflow date, :math:`u_i`. And :math:`\\mathbf{b}`, the calibration\n    parameters, must have length *N*.\n\n    The Smith-Wilson concept is to use that same equation replacing, *t*, with each :math:`u_i`,\n    and then multiplying each cashflow of any bond by those relevant discount factors to return the\n    market price, :math:`\\mathbf{p}`, i.e.\n\n    .. math::\n\n       \\mathbf{p} = \\mathbf{C^T} v[\\mathbf{u}] = \\mathbf{C^T} e^{-w\\mathbf{u}} + \\mathbf{C^T W[u,u] C b}\n\n    After this is rearranged it yields,\n\n    .. math::\n\n       \\mathbf{b} = \\left ( \\mathbf{C^T W[u,u] C} \\right )^{-1} ( \\mathbf{p} - \\mathbf{C^T} e^{-w \\mathbf{u}} )\n\n    which is transformable into the equations recognisable in the EIOPA document using their same\n    substitutions,\n\n    .. math::\n\n       \\mathbf{b} &= \\left ( \\mathbf{Q^T H[u,u] Q} \\right )^{-1} ( \\mathbf{p} - \\mathbf{q} )  \\\\\n       \\mathbf{d} &= e^{-w \\mathbf{u}} \\\\\n       \\mathbf{Q} &= \\mathbf{d_\\Delta C} \\\\\n       \\mathbf{W[u,u]} &= \\mathbf{d_\\Delta H[u,u] d_\\Delta} \\\\\n       \\mathbf{q} &= \\mathbf{C^T d} \\\\\n\n    **Rateslib's Approach**\n\n    *Rateslib* makes two key changes. Firstly it recognises that for an unchanged :math:`\\mathbf{u}`\n    vector, i.e. the cashflow dates remain the same, and unchanged discount factors at those dates,\n    i.e. unchanged :math:`v[\\mathbf{u}]` the system can be equivalently formulated\n    in terms of zero coupon bonds, so that:\n\n    .. math::\n\n       \\underbrace{\\mathbf{C b}}_{(M \\times N) (N \\times 1)} = \\underbrace{\\mathbf{I \\hat{b}}}_{(M \\times M) (M \\times 1)}\n\n    Since the market prices of the bonds are known and the discount factors of these synthesised\n    zero coupon bonds are not known apriori this transformation does not allow the linear\n    algebraic solution (EIOPA's approach) to remain viable. That leads to the second change.\n\n    *Rateslib* does not bootstrap or algebraically solve *Curves*. It uses a global solver.\n    This is why the above change is permissible because even under the\n    reformulation it will still converge on *a* solution for :math:`\\mathbf{\\hat{b}}` which\n    reprices the bonds.\n\n    **Implication**\n\n    The general rules for *Curve* solving remain applicable; if M > N then the system is\n    underspecified and may result in spurious behavior. If M = N and maturities are all\n    appropriately chosen the solution is exact and unique.\n\n    Because *rateslib* treats *Curve* parameterization and *Instrument* calibration as two\n    separate processes there is increased flexibility in both aspects. The calibrating bonds\n    do not necessarily have to match the *nodes* of the Smith-Wilson *Curve*. Under EIOPA's\n    approach this is obviously not possible because the framework of equations relies on\n    setting up the appropriate cashflow matrix and array of cashflow dates.\n\n    .. note::\n\n       *Rateslib* will not determine the matrices :math:`\\mathbf{W[u,u], H[u,u], Q, C}` etc.\n       becuase its methods does not require them\n\n    Examples\n    --------\n    The `standard EIOPA example <https://register.eiopa.europa.eu/Publications/Consultations/Consultation_RFR_Example_Extrapolation.xlsx>`__\n    happens to include a 20x20 cashflow matrix, each bond valued at par with increasing coupon\n    rates, implying increasing YTM.\n\n    .. image:: ../_static/eiopa_c.png\n       :align: center\n       :alt: EIOPA Example of Smith-Wilson Curve\n       :height: 304\n       :width: 597\n\n    Because this is a square matrix and satisfies the criteria above the *rateslib* solution\n    will match EIOPA's.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import FixedRateBond, Solver, SmithWilsonCurve, dt\n\n    .. ipython:: python\n\n        sw = SmithWilsonCurve(\n            nodes={\n                dt(2000, 1, 1): 0.12376,       #  <--  alpha value used in EIOPA file\n                **{dt(2000+i, 1, 1): 0.1 for i in range(1, 21)}\n            },\n            solve_alpha=False,\n            ufr= 4.2,\n            id=\"academic_curve\",\n        )\n        coupons = [0.2, 0.225, 0.3, 0.425, 0.55, 0.7, 0.85, 1.0, 1.15, 1.275, 1.4, 1.475, 1.575, 1.65, 1.7, 1.75, 1.8, 1.825, 1.85, 1.875]\n        bonds = [\n            FixedRateBond(\n                effective=dt(2000, 1, 1),\n                termination=f\"{i}Y\",         #  <-  1Y to 20Y\n                fixed_rate=coupons[i-1],     #  <-  Coupons as specified\n                calendar=\"all\",\n                ex_div=1,\n                convention=\"actacticma\",\n                frequency=\"A\",\n                curves=\"academic_curve\",\n                metric=\"dirty_price\"\n            )\n            for i in range(1, 21)\n        ]\n        prices = [100.0] * 20                #  <-  All bonds priced to par\n        Solver(curves=[sw], instruments=bonds, s=prices)\n\n    We can plot the resultant curves, which can be compared directly with the EIOPA file.\n\n    .. ipython:: python\n\n       sw.plot(\"Z\")\n       sw.plot(\"1b\")\n\n    .. plot::\n\n       from rateslib import SmithWilsonCurve, Solver, dt, FixedRateBond\n       import matplotlib.pyplot as plt\n\n       sw = SmithWilsonCurve(\n           nodes={\n               dt(2000, 1, 1): 0.12376,\n               **{dt(2000+i, 1, 1): 0.1 for i in range(1, 21)}\n           },\n           solve_alpha=False,\n           ufr= 4.2,\n           id=\"academic_curve\",\n       )\n       coupons = [0.2, 0.225, 0.3, 0.425, 0.55, 0.7, 0.85, 1.0, 1.15, 1.275, 1.4, 1.475, 1.575, 1.65, 1.7, 1.75, 1.8, 1.825, 1.85, 1.875]\n       bonds = [\n           FixedRateBond(\n               effective=dt(2000, 1, 1),\n               termination=f\"{i}Y\",         #  <-  1Y to 20Y\n               fixed_rate=coupons[i-1],     #  <-  Coupons as specified\n               calendar=\"all\",\n               ex_div=1,\n               convention=\"actacticma\",\n               frequency=\"A\",\n               curves=\"academic_curve\",\n               metric=\"dirty_price\"\n           )\n           for i in range(1, 21)\n       ]\n       prices = [100.0] * 20                #  <-  All bonds priced to par\n       Solver(curves=[sw], instruments=bonds, s=prices)\n\n       fig1, ax1, lines = sw.plot(\"z\")\n       del fig1, ax1\n       plt.close()\n       fig, ax, _ = sw.plot(\"1b\")\n       ax.plot(lines[0]._x, lines[0]._y)\n       plt.show()\n       plt.close()\n\n    \"\"\"  # noqa: E501\n\n    # ABC properties\n\n    _ini_solve = 0\n    _base_type: _CurveType = _CurveType.dfs\n    _id: str = None  # type: ignore[assignment]\n    _ad: int = None  # type: ignore[assignment]\n    _meta: _CurveMeta = None  # type: ignore[assignment]\n    _nodes: _CurveNodes = None  # type: ignore[assignment]\n    _interpolator = _NullInterpolator()  # type: ignore[assignment]\n\n    @_new_state_post\n    def __init__(\n        self,\n        nodes: dict[datetime, DualTypes],\n        ufr: DualTypes,\n        solve_alpha: bool = False,\n        id: str_ = NoInput(0),  # noqa: A002\n        *,\n        convention: Convention | str | NoInput = NoInput(0),\n        modifier: str | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        ad: int = 0,\n        index_base: Variable | float_ = NoInput(0),\n        index_lag: int | NoInput = NoInput(0),\n        collateral: str_ = NoInput(0),\n        credit_discretization: int_ = NoInput(0),\n        credit_recovery_rate: Variable | float_ = NoInput(0),\n    ):\n        self._nodes = _CurveNodes(_nodes=nodes)\n        if not solve_alpha:\n            self._ini_solve = 1\n\n        self._ufr = ufr\n\n        self._meta = _CurveMeta(\n            _calendar=get_calendar(calendar),\n            _convention=_get_convention(_drb(Convention.Act365_25, convention)),\n            _modifier=_drb(defaults.modifier, modifier).upper(),\n            _index_base=index_base,\n            _index_lag=_drb(defaults.index_lag_curve, index_lag),\n            _collateral=_drb(None, collateral),\n            _credit_discretization=_drb(\n                defaults.cds_protection_discretization, credit_discretization\n            ),\n            _credit_recovery_rate=_drb(defaults.cds_recovery_rate, credit_recovery_rate),\n        )\n\n        self._id = _drb(uuid4().hex[:5], id)  # 1 in a million clash\n        self._set_ad_order(order=ad)  # will also clear and initialise the cache\n\n    @property\n    def alpha(self) -> DualTypes:\n        r\"\"\"The :math:`\\alpha` value of the *Curve*.\"\"\"\n        return self.nodes.nodes[self.nodes.initial]\n\n    @property\n    def b(self) -> NDArray[Nobject]:\n        r\"\"\"The :math:`\\mathbf{\\hat{b}}` parameters value of the *Curve*.\"\"\"\n        return np.array(self.nodes.values[1:])\n\n    @property\n    def ufr(self) -> DualTypes:\n        \"\"\"The UFR value of the *Curve*.\"\"\"\n        return self._ufr\n\n    @property\n    def k(self) -> DualTypes:\n        r\"\"\"\n        The :math:`\\kappa` value as defined in the EIOPA documentation.\n\n        Under EIOPA:\n\n        .. math::\n\n           \\kappa = \\frac{ 1 + \\alpha \\mathbf{u^T Q b} }{ sinh[\\alpha \\mathbf{u^T}] \\mathbf{Q b} }\n\n        \"\"\"\n        Q = np.diag([dual_exp(-self.w * _) for _ in self.u])  # Q is d_delta\n        numerator: DualTypes = 1 + self.alpha * np.matmul(\n            np.matmul(self.u[None, :], Q), self.b[:, None]\n        )\n        denominator: DualTypes = np.matmul(\n            np.matmul(np.array([_dual_sinh(self.alpha * _) for _ in self.u])[None, :], Q),\n            self.b[:, None],\n        )\n        return numerator / denominator\n\n    @cached_property\n    def w(self) -> DualTypes:\n        \"\"\"The :math:`w` value of the *Curve* derived from the UFR.\"\"\"\n        return dual_log(1 + self.ufr / 100.0)\n\n    @cached_property\n    def u(self) -> NDArray[Nf64]:\n        r\"\"\"The :math:`\\mathbf{u}` vector of the *Curve* derived from the node dates.\"\"\"\n        # 31557600 = 365.25 days * 86400 seconds per day\n        return (np.array(self.nodes.posix_keys[1:]) - self.nodes.posix_keys[0]) / 31557600.0\n\n    def __getitem__(self, date: datetime) -> DualTypes:\n        if defaults.curve_caching and date in self._cache:\n            return self._cache[date]\n\n        if date < self.nodes.initial:\n            return 0.0\n        elif date == self.nodes.initial:\n            return 1.0\n\n        # 31557600 = 365.25 days * 86400 seconds per day\n        t = (date.replace(tzinfo=UTC).timestamp() - self.nodes.posix_keys[0]) / 31557600.0\n        a = self.alpha\n        w = self.w\n\n        v = dual_exp(-t * w)\n\n        mins = [min(t, _) for _ in self.u]\n        maxs = [max(t, _) for _ in self.u]\n        ww = np.array(\n            [\n                dual_exp(-u * w) * (a * min_ - dual_exp(-a * max_) * _dual_sinh(a * min_))\n                for (u, min_, max_) in zip(self.u, mins, maxs, strict=False)\n            ]\n        )\n\n        v += np.inner(self.b, ww) * v\n        return self._cached_value(date, v)\n"
  },
  {
    "path": "python/rateslib/curves/curves.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport json\nimport pickle\nimport warnings\nfrom abc import ABC, abstractmethod\nfrom calendar import monthrange\nfrom dataclasses import replace\nfrom datetime import datetime, timedelta, timezone\nfrom math import comb, prod\nfrom typing import TYPE_CHECKING, TypeAlias\nfrom uuid import uuid4\n\nimport numpy as np\nfrom pandas import Series\n\nimport rateslib.errors as err\nfrom rateslib import defaults, fixings\nfrom rateslib.curves.interpolation import InterpolationFunction\nfrom rateslib.curves.utils import (\n    _CreditImpliedType,\n    _CurveInterpolator,\n    _CurveMeta,\n    _CurveNodes,\n    _CurveType,\n    _ProxyCurveInterpolator,\n    average_rate,\n)\nfrom rateslib.data.loader import FixingMissingDataError, FixingRangeError\nfrom rateslib.default import PlotOutput, plot\nfrom rateslib.dual import Dual, Dual2, Variable, dual_exp, dual_log, set_order_convert\nfrom rateslib.dual.utils import _dual_float, _get_order_of\nfrom rateslib.enums.generics import Err, NoInput, Ok, _drb\nfrom rateslib.enums.parameters import IndexMethod, _get_index_method\nfrom rateslib.mutability import (\n    _clear_cache_post,\n    _new_state_post,\n    _no_interior_validation,\n    _validate_states,\n    _WithCache,\n    _WithState,\n)\nfrom rateslib.scheduling import Adjuster, Convention, add_tenor, dcf, get_calendar\nfrom rateslib.scheduling.convention import _get_convention\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalInput,\n        CurveOption_,\n        FXForwards,\n        Number,\n        Result,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\nUTC = timezone.utc\n\nDualTypes: TypeAlias = (\n    \"Dual | Dual2 | Variable | float\"  # required for non-cyclic import on _WithCache\n)\n\n\nclass _WithOperations:\n    \"\"\"Provides automatic implementation of the curve operations required on a\n    :class:`~rateslib.curves._BaseCurve`.\"\"\"\n\n    # Operations\n\n    @_validate_states\n    def shift(\n        self,\n        spread: DualTypes,\n        id: str_ = NoInput(0),  # noqa: A002\n    ) -> ShiftedCurve:\n        \"\"\"\n        Create a :class:`~rateslib.curves.ShiftedCurve`: moving *Self* vertically in rate space.\n\n        For examples see the documentation for :class:`~rateslib.curves.ShiftedCurve`.\n\n        Parameters\n        ----------\n        spread : float, Dual, Dual2, Variable\n            The number of basis points added to the existing curve.\n        id : str, optional\n            Set the id of the returned curve.\n\n        Returns\n        -------\n        ShiftedCurve\n\n        \"\"\"\n        _: _BaseCurve = self  # type: ignore[assignment]\n        return ShiftedCurve(curve=_, shift=spread, id=id)\n\n    @_validate_states\n    def translate(self, start: datetime, id: str_ = NoInput(0)) -> TranslatedCurve:  # noqa: A002\n        \"\"\"\n        Create a :class:`~rateslib.curves.TranslatedCurve`: maintaining an identical rate space,\n        but moving the initial node date forwards in time.\n\n        For examples see the documentation for :class:`~rateslib.curves.TranslatedCurve`.\n\n        Parameters\n        ----------\n        start : datetime\n            The new initial node date for the curve. Must be after the original initial node date.\n        id : str, optional\n            Set the id of the returned curve.\n\n        Returns\n        -------\n        TranslatedCurve\n        \"\"\"  # noqa: E501\n        _: _BaseCurve = self  # type: ignore[assignment]\n        return TranslatedCurve(curve=_, start=start, id=id)\n\n    @_validate_states\n    def roll(self, tenor: datetime | str | int, id: str_ = NoInput(0)) -> RolledCurve:  # noqa: A002\n        \"\"\"\n        Create a :class:`~rateslib.curves.RolledCurve`: translating the rate space of *Self* in\n        time.\n\n        For examples see the documentation for :class:`~rateslib.curves.RolledCurve`.\n\n        Parameters\n        ----------\n        tenor : datetime, str or int\n            The measure of time by which to translate the curve through time.\n        id : str, optional\n            Set the id of the returned curve.\n\n        Returns\n        -------\n        RolledCurve\n\n        \"\"\"  # noqa: E501\n        _: _BaseCurve = self  # type: ignore[assignment]\n        if isinstance(tenor, str):\n            tenor_: datetime | int = add_tenor(_._nodes.initial, tenor, \"NONE\", NoInput(0))\n        else:\n            tenor_ = tenor\n\n        if isinstance(tenor_, int):\n            roll_days: int = tenor_\n        else:\n            roll_days = (tenor_ - _._nodes.initial).days\n\n        return RolledCurve(curve=_, roll_days=roll_days, id=id)\n\n\nclass _BaseCurve(_WithState, _WithCache[datetime, DualTypes], _WithOperations, ABC):\n    \"\"\"\n    An ABC defining the base methods of a *Curve*.\n\n    Provided that the abstract base properties and methods of this class are implemented any\n    custom curve can be used within *rateslib*. Often the default implementations for some of\n    these, via ``super()`` are sufficient. The required base methods are:\n\n    - ``_meta``: returns a :class:`~rateslib.curves._CurveMeta` class.\n    - ``_interpolator``: returns a :class:`~rateslib.curves._CurveInterpolator` class.\n    - ``_nodes``: returns a :class:`~rateslib.curves._CurveNodes` class.\n    - ``_id``: returns a str representing the *Curve* id.\n    - ``_ad``: returns an integer in {0, 1, 2} indicating the automatic differentiation state.\n    - ``_base_type``: returns a :class:`~rateslib.curves._CurveType`.\n    - ``__getitem__(date)``: returns a float, :class:`~rateslib.dual.Dual`,\n      :class:`~rateslib.dual.Dual2`, or :class:`~rateslib.dual.Variable` given an input date.\n    - ``_set_ad_order(ad)``: mutates the node values of the *Curve* to adopt new automatic\n      differentiation states for facilitating other features, such as\n      :class:`~rateslib.solver.Solver` calibration and risk sensitivity calculation.\n\n    To automatically provide some of the operations the class\n    :class:`~rateslib.curves._WithOperations` can, and is likely to always be, inherited, without\n    the need for any additional implementation. In certain cases the `_base_type` will prevent\n    some methods from calculating and will raise `TypeError`.\n\n    To allow custom user curves to be calibrated by the :class:`~rateslib.solver.Solver` framework\n    the :class:`~rateslib.curves._WithMutability` class can be inherited. This requires two\n    additional implementation to allow a :class:`~rateslib.solver.Solver` to interact directly with\n    it:\n\n    - ``_get_node_vector()``: returns a NumPy array of the ordered node values consumed.\n    - ``_get_node_vars()``: returns a tuple of ordered string variable names associated with\n      each node of the *Curve*.\n    - ``_set_node_vector(array)``: accepts a NumPy array of the ordered node values and sets\n      these directly for the *Curve*.\n\n    .. rubric:: Examples\n\n    A demonstration of using this class to build a user custom *Curve* is presented at\n    `Cookbook: Building Custom Curves with _BaseCurve (e.g. Nelson-Siegel) <../z_basecurve.html>`_\n    \"\"\"\n\n    # Required properties\n\n    @property\n    @abstractmethod\n    def _meta(self) -> _CurveMeta:\n        return _CurveMeta(\n            _calendar=get_calendar(NoInput(0)),\n            _collateral=None,\n            _convention=_get_convention(defaults.convention),\n            _credit_discretization=defaults.cds_protection_discretization,\n            _credit_recovery_rate=defaults.cds_recovery_rate,\n            _index_base=NoInput(0),\n            _index_lag=defaults.index_lag_curve,\n            _modifier=defaults.modifier,\n        )\n\n    @property\n    @abstractmethod\n    def _interpolator(self) -> _CurveInterpolator:\n        # create a default CurveInterpolator that is functionless\n        # this is a placeholder obj that cannot be used for interpolation\n        return _CurveInterpolator(\n            local=\"log_linear\",\n            t=NoInput(0),\n            endpoints=(\"natural\", \"natural\"),\n            node_dates=[],\n            convention=defaults.convention.lower(),\n            curve_type=_CurveType.dfs,\n        )\n\n    @property\n    @abstractmethod\n    def _nodes(self) -> _CurveNodes: ...\n\n    @property\n    @abstractmethod\n    def _id(self) -> str:\n        return uuid4().hex[:5]\n\n    @property\n    @abstractmethod\n    def _ad(self) -> int: ...\n\n    @property\n    @abstractmethod\n    def _base_type(self) -> _CurveType: ...\n\n    # Required methods\n\n    @abstractmethod\n    def __getitem__(self, date: datetime) -> DualTypes:\n        \"\"\"\n        The get item method for any *Curve* type will allow the inheritance of the below\n        methods.\n        \"\"\"\n        if defaults.curve_caching and date in self._cache:\n            return self._cache[date]\n\n        if date < self.nodes.initial:\n            return 0.0\n\n        if self.interpolator.spline is None or date < self.interpolator.spline.t[0]:\n            val = self.interpolator.local_func(date, self)\n        else:\n            date_posix = date.replace(tzinfo=UTC).timestamp()\n            if date > self.interpolator.spline.t[-1]:\n                warnings.warn(\n                    \"Evaluating points on a curve beyond the endpoint of the basic \"\n                    \"spline interval is undefined.\\n\"\n                    f\"date: {date.strftime('%Y-%m-%d')}, spline end: \"\n                    f\"{self.interpolator.spline.t[-1].strftime('%Y-%m-%d')}, curve id: \"\n                    f\"'{self.id}'\\n\"\n                    \"This often occurs when a curve is constructed with a final node date \"\n                    \"that aligns with the maturity of an instrument with a payment lag.\\nIn the \"\n                    \"case that the instrument has a payment lag (e.g. a SOFR swap or ESTR swap or \"\n                    \"bond terminating on a non-business day) then a cashflow will occur after the \"\n                    \"maturity of the instrument.\\nThe solution is to ensure that the final node \"\n                    \"date of the curve is changed to be beyond that expected payment date.\",\n                    UserWarning,\n                )\n            if self._base_type == _CurveType.dfs:\n                val = dual_exp(self.interpolator.spline.spline.ppev_single(date_posix))  # type: ignore[union-attr]\n            else:  # self._base_type == _CurveType.values:\n                val = self.interpolator.spline.spline.ppev_single(date_posix)  # type: ignore[union-attr]\n\n        return self._cached_value(date, val)\n\n    @abstractmethod\n    def _set_ad_order(self, order: int) -> None: ...\n\n    # Properties\n\n    @property\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the *Curve*.\"\"\"\n        return self._ad\n\n    @property\n    def meta(self) -> _CurveMeta:\n        \"\"\"An instance of :class:`~rateslib.curves._CurveMeta`.\"\"\"\n        return self._meta\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Curve* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def nodes(self) -> _CurveNodes:\n        \"\"\"An instance of :class:`~rateslib.curves._CurveNodes`.\"\"\"\n        return self._nodes\n\n    @property\n    def _n(self) -> int:\n        \"\"\"The number of pricing parameters of the *Curve*.\"\"\"\n        return self.nodes.n\n\n    @property\n    def interpolator(self) -> _CurveInterpolator:\n        \"\"\"An instance of :class:`~rateslib.curves._CurveInterpolator`.\"\"\"\n        return self._interpolator\n\n    # Rate Calculation\n\n    def rate(\n        self,\n        effective: datetime,\n        termination: datetime | str | NoInput = NoInput(0),\n        modifier: str | NoInput = NoInput(1),\n        float_spread: float | NoInput = NoInput(0),\n        spread_compound_method: str | NoInput = NoInput(0),\n    ) -> DualTypes | None:\n        \"\"\"\n        Calculate the rate on the `Curve` using DFs.\n\n        If rates are sought for dates prior to the initial node of the curve `None`\n        will be returned.\n\n        Parameters\n        ----------\n        effective : datetime\n            The start date of the period for which to calculate the rate.\n        termination : datetime or str\n            The end date of the period for which to calculate the rate.\n        modifier : str, optional\n            The day rule if determining the termination from tenor. If `False` is\n            determined from the `Curve` modifier.\n        float_spread : float, optional\n            A float spread can be added to the rate in certain cases.\n        spread_compound_method : str in {\"none_simple\", \"isda_compounding\"}\n            The method if adding a float spread.\n            If *\"none_simple\"* is used this results in an exact calculation.\n            If *\"isda_compounding\"* or *\"isda_flat_compounding\"* is used this results\n            in an approximation.\n\n        Returns\n        -------\n        Dual, Dual2 or float\n\n        Notes\n        -----\n        Calculating rates from a curve implies that the conventions attached to the\n        specific index, e.g. USD SOFR, or GBP SONIA, are applicable and these should\n        be set at initialisation of the ``Curve``. Thus, the convention used to\n        calculate the ``rate`` is taken from the ``Curve`` from which ``rate``\n        is called.\n\n        ``modifier`` is only used if a tenor is given as the termination.\n\n        Major indexes, such as legacy IBORs, and modern RFRs typically use a\n        ``convention`` which is either `\"Act365F\"` or `\"Act360\"`. These conventions\n        do not need additional parameters, such as the `termination` of a leg,\n        the `frequency` or a leg or whether it is a `stub` to calculate a DCF.\n\n        **Adding Floating Spreads**\n\n        An optimised method for adding floating spreads to a curve rate is provided.\n        This is quite restrictive and mainly used internally to facilitate other parts\n        of the library.\n\n        - When ``spread_compound_method`` is *\"none_simple\"* the spread is a simple\n          linear addition.\n        - When using *\"isda_compounding\"* or *\"isda_flat_compounding\"* the curve is\n          assumed to be comprised of RFR\n          rates and an approximation is used to derive to total rate.\n\n        Examples\n        --------\n\n        .. ipython:: python\n\n            curve_act365f = Curve(\n                nodes={\n                    dt(2022, 1, 1): 1.0,\n                    dt(2022, 2, 1): 0.98,\n                    dt(2022, 3, 1): 0.978,\n                },\n                convention='Act365F'\n            )\n            curve_act365f.rate(dt(2022, 2, 1), dt(2022, 3, 1))\n\n        Using a different convention will result in a different rate:\n\n        .. ipython:: python\n\n            curve_act360 = Curve(\n                nodes={\n                    dt(2022, 1, 1): 1.0,\n                    dt(2022, 2, 1): 0.98,\n                    dt(2022, 3, 1): 0.978,\n                },\n                convention='Act360'\n            )\n            curve_act360.rate(dt(2022, 2, 1), dt(2022, 3, 1))\n        \"\"\"\n        try:\n            _: DualTypes = self._rate_with_raise(\n                effective, termination, modifier, float_spread, spread_compound_method\n            )\n        except ZeroDivisionError as e:\n            if \"effective:\" not in str(e):\n                return None  # TODO (low): is this an unreachable line?\n            raise e\n        except ValueError as e:\n            if \"`effective` date for rate period is before\" in str(e):\n                return None\n            raise e\n        return _\n\n    def _rate_with_raise(\n        self,\n        effective: datetime,\n        termination: datetime | str | NoInput,\n        modifier: str | NoInput = NoInput(1),\n        float_spread: float | NoInput = NoInput(0),\n        spread_compound_method: str | NoInput = NoInput(0),\n    ) -> DualTypes:\n        if self._base_type == _CurveType.dfs:\n            return self._rate_with_raise_dfs(\n                effective, termination, modifier, float_spread, spread_compound_method\n            )\n        else:  # is _CurveType.values\n            return self._rate_with_raise_values(\n                effective, termination, modifier, float_spread, spread_compound_method\n            )\n\n    def _rate_with_raise_values(\n        self,\n        effective: datetime,\n        *args: Any,\n        **kwargs: Any,\n    ) -> DualTypes:\n        if effective < self.nodes.initial:  # Alternative solution to PR 172.\n            raise ValueError(\n                \"`effective` date for rate period is before the initial node date of the Curve.\\n\"\n                \"If you are trying to calculate a rate for an historical FloatPeriod have you \"\n                \"neglected to supply appropriate `fixings`?\\n\"\n                \"See Documentation > Cookbook > Working with Fixings.\"\n            )\n        return self.__getitem__(effective)\n\n    def _rate_with_raise_dfs(\n        self,\n        effective: datetime,\n        termination: datetime | str | NoInput,\n        modifier: str | NoInput = NoInput(1),\n        float_spread: float | NoInput = NoInput(0),\n        spread_compound_method: str | NoInput = NoInput(0),\n    ) -> DualTypes:\n        modifier_ = _drb(self.meta.modifier, modifier)\n\n        if effective < self.nodes.initial:  # Alternative solution to PR 172.\n            raise ValueError(\n                \"`effective` date for rate period is before the initial node date of the Curve.\\n\"\n                \"If you are trying to calculate a rate for an historical FloatPeriod have you \"\n                \"neglected to supply appropriate `fixings`?\\n\"\n                \"See Documentation > Cookbook > Working with Fixings.\"\n            )\n        if isinstance(termination, str):\n            termination = add_tenor(effective, termination, modifier_, self.meta.calendar)\n        elif isinstance(termination, NoInput):\n            raise ValueError(\"`termination` must be supplied for rate of DF based Curve.\")\n\n        if termination == effective:\n            raise ZeroDivisionError(f\"effective: {effective}, termination: {termination}\")\n\n        df_ratio = self.__getitem__(effective) / self.__getitem__(termination)\n        n_ = df_ratio - 1.0\n        d_ = dcf(effective, termination, self.meta.convention, calendar=self.meta.calendar)\n        _: DualTypes = n_ / d_ * 100\n\n        if not isinstance(float_spread, NoInput) and abs(float_spread) > 1e-9:\n            if spread_compound_method == \"none_simple\":\n                return _ + float_spread / 100\n            elif spread_compound_method == \"isda_compounding\":\n                # this provides an approximated rate\n                r_bar, d, n = average_rate(effective, termination, self.meta.convention, _, d_)\n                _ = ((1 + (r_bar + float_spread / 100) / 100 * d) ** n - 1) / (n * d)\n                return 100 * _\n            elif spread_compound_method == \"isda_flat_compounding\":\n                # this provides an approximated rate\n                r_bar, d, n = average_rate(effective, termination, self.meta.convention, _, d_)\n                rd = r_bar / 100 * d\n                _ = (\n                    (r_bar + float_spread / 100)\n                    / n\n                    * (comb(int(n), 1) + comb(int(n), 2) * rd + comb(int(n), 3) * rd**2)\n                )\n                return _\n            else:\n                raise ValueError(\n                    \"Must supply a valid `spread_compound_method`, when `float_spread` \"\n                    \" is not `None`.\",\n                )\n\n        return _\n\n    # Index Calculations\n\n    def _try_index_value(\n        self, index_date: datetime, index_lag: int, index_method: IndexMethod = IndexMethod.Curve\n    ) -> Result[DualTypes]:\n        if self._base_type == _CurveType.values:\n            return Err(TypeError(\"A 'values' type Curve cannot be used to forecast index values.\"))\n\n        if isinstance(self.meta.index_base, NoInput):\n            return Err(\n                ValueError(\n                    \"Curve must be initialised with an `index_base` value to derive `index_value`.\"\n                )\n            )\n\n        lag_months = index_lag - self.meta.index_lag\n        if index_method == IndexMethod.Curve:\n            if lag_months != 0:\n                return Err(\n                    ValueError(\n                        \"'curve' interpolation can only be used with `index_value` when the Curve \"\n                        \"`index_lag` matches the input `index_lag`.\"\n                    )\n                )\n            # use traditional discount factor from Index base to determine index value.\n            if index_date < self.nodes.initial:\n                warnings.warn(\n                    \"The date queried on the Curve for an `index_value` is prior to the \"\n                    \"initial node on the Curve.\\nThis is returned as zero and likely \"\n                    f\"causes downstream calculation error.\\ndate queried: {index_date}\"\n                    \"Either providing `index_fixings` to the object or extend the Curve backwards.\",\n                    UserWarning,\n                )\n                return Ok(0.0)\n                # return zero for index dates in the past\n                # the proper way for instruments to deal with this is to supply i_fixings\n            elif index_date == self.nodes.initial:\n                return Ok(self.meta.index_base)\n            else:\n                return Ok(self.meta.index_base * 1.0 / self.__getitem__(index_date))\n        elif index_method == IndexMethod.Monthly:\n            index_date_ = add_tenor(index_date, f\"{lag_months * -1}M\", \"none\", NoInput(0), 1)\n            return self._try_index_value(\n                index_date=index_date_,\n                index_lag=self.meta.index_lag,\n                index_method=IndexMethod.Curve,\n            )\n        elif index_method == IndexMethod.Daily:\n            n = monthrange(index_date.year, index_date.month)[1]\n            date_som = datetime(index_date.year, index_date.month, 1)\n            date_sonm = add_tenor(index_date, \"1M\", \"none\", NoInput(0), 1)\n            m1 = self._try_index_value(\n                index_date=date_som, index_lag=index_lag, index_method=IndexMethod.Monthly\n            )\n            m2 = self._try_index_value(\n                index_date=date_sonm, index_lag=index_lag, index_method=IndexMethod.Monthly\n            )\n            if m1.is_err:\n                return m1\n            if m2.is_err:\n                return m2\n            m1_, m2_ = m1.unwrap(), m2.unwrap()\n            return Ok(m1_ + (index_date.day - 1) / n * (m2_ - m1_))\n        else:\n            return Err(  # pragma: no cover\n                ValueError(\n                    \"`interpolation` for `index_value` must be in {'curve', 'daily', 'monthly'}.\"\n                )\n            )\n\n    def index_value(\n        self,\n        index_date: datetime,\n        index_lag: int,\n        index_method: IndexMethod | str = IndexMethod.Curve,\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the accrued value of the index from the ``index_base``.\n\n        This method will raise if performed on a *'values'* type *Curve*.\n\n        Parameters\n        ----------\n        index_date : datetime\n            The reference date for which the index value will be returned.\n        index_lag : int\n            The number of months by which to lag the index when determining the value.\n        index_method : IndexMethod or str in {\"curve\", \"monthly\", \"daily\"}\n            The interpolation method for returning the index value. Monthly returns the index value\n            for the start of the month and daily returns a value based on the\n            interpolation between nodes (which is recommended *\"linear_index*) for\n            :class:`InflationCurve`.\n\n        Returns\n        -------\n        None, float, Dual, Dual2\n\n        Notes\n        ------\n        The interpolation methods function as follows:\n\n        - **\"curve\"**: will raise if the requested ``index_lag`` does not match the lag attributed\n          to the *Curve*. In the case the ``index_lag`` matches, then the *index value* for any\n          date is derived via the implied interpolation for the discount factors of the *Curve*.\n\n          .. math::\n\n             I_v(m) = \\\\frac{I_b}{v(m)}\n\n        - **\"monthly\"**: For any date, *m*, uses the *\"curve\"* method having adjusted *m* in two\n          ways. Firstly it deducts a number of months equal to :math:`L - L_c`, where *L* is\n          the given ``index_lag`` and :math:`L_c` is the *index lag* of the *Curve*. And the day\n          of the month is set to 1.\n\n          .. math::\n\n             &I^{monthly}_v(m) = I_v(m_adj) \\\\\\\\\n             &\\\\text{where,} \\\\\\\\\n             &m_adj = Date(Year(m), Month(m) - L + L_c, 1) \\\\\\\\\n\n        - **\"daily\"**: For any date, *m*, with a given ``index_lag`` performs calendar day\n          interpolation on surrounding *\"monthly\"* values.\n\n          .. math::\n\n             &I^{daily}_v(m) = I^{monthly}_v(m) + \\\\frac{Day(m) - 1}{n} \\\\left ( I^{monthly}_v(m_+) - I^{monthly}_v(m) \\\\right ) \\\\\\\\\n             &\\\\text{where,} \\\\\\\\\n             &m_+ = \\\\text{Any date in the month following, }m\n             &n = \\\\text{Calendar days in, } Month(m)\n\n        Examples\n        --------\n        The SWESTR rate, for reference value date 6th Sep 2021, was published as\n        2.375% and the RFR index for that date was 100.73350964. Below we calculate\n        the value that was published for the RFR index on 7th Sep 2021 by the Riksbank.\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import Curve, dt\n\n        .. ipython:: python\n\n           index_curve = Curve(\n               nodes={\n                   dt(2021, 9, 6): 1.0,\n                   dt(2021, 9, 7): 1 / (1 + 2.375/36000)\n               },\n               index_base=100.73350964,\n               convention=\"Act360\",\n               index_lag=0,\n           )\n           index_curve.rate(dt(2021, 9, 6), \"1d\")\n           index_curve.index_value(dt(2021, 9, 7), 0)\n        \"\"\"  # noqa: E501\n        return self._try_index_value(\n            index_date=index_date,\n            index_lag=index_lag,\n            index_method=_get_index_method(index_method),\n        ).unwrap()\n\n    # Rate Plotting\n\n    def plot(\n        self,\n        tenor: str,\n        right: datetime | str | NoInput = NoInput(0),\n        left: datetime | str | NoInput = NoInput(0),\n        comparators: list[_BaseCurve] | NoInput = NoInput(0),\n        difference: bool = False,\n        labels: list[str] | NoInput = NoInput(0),\n    ) -> PlotOutput:\n        \"\"\"\n        Plot given forward tenor rates from the curve. See notes.\n\n        Parameters\n        ----------\n        tenor : str\n            The tenor of the forward rates to plot, e.g. \"1D\", \"3M\".\n        right : datetime or str, optional\n            The right bound of the graph. If given as str should be a tenor format\n            defining a point measured from the initial node date of the curve.\n            Defaults to the final node of the curve minus the ``tenor``.\n        left : datetime or str, optional\n            The left bound of the graph. If given as str should be a tenor format\n            defining a point measured from the initial node date of the curve.\n            Defaults to the initial node of the curve.\n        comparators: list[Curve]\n            A list of curves which to include on the same plot as comparators.\n        difference : bool\n            Whether to plot as comparator minus base curve or outright curve levels in\n            plot. Default is `False`.\n        labels : list[str]\n            A list of strings associated with the plot and comparators. Must be same\n            length as number of plots.\n\n        Returns\n        -------\n        (fig, ax, line) : Matplotlib.Figure, Matplotplib.Axes, Matplotlib.Lines2D\n\n        Notes\n        ------\n        This function plots single-period, **simple interest** curve rates, which are defined as:\n\n        .. math::\n\n           1 + r d = \\\\frac{v_{start}}{v_{end}}\n\n        where *d* is the day count fraction determined using the ``convention`` associated\n        with the *Curve*.\n\n        This function does **not** plot swap rates,\n        which is impossible since the *Curve* object contains no information regarding the\n        parameters of the *'swap'* (e.g. its *frequency* or its *convention* etc.).\n        If ``tenors`` longer than one year are sought results may start to deviate from those\n        one might expect. See `Issue 246 <https://github.com/attack68/rateslib/issues/246>`_.\n\n        \"\"\"\n        comparators_: list[_BaseCurve] = _drb([], comparators)\n        labels = _drb([], labels)\n        upper_tenor = tenor.upper()\n        x, y = self._plot_rates(upper_tenor, left, right)\n        y_ = [y] if not difference else []\n        for _, comparator in enumerate(comparators_):\n            if difference:\n                y_.append(\n                    [\n                        self._plot_diff(_x, tenor, _y, comparator)\n                        for _x, _y in zip(x, y, strict=False)\n                    ]\n                )\n            else:\n                pm_ = comparator._plot_modifier(tenor)\n                if upper_tenor == \"Z\":\n                    y_.append([comparator._plot_zero_rate(_x) for _x in x])\n                else:\n                    y_.append([comparator._plot_rate(_x, tenor, pm_) for _x in x])\n\n        return plot([x] * len(y_), y_, labels)\n\n    def _plot_diff(\n        self, date: datetime, tenor: str, rate: DualTypes | None, comparator: _BaseCurve\n    ) -> DualTypes | None:  # pragma: no cover\n        if rate is None:\n            return None\n        if tenor == \"Z\" or tenor == \"z\":\n            rate2 = comparator._plot_zero_rate(date)\n        else:\n            rate2 = comparator._plot_rate(date, tenor, comparator._plot_modifier(tenor))\n        if rate2 is None:\n            return None\n        return rate2 - rate\n\n    def _plot_modifier(self, upper_tenor: str) -> str:\n        \"\"\"If tenor is in days do not allow modified for plot purposes\"\"\"\n        if \"B\" in upper_tenor or \"D\" in upper_tenor or \"W\" in upper_tenor:\n            if \"F\" in self.meta.modifier:\n                return \"F\"\n            elif \"P\" in self.meta.modifier:  # pragma: no cover\n                return \"P\"\n        return self.meta.modifier\n\n    def _plot_rates(\n        self,\n        upper_tenor: str,\n        left: datetime | str | NoInput,\n        right: datetime | str | NoInput,\n    ) -> tuple[list[datetime], list[DualTypes | None]]:\n        if isinstance(left, NoInput):\n            left_: datetime = self.nodes.initial\n        elif isinstance(left, str):\n            left_ = add_tenor(self.nodes.initial, left, \"F\", self.meta.calendar)\n        elif isinstance(left, datetime):\n            left_ = left\n        else:\n            raise ValueError(\"`left` must be supplied as datetime or tenor string.\")\n\n        if isinstance(right, NoInput):\n            if upper_tenor == \"Z\":\n                # then plotting zero rates just use the last date\n                right_: datetime = self.nodes.final\n            else:\n                # pre-adjust the end date to enforce business date.\n                right_ = add_tenor(\n                    self.meta.calendar.adjust(self.nodes.final, Adjuster.Previous()),\n                    \"-\" + upper_tenor,\n                    \"P\",\n                    self.meta.calendar,\n                )\n        elif isinstance(right, str):\n            right_ = add_tenor(self.nodes.initial, right, \"P\", NoInput(0))\n        elif isinstance(right, datetime):\n            right_ = right\n        else:\n            raise ValueError(\"`right` must be supplied as datetime or tenor string.\")\n\n        dates = self.meta.calendar.cal_date_range(start=left_, end=right_)\n        if upper_tenor == \"Z\":\n            rates = [self._plot_zero_rate(_) for _ in dates]\n        else:\n            rates = [\n                self._plot_rate(_, upper_tenor, self._plot_modifier(upper_tenor)) for _ in dates\n            ]\n        return dates, rates\n\n    def _plot_rate(\n        self,\n        effective: datetime,\n        termination: str,\n        modifier: str,\n    ) -> DualTypes | None:\n        try:\n            rate = self.rate(effective, termination, modifier)\n        except ValueError:\n            return None\n        return rate\n\n    def _plot_zero_rate(\n        self,\n        maturity: datetime,\n    ) -> DualTypes | None:\n        \"\"\"plotting a continuously compounded zero rate is done using the ActActISDA convention\"\"\"\n        if self._base_type != _CurveType.dfs:\n            raise ValueError(\n                \"To plot continuously compounded zero rates ('Z') the Curve `_base_type` must be \"\n                f\"discount factor based. Got: '{self._base_type}'.\"\n            )\n\n        if maturity <= self.nodes.initial:\n            return None\n        else:\n            t = dcf(self.nodes.initial, maturity, Convention.ActActISDA)\n            return (dual_log(self[maturity]) / -t) * 100.0\n\n    # Index Plotting\n\n    def plot_index(\n        self,\n        right: datetime | str | NoInput = NoInput(0),\n        left: datetime | str | NoInput = NoInput(0),\n        comparators: list[_BaseCurve] | NoInput = NoInput(0),\n        difference: bool = False,\n        labels: list[str] | NoInput = NoInput(0),\n        interpolation: str = \"curve\",\n    ) -> PlotOutput:\n        \"\"\"\n        Plot given index values on a *Curve*.\n\n        Parameters\n        ----------\n        right : datetime or str, optional\n            The right bound of the graph. If given as str should be a tenor format\n            defining a point measured from the initial node date of the curve.\n            Defaults to the final node of the curve minus the ``tenor``.\n        left : datetime or str, optional\n            The left bound of the graph. If given as str should be a tenor format\n            defining a point measured from the initial node date of the curve.\n            Defaults to the initial node of the curve.\n        comparators: list[Curve]\n            A list of curves which to include on the same plot as comparators.\n        difference : bool\n            Whether to plot as comparator minus base curve or outright curve levels in\n            plot. Default is `False`.\n        labels : list[str]\n            A list of strings associated with the plot and comparators. Must be same\n            length as number of plots.\n        interpolation : str in {\"curve\", \"daily\", \"monthly\"}\n            The type of index interpolation method to use.\n\n        Returns\n        -------\n        (fig, ax, line) : Matplotlib.Figure, Matplotplib.Axes, Matplotlib.Lines2D\n\n        \"\"\"\n        comparators = _drb([], comparators)\n        labels = _drb([], labels)\n        if left is NoInput.blank:\n            left_: datetime = self.nodes.initial\n        elif isinstance(left, str):\n            left_ = add_tenor(self.nodes.initial, left, \"NONE\", NoInput(0))\n        elif isinstance(left, datetime):\n            left_ = left\n        else:\n            raise ValueError(\"`left` must be supplied as datetime or tenor string.\")\n\n        if right is NoInput.blank:\n            right_: datetime = self.nodes.final\n        elif isinstance(right, str):\n            right_ = add_tenor(self.nodes.initial, right, \"NONE\", NoInput(0))\n        elif isinstance(right, datetime):\n            right_ = right\n        else:\n            raise ValueError(\"`right` must be supplied as datetime or tenor string.\")\n\n        points: int = (right_ - left_).days + 1\n        x = [left_ + timedelta(days=i) for i in range(points)]\n        rates = [self.index_value(_, self.meta.index_lag, interpolation) for _ in x]\n        if not difference:\n            y = [rates]\n            if not isinstance(comparators, NoInput) and len(comparators) > 0:\n                for comparator in comparators:\n                    y.append([comparator.index_value(_, self.meta.index_lag) for _ in x])\n        elif difference and (isinstance(comparators, NoInput) or len(comparators) == 0):\n            raise ValueError(\"If `difference` is True must supply at least one `comparators`.\")\n        else:\n            y = []\n            for comparator in comparators:\n                diff = [\n                    comparator.index_value(_, self.meta.index_lag, interpolation) - rates[i]\n                    for i, _ in enumerate(x)\n                ]\n                y.append(diff)\n        return plot([x] * len(y), y, labels)\n\n    # Dunder operators\n\n    def __eq__(self, other: Any) -> bool:\n        \"\"\"Test two curves are identical\"\"\"\n        if type(self) is not type(other):\n            return False\n        attrs = [attr for attr in dir(self) if attr[:1] != \"_\"]\n        for attr in attrs:\n            if callable(getattr(self, attr, None)):\n                continue\n            elif getattr(self, attr, None) != getattr(other, attr, None):\n                return False\n        return True\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__}:{self._id} at {hex(id(self))}>\"\n\n    def copy(self) -> _BaseCurve:\n        \"\"\"\n        Create an identical copy of the curve object.\n\n        Returns\n        -------\n        Self\n        \"\"\"\n        ret: _BaseCurve = pickle.loads(pickle.dumps(self, -1))  # noqa: S301\n        return ret\n\n        # from rateslib.serialization import from_json\n        # return from_json(self.to_json())\n\n\nclass ShiftedCurve(_BaseCurve):\n    \"\"\"\n    Create a new :class:`~rateslib.curves._BaseCurve` type by compositing an input with\n    another flat curve of a set number of basis points.\n\n    Parameters\n    ----------\n    curve: _BaseCurve\n        Any *BaseCurve* type.\n    shift: float | Variable\n        The amount by which to shift the curve.\n    id: str, optional\n        Identifier used for :class:`~rateslib.solver.Solver` mappings.\n\n    Notes\n    -----\n    For **values** based curves this will add the ``shift`` to every output *rate* generated\n    by ``curve``.\n\n    For **discount factor** based curves this will add the ``shift`` as a geometric 1-day average\n    rate to the input ``curve``, in accordance with *rateslib*'s definition of curve metric spaces.\n\n    This implies that the *shape* of the ``curve`` is preserved but it undergoes a vertical\n    translation in rate space. This class works by wrapping a\n    :class:`~rateslib.curves.CompositeCurve` and designing the spread curve according to these\n    definitions.\n\n    The **ad order** will be the maximum order of ``curve`` and ``spread``. The usual `TypeError`\n    will be raised if mixing of :class:`~rateslib.dual.Dual` and :class:`~rateslib.dual.Dual2`\n    is attempted.\n\n    Examples\n    --------\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.curves import Curve\n\n    .. ipython:: python\n\n       curve = Curve(\n           nodes = {\n               dt(2022, 1, 1): 1.0,\n               dt(2023, 1, 1): 0.988,\n               dt(2024, 1, 1): 0.975,\n               dt(2025, 1, 1): 0.965,\n               dt(2026, 1, 1): 0.955,\n               dt(2027, 1, 1): 0.9475\n           },\n           t = [\n               dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1),\n               dt(2025, 1, 1),\n               dt(2026, 1, 1),\n               dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1),\n           ],\n       )\n       shifted_curve = curve.shift(25)\n       curve.plot(\"1d\", comparators=[shifted_curve], labels=[\"orig\", \"shift\"])\n\n    .. plot::\n\n       from rateslib.curves import *\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       curve = Curve(\n           nodes = {\n               dt(2022, 1, 1): 1.0,\n               dt(2023, 1, 1): 0.988,\n               dt(2024, 1, 1): 0.975,\n               dt(2025, 1, 1): 0.965,\n               dt(2026, 1, 1): 0.955,\n               dt(2027, 1, 1): 0.9475\n           },\n           t = [\n               dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1),\n               dt(2025, 1, 1),\n               dt(2026, 1, 1),\n               dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1),\n           ],\n       )\n       spread_curve = curve.shift(25)\n       fig, ax, line = curve.plot(\"1d\", comparators=[spread_curve], labels=[\"orig\", \"shift\"])\n       plt.show()\n       plt.close()\n    \"\"\"\n\n    _obj: _BaseCurve\n\n    def __init__(\n        self,\n        curve: _BaseCurve,\n        shift: DualTypes,\n        id: str_ = NoInput(0),  # noqa: A002\n    ) -> None:\n        start, end = curve._nodes.initial, curve._nodes.final\n\n        if curve._base_type == _CurveType.dfs:\n            dcf_ = dcf(start, end, curve.meta.convention, calendar=curve.meta.calendar)\n            _, d, n = average_rate(start, end, curve.meta.convention, 0.0, dcf_)\n            shifted: _BaseCurve = Curve(\n                nodes={start: 1.0, end: 1.0 / (1 + d * shift / 10000) ** n},\n                convention=curve.meta.convention,\n                calendar=curve.meta.calendar,\n                modifier=curve.meta.modifier,\n                interpolation=\"log_linear\",\n                index_base=curve.meta.index_base,\n                index_lag=curve.meta.index_lag,\n                ad=_get_order_of(shift),\n            )\n        else:  # base type is values: LineCurve\n            shifted = LineCurve(\n                nodes={start: shift / 100.0, end: shift / 100.0},\n                convention=curve.meta.convention,\n                calendar=curve.meta.calendar,\n                modifier=curve.meta.modifier,\n                interpolation=\"flat_backward\",\n                ad=_get_order_of(shift),\n            )\n\n        id_ = _drb(curve.id + \"_shift_\" + f\"{_dual_float(shift):.1f}\", id)\n\n        if shifted._ad + curve._ad == 3:\n            raise TypeError(\n                \"Cannot create a ShiftedCurve with mixed AD orders.\\n\"\n                f\"`curve` has AD order: {curve.ad}\\n\"\n                f\"`shift` has AD order: {shifted.ad}\"\n            )\n        self._obj = CompositeCurve(curves=[curve, shifted], id=id_, _no_validation=True)\n\n    def __getitem__(self, date: datetime) -> DualTypes:\n        return self.obj.__getitem__(date)\n\n    def _set_ad_order(self, ad: int) -> None:\n        return self.obj._set_ad_order(ad)\n\n    @property\n    def obj(self) -> _BaseCurve:\n        \"\"\"The wrapped :class:`~rateslib.curves.CompositeCurve` that performs calculations.\"\"\"\n        return self._obj\n\n    @property\n    def _ad(self) -> int:\n        return self.obj.ad\n\n    @property\n    def _meta(self) -> _CurveMeta:\n        return self.obj.meta\n\n    @property\n    def _id(self) -> str:\n        return self.obj.id\n\n    @property\n    def _nodes(self) -> _CurveNodes:\n        return self.obj.nodes\n\n    @property\n    def _interpolator(self) -> _CurveInterpolator:\n        return self.obj.interpolator\n\n    @property\n    def _base_type(self) -> _CurveType:\n        return self.obj._base_type\n\n\nclass TranslatedCurve(_BaseCurve):\n    \"\"\"\n    Create a new :class:`~rateslib.curves._BaseCurve` type by maintaining the rate space of an\n    input curve but shifting the initial node date forwards in time.\n\n    A class which wraps the underlying curve and returns rates and/or discount factors which are\n    impacted by a change to initial node date. This is mostly used by discount factor (DF) based\n    curves whose DFs are adjusted to have a value of 1.0 on the requested start date.\n\n    Parameters\n    ----------\n    curve: _BaseCurve\n        Any *BaseCurve* type.\n    start: datetime\n        The new initial node date for the curve. Must be after the initial node date of the input\n        ``curve``.\n    id: str, optional\n        Identifier used for :class:`~rateslib.solver.Solver` mappings.\n\n    Examples\n    ---------\n    .. ipython:: python\n\n       curve = Curve(\n           nodes = {\n               dt(2022, 1, 1): 1.0,\n               dt(2023, 1, 1): 0.988,\n               dt(2024, 1, 1): 0.975,\n               dt(2025, 1, 1): 0.965,\n               dt(2026, 1, 1): 0.955,\n               dt(2027, 1, 1): 0.9475\n           },\n           t = [\n               dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1),\n               dt(2025, 1, 1),\n               dt(2026, 1, 1),\n               dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1),\n           ],\n       )\n       translated_curve = curve.translate(dt(2022, 12, 1))\n\n       # Discount factors\n       curve[dt(2022, 12, 1)]\n       translated_curve[dt(2022, 12, 1)]\n\n       curve.plot(\n           \"1d\",\n           comparators=[translated_curve],\n           labels=[\"orig\", \"translated\"],\n           left=dt(2022, 12, 1),\n       )\n\n    .. plot::\n\n       from rateslib.curves import *\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       curve = Curve(\n           nodes = {\n               dt(2022, 1, 1): 1.0,\n               dt(2023, 1, 1): 0.988,\n               dt(2024, 1, 1): 0.975,\n               dt(2025, 1, 1): 0.965,\n               dt(2026, 1, 1): 0.955,\n               dt(2027, 1, 1): 0.9475\n           },\n           t = [\n               dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1),\n               dt(2025, 1, 1),\n               dt(2026, 1, 1),\n               dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1),\n           ],\n           interpolation=\"log_linear\",\n       )\n       translated_curve = curve.translate(dt(2022, 12, 1))\n       fig, ax, line = curve.plot(\"1d\", comparators=[translated_curve], labels=[\"orig\", \"translated\"], left=dt(2022, 12, 1))\n       plt.show()\n       plt.close()\n    \"\"\"  # noqa: E501\n\n    _obj: _BaseCurve\n\n    # abcs\n\n    _id: str = None  # type: ignore[assignment]\n    _nodes: _CurveNodes = None  # type: ignore[assignment]\n\n    def __init__(\n        self,\n        curve: _BaseCurve,\n        start: datetime,\n        id: str_ = NoInput(0),  # noqa: A002\n    ) -> None:\n        if start < curve.nodes.initial:\n            raise ValueError(\"Cannot translate into the past.\")\n        self._id = _drb(curve.id + \"_translated_\" + f\"{start.strftime('yy_mm_dd')}\", id)\n        self._nodes = _CurveNodes(_nodes={start: 0.0, curve.nodes.final: 0.0})\n        self._obj = curve\n\n    def __getitem__(self, date: datetime) -> DualTypes:\n        if date < self.nodes.initial:\n            return 0.0\n        elif self._base_type == _CurveType.dfs:\n            return self.obj.__getitem__(date) / self.obj.__getitem__(self.nodes.initial)\n        else:  # _CurveType.values\n            return self.obj.__getitem__(date)\n\n    def _set_ad_order(self, ad: int) -> None:\n        return self.obj._set_ad_order(ad)\n\n    @property\n    def obj(self) -> _BaseCurve:\n        \"\"\"The wrapped :class:`~rateslib.curves._BaseCurve` object that performs calculations.\"\"\"\n        return self._obj\n\n    @property\n    def _ad(self) -> int:\n        return self.obj.ad\n\n    @property\n    def _interpolator(self) -> _CurveInterpolator:\n        return self.obj.interpolator\n\n    @property\n    def _meta(self) -> _CurveMeta:\n        if self._base_type == _CurveType.dfs and not isinstance(self.obj.meta.index_base, NoInput):\n            return replace(\n                self.obj.meta,\n                _index_base=self.obj.index_value(self.nodes.initial, self.obj.meta.index_lag),  # type: ignore[arg-type]\n            )\n        else:\n            return self.obj.meta\n\n    @property\n    def _base_type(self) -> _CurveType:\n        return self.obj._base_type\n\n\nclass RolledCurve(_BaseCurve):\n    \"\"\"\n    Create a new :class:`~rateslib.curves._BaseCurve` type by translating the rate space of an\n    input curve horizontally in time.\n\n    A class which wraps the underlying curve and returns rates which are rolled in time,\n    measured by a set number of calendar days.\n\n    Parameters\n    ----------\n    curve: _BaseCurve\n        Any *BaseCurve* type.\n    roll_days: int\n        The number of calendar days by which to translate the curve's rate space.\n    id: str, optional\n        Identifier used for :class:`~rateslib.solver.Solver` mappings.\n\n    Notes\n    -----\n    A positive number of ``roll_days`` will shift the ``curve`` rate space to the right.\n    This is the traditional direction for measuring *roll down* on a trade strategy.\n\n    The gap between the initial node date and the roll date (if ``roll_days`` is positive) is\n    determined by forward filling the first rate on a **values** based curve, or forward filling\n    the first overnight rate on a **discount factor** based curve.\n\n    Examples\n    ---------\n    .. ipython:: python\n\n       curve = Curve(\n           nodes = {\n               dt(2022, 1, 1): 1.0,\n               dt(2023, 1, 1): 0.988,\n               dt(2024, 1, 1): 0.975,\n               dt(2025, 1, 1): 0.965,\n               dt(2026, 1, 1): 0.955,\n               dt(2027, 1, 1): 0.9475\n           },\n           t = [\n               dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1),\n               dt(2025, 1, 1),\n               dt(2026, 1, 1),\n               dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1),\n           ],\n       )\n       rolled_curve = curve.roll(\"6m\")\n       rolled_curve2 = curve.roll(\"-6m\")\n       curve.plot(\n           \"1d\",\n           comparators=[rolled_curve, rolled_curve2],\n           labels=[\"orig\", \"6m roll\", \"-6m roll\"],\n           right=dt(2026, 6, 30),\n       )\n\n    .. plot::\n\n       from rateslib.curves import *\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       curve = Curve(\n           nodes = {\n               dt(2022, 1, 1): 1.0,\n               dt(2023, 1, 1): 0.988,\n               dt(2024, 1, 1): 0.975,\n               dt(2025, 1, 1): 0.965,\n               dt(2026, 1, 1): 0.955,\n               dt(2027, 1, 1): 0.9475\n           },\n           t = [\n               dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1),\n               dt(2025, 1, 1),\n               dt(2026, 1, 1),\n               dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1),\n           ],\n       )\n       rolled_curve = curve.roll(\"6m\")\n       rolled_curve2 = curve.roll(\"-6m\")\n       fig, ax, line = curve.plot(\"1d\", comparators=[rolled_curve, rolled_curve2], labels=[\"orig\", \"6m roll\", \"-6m roll\"], right=dt(2026,6,30))\n       plt.show()\n       plt.close()\n    \"\"\"  # noqa: E501\n\n    _obj: _BaseCurve\n    _roll_days: int\n\n    # abcs\n\n    _id: str = None  # type: ignore[assignment]\n\n    def __init__(\n        self,\n        curve: _BaseCurve,\n        roll_days: int,\n        id: str_ = NoInput(0),  # noqa: A002\n    ) -> None:\n        self._roll_days = roll_days\n        self._id = _drb(curve.id + \"_rolled_\" + f\"{roll_days}\", id)\n        self._obj = curve\n\n    def __getitem__(self, date: datetime) -> DualTypes:\n        if date < self.nodes.initial:\n            return 0.0\n\n        boundary = self.nodes.initial + timedelta(days=self._roll_days)\n        if self._base_type == _CurveType.dfs:\n            if self._roll_days <= 0:\n                # boundary is irrelevant\n                scalar_date = self.obj.nodes.initial + timedelta(days=-self._roll_days)\n                return self.obj.__getitem__(\n                    date - timedelta(days=self._roll_days)\n                ) / self.obj.__getitem__(scalar_date)\n            else:\n                next_day = add_tenor(self.nodes.initial, \"1b\", \"F\", self.obj.meta.calendar)\n                on_rate = self.obj._rate_with_raise(self.nodes.initial, next_day)\n                dcf_ = dcf(\n                    self.nodes.initial,\n                    next_day,\n                    self.obj.meta.convention,\n                    calendar=self.obj.meta.calendar,\n                )\n                r_, d_, n_ = average_rate(\n                    self.nodes.initial, next_day, self.obj.meta.convention, on_rate, dcf_\n                )\n                if self.nodes.initial <= date < boundary:\n                    # must project forward\n                    return 1.0 / (1 + r_ * d_ / 100.0) ** (date - self.nodes.initial).days\n                else:  # boundary <= date:\n                    scalar = (1.0 + d_ * r_ / 100) ** self._roll_days\n                    return self.obj.__getitem__(date - timedelta(days=self._roll_days)) / scalar\n        else:  # _CurveType.values\n            if self.nodes.initial <= date < boundary:\n                return self.obj.__getitem__(self.nodes.initial)\n            else:  # boundary <= date:\n                return self.obj.__getitem__(date - timedelta(days=self._roll_days))\n\n    def _set_ad_order(self, order: int) -> None:\n        return self.obj._set_ad_order(order)\n\n    @property\n    def obj(self) -> _BaseCurve:\n        \"\"\"The wrapped :class:`~rateslib.curves._BaseCurve` object that performs calculations.\"\"\"\n        return self._obj\n\n    @property\n    def roll_days(self) -> int:\n        \"\"\"The number of calendar days by which rates are rolled on the underlying curve.\"\"\"\n        return self._roll_days\n\n    @property\n    def _ad(self) -> int:\n        return self.obj.ad\n\n    @property\n    def _interpolator(self) -> _CurveInterpolator:\n        return self.obj.interpolator\n\n    @property\n    def _meta(self) -> _CurveMeta:\n        return self.obj.meta\n\n    @property\n    def _nodes(self) -> _CurveNodes:\n        return self.obj.nodes\n\n    @property\n    def _base_type(self) -> _CurveType:\n        return self.obj._base_type\n\n\nclass _WithMutability:\n    \"\"\"\n    This class is designed as a mixin for the methods for *Curve Pricing Objects*, i.e.\n    the :class:`~rateslib.curves.Curve` and :class:`~rateslib.curves.LineCurve`.\n\n    It permits initialization, configuration of ``nodes`` and ``meta`` and\n    mutability when interacting with a :class:`~rateslib.solver.Solver`, when\n    getting and setting nodes, as well as user update methods, spline interpolation solving and\n    state validation.\n    \"\"\"\n\n    _ini_solve: int\n    _base_type: _CurveType\n    _nodes: _CurveNodes\n    _interpolator: _CurveInterpolator\n    _ad: int\n    _meta: _CurveMeta\n    _id: str\n\n    @_new_state_post\n    def __init__(  # type: ignore[no-untyped-def]\n        self,\n        nodes: dict[datetime, DualTypes],\n        *,\n        interpolation: str | InterpolationFunction | NoInput = NoInput(0),\n        t: list[datetime] | NoInput = NoInput(0),\n        endpoints: str | tuple[str, str] | NoInput = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        convention: Convention | str | NoInput = NoInput(0),\n        modifier: str | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        ad: int = 0,\n        index_base: Variable | float_ = NoInput(0),\n        index_lag: int | NoInput = NoInput(0),\n        collateral: str_ = NoInput(0),\n        credit_discretization: int_ = NoInput(0),\n        credit_recovery_rate: Variable | float_ = NoInput(0),\n        **kwargs,\n    ) -> None:\n        self._id = _drb(uuid4().hex[:5], id)  # 1 in a million clash\n\n        # Parameters for the rate/values derivation\n        self._meta = _CurveMeta(\n            _calendar=get_calendar(calendar),\n            _convention=_get_convention(_drb(defaults.convention, convention)),\n            _modifier=_drb(defaults.modifier, modifier).upper(),\n            _index_base=index_base,\n            _index_lag=_drb(defaults.index_lag_curve, index_lag),\n            _collateral=_drb(None, collateral),\n            _credit_discretization=_drb(\n                defaults.cds_protection_discretization, credit_discretization\n            ),\n            _credit_recovery_rate=_drb(defaults.cds_recovery_rate, credit_recovery_rate),\n        )\n        self._nodes = _CurveNodes(nodes)\n\n        temp: str | tuple[str, str] = _drb(defaults.endpoints, endpoints)\n        if isinstance(temp, str):\n            endpoints_: tuple[str, str] = (temp.lower(), temp.lower())\n        else:\n            endpoints_ = (temp[0].lower(), temp[1].lower())\n\n        self._interpolator = _CurveInterpolator(\n            local=interpolation,\n            t=t,\n            endpoints=endpoints_,\n            node_dates=self._nodes.keys,\n            convention=self._meta.convention,\n            curve_type=self._base_type,\n        )\n        self._set_ad_order(order=ad)  # will also clear and initialise the cache\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        \"\"\"\n        Change the node values to float, Dual or Dual2 based on input parameter.\n        \"\"\"\n        if order == getattr(self, \"ad\", None):\n            return None\n        elif order not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = order\n        nodes_: dict[datetime, DualTypes] = {\n            k: set_order_convert(v, order, [f\"{self._id}{i}\"])\n            for i, (k, v) in enumerate(self._nodes.nodes.items())\n        }\n        self._nodes = _CurveNodes(nodes_)\n        self._interpolator._csolve(self._base_type, self._nodes, self._ad)\n\n    # Solver interaction\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[Any]]:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return np.array(list(self._nodes.nodes.values())[self._ini_solve :])\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        return tuple(f\"{self._id}{i}\" for i in range(self._ini_solve, self._nodes.n))\n\n    # Mutation\n\n    @_new_state_post\n    @_clear_cache_post\n    def csolve(self) -> None:\n        \"\"\"\n        Solves **and sets** the coefficients, ``c``, of the :class:`PPSpline`.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n        Only impacts curves which have a knot sequence, ``t``, and a ``PPSpline``.\n        Only solves if ``c`` not given at curve initialisation.\n\n        Uses the ``spline_endpoints`` attribute on the class to determine the solving\n        method.\n        \"\"\"\n        self._interpolator._csolve(self._base_type, self._nodes, self._ad)\n\n    @_new_state_post\n    @_clear_cache_post\n    def update(\n        self,\n        nodes: dict[datetime, DualTypes] | NoInput = NoInput(0),\n    ) -> None:\n        \"\"\"\n        Update a curves nodes with new, manually input values.\n\n        For arguments see :class:`~rateslib.curves.curves.Curve`. Any value not given will not\n        change the underlying *Curve*.\n\n        Parameters\n        ----------\n        nodes: dict[datetime, DualTypes], optional\n            New nodes to assign to the curve.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Curve* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Curve* instance.\n           This class is labelled as a **mutable on update** object.\n\n        \"\"\"\n        if not isinstance(nodes, NoInput):\n            self._nodes = _CurveNodes(nodes)\n\n        self._interpolator._csolve(self._base_type, self._nodes, self._ad)\n\n    @_new_state_post\n    @_clear_cache_post\n    def update_node(self, key: datetime, value: DualTypes) -> None:\n        \"\"\"\n        Update a single node value on the *Curve*.\n\n        Parameters\n        ----------\n        key: datetime\n            The node date to update. Must exist in ``nodes``.\n        value: float, Dual, Dual2, Variable\n            Value to update on the *Curve*.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Curve* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Curve* instance.\n           This class is labelled as a **mutable on update** object.\n\n        \"\"\"\n        if key not in self._nodes.nodes:\n            raise KeyError(\"`key` is not in *Curve* ``nodes``.\")\n\n        nodes_ = self._nodes.nodes.copy()\n        nodes_[key] = value\n        self._nodes = _CurveNodes(nodes_)\n        self._interpolator._csolve(self._base_type, self._nodes, self._ad)\n\n    @_new_state_post\n    @_clear_cache_post\n    def update_meta(self, key: datetime, value: Any) -> None:\n        \"\"\"\n        Update a single meta value on the *Curve*.\n\n        Parameters\n        ----------\n        key: datetime\n            The meta descriptor to update. Must be a documented attribute of\n            :class:`~rateslib.curves.utils._CurveMeta`.\n        value: Any\n            Value to update on the *Curve*.\n\n        Returns\n        -------\n        None\n        \"\"\"\n        _key = f\"_{key}\"\n        self._meta = replace(self._meta, **{_key: value})\n\n    @_new_state_post\n    @_clear_cache_post\n    def _set_node_vector(self, vector: list[DualTypes], ad: int) -> None:\n        \"\"\"Used to update curve values during a Solver iteration. ``ad`` in {1, 2}.\"\"\"\n        self._set_node_vector_direct(vector, ad)\n\n    def _set_node_vector_direct(self, vector: list[DualTypes], ad: int) -> None:\n        nodes_ = self._nodes.nodes.copy()\n        if ad == 0:\n            if self._ini_solve == 1 and self._nodes.n > 0:\n                nodes_[self._nodes.initial] = _dual_float(nodes_[self._nodes.initial])\n            for i, k in enumerate(self._nodes.keys[self._ini_solve :]):\n                nodes_[k] = _dual_float(vector[i])\n        else:\n            DualType: type[Dual | Dual2] = Dual if ad == 1 else Dual2\n            DualArgs: tuple[list[float]] | tuple[list[float], list[float]] = (\n                ([],) if ad == 1 else ([], [])\n            )\n            base_obj = DualType(0.0, [f\"{self._id}{i}\" for i in range(self._nodes.n)], *DualArgs)\n            ident: np.ndarray[tuple[int, ...], np.dtype[np.float64]] = np.eye(\n                self._nodes.n, dtype=np.float64\n            )\n\n            if self._ini_solve == 1:\n                # then the first node on the Curve is not updated but\n                # set it as a dual type with consistent vars.\n                nodes_[self._nodes.initial] = DualType.vars_from(\n                    base_obj,  # type: ignore[arg-type]\n                    _dual_float(nodes_[self._nodes.initial]),\n                    base_obj.vars,\n                    ident[0, :].tolist(),\n                    *DualArgs[1:],\n                )\n\n            for i, k in enumerate(self._nodes.keys[self._ini_solve :]):\n                nodes_[k] = DualType.vars_from(\n                    base_obj,  # type: ignore[arg-type]\n                    _dual_float(vector[i]),\n                    base_obj.vars,\n                    ident[i + self._ini_solve, :].tolist(),\n                    *DualArgs[1:],\n                )\n        self._ad = ad\n        self._nodes = _CurveNodes(nodes_)\n        self._interpolator._csolve(self._base_type, self._nodes, self._ad)\n\n    # Serialization\n\n    @classmethod\n    def _from_json(cls, loaded_json: dict[str, Any]) -> _BaseCurve:\n        \"\"\"\n        Reconstitute a curve from JSON.\n\n        Parameters\n        ----------\n        curve : str\n            The JSON string representation of the curve.\n\n        Returns\n        -------\n        Curve or LineCurve\n        \"\"\"\n        from rateslib.serialization import from_json\n\n        meta = from_json(loaded_json[\"meta\"])\n        interpolator = from_json(loaded_json[\"interpolator\"])\n        nodes = from_json(loaded_json[\"nodes\"])\n        spl = interpolator.spline\n\n        if interpolator.local_name == \"spline\":\n            t = NoInput(0)\n        else:\n            t = NoInput(0) if spl is None else spl.t\n\n        _: _BaseCurve = cls(  # type: ignore[assignment]\n            nodes=nodes.nodes,\n            interpolation=interpolator.local_name,\n            t=t,\n            endpoints=spl.endpoints if spl is not None else NoInput(0),\n            id=loaded_json[\"id\"],\n            convention=meta.convention,\n            modifier=meta.modifier,\n            calendar=meta.calendar,\n            ad=loaded_json[\"ad\"],\n            index_base=meta.index_base,\n            index_lag=meta.index_lag,\n            collateral=meta.collateral,\n            credit_discretization=meta.credit_discretization,\n            credit_recovery_rate=meta.credit_recovery_rate,\n        )\n        return _\n\n    def to_json(self) -> str:\n        \"\"\"\n        Serialize this object to JSON format.\n\n        The object can be deserialized using the :meth:`~rateslib.serialization.from_json` method.\n\n        Returns\n        -------\n        str\n\n        Notes\n        -----\n        Some *Curves* will **not** be serializable, for example those that possess user defined\n        interpolation functions.\n        \"\"\"\n        obj = dict(\n            PyNative={\n                f\"{type(self).__name__}\": dict(\n                    meta=self._meta.to_json(),\n                    interpolator=self._interpolator.to_json(),\n                    id=self._id,\n                    ad=self._ad,\n                    nodes=self._nodes.to_json(),\n                )\n            }\n        )\n        return json.dumps(obj)\n\n\nclass Curve(_WithMutability, _BaseCurve):\n    \"\"\"\n    A :class:`~rateslib.curves._BaseCurve` with DF parametrisation at given node dates with\n    interpolation.\n\n    Parameters\n    ----------\n    nodes : dict[datetime: float]\n        Parameters of the curve denoted by a node date and a corresponding\n        DF at that point.\n    interpolation : str or callable\n        The interpolation used in the non-spline section of the curve. That is the part\n        of the curve between the first node in ``nodes`` and the first knot in ``t``.\n        If a callable, this allows a user-defined interpolation scheme, and this must\n        have the signature ``method(date, curve)``, where ``date`` is the datetime\n        whose DF will be returned and ``curve`` is passed as ``self``.\n    t : list[datetime], optional\n        The knot locations for the B-spline log-cubic interpolation section of the\n        curve. If *None* all interpolation will be done by the local method specified in\n        ``interpolation``.\n    endpoints : 2-tuple of str, optional\n        The left and then right endpoint constraint for the spline solution. Valid values are\n        in {\"natural\", \"not_a_knot\"}.\n    id : str, optional, set by Default\n        The unique identifier to distinguish between curves in a multicurve framework.\n    convention : str, optional, set by Default\n        The convention of the curve for determining rates. Please see\n        :meth:`dcf()<rateslib.scheduling.dcf>` for all available options.\n    modifier : str, optional\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"}, for determining rates when input as\n        a tenor, e.g. \"3M\".\n    calendar : Cal, UnionCal, NamedCal, str, optional\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data. Used for determining rates.\n    ad : int in {0, 1, 2}, optional\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n    index_base: float, optional\n        The initial index value at the initial node date of the curve. Used for\n        forecasting future index values.\n    index_lag : int, optional\n        Number of months of by which the index lags the date. For example if the initial\n        curve node date is 1st Sep 2021 based on the inflation index published\n        17th June 2023 then the lag is 3 months. Best practice is to use 0 months.\n    collateral : str\n        A currency identifier to denote the collateral currency against which the discount factors\n        for this *Curve* are measured.\n    credit_discretization : int\n        A parameter for numerically solving the integral for credit protection legs and default\n        events. Expressed in calendar days. Only used by *Curves* functioning as *hazard Curves*.\n    credit_recovery_rate : Variable | float\n        A parameter used in pricing credit protection legs and default events.\n\n    Notes\n    -----\n    This curve type is **discount factor (DF)** based and is parametrised by a set of\n    (date, DF) pairs set as ``nodes``. The initial node date of the curve is defined\n    to be today and should **always** have a DF of precisely 1.0. The initial DF\n    will **not** be affected by a :class:`~rateslib.solver.Solver`.\n\n    Intermediate DFs are determined through ``interpolation``. If local interpolation\n    is adopted a DF for an arbitrary date is dependent only on its immediately\n    neighbouring nodes via the interpolation routine. Available options are:\n\n    - *\"log_linear\"* (default for this curve type)\n    - *\"linear_index\"*\n\n    And also the following which are not recommended for this curve type:\n\n    - *\"linear\"*,\n    - *\"linear_zero_rate\"*,\n    - *\"flat_forward\"*,\n    - *\"flat_backward\"*,\n\n    **Spline Interpolation**\n\n    Global interpolation in the form of a **log-cubic** spline is also configurable\n    with the parameters ``t``, and ``endpoints``. Setting an ``interpolation`` of *\"spline\"*\n    is syntactic sugar for automatically determining the most obvious\n    knot sequence ``t`` to use all specified *node dates*. See\n    :ref:`splines<splines-doc>` for instruction of knot sequence calibration.\n\n    If the knot sequence is provided directly then any dates prior to the first knot date in ``t``\n    will be determined through the local interpolation method. This allows for\n    **mixed interpolation**, permitting the most common form of a stepped curve followed by a\n    smooth curve at some boundary.\n\n    For defining rates by a given tenor, the ``modifier`` and ``calendar`` arguments\n    will be used. For correct scaling of the rate a ``convention`` is attached to the\n    curve, which is usually one of \"Act360\" or \"Act365F\".\n\n    Examples\n    --------\n\n    .. ipython:: python\n\n       nodes={\n           dt(2022,1,1): 1.0,  # <- initial DF should always be 1.0\n           dt(2023,1,1): 0.99,\n           dt(2024,1,1): 0.979,\n           dt(2025,1,1): 0.967,\n           dt(2026,1,1): 0.956,\n           dt(2027,1,1): 0.946,\n       }\n       curve1 = Curve(nodes=nodes, interpolation=\"log_linear\")\n       curve2 = Curve(nodes=nodes, interpolation=\"spline\")\n       curve1.plot(\"1d\", comparators=[curve2], labels=[\"log_linear\", \"log_cubic_spline\"])\n\n    .. plot::\n\n       from rateslib.curves import *\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       import numpy as np\n\n       nodes={\n           dt(2022,1,1): 1.0,  # <- initial DF should always be 1.0\n           dt(2023,1,1): 0.99,\n           dt(2024,1,1): 0.979,\n           dt(2025,1,1): 0.967,\n           dt(2026,1,1): 0.956,\n           dt(2027,1,1): 0.946,\n       }\n       curve1 = Curve(nodes=nodes, interpolation=\"log_linear\")\n       curve2 = Curve(nodes=nodes, interpolation=\"spline\")\n       fig, ax, line = curve1.plot(\"1d\", comparators=[curve2], labels=[\"log_linear\", \"log_cubic_spline\"])\n       plt.show()\n       plt.close()\n    \"\"\"  # noqa: E501\n\n    _ini_solve: int = 1  # Curve is assumed to have initial DF node at 1.0 as constraint\n\n    # abcs - set by init\n\n    _base_type: _CurveType = _CurveType.dfs\n    _id: str = None  # type: ignore[assignment]\n    _ad: int = None  # type: ignore[assignment]\n    _meta: _CurveMeta = None  # type: ignore[assignment]\n    _nodes: _CurveNodes = None  # type: ignore[assignment]\n    _interpolator: _CurveInterpolator = None  # type: ignore[assignment]\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n\n    def __getitem__(self, date: datetime) -> DualTypes:\n        return super().__getitem__(date)\n\n    # Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n    # Commercial use of this code, and/or copying and redistribution is prohibited.\n    # Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n\nclass LineCurve(_WithMutability, _BaseCurve):\n    \"\"\"\n    A :class:`~rateslib.curves._BaseCurve` with value parametrisation at given node dates with\n    interpolation.\n\n    Parameters\n    ----------\n    nodes : dict[datetime: float]\n        Parameters of the curve denoted by a node date and a corresponding\n        value at that point.\n    interpolation : str in {\"log_linear\", \"linear\"} or callable\n        The interpolation used in the non-spline section of the curve. That is the part\n        of the curve between the first node in ``nodes`` and the first knot in ``t``.\n        If a callable, this allows a user-defined interpolation scheme, and this must\n        have the signature ``method(date, nodes)``, where ``date`` is the datetime\n        whose DF will be returned and ``nodes`` is as above and is passed to the\n        callable.\n    t : list[datetime], optional\n        The knot locations for the B-spline cubic interpolation section of the\n        curve. If *None* all interpolation will be done by the method specified in\n        ``interpolation``.\n    endpoints : str or list, optional\n        The left and right endpoint constraint for the spline solution. Valid values are\n        in {\"natural\", \"not_a_knot\"}. If a list, supply the left endpoint then the\n        right endpoint.\n    id : str, optional, set by Default\n        The unique identifier to distinguish between curves in a multi-curve framework.\n        convention : str, optional, set by Default\n        The convention of the curve for determining rates. Please see\n        :meth:`dcf()<rateslib.scheduling.dcf>` for all available options.\n    convention : str, optional, set by Default\n        The convention of the curve for determining rates. Please see\n        :meth:`dcf()<rateslib.scheduling.dcf>` for all available options.\n    modifier : str, optional\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"}, for determining rates when input as\n        a tenor, e.g. \"3M\".\n    calendar : Cal, UnionCal, NamedCal, str, optional\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data. Used for determining rates.\n    ad : int in {0, 1, 2}, optional\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`Dual` or :class:`Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    The arguments ``index_base``, ``index_lag``, and ``collateral`` available on\n    :class:`~rateslib.curves.Curve` are not used by, or relevant for, a :class:`LineCurve`.\n\n    This curve type is **value** based and it is parametrised by a set of\n    (date, value) pairs set as ``nodes``. The initial node date of the curve is defined\n    to be today, and can take a general value. The initial value\n    will be affected by a :class:`~rateslib.solver.Solver`.\n\n    .. note::\n\n       This curve type can only ever be used for **forecasting** rates and projecting\n       cashflow calculations. It cannot be used to discount cashflows becuase it is\n       not DF based and there is no mathematical one-to-one conversion available to\n       imply DFs.\n\n    Intermediate values are determined through ``interpolation``. If local interpolation\n    is adopted a value for an arbitrary date is dependent only on its immediately\n    neighbouring nodes via the interpolation routine. Available options are:\n\n    - *\"linear\"* (default for this curve type)\n    - *\"log_linear\"* (useful for values that exponential, e.g. stock indexes or GDP)\n    - *\"spline\"*\n    - *\"flat_forward\"*, (useful for replicating a DF based log-linear type curve)\n    - *\"flat_backward\"*,\n\n    And also the following which are not recommended for this curve type:\n\n    - *\"linear_index\"*\n    - *\"linear_zero_rate\"*,\n\n    **Spline Interpolation**\n\n    Global interpolation in the form of a **cubic** spline is also configurable\n    with the parameters ``t``, and ``endpoints``. Setting an ``interpolation`` of *\"spline\"*\n    is syntactic sugar for automatically determining the most obvious\n    knot sequence ``t`` to use all specified *node dates*. See\n    :ref:`splines<splines-doc>` for instruction of knot sequence calibration.\n\n    If the knot sequence is provided directly then any dates prior to the first knot date in ``t``\n    will be determined through the local interpolation method. This allows for\n    **mixed interpolation**.\n\n    This curve type cannot return arbitrary tenor rates. It will only return a single\n    value which is applicable to that date. It is recommended to review\n    :ref:`RFR and IBOR Indexing<c-curves-ibor-rfr>` to ensure indexing is done in a\n    way that is consistent with internal instrument configuration.\n\n    Examples\n    --------\n\n    .. ipython:: python\n\n       nodes = {\n           dt(2022,1,1): 0.975,  # <- initial value is general\n           dt(2023,1,1): 1.10,\n           dt(2024,1,1): 1.22,\n           dt(2025,1,1): 1.14,\n           dt(2026,1,1): 1.03,\n           dt(2027,1,1): 1.03,\n       }\n       line_curve1 = LineCurve(nodes=nodes, interpolation=\"linear\")\n       line_curve2 = LineCurve(nodes=nodes, interpolation=\"spline\")\n       line_curve1.plot(\"1d\", comparators=[line_curve2], labels=[\"linear\", \"cubic spline\"])\n\n    .. plot::\n\n       from rateslib.curves import *\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       import numpy as np\n       nodes = {\n           dt(2022,1,1): 0.975,  # <- initial value is general\n           dt(2023,1,1): 1.10,\n           dt(2024,1,1): 1.22,\n           dt(2025,1,1): 1.14,\n           dt(2026,1,1): 1.03,\n           dt(2027,1,1): 1.03,\n       }\n       line_curve1 = LineCurve(nodes=nodes, interpolation=\"linear\")\n       line_curve2 = LineCurve(nodes=nodes, interpolation=\"spline\")\n       fig, ax, line = line_curve1.plot(\"1d\", comparators=[line_curve2], labels=[\"linear\", \"cubic spline\"])\n       plt.show()\n       plt.close()\n\n    \"\"\"  # noqa: E501\n\n    _ini_solve = 0  # No constraint placed on initial node in Solver\n\n    # abcs - set by init\n\n    _base_type: _CurveType = _CurveType.values\n    _id: str = None  # type: ignore[assignment]\n    _ad: int = None  # type: ignore[assignment]\n    _meta: _CurveMeta = None  # type: ignore[assignment]\n    _nodes: _CurveNodes = None  # type: ignore[assignment]\n    _interpolator: _CurveInterpolator = None  # type: ignore[assignment]\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n\n    def __getitem__(self, date: datetime) -> DualTypes:\n        return super().__getitem__(date)\n\n\nclass CompositeCurve(_BaseCurve):\n    \"\"\"\n    A dynamic composition of a sequence of other :class:`~rateslib.curves._BaseCurve`.\n\n    .. note::\n       Can only composite curves of the same type: :class:`Curve`\n       or :class:`LineCurve`. Other curve parameters such as ``modifier``, ``calendar``\n       and ``convention`` must also match.\n\n    Parameters\n    ----------\n    curves : sequence of :class:`Curve` or sequence of :class:`LineCurve`\n        The curves to be composited.\n    id : str, optional, set by Default\n        The unique identifier to distinguish between curves in a multi-curve framework.\n\n    Examples\n    --------\n    Composite two :class:`LineCurve` s. Here, simulating the effect of adding\n    quarter-end turns to a cubic spline interpolator, which is otherwise difficult to\n    mathematically derive.\n\n    .. ipython:: python\n       :suppress:\n\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       from rateslib.curves import LineCurve, CompositeCurve\n       line_curve1 = LineCurve(\n           nodes={\n               dt(2022, 1, 1): 2.5,\n               dt(2023, 1, 1): 3.5,\n               dt(2024, 1, 1): 3.0,\n           },\n           t=[dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1),\n              dt(2023, 1, 1),\n              dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)],\n       )\n       line_curve2 = LineCurve(\n           nodes={\n               dt(2022, 1, 1): 0,\n               dt(2022, 3, 31): -0.2,\n               dt(2022, 4, 1): 0,\n               dt(2022, 6, 30): -0.2,\n               dt(2022, 7, 1): 0,\n               dt(2022, 9, 30): -0.2,\n               dt(2022, 10, 1): 0,\n               dt(2022, 12, 31): -0.2,\n               dt(2023, 1, 1): 0,\n               dt(2023, 3, 31): -0.2,\n               dt(2023, 4, 1): 0,\n               dt(2023, 6, 30): -0.2,\n               dt(2023, 7, 1): 0,\n               dt(2023, 9, 30): -0.2,\n           },\n           interpolation=\"flat_forward\",\n       )\n       curve = CompositeCurve([line_curve1, line_curve2])\n       curve.plot(\"1d\")\n\n    .. plot::\n\n       from rateslib.curves import LineCurve, CompositeCurve\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       line_curve1 = LineCurve(\n           nodes={\n               dt(2022, 1, 1): 2.5,\n               dt(2023, 1, 1): 3.5,\n               dt(2024, 1, 1): 3.0,\n           },\n           t=[dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1),\n              dt(2023, 1, 1),\n              dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1)],\n       )\n       line_curve2 = LineCurve(\n           nodes={\n               dt(2022, 1, 1): 0,\n               dt(2022, 3, 31): -0.2,\n               dt(2022, 4, 1): 0,\n               dt(2022, 6, 30): -0.2,\n               dt(2022, 7, 1): 0,\n               dt(2022, 9, 30): -0.2,\n               dt(2022, 10, 1): 0,\n               dt(2022, 12, 31): -0.2,\n               dt(2023, 1, 1): 0,\n               dt(2023, 3, 31): -0.2,\n               dt(2023, 4, 1): 0,\n               dt(2023, 6, 30): -0.2,\n               dt(2023, 7, 1): 0,\n               dt(2023, 9, 30): -0.2,\n           },\n           interpolation=\"flat_forward\",\n       )\n       curve = CompositeCurve([line_curve1, line_curve2])\n       fig, ax, line = curve.plot(\"1D\")\n       plt.show()\n\n    We can also composite DF based curves by using a fast approximation or an\n    exact match.\n\n    .. ipython:: python\n\n       from rateslib.curves import Curve, CompositeCurve\n       curve1 = Curve(\n           nodes={\n               dt(2022, 1, 1): 1.0,\n               dt(2023, 1, 1): 0.98,\n               dt(2024, 1, 1): 0.965,\n               dt(2025, 1, 1): 0.955\n           },\n           t=[dt(2023, 1, 1), dt(2023, 1, 1), dt(2023, 1, 1), dt(2023, 1, 1),\n              dt(2024, 1, 1),\n              dt(2025, 1, 1), dt(2025, 1, 1), dt(2025, 1, 1), dt(2025, 1, 1)],\n       )\n       curve2 =Curve(\n           nodes={\n               dt(2022, 1, 1): 1.0,\n               dt(2022, 6, 30): 1.0,\n               dt(2022, 7, 1): 0.999992,\n               dt(2022, 12, 31): 0.999992,\n               dt(2023, 1, 1): 0.999984,\n               dt(2023, 6, 30): 0.999984,\n               dt(2023, 7, 1): 0.999976,\n               dt(2023, 12, 31): 0.999976,\n               dt(2024, 1, 1): 0.999968,\n               dt(2024, 6, 30): 0.999968,\n               dt(2024, 7, 1): 0.999960,\n               dt(2025, 1, 1): 0.999960,\n           },\n       )\n       curve = CompositeCurve([curve1, curve2])\n       curve.plot(\"1D\", comparators=[curve1, curve2], labels=[\"Composite\", \"C1\", \"C2\"])\n\n    .. plot::\n\n       from rateslib.curves import Curve, CompositeCurve\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       curve1 = Curve(\n           nodes={\n               dt(2022, 1, 1): 1.0,\n               dt(2023, 1, 1): 0.98,\n               dt(2024, 1, 1): 0.965,\n               dt(2025, 1, 1): 0.955\n           },\n           t=[dt(2023, 1, 1), dt(2023, 1, 1), dt(2023, 1, 1), dt(2023, 1, 1),\n              dt(2024, 1, 1),\n              dt(2025, 1, 1), dt(2025, 1, 1), dt(2025, 1, 1), dt(2025, 1, 1)],\n       )\n       curve2 =Curve(\n           nodes={\n               dt(2022, 1, 1): 1.0,\n               dt(2022, 6, 30): 1.0,\n               dt(2022, 7, 1): 0.999992,\n               dt(2022, 12, 31): 0.999992,\n               dt(2023, 1, 1): 0.999984,\n               dt(2023, 6, 30): 0.999984,\n               dt(2023, 7, 1): 0.999976,\n               dt(2023, 12, 31): 0.999976,\n               dt(2024, 1, 1): 0.999968,\n               dt(2024, 6, 30): 0.999968,\n               dt(2024, 7, 1): 0.999960,\n               dt(2025, 1, 1): 0.999960,\n           },\n       )\n       curve = CompositeCurve([curve1, curve2])\n       fig, ax, line = curve.plot(\"1D\", comparators=[curve1, curve2], labels=[\"Composite\", \"C1\", \"C2\"])\n       plt.show()\n\n    \"\"\"  # noqa: E501\n\n    _mutable_by_association = True\n    _do_not_validate = False\n    _composite_scalars: list[float | Dual | Dual2 | Variable]\n\n    # abcs - set by init\n\n    _base_type: _CurveType = None  # type: ignore[assignment]\n    _id: str = None  # type: ignore[assignment]\n    _ad: int = None  # type: ignore[assignment]\n    _meta: _CurveMeta = None  # type: ignore[assignment]\n    _nodes: _CurveNodes = None  # type: ignore[assignment]\n    _interpolator: _CurveInterpolator = None  # type: ignore[assignment]\n\n    @_new_state_post\n    @_clear_cache_post\n    def __init__(\n        self,\n        curves: list[_BaseCurve] | tuple[_BaseCurve, ...],\n        id: str_ = NoInput(0),  # noqa: A002\n        _no_validation: bool = False,\n    ) -> None:\n        self._id = _drb(super()._id, id)\n        self.curves = tuple(curves)\n\n        nodes_proxy: dict[datetime, DualTypes] = dict.fromkeys(self.curves[0].nodes.keys, 0.0)\n        self._nodes = _CurveNodes(nodes_proxy)\n        self._base_type = curves[0]._base_type\n        self._meta = replace(self.curves[0].meta)\n\n        if _no_validation:\n            pass\n        else:\n            _validate_composited_curve_collection(self, self.curves, False)\n        self._composite_scalars = [1.0] * len(self.curves)\n        self._ad = max(_._ad for _ in self.curves)\n\n    @property\n    @_validate_states  # this ensures that the _meta attribute is updated if the curve state changes\n    def meta(self) -> _CurveMeta:\n        return self._meta\n\n    @_validate_states\n    @_no_interior_validation\n    def __getitem__(self, date: datetime) -> DualTypes:\n        if defaults.curve_caching and date in self._cache:\n            return self._cache[date]\n        if self._base_type == _CurveType.dfs:\n            # will return a composited discount factor\n            if date == self.nodes.initial:\n                # this value is 1.0, but by multiplying capture AD versus initial nodes.\n                ret: DualTypes = prod(crv[date] for crv in self.curves)\n                return ret\n            elif date < self.nodes.initial:\n                return 0.0  # Any DF in the past is set to zero consistent with behaviour on `Curve`\n\n            dcf_ = dcf(\n                start=self.nodes.initial,\n                end=date,\n                convention=self.meta.convention,\n                calendar=self.meta.calendar,\n            )\n            _, d, n = average_rate(self.nodes.initial, date, self.meta.convention, 0.0, dcf_)\n            total_rate: Number = 0.0\n            for scalar, curve in zip(self._composite_scalars, self.curves, strict=False):\n                avg_rate = ((1.0 / curve[date]) ** (1.0 / n) - 1) / d\n                total_rate += avg_rate * scalar  # type: ignore[assignment]\n            ret = 1.0 / (1 + total_rate * d) ** n\n            return self._cached_value(date, ret)\n\n        else:  # self._base_type == _CurveType.values:\n            # will return a composited rate\n            _ = 0.0\n            for scalar, curve in zip(self._composite_scalars, self.curves, strict=False):\n                _ += curve[date] * scalar\n            return self._cached_value(date, _)\n\n    # Solver interaction\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        \"\"\"\n        Change the node values on each curve to float, Dual or Dual2 based on input parameter.\n        \"\"\"\n        if order not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = order\n        for curve in self.curves:\n            curve._set_ad_order(order)\n\n    # Mutation\n\n    def _validate_state(self) -> None:\n        if self._do_not_validate:\n            return None\n        if self._state != self._get_composited_state():\n            # re-reference meta preserving own collateral status\n            self._meta = replace(self.curves[0].meta, _collateral=self._meta.collateral)\n            # If any of the associated curves have been mutated then the cache is invalidated\n            self._clear_cache()\n            self._set_new_state()\n\n    def _get_composited_state(self) -> int:\n        _: int = hash(sum(curve._state for curve in self.curves))\n        return _\n\n\nclass MultiCsaCurve(_BaseCurve):\n    \"\"\"\n    A dynamic composition of a sequence of other :class:`~rateslib.curves._BaseCurve`.\n\n    .. note::\n       Can only combine curves of the type: :class:`Curve`. Other curve parameters such as\n       ``modifier``, and ``convention`` must also match.\n\n    .. warning::\n       Intrinsic *MultiCsaCurves*, by definition, are not natively AD safe, due to having\n       discontinuities and no available derivatives in certain cases. See\n       :ref:`discontinuous MultiCsaCurves <cook-multicsadisc-doc>`.\n\n    Parameters\n    ----------\n    curves : sequence of :class:`Curve`\n        The curves to be composited.\n    id : str, optional, set by Default\n        The unique identifier to distinguish between curves in a multi-curve framework.\n    multi_csa_min_step: int, optional\n        The minimum calculation step between subsequent DF evaluations to determine a multi-CSA\n        curve term DF. Higher numbers make faster calculations but are less accurate. Should be\n        in [1, max_step].\n    multi_csa_max_step: int, optional\n        The minimum calculation step between subsequent DF evaluations to determine a multi-CSA\n        curve term DF. Higher numbers make faster calculations but are less accurate. Should be\n        in [min_step, 1825].\n\n    Notes\n    -----\n    A *MultiCsaCurve* uses a different calculation methodology than a *CompositeCurve* for\n    determining the *rate* by selecting the curve within the collection with the highest rate.\n    \"\"\"\n\n    _mutable_by_association = True\n    _do_not_validate = False\n\n    # abcs - set by init\n\n    _base_type: _CurveType = None  # type: ignore[assignment]\n    _id: str = None  # type: ignore[assignment]\n    _ad: int = None  # type: ignore[assignment]\n    _meta: _CurveMeta = None  # type: ignore[assignment]\n    _nodes: _CurveNodes = None  # type: ignore[assignment]\n    _interpolator: _CurveInterpolator = None  # type: ignore[assignment]\n\n    @property\n    @_validate_states  # this ensures that the _meta attribute is updated if the curve state changes\n    def meta(self) -> _CurveMeta:\n        return self._meta\n\n    @_new_state_post\n    @_clear_cache_post\n    def __init__(\n        self,\n        curves: list[_BaseCurve] | tuple[_BaseCurve, ...],\n        id: str | NoInput = NoInput(0),  # noqa: A002\n    ) -> None:\n        self._id = _drb(super()._id, id)\n        self.curves = tuple(curves)\n        nodes_proxy: dict[datetime, DualTypes] = dict.fromkeys(self.curves[0].nodes.keys, 0.0)\n        self._nodes = _CurveNodes(nodes_proxy)\n        self._base_type = curves[0]._base_type\n        self._meta = replace(self.curves[0].meta)\n        _validate_composited_curve_collection(self, self.curves, True)\n        self._ad = max(_._ad for _ in self.curves)\n\n    @_validate_states\n    @_no_interior_validation\n    def __getitem__(self, date: datetime) -> DualTypes:\n        # TODO: changing the multi_csa_step size should force a cache clear. This is a mutation.\n\n        # will return a composited discount factor\n        if defaults.curve_caching and date in self._cache:\n            return self._cache[date]\n\n        if date == self.nodes.initial:\n            # this value is 1.0, but by multiplying capture AD versus initial nodes.\n            ret: DualTypes = prod(crv[date] for crv in self.curves)\n            return ret\n        elif date < self.nodes.initial:\n            return 0.0  # Any DF in the past is set to zero consistent with behaviour on `Curve`\n\n        def _get_step(step: int) -> int:\n            mins = defaults.multi_csa_min_step\n            maxs = defaults.multi_csa_max_step\n            return min(max(step, mins), maxs)\n\n        # method uses the step and picks the highest (cheapest rate) in each step\n        d1 = self.nodes.initial\n        d2 = d1 + timedelta(days=_get_step(defaults.multi_csa_steps[0]))\n\n        v: DualTypes = self.__getitem__(d1)\n        v_i_1_j: list[DualTypes] = [curve[d1] for curve in self.curves]\n        v_i_j: list[DualTypes] = [0.0 for curve in self.curves]\n\n        k: int = 1\n        while d2 < date:\n            if defaults.curve_caching and d2 in self._cache:\n                v = self._cache[d2]\n                v_i_1_j = [curve[d2] for curve in self.curves]\n            else:\n                min_ratio: DualTypes = 1e5\n                for j, curve in enumerate(self.curves):\n                    v_i_j[j] = curve[d2]\n                    ratio_ = v_i_j[j] / v_i_1_j[j]\n                    min_ratio = ratio_ if ratio_ < min_ratio else min_ratio\n                    v_i_1_j[j] = v_i_j[j]\n                v *= min_ratio\n                self._cached_value(d2, v)\n\n            try:\n                step = _get_step(defaults.multi_csa_steps[k])\n            except IndexError:\n                step = defaults.multi_csa_max_step\n            d1, d2, k = d2, d2 + timedelta(days=step), k + 1\n\n        # finish the loop on the correct date\n        if date == d1:\n            return self._cached_value(date, v)\n        else:\n            min_ratio = 1e5\n            for j, curve in enumerate(self.curves):\n                ratio_ = curve[date] / v_i_1_j[j]\n                min_ratio = ratio_ if ratio_ < min_ratio else min_ratio\n            v *= min_ratio\n            return self._cached_value(date, v)\n\n    # Solver interaction\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        \"\"\"\n        Change the node values on each curve to float, Dual or Dual2 based on input parameter.\n        \"\"\"\n        if order not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = order\n        for curve in self.curves:\n            curve._set_ad_order(order)\n\n    # Mutation\n\n    def _validate_state(self) -> None:\n        if self._do_not_validate:\n            return None\n        if self._state != self._get_composited_state():\n            # re-reference meta preserving own collateral status\n            self._meta = replace(self.curves[0].meta, _collateral=self._meta.collateral)\n            # If any of the associated curves have been mutated then the cache is invalidated\n            self._clear_cache()\n            self._set_new_state()\n\n    def _get_composited_state(self) -> int:\n        _: int = hash(sum(curve._state for curve in self.curves))\n        return _\n\n\ndef _validate_composited_curve_collection(\n    obj: _BaseCurve, curves: tuple[_BaseCurve, ...], force_dfs: bool\n) -> None:\n    \"\"\"Perform checks to ensure CompositeCurve can exist\"\"\"\n    _base_type = curves[0]._base_type\n\n    if force_dfs and _base_type != _CurveType.dfs:\n        raise TypeError(f\"{type(obj).__name__} must use discount factors, i.e have _CurveType.dfs.\")\n\n    if not all(_._base_type == _base_type for _ in curves):\n        # then at least one curve is value based and one is DF based\n        raise TypeError(f\"{type(obj).__name__} can only contain curves of the same type.\")\n\n    ini_dates = [_.nodes.initial for _ in curves]\n    if not all(_ == ini_dates[0] for _ in ini_dates[1:]):\n        raise ValueError(f\"`curves` must share the same initial node date, got {ini_dates}\")\n\n    # if type(self) is not MultiCsaCurve:  # for multi_csa DF curve do not check calendars\n    #     self._check_meta_attribute(\"calendar\")\n\n    if _base_type == _CurveType.dfs:\n        _check_meta_attribute(curves, \"modifier\")\n        _check_meta_attribute(curves, \"convention\")\n        _check_meta_attribute(curves, \"calendar\")\n        # self._check_meta_attribute(\"collateral\")  # not used due to inconsistent labelling\n\n    _ad = [_._ad for _ in curves]\n    if 1 in _ad and 2 in _ad:\n        raise TypeError(\n            f\"{type(obj).__name__} cannot composite curves of AD order 1 and 2.\\n\"\n            \"Either downcast curves using `curve._set_ad_order(1)`.\\n\"\n            \"Or upcast curves using `curve._set_ad_order(2)`.\\n\"\n        )\n\n\ndef _check_meta_attribute(curves: tuple[_BaseCurve, ...], attr: str) -> None:\n    \"\"\"Ensure attributes are the same across curve collection\"\"\"\n    attrs = [getattr(_.meta, attr, None) for _ in curves]\n    if not all(_ == attrs[0] for _ in attrs[1:]):\n        raise ValueError(\n            f\"Cannot composite curves with different attributes, got for \"\n            f\"'{attr}': {[getattr(_.meta, attr, None) for _ in curves]},\",\n        )\n\n\nclass ProxyCurve(_BaseCurve):\n    \"\"\"\n    A :class:`~rateslib.curves._BaseCurve` which returns dynamic DFs from an\n    :class:`~rateslib.fx.FXForwards` object and FX parity.\n\n    Parameters\n    ----------\n    cashflow : str\n        The currency in which cashflows are represented (3-digit code).\n    collateral : str\n        The currency of the CSA against which cashflows are collateralised (3-digit\n        code).\n    fx_forwards : FXForwards\n        The :class:`~rateslib.fx.FXForwards` object which contains the relating\n        FX information and the available :class:`~rateslib.curves.Curve` s.\n    id : str, optional, set by Default\n        The unique identifier to distinguish between curves in a multi-curve framework.\n\n    Notes\n    -----\n    The DFs returned are calculated via the chaining method and the below formula,\n    relating the DF curve in the local collateral currency and FX forward rates.\n\n    .. math::\n\n       w_{dom:for,i} = \\\\frac{f_{DOMFOR,i}}{F_{DOMFOR,0}} v_{for:for,i}\n\n    The returned curve contains contrived methods to calculate this dynamically and\n    efficiently from the combination of curves and FX rates that are available within\n    the given :class:`FXForwards` instance.\n    \"\"\"\n\n    _mutable_by_association = True\n    _do_not_validate = False\n\n    # abcs\n\n    _base_type: _CurveType = None  # type: ignore[assignment]\n    _interpolator: _ProxyCurveInterpolator = None  # type: ignore[assignment]\n    _nodes: _CurveNodes = None  # type: ignore[assignment]\n    _meta: _CurveMeta = None  # type: ignore[assignment]\n    _id: str = None  # type: ignore[assignment]\n\n    @property\n    def _ad(self) -> int:\n        return self.interpolator.fx_forwards._ad\n\n    @property\n    def interpolator(self) -> _ProxyCurveInterpolator:  # type: ignore[override]\n        \"\"\"An instance of :class:`~rateslib.curves.utils._ProxyCurveInterpolator`.\"\"\"\n        return self._interpolator\n\n    @property\n    @_validate_states  # this ensures that the _meta attribute is updated if the curve state changes\n    def meta(self) -> _CurveMeta:\n        return self._meta\n\n    @_new_state_post\n    @_clear_cache_post\n    def __init__(\n        self,\n        cashflow: str,\n        collateral: str,\n        fx_forwards: FXForwards,\n        id: str_ = NoInput(0),  # noqa: A002\n    ):\n        self._interpolator = _ProxyCurveInterpolator(\n            _fx_forwards=fx_forwards, _cash=cashflow.lower(), _collateral=collateral.lower()\n        )\n        self._id = _drb(super()._id, id)\n        self._base_type = fx_forwards.fx_curves[self.interpolator.cash_pair]._base_type\n        self._meta = replace(\n            self.interpolator.fx_forwards.fx_curves[self.interpolator.cash_pair].meta,\n            _collateral=collateral.lower(),\n        )\n        # CurveNodes attached for date attribution\n        self._nodes = _CurveNodes(\n            {\n                fx_forwards.immediate: 0.0,\n                fx_forwards.fx_curves[self.interpolator.cash_pair].nodes.final: 0.0,\n            }\n        )\n\n    @_validate_states\n    @_no_interior_validation\n    def __getitem__(self, date: datetime) -> DualTypes:\n        _1: DualTypes = self.interpolator.fx_forwards.rate(self.interpolator.pair, date)\n        _2: DualTypes = self.interpolator.fx_forwards.fx_rates_immediate._fx_array_el(\n            self.interpolator.cash_index, self.interpolator.collateral_index\n        )\n        _3: DualTypes = self.interpolator.fx_forwards.fx_curves[self.interpolator.collateral_pair][\n            date\n        ]\n        return _1 / _2 * _3\n\n    def _set_ad_order(self, order: int) -> None:\n        return self.interpolator.fx_forwards._set_ad_order(order)\n\n    def _validate_state(self) -> None:\n        \"\"\"Used by 'mutable by association' objects to evaluate if their own record of\n        associated objects states matches the current state of those objects.\n\n        Mutable by update objects have no concept of state validation, they simply maintain\n        a *state* id.\n        \"\"\"\n        self.interpolator.fx_forwards._validate_state()  # validate the state of sub-object\n        if self._state != self._get_composited_state():\n            # re-reference meta preserving own collateral status\n            self._meta = replace(\n                self.interpolator.fx_forwards.fx_curves[self.interpolator.cash_pair].meta,\n                _collateral=self._meta.collateral,\n            )\n            # If any of the associated curves have been mutated then the cache is invalidated\n            self._clear_cache()\n            self._set_new_state()\n\n    def _get_composited_state(self) -> int:\n        return self.interpolator.fx_forwards._state\n\n\nclass CreditImpliedCurve(_BaseCurve):\n    \"\"\"\n    Imply a :class:`~rateslib.curves._BaseCurve` from credit components.\n\n    .. warning::\n\n       This class is in **beta** status as of v2.1.0\n\n    Parameters\n    ----------\n    risk_free: _BaseCurve, optional\n        The known risk free curve. If not given will be the implied curve.\n    credit: _BaseCurve, optional\n        The known credit curve.  If not given will be the implied curve.\n    hazard: _BaseCurve, optional\n        The known hazard curve. If not given will be the implied curve.\n\n    Notes\n    -----\n    A *risk free*, *credit* or *hazard* curve will be implied from the other known, provided\n    curves.\n\n    This class is a wrapper for a :class:`~rateslib.curves.CompositeCurve` where the two known\n    curves are added and multiplied by the appropriate recovery rate, obtained from the\n    :class:`~rateslib.curves._CurveMeta` (either from the\n    ``hazard`` curve or the ``credit`` curve in that order of precedence) to derive the third.\n\n    In traditional papers, such as *Duffie and Singleton (1999)*, the *credit* DF is expressed\n    relative to a *risk free* and *hazard* process. I.e.\n\n    .. math::\n\n       exp \\\\left ( \\\\int_0^T -r_f(t) - (1-R)\\\\lambda(t) .dt \\\\right ) = exp \\\\left ( \\\\int_0^T -r_c(t) .dt \\\\right )\n\n    where :math:`r_f` is the instantaneous risk free rate, :math:`r_c` the instantaneous credit rate\n    and :math:`\\\\lambda` the hazard intensity process.\n\n    In an approximation *rateslib* converts these to discrete overnight rate equivalents and implies\n    the curves as follows under rate vector addition:\n\n    - **Credit curve rates**: :math:`r_f(t) + (1-R)\\\\lambda(t)`\n    - **Hazard curve rates**: :math:`\\\\frac{r_c(t) - r_f(t)}{1-R}`\n    - **Risk free rates**: :math:`r_c(t) - (1-R)\\\\lambda(t)`\n\n    Example\n    -------\n    Given the following **risk free** curve and **hazard** curve, a **credit** curve is implied.\n\n    .. ipython:: python\n\n       from rateslib.curves import CreditImpliedCurve\n\n       risk_free = Curve(\n           nodes={dt(2000, 1, 1): 1.0, dt(2000, 9, 1): 0.98, dt(2001, 4, 1): 0.95, dt(2002, 1, 1): 0.92},\n           interpolation=\"spline\",\n       )\n       hazard = Curve(\n           nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98, dt(2002, 1, 1): 0.95},\n           credit_recovery_rate=0.25,\n       )\n       credit = CreditImpliedCurve(risk_free=risk_free, hazard=hazard)\n       risk_free.plot(\"1b\", comparators=[hazard, credit], labels=[\"risk free\", \"hazard\", \"credit\"])\n\n    .. plot::\n\n       from rateslib.curves import *\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       risk_free = Curve({dt(2000, 1, 1): 1.0, dt(2000, 9, 1): 0.98, dt(2001, 4, 1): 0.95, dt(2002, 1, 1): 0.92}, interpolation=\"spline\")\n       hazard = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98, dt(2002, 1, 1): 0.95}, credit_recovery_rate=0.25)\n       credit = CreditImpliedCurve(risk_free=risk_free, hazard=hazard)\n       fig, ax, line = risk_free.plot(\"1b\", comparators=[hazard, credit], labels=[\"risk free\", \"hazard\", \"credit\"])\n       plt.show()\n       plt.close()\n\n    These associations are dynamic so changes to any of the curves will naturally update the\n    :class:`~rateslib.curves.CreditImpliedCurve`.\n\n    .. ipython:: python\n\n       hazard.update_meta(\"credit_recovery_rate\", 0.90)\n       risk_free.plot(\"1b\", comparators=[hazard, credit], labels=[\"risk free\", \"hazard\", \"credit\"])\n\n    .. plot::\n\n       from rateslib.curves import *\n       import matplotlib.pyplot as plt\n       from datetime import datetime as dt\n       risk_free = Curve({dt(2000, 1, 1): 1.0, dt(2000, 9, 1): 0.98, dt(2001, 4, 1): 0.95, dt(2002, 1, 1): 0.92}, interpolation=\"spline\")\n       hazard = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98, dt(2002, 1, 1): 0.95}, credit_recovery_rate=0.25)\n       credit = CreditImpliedCurve(risk_free=risk_free, hazard=hazard)\n       hazard.update_meta(\"credit_recovery_rate\", 0.90)\n       fig, ax, line = risk_free.plot(\"1b\", comparators=[hazard, credit], labels=[\"risk free\", \"hazard\", \"credit\"])\n       plt.show()\n       plt.close()\n\n    \"\"\"  # noqa: E501\n\n    _mutable_by_association = True\n    _do_not_validate = False\n    _obj: CompositeCurve\n\n    # abcs\n\n    _meta: _CurveMeta = None  # type: ignore[assignment]\n    _interpolator: _CurveInterpolator = None  # type: ignore[assignment]\n\n    @property\n    def _base_type(self) -> _CurveType:\n        return self.obj._base_type\n\n    @property\n    def _id(self) -> str:\n        return self.obj.id\n\n    @property\n    def _ad(self) -> int:\n        return self.obj.ad\n\n    @_new_state_post\n    @_clear_cache_post\n    def __init__(\n        self,\n        risk_free: Curve | NoInput = NoInput(0),\n        credit: Curve | NoInput = NoInput(0),\n        hazard: Curve | NoInput = NoInput(0),\n        id: str_ = NoInput(0),  # noqa: A002\n    ) -> None:\n        if sum([isinstance(_, NoInput) for _ in [risk_free, credit, hazard]]) != 1:\n            raise ValueError(\n                \"One, and only one, curve must be NoInput in order to be a CreditImpliedCurve.\"\n            )\n        elif not isinstance(hazard, NoInput) and not isinstance(credit, NoInput):\n            self._implied = _CreditImpliedType.risk_free\n            self._obj = CompositeCurve(curves=[hazard, credit], id=id)\n        elif not isinstance(hazard, NoInput) and not isinstance(risk_free, NoInput):\n            self._implied = _CreditImpliedType.credit\n            self._obj = CompositeCurve(curves=[hazard, risk_free], id=id)\n        else:  # not isinstance(credit, NoInput) and not isinstance(risk_free, NoInput):\n            self._implied = _CreditImpliedType.hazard\n            self._obj = CompositeCurve(curves=[credit, risk_free], id=id)  # type: ignore[list-item]\n        self._meta = replace(self._obj.meta)\n\n    @_validate_states\n    @_no_interior_validation\n    def __getitem__(self, date: datetime) -> DualTypes:\n        self.obj._composite_scalars = self._composite_scalars()\n        return self.obj.__getitem__(date)\n\n    def _set_ad_order(self, order: int) -> None:\n        return self.obj._set_ad_order(order)\n\n    @property\n    def obj(self) -> CompositeCurve:\n        \"\"\"The wrapped :class:`~rateslib.curves.CompositeCurve` for making calculations.\"\"\"\n        return self._obj\n\n    @property\n    @_validate_states  # this ensures that the _meta attribute is updated if the curve state changes\n    def meta(self) -> _CurveMeta:\n        \"\"\"An instance of :class:`~rateslib.curves._CurveMeta`.\"\"\"\n        return self._meta\n\n    @property\n    def _nodes(self) -> _CurveNodes:\n        return self.obj.nodes\n\n    def _composite_scalars(self) -> list[float | Dual | Dual2 | Variable]:\n        lr = 1.0 - self.meta.credit_recovery_rate\n        if self._implied == _CreditImpliedType.credit:\n            return [lr, 1.0]\n        elif self._implied == _CreditImpliedType.hazard:\n            return [1.0 / lr, -1.0 / lr]\n        else:\n            return [-lr, 1.0]\n\n    def _get_composited_state(self) -> int:\n        # return the state of the CompositeCurve\n        return self._obj._state\n\n    def _validate_state(self) -> None:\n        \"\"\"Used by 'mutable by association' objects to evaluate if their own record of\n        associated objects states matches the current state of those objects.\n\n        Mutable by update objects have no concept of state validation, they simply maintain\n        a *state* id.\n        \"\"\"\n        if self._do_not_validate:\n            return None\n\n        self.obj._validate_state()  # validate the obj state in case one its sub components changed\n        if self._state != self._get_composited_state():\n            self._clear_cache()  # CreditImpliedCurve has no cache but future proofing here\n            self._set_new_state()\n            self._meta = replace(\n                self._obj.meta,\n                _collateral=self._meta.collateral,\n                _credit_recovery_rate=self._obj._meta.credit_recovery_rate,\n                _credit_discretization=self._obj._meta.credit_discretization,\n            )\n            self._obj._composite_scalars = self._composite_scalars()\n\n\ndef index_value(\n    index_lag: int,\n    index_method: str | IndexMethod,\n    index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n    index_date: datetime_ = NoInput(0),\n    index_curve: CurveOption_ = NoInput(0),\n) -> DualTypes:\n    \"\"\"\n    Determine an index value from a reference date using combinations of known fixings and\n    forecast from a *Curve*.\n\n    Parameters\n    ----------\n    index_lag: int\n        The number of months by which the reference ``index_date`` should be lagged to derive a\n        value.\n    index_method: str in {\"curve\", \"daily\", \"monthly\"}\n        The method used to derive and interpolate index values.\n    index_fixings: float, Dual, Dual2, Variable, Series[DualTypes], str, optional\n        A specific index value which is returned directly, or if given as a Series applies the\n        appropriate ``index_method`` to determine a value. May also forecast from *Curve* if\n        necessary. See notes.\n    index_date: datetime, optional\n        The reference index date for which the index value is sought. Not required if\n        ``index_fixings`` is returned directly.\n    index_curve: Curve, optional\n        The forecast curve from which to derive index values under the appropriate ``index_method``.\n        If using *'curve'*, then curve calculations are used directly.\n\n    Returns\n    -------\n    DualTypes\n\n    Notes\n    -----\n    A *Series* **must** be given with a unique, monotonic increasing index. This will **not** be\n    validated.\n\n    When using the *'daily'* or *'monthly'* type ``index_methods`` index values **must** be\n    assigned to **the first of the month** to which the publication is relevant.\n\n    The below image is a snippet taken from the UK DMO *'Formulae for Calculating Gilt Prices\n    and Yield'*. It outlines the calculation of an *index value* for a reference date using their\n    3 month lag and *'daily'* indexing method.\n\n    .. image:: _static/ukdmo_rpi_ex.png\n       :alt: Index value calculations\n       :align: center\n       :width: 291\n\n    This calculation is replicated in *rateslib* in the following way:\n\n    .. ipython:: python\n\n       from rateslib import index_value\n       from pandas import Series\n\n       rpi_series = Series(\n           [172.2, 173.1, 174.2, 174.4],\n           index=[dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 5, 1), dt(2001, 6, 1)]\n       )\n\n       index_value(\n           index_lag=3,\n           index_method=\"daily\",\n           index_fixings=rpi_series,\n           index_date=dt(2001, 7, 20)\n       )\n    \"\"\"\n    index_method_ = _get_index_method(index_method)\n    iv_result = _try_index_value(\n        index_lag=index_lag,\n        index_method=index_method_,\n        index_fixings=index_fixings,\n        index_date=index_date,\n        index_curve=index_curve,\n    )\n    return iv_result.unwrap()\n\n\ndef _try_index_value(\n    index_lag: int,\n    index_method: IndexMethod,\n    index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n    index_date: datetime_ = NoInput(0),\n    index_curve: CurveOption_ = NoInput(0),\n) -> Result[DualTypes]:\n    if isinstance(index_fixings, int | float | Dual | Dual2 | Variable):\n        # i_fixings is a given value, probably aligned with an ``index_base``: return directly\n        return Ok(index_fixings)\n\n    if isinstance(index_curve, dict):\n        return Err(\n            NotImplementedError(\n                \"`index_curve` cannot currently be supplied as dict. Use a Curve type or \"\n                \"NoInput(0).\"\n            )\n        )\n\n    if isinstance(index_date, NoInput):\n        return Err(\n            ValueError(\n                \"Must supply an `index_date` from which to forecast if `index_fixings` is \"\n                \"not a value.\"\n            )\n        )\n\n    if isinstance(index_fixings, NoInput | None):\n        # forecast from curve if available\n        if isinstance(index_curve, NoInput):\n            return Err(\n                ValueError(\n                    \"`index_value` must be forecast from a `index_curve` but no such argument \"\n                    \"was provided.\"\n                )\n            )\n        return index_curve._try_index_value(\n            index_date=index_date,\n            index_lag=index_lag,\n            index_method=index_method,\n        )\n    elif isinstance(index_fixings, str):\n        try:\n            fixings_series = fixings.__getitem__(index_fixings)\n        except Exception as e:\n            return Err(e)\n        if isinstance(index_curve, NoInput):\n            return _index_value_from_series_no_curve(\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=fixings_series[1],\n                index_date=index_date,\n                index_fixings_boundary=fixings_series[2],\n            )\n        else:\n            return _index_value_from_mixed_series_and_curve(\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=fixings_series[1],\n                index_date=index_date,\n                index_curve=index_curve,\n            )\n    elif isinstance(index_fixings, Series):\n        if isinstance(index_curve, NoInput):\n            return _index_value_from_series_no_curve(\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings,\n                index_date=index_date,\n            )\n        else:\n            return _index_value_from_mixed_series_and_curve(\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings,\n                index_date=index_date,\n                index_curve=index_curve,\n            )\n    else:\n        return Err(\n            TypeError(\n                \"`index_fixings` must be of type: Str, Series, DualTypes or NoInput.\\n\"\n                f\"{type(index_fixings)} was given.\"\n            )\n        )\n\n\ndef _index_value_from_mixed_series_and_curve(\n    index_lag: int,\n    index_method: IndexMethod,\n    index_fixings: Series[DualTypes],  # type: ignore[type-var]\n    index_date: datetime,\n    index_curve: _BaseCurve,\n) -> Result[DualTypes]:\n    \"\"\"\n    Iterate through possibilities assuming a Curve and fixings as series exists.\n\n    For returning a value from the Series the ``index_lag`` must be zero.\n    If the lag is not zero then a Curve method will be used instead which will omit the Series.\n    \"\"\"\n    if index_method == IndexMethod.Curve:\n        if index_date in index_fixings.index:\n            # simplest case returns Series value if all checks pass.\n            if index_lag == 0:\n                return Ok(index_fixings.loc[index_date])\n            else:\n                return Err(\n                    ValueError(\n                        \"`index_lag` must be zero when using a 'curve' `index_method`.\\n\"\n                        f\"`index_date`: {index_date}, is in Series but got \"\n                        f\"`index_lag`: {index_lag}.\"\n                    )\n                )\n        elif len(index_fixings.index) == 0:\n            # recall with the curve\n            return index_curve._try_index_value(\n                index_date=index_date, index_lag=index_lag, index_method=index_method\n            )\n        elif index_lag == 0 and (index_fixings.index[0] < index_date < index_fixings.index[-1]):\n            # index date is within the Series index range but not found and the index lag is\n            # zero so this should be available\n            return Err(\n                ValueError(\n                    f\"The Series given for `index_fixings` requires, but does not contain, \"\n                    f\"the value for date: {index_date}.\\n\"\n                    \"For inflation indexes using 'monthly' or 'daily' `index_method` the \"\n                    \"values associated for a month should be assigned \"\n                    \"to the first day of that month.\"\n                )\n            )\n        else:\n            return index_curve._try_index_value(\n                index_date=index_date, index_lag=index_lag, index_method=index_method\n            )\n    elif index_method == IndexMethod.Monthly:\n        date_ = add_tenor(index_date, f\"-{index_lag}M\", \"none\", NoInput(0), 1)\n        # a monthly value can only be derived from one source.\n        # make separate determinations to avoid the issue of mis-matching index lags\n        value_from_fixings = _try_index_value(\n            index_lag=0,\n            index_method=IndexMethod.Curve,\n            index_fixings=index_fixings,\n            index_date=date_,\n            index_curve=NoInput(0),\n        )\n        if value_from_fixings.is_ok:\n            return value_from_fixings\n        else:\n            value_from_curve = _try_index_value(\n                index_lag=index_lag,\n                index_method=IndexMethod.Monthly,\n                index_fixings=NoInput(0),\n                index_date=index_date,\n                index_curve=index_curve,\n            )\n            return value_from_curve\n    else:  # i_method == IndexMethod.Daily:\n        n = monthrange(index_date.year, index_date.month)[1]\n        date_som = datetime(index_date.year, index_date.month, 1)\n        date_sonm = add_tenor(index_date, \"1M\", \"none\", NoInput(0), 1)\n        m1 = _try_index_value(\n            index_lag=index_lag,\n            index_method=IndexMethod.Monthly,\n            index_fixings=index_fixings,\n            index_date=date_som,\n            index_curve=index_curve,\n        )\n        if index_date == date_som:\n            return m1\n        m2 = _try_index_value(\n            index_lag=index_lag,\n            index_method=IndexMethod.Monthly,\n            index_fixings=index_fixings,\n            index_date=date_sonm,\n            index_curve=index_curve,\n        )\n        if m2.is_err or m1.is_err:\n            return Err(\n                ValueError(\n                    \"The `index_value` could not be determined.\\nThe period may be 'future' based \"\n                    \"and there is no `index_fixing` available, or an `index_curve` has not be \"\n                    \"able to forecast it.\"\n                )\n            )\n            # this line cannot be hit when a curve returns DualTypes and not a NoInput\n            # will raise a warning when the curve returns 0.0\n        m1_, m2_ = m1.unwrap(), m2.unwrap()\n        return Ok(m1_ + (index_date.day - 1) / n * (m2_ - m1_))\n\n\ndef _index_value_from_series_no_curve(\n    index_lag: int,\n    index_method: IndexMethod,\n    index_fixings: Series[DualTypes],  # type: ignore[type-var]\n    index_date: datetime,\n    index_fixings_boundary: tuple[datetime, datetime] | None = None,\n) -> Result[DualTypes]:\n    \"\"\"\n    Derive a value from a Series only, detecting cases where the errors might be raised.\n    \"\"\"\n    fixings_series = index_fixings\n\n    if index_method == IndexMethod.Curve:\n        if index_lag != 0:\n            return Err(ValueError(err.VE_INDEX_LAG_MUST_BE_ZERO.format(index_date, index_lag)))\n\n        if len(fixings_series.index) == 0:\n            return Err(ValueError(err.VE_EMPTY_SERIES))\n\n        if index_fixings_boundary is not None:\n            left, right = index_fixings_boundary\n            if index_date < left or index_date > right:\n                return Err(FixingRangeError(index_date, index_fixings_boundary))\n        else:\n            right = fixings_series.index[-1]\n            if index_date > right:\n                return Err(FixingRangeError(index_date, (datetime(1, 1, 1), right)))\n            left = fixings_series.index[0]\n            if index_date < left:\n                return Err(FixingRangeError(index_date, (left, right)))\n\n        if index_date in fixings_series.index:\n            # simplest case returns Series value if all checks pass.\n            return Ok(fixings_series.loc[index_date])\n\n        # date falls inside the dates of the Series but does not exist.\n        return Err(FixingMissingDataError(index_date, (left, right)))\n    elif index_method == IndexMethod.Monthly:\n        date_ = add_tenor(index_date, f\"-{index_lag}M\", \"none\", NoInput(0), 1)\n        return _index_value_from_series_no_curve(\n            index_lag=0,\n            index_method=IndexMethod.Curve,\n            index_fixings=index_fixings,\n            index_date=date_,\n            index_fixings_boundary=index_fixings_boundary,\n        )\n    else:  # i_method == IndexMethod.Daily:\n        n = monthrange(index_date.year, index_date.month)[1]\n        date_som = datetime(index_date.year, index_date.month, 1)\n        date_sonm = add_tenor(index_date, \"1M\", \"none\", NoInput(0), 1)\n        m1 = _index_value_from_series_no_curve(\n            index_lag=index_lag,\n            index_method=IndexMethod.Monthly,\n            index_fixings=index_fixings,\n            index_date=date_som,\n            index_fixings_boundary=index_fixings_boundary,\n        )\n        if index_date == date_som:\n            return m1\n        m2 = _index_value_from_series_no_curve(\n            index_lag=index_lag,\n            index_method=IndexMethod.Monthly,\n            index_fixings=index_fixings,\n            index_date=date_sonm,\n            index_fixings_boundary=index_fixings_boundary,\n        )\n        if m1.is_err:\n            return m1\n        if m2.is_err:\n            return m2\n        m1_, m2_ = m1.unwrap(), m2.unwrap()\n        return Ok(m1_ + (index_date.day - 1) / n * (m2_ - m1_))\n\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n"
  },
  {
    "path": "python/rateslib/curves/interpolation.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nfrom datetime import timezone\nfrom math import floor\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.dual import dual_exp, dual_log\nfrom rateslib.rs import index_left_f64\nfrom rateslib.scheduling import Convention, dcf\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        DualTypes,\n        Sequence,\n        _BaseCurve,\n        datetime,\n    )\n\n\nUTC = timezone.utc\n\n\nclass InterpolationFunction(Protocol):\n    # Callable type for Interpolation Functions\n    def __call__(self, date: datetime, curve: _BaseCurve) -> DualTypes: ...\n\n\ndef _linear(date: datetime, curve: _BaseCurve) -> DualTypes:\n    x, x_1, x_2, i = _get_posix(date, curve)\n    node_values = list(curve.nodes.nodes.values())\n    y_1, y_2 = node_values[i], node_values[i + 1]\n    return y_1 + (y_2 - y_1) * (x - x_1) / (x_2 - x_1)\n\n\ndef _linear_bus(date: datetime, curve: _BaseCurve) -> DualTypes:\n    i = index_left(curve.nodes.keys, curve.nodes.n, date)\n    x_1, x_2 = curve.nodes.keys[i], curve.nodes.keys[i + 1]\n    d_n = dcf(x_1, x_2, \"bus252\", calendar=curve.meta.calendar)\n    d_m = dcf(x_1, date, \"bus252\", calendar=curve.meta.calendar)\n    node_values = list(curve.nodes.nodes.values())\n    y_1, y_2 = node_values[i], node_values[i + 1]\n    return y_1 + (y_2 - y_1) * d_m / d_n\n\n\ndef _log_linear(date: datetime, curve: _BaseCurve) -> DualTypes:\n    x, x_1, x_2, i = _get_posix(date, curve)\n    node_values = list(curve.nodes.nodes.values())\n    y_1, y_2 = dual_log(node_values[i]), dual_log(node_values[i + 1])\n    return dual_exp(y_1 + (y_2 - y_1) * (x - x_1) / (x_2 - x_1))\n\n\ndef _log_linear_bus(date: datetime, curve: _BaseCurve) -> DualTypes:\n    i = index_left(curve.nodes.keys, curve.nodes.n, date)\n    x_1, x_2 = curve.nodes.keys[i], curve.nodes.keys[i + 1]\n    d_n = dcf(x_1, x_2, \"bus252\", calendar=curve.meta.calendar)\n    d_m = dcf(x_1, date, \"bus252\", calendar=curve.meta.calendar)\n    node_values = list(curve.nodes.nodes.values())\n    y_1, y_2 = dual_log(node_values[i]), dual_log(node_values[i + 1])\n    return dual_exp(y_1 + (y_2 - y_1) * d_m / d_n)\n\n\ndef _flat_forward(date: datetime, curve: _BaseCurve) -> DualTypes:\n    x, x_1, x_2, i = _get_posix(date, curve)\n    node_values = list(curve.nodes.nodes.values())\n    y_1, y_2 = node_values[i], node_values[i + 1]\n    if x >= x_2:\n        return y_2\n    return y_1\n\n\ndef _flat_backward(date: datetime, curve: _BaseCurve) -> DualTypes:\n    x, x_1, x_2, i = _get_posix(date, curve)\n    node_values = list(curve.nodes.nodes.values())\n    y_1, y_2 = node_values[i], node_values[i + 1]\n    if x <= x_1:\n        return y_1\n    return y_2\n\n\ndef _linear_zero_rate(date: datetime, curve: _BaseCurve) -> DualTypes:\n    # base time on DCF, which depends on the curve convention.\n    i = index_left(curve.nodes.keys, curve.nodes.n, date)\n    nvs = list(curve.nodes.nodes.values())\n    nds = curve.nodes.keys\n\n    d_2 = dcf(nds[0], nds[i + 1], curve.meta.convention, calendar=curve.meta.calendar)\n    r_2 = -dual_log(nvs[i + 1]) / dcf(\n        nds[0], nds[i + 1], curve.meta.convention, calendar=curve.meta.calendar\n    )\n    if i == 0:\n        # first period must use flat backwards zero rate\n        d_m = dcf(nds[0], date, curve.meta.convention, calendar=curve.meta.calendar)\n        r_m = r_2\n    else:\n        d_1 = dcf(nds[0], nds[i], curve.meta.convention, calendar=curve.meta.calendar)\n        r_1 = -dual_log(nvs[i]) / d_1\n        d_m = dcf(nds[0], date, curve.meta.convention, calendar=curve.meta.calendar)\n        r_m = r_1 + (r_2 - r_1) * (d_m - d_1) / (d_2 - d_1)\n\n    return dual_exp(-r_m * d_m)\n\n\ndef _linear_index(date: datetime, curve: _BaseCurve) -> DualTypes:\n    x, x_1, x_2, i = _get_posix(date, curve)\n    node_values = list(curve.nodes.nodes.values())\n    y_1, y_2 = node_values[i], node_values[i + 1]\n    return (1 / y_1 + (1 / y_2 - 1 / y_1) * (x - x_1) / (x_2 - x_1)) ** -1.0\n\n\ndef _runtime_error(date: datetime, curve: _BaseCurve) -> DualTypes:\n    \"\"\"Spline interpolation is performed by a PPSpline over the whole nodes domain.\"\"\"\n    raise RuntimeError(  # pragma: no cover\n        \"An `interpolation` mode of 'spline' should never call this function.\\n\"\n        \"The configured knot sequence `t` for the PPSpline should cover the entire `nodes` domain.\"\n    )\n\n\nINTERPOLATION: dict[tuple[str, Convention | None], InterpolationFunction] = {\n    (\"linear\", None): _linear,  # default linear interpolation for all Convention types\n    (\"linear\", Convention.Bus252): _linear_bus,  # overload for Bus252 type\n    (\"log_linear\", None): _log_linear,\n    (\"log_linear\", Convention.Bus252): _log_linear_bus,\n    (\"linear_zero_rate\", None): _linear_zero_rate,\n    (\"linear_index\", None): _linear_index,\n    (\"flat_forward\", None): _flat_forward,\n    (\"flat_backward\", None): _flat_backward,\n    (\"spline\", None): _runtime_error,\n}\n\n\ndef _get_posix(date: datetime, curve: _BaseCurve) -> tuple[float, float, float, int]:\n    \"\"\"\n    Convert a datetime and curve_nodes to posix timestamps and return the index_left.\n    \"\"\"\n    date_posix: float = date.replace(tzinfo=UTC).timestamp()\n    l_index = index_left_f64(curve.nodes.posix_keys, date_posix, None)\n    node_left_posix, node_right_posix = (\n        curve.nodes.posix_keys[l_index],\n        curve.nodes.posix_keys[l_index + 1],\n    )\n    return date_posix, node_left_posix, node_right_posix, l_index\n\n\ndef index_left(\n    list_input: Sequence[Any],\n    list_length: int,\n    value: Any,\n    left_count: int = 0,\n) -> int:\n    \"\"\"\n    Return the interval index of a value from an ordered input list on the left side.\n\n    Parameters\n    ----------\n    input : list\n        Ordered list (lowest to highest) containing datatypes the same as value.\n    length : int\n        The length of ``input``.\n    value : Any\n        The value for which to determine the list index of.\n    left_count : int\n        The counter to pass recursively to determine the output. Users should not\n        directly specify, it is used in internal calculation only.\n\n    Returns\n    -------\n    int : The left index of the interval within which value is found (or extrapolated\n          from)\n\n    Notes\n    -----\n    Uses a binary search method which operates with time :math:`O(log_2 n)`.\n\n    Examples\n    --------\n    .. ipython:: python\n\n       from rateslib.curves import index_left\n\n    Out of domain values return the left-side index of the closest matching interval.\n    100 is attributed to the interval (1, 2].\n\n    .. ipython:: python\n\n       list = [0, 1, 2]\n       index_left(list, 3, 100)\n\n    -100 is attributed to the interval (0, 1].\n\n    .. ipython:: python\n\n       index_left(list, 3, -100)\n\n    Interior values return the left-side index of the interval.\n    1.45 is attributed to the interval (1, 2].\n\n    .. ipython:: python\n\n       index_left(list, 3, 1.45)\n\n    1 is attributed to the interval (0, 1].\n\n    .. ipython:: python\n\n       index_left(list, 3, 1)\n\n    \"\"\"\n    if list_length == 1:\n        raise ValueError(\"`index_left` designed for intervals. Cannot index list of length 1.\")\n\n    if list_length == 2:\n        return left_count\n\n    split: int = floor((list_length - 1) / 2)\n    if list_length == 3 and value == list_input[split]:\n        return left_count\n\n    if value <= list_input[split]:\n        return index_left(list_input[: split + 1], split + 1, value, left_count)\n    else:\n        return index_left(list_input[split:], list_length - split, value, left_count + split)\n\n\n# # ALTERNATIVE index_left: exhaustive search which is inferior to binary search\n# def index_left_exhaustive(list_input, value, left_count=0):\n#     if left_count == 0:\n#         if value > list_input[-1]:\n#             return len(list_input)-2\n#         if value <= list_input[0]:\n#             return 0\n#\n#     if list_input[0] < value <= list_input[1]:\n#         return left_count\n#     else:\n#         return index_left_exhaustive(list_input[1:], value, left_count + 1)\n"
  },
  {
    "path": "python/rateslib/curves/rs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nfrom collections.abc import Callable\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Any\nfrom uuid import uuid4\n\nfrom rateslib import defaults\nfrom rateslib.default import _make_py_json\nfrom rateslib.dual.utils import _get_adorder\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.rs import (\n    ADOrder,\n    FlatBackwardInterpolator,\n    FlatForwardInterpolator,\n    LinearInterpolator,\n    LinearZeroRateInterpolator,\n    LogLinearInterpolator,\n    Modifier,\n    NullInterpolator,\n    _get_modifier_str,\n)\nfrom rateslib.rs import Curve as CurveObj  # noqa: F401\nfrom rateslib.scheduling import get_calendar\nfrom rateslib.scheduling.convention import _get_convention\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurveInterpolator,\n        DualTypes,\n        Number,\n    )\n\n\nclass CurveRs:\n    def __init__(\n        self,\n        nodes: dict[datetime, Number],\n        *,\n        interpolation: str\n        | Callable[[datetime, dict[datetime, DualTypes]], DualTypes]\n        | NoInput = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        convention: str | NoInput = NoInput(0),\n        modifier: str | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        ad: int = 0,\n        index_base: float | NoInput = NoInput(0),\n    ):\n        self._py_interpolator: Callable[[datetime, dict[datetime, DualTypes]], DualTypes] | None = (\n            interpolation if callable(interpolation) else None\n        )\n\n        self.obj = CurveObj(\n            nodes=nodes,\n            interpolator=self._validate_interpolator(interpolation),\n            ad=_get_adorder(ad),\n            id=_drb(uuid4().hex[:5] + \"_\", id),  # 1 in a million clash\n            convention=_get_convention(_drb(defaults.convention, convention)),\n            modifier=Modifier.ModF,\n            calendar=get_calendar(calendar),\n            index_base=_drb(None, index_base),\n        )\n\n    @property\n    def id(self) -> str:\n        return self.obj.id\n\n    @property\n    def convention(self) -> str:\n        return str(self.obj.convention)\n\n    @property\n    def modifier(self) -> str:\n        return _get_modifier_str(self.obj.modifier)\n\n    @property\n    def interpolation(self) -> str:\n        return self.obj.interpolation\n\n    @property\n    def nodes(self) -> dict[datetime, Number]:\n        return self.obj.nodes\n\n    @property\n    def ad(self) -> int:\n        _ = self.obj.ad\n        if _ == ADOrder.One:\n            return 1\n        elif _ == ADOrder.Two:\n            return 2\n        return 0\n\n    def _set_ad_order(self, ad: int) -> None:\n        self.obj.set_ad_order(_get_adorder(ad))\n\n    @staticmethod\n    def _validate_interpolator(\n        interpolation: str | Callable[[datetime, dict[datetime, DualTypes]], DualTypes] | NoInput,\n    ) -> CurveInterpolator:\n        if interpolation is NoInput.blank:\n            return _get_interpolator(defaults.interpolation[\"Curve\"])\n        elif isinstance(interpolation, str):\n            return _get_interpolator(interpolation)\n        else:\n            return NullInterpolator()\n\n    def to_json(self) -> str:\n        return _make_py_json(self.obj.to_json(), \"CurveRs\")\n\n    @classmethod\n    def __init_from_obj__(cls, obj: CurveObj) -> CurveRs:\n        new = cls(\n            nodes={datetime(2000, 1, 1): 1.0},\n            interpolation=\"linear\",\n            id=\"_\",\n            ad=0,\n            index_base=NoInput(0),\n        )\n        new.obj = obj\n        return new\n\n    def __eq__(self, other: Any) -> bool:\n        if not isinstance(other, CurveRs):\n            return False\n        return self.obj.__eq__(other.obj)\n\n    def __getitem__(self, value: datetime) -> Number:\n        return self.obj[value]\n\n\ndef _get_interpolator(name: str) -> CurveInterpolator:\n    name_ = name.lower()\n    if name_ == \"log_linear\":\n        return LogLinearInterpolator()\n    elif name_ == \"linear\":\n        return LinearInterpolator()\n    elif name_ == \"linear_zero_rate\":\n        return LinearZeroRateInterpolator()\n    elif name_ == \"flat_forward\":\n        return FlatForwardInterpolator()\n    elif name_ == \"flat_backward\":\n        return FlatBackwardInterpolator()\n    else:\n        raise ValueError(\"Interpolator `name` is invalid.\")\n"
  },
  {
    "path": "python/rateslib/curves/utils.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport json\nfrom dataclasses import dataclass\nfrom datetime import datetime, timezone\nfrom enum import Enum\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.curves.interpolation import INTERPOLATION, InterpolationFunction\nfrom rateslib.dual import dual_log, set_order_convert\nfrom rateslib.dual.utils import _to_number\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.scheduling import Convention\nfrom rateslib.scheduling.convention import _get_convention\nfrom rateslib.splines import PPSplineDual, PPSplineDual2, PPSplineF64\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        CalTypes,\n        DualTypes,\n        FXForwards,\n        Number,\n        Variable,\n        float_,\n        str_,\n    )  # pragma: no cover\n\n\nUTC = timezone.utc\n\n\nclass _CurveType(Enum):\n    \"\"\"\n    Enumerable type to define the difference between a discount factor (DF) based and\n    values based :class:`~rateslib.curves._BaseCurve`.\n    \"\"\"\n\n    dfs = 0\n    values = 1\n\n\nclass _CreditImpliedType(Enum):\n    \"\"\"\n    Enumerable type to define which calculation is performed on a\n    :class:`~rateslib.curves.CreditImpliedCurve`.\n    \"\"\"\n\n    credit = 0\n    hazard = 1\n    risk_free = 2\n\n\n@dataclass\nclass _CurveMeta:\n    \"\"\"\n    A container of meta data associated with a\n    :class:`~rateslib.curves._BaseCurve` used to make calculations.\n    \"\"\"\n\n    _calendar: CalTypes\n    _convention: Convention\n    _modifier: str\n    _index_base: float_ | Variable\n    _index_lag: int\n    _collateral: str | None\n    _credit_discretization: int\n    _credit_recovery_rate: float | Variable\n\n    @property\n    def calendar(self) -> CalTypes:\n        \"\"\"Settlement calendar used to determine fixing dates and tenor end dates.\"\"\"\n        return self._calendar\n\n    @property\n    def convention(self) -> Convention:\n        \"\"\"Day count convention for determining rates and interpolation.\"\"\"\n        return self._convention\n\n    @property\n    def modifier(self) -> str:\n        \"\"\"Modification rule for adjusting non-business tenor end dates.\"\"\"\n        return self._modifier\n\n    @property\n    def index_base(self) -> Variable | float_:\n        \"\"\"The index value associated with the initial node date of the *Curve*.\"\"\"\n        return self._index_base\n\n    @property\n    def index_lag(self) -> int:\n        \"\"\"The number of months by which curve nodes are lagged to determine index values.\"\"\"\n        return self._index_lag\n\n    @property\n    def collateral(self) -> str | None:\n        \"\"\"The currency(ies) identified as being the collateral choice for DFs associated with\n        the *Curve*.\"\"\"\n        return self._collateral\n\n    @property\n    def credit_discretization(self) -> int:\n        \"\"\"A parameter for numerically solving the integral for a *Credit Protection Period*.\"\"\"\n        return self._credit_discretization\n\n    @property\n    def credit_recovery_rate(self) -> float | Variable:\n        \"\"\"The recovery rate applied to *Credit Protection Period* cashflows.\"\"\"\n        return self._credit_recovery_rate\n\n    def to_json(self) -> str:\n        \"\"\"\n        Serialize this object to JSON format.\n\n        The object can be deserialized using the :meth:`~rateslib.serialization.from_json` method.\n\n        Returns\n        -------\n        str\n        \"\"\"\n        from rateslib.serialization.utils import _obj_to_json\n\n        obj = dict(\n            PyNative=dict(\n                _CurveMeta=dict(\n                    calendar=self.calendar.to_json(),\n                    convention=self.convention.to_json(),\n                    modifier=self.modifier,\n                    index_base=_obj_to_json(self.index_base),\n                    index_lag=self.index_lag,\n                    collateral=self.collateral,\n                    credit_discretization=self.credit_discretization,\n                    credit_recovery_rate=_obj_to_json(self.credit_recovery_rate),\n                )\n            )\n        )\n        return json.dumps(obj)\n\n    @classmethod\n    def _from_json(cls, loaded_json: dict[str, Any]) -> _CurveMeta:\n        from rateslib.serialization import from_json\n\n        return _CurveMeta(\n            _convention=from_json(loaded_json[\"convention\"]),\n            _modifier=loaded_json[\"modifier\"],\n            _index_lag=loaded_json[\"index_lag\"],\n            _collateral=loaded_json[\"collateral\"],\n            _index_base=from_json(loaded_json[\"index_base\"]),\n            _calendar=from_json(loaded_json[\"calendar\"]),\n            _credit_discretization=loaded_json[\"credit_discretization\"],\n            _credit_recovery_rate=from_json(loaded_json[\"credit_recovery_rate\"]),\n        )\n\n\nclass _CurveSpline:\n    \"\"\"\n    A container for data relating to interpolating :class:`~rateslib.curves._CurveNodes` using\n    a cubic PPSpline.\n    \"\"\"\n\n    _t: list[datetime]\n    _spline: PPSplineF64 | PPSplineDual | PPSplineDual2 | None\n    _endpoints: tuple[str, str]\n\n    def __init__(self, t: list[datetime], endpoints: tuple[str, str]) -> None:\n        self._t = t\n        self._endpoints = endpoints\n        self._spline = None  # will be set in later in csolve\n        if len(self._t) < 10 and \"not_a_knot\" in self.endpoints:\n            raise ValueError(\n                \"`endpoints` cannot be 'not_a_knot' with only 1 interior breakpoint\",\n            )\n\n    @property\n    def t(self) -> list[datetime]:\n        \"\"\"The knot sequence of the PPSpline.\"\"\"\n        return self._t\n\n    @cached_property\n    def t_posix(self) -> list[float]:\n        \"\"\"The knot sequence of the PPSpline converted to float unix timestamps.\"\"\"\n        return [_.replace(tzinfo=UTC).timestamp() for _ in self.t]\n\n    @property\n    def spline(self) -> PPSplineF64 | PPSplineDual | PPSplineDual2 | None:\n        \"\"\"An instance of :class:`~rateslib.splines.PPSplineF64`,\n        :class:`~rateslib.splines.PPSplineDual` or :class:`~rateslib.splines.PPSplineDual2`.\n        \"\"\"\n        return self._spline\n\n    @property\n    def endpoints(self) -> tuple[str, str]:\n        \"\"\"The endpoints method used to determine the spline coefficients.\"\"\"\n        return self._endpoints\n\n    # All calling methods should clear the cache and/or set new state after `_csolve`\n    def _csolve(self, curve_type: _CurveType, nodes: _CurveNodes, ad: int) -> None:\n        t_posix = self.t_posix.copy()\n        tau_posix = [k.replace(tzinfo=UTC).timestamp() for k in nodes.keys if k >= self.t[0]]\n        if curve_type == _CurveType.dfs:\n            # then use log\n            y = [dual_log(v) for k, v in nodes.nodes.items() if k >= self.t[0]]\n        else:\n            # use values directly\n            y = [_to_number(v) for k, v in nodes.nodes.items() if k >= self.t[0]]\n\n        # Left side constraint\n        if self.endpoints[0].lower() == \"natural\":\n            tau_posix.insert(0, t_posix[0])\n            y.insert(0, set_order_convert(0.0, ad, None))\n            left_n = 2\n        elif self.endpoints[0].lower() == \"not_a_knot\":\n            t_posix.pop(4)\n            left_n = 0\n        else:\n            raise NotImplementedError(\n                f\"Endpoint method '{self.endpoints[0]}' not implemented.\",\n            )\n\n        # Right side constraint\n        if self.endpoints[1].lower() == \"natural\":\n            tau_posix.append(self.t_posix[-1])\n            y.append(set_order_convert(0, ad, None))\n            right_n = 2\n        elif self.endpoints[1].lower() == \"not_a_knot\":\n            t_posix.pop(-5)\n            right_n = 0\n        else:\n            raise NotImplementedError(\n                f\"Endpoint method '{self.endpoints[0]}' not implemented.\",\n            )\n\n        # Get the Spline class by data types\n        if ad == 0:\n            self._spline = PPSplineF64(4, t_posix, None)\n        elif ad == 1:\n            self._spline = PPSplineDual(4, t_posix, None)\n        else:\n            self._spline = PPSplineDual2(4, t_posix, None)\n\n        self._spline.csolve(tau_posix, y, left_n, right_n, False)  # type: ignore[arg-type]\n\n    def to_json(self) -> str:\n        \"\"\"\n        Serialize this object to JSON format.\n\n        The object can be deserialized using the :meth:`~rateslib.serialization.from_json` method.\n\n        Returns\n        -------\n        str\n        \"\"\"\n        obj = dict(\n            PyNative=dict(\n                _CurveSpline=dict(\n                    t=[_.strftime(\"%Y-%m-%d\") for _ in self.t],\n                    endpoints=self.endpoints,\n                )\n            )\n        )\n        return json.dumps(obj)\n\n    @classmethod\n    def _from_json(cls, loaded_json: dict[str, Any]) -> _CurveSpline:\n        return _CurveSpline(\n            t=[datetime.strptime(_, \"%Y-%m-%d\") for _ in loaded_json[\"t\"]],\n            endpoints=tuple(loaded_json[\"endpoints\"]),\n        )\n\n    def __eq__(self, other: Any) -> bool:\n        \"\"\"CurveSplines are considered equal if their knot sequence and endpoints are equivalent.\n        For the same nodes this will resolve to give the same spline coefficients.\n        \"\"\"\n        if not isinstance(other, _CurveSpline):\n            return False\n        else:\n            return all(iter([self.t == other.t, self.endpoints == other.endpoints]))\n\n\nclass _CurveInterpolator:\n    \"\"\"\n    A container for data relating to interpolating :class:`~rateslib.curves._CurveNodes`.\n    \"\"\"\n\n    _local_name: str\n    _local_func: InterpolationFunction\n    _convention: Convention\n    _spline: _CurveSpline | None\n\n    def __init__(\n        self,\n        local: str_ | InterpolationFunction,\n        t: list[datetime] | NoInput,\n        endpoints: tuple[str, str],\n        node_dates: list[datetime],\n        convention: Convention | str,\n        curve_type: _CurveType,\n    ) -> None:\n        if not isinstance(t, NoInput) and local == \"spline\":\n            raise ValueError(\n                \"When defining 'spline' interpolation, the argument `t` will be \"\n                \"automatically generated.\\n\"\n                f\"It should not be specified directly. Got: {t}\"\n            )\n\n        self._convention = _get_convention(convention)\n        if isinstance(local, NoInput):\n            local = defaults.interpolation[curve_type.name]\n\n        if isinstance(local, str):\n            self._local_name = local.lower()\n            if self.local_name == \"spline\":\n                # then refactor t\n                t = (\n                    [node_dates[0], node_dates[0], node_dates[0]]\n                    + node_dates\n                    + [node_dates[-1], node_dates[-1], node_dates[-1]]\n                )\n\n            if (self._local_name, self.convention) in INTERPOLATION:\n                self._local_func = INTERPOLATION[(self.local_name, self.convention)]\n            else:\n                try:\n                    self._local_func = INTERPOLATION[(self.local_name, None)]\n                except KeyError:\n                    raise ValueError(\n                        f\"Curve interpolation: '{self.local_name}' not available.\\n\"\n                        f\"Consult the documentation for available methods.\"\n                    )\n        else:\n            self._local_name = \"user_defined_callable\"\n            self._local_func = local\n\n        if isinstance(t, NoInput):\n            self._spline = None\n        else:\n            self._spline = _CurveSpline(t, endpoints)\n\n    @property\n    def local(self) -> str | InterpolationFunction:\n        \"\"\"The local interpolation name or function, if user defined.\"\"\"\n        if self.local_name == \"user_defined_callable\":\n            return self.local_func\n        return self.local_name\n\n    @property\n    def local_name(self) -> str:\n        \"\"\"The str name of the local interpolation function.\"\"\"\n        return self._local_name\n\n    @property\n    def local_func(self) -> InterpolationFunction:\n        \"\"\"The callable used for local interpolation\"\"\"\n        return self._local_func\n\n    @property\n    def spline(self) -> _CurveSpline | None:\n        \"\"\"The :class:`~rateslib.curves.utils._CurveSpline` used for PPSpline interpolation.\"\"\"\n        return self._spline\n\n    @property\n    def convention(self) -> Convention:\n        \"\"\"The day count convention used to adjust interpolation functions.\"\"\"\n        return self._convention\n\n    # All calling methods should clear the cache and/or set new state after `_csolve`\n    def _csolve(self, curve_type: _CurveType, nodes: _CurveNodes, ad: int) -> None:\n        if self.spline is None:\n            return None\n        self.spline._csolve(curve_type, nodes, ad)\n\n    def __eq__(self, other: Any) -> bool:\n        if (\n            not isinstance(other, _CurveInterpolator)\n            or self.local_name == \"user_defined_callable\"\n            and self.local_func != other.local_func\n        ):\n            return False\n\n        return all(iter([self.local_name == other.local_name, self.spline == other.spline]))\n\n    def to_json(self) -> str:\n        \"\"\"\n        Serialize this object to JSON format.\n\n        The object can be deserialized using the :meth:`~rateslib.serialization.from_json` method.\n\n        Returns\n        -------\n        str\n        \"\"\"\n        from rateslib.serialization.utils import _obj_to_json\n\n        obj = dict(\n            PyNative=dict(\n                _CurveInterpolator=dict(\n                    local=self.local_name,\n                    spline=_obj_to_json(self.spline),\n                    convention=_obj_to_json(self.convention),\n                )\n            )\n        )\n        return json.dumps(obj)\n\n    @classmethod\n    def _from_json(cls, loaded_json: dict[str, Any]) -> _CurveInterpolator:\n        from rateslib.serialization import from_json\n\n        spl = from_json(loaded_json[\"spline\"])\n\n        if loaded_json[\"local\"] == \"spline\":\n            t = NoInput(0)\n            node_dates = spl.t[3:-3]\n        else:\n            t = NoInput(0) if spl is None else spl.t\n            node_dates = NoInput(0)\n\n        return _CurveInterpolator(\n            local=loaded_json[\"local\"],\n            t=t,\n            endpoints=NoInput(0) if spl is None else spl.endpoints,  # type: ignore[arg-type]\n            node_dates=node_dates,\n            convention=from_json(loaded_json[\"convention\"]),\n            curve_type=NoInput(0),  # type: ignore[arg-type]\n        )\n\n\n@dataclass(frozen=True)\nclass _ProxyCurveInterpolator:\n    \"\"\"\n    A container for data relating to interpolating the DFs of a\n    :class:`~rateslib.curves.ProxyCurve`.\n    \"\"\"\n\n    _fx_forwards: FXForwards\n    _cash: str\n    _collateral: str\n\n    @property\n    def fx_forwards(self) -> FXForwards:\n        \"\"\"The :class:`~rateslib.fx.FXForwards` object containing :class:`~rateslib.fx.FXRates`\n        and :class:`~rateslib.curves.Curve` objects.\"\"\"\n        return self._fx_forwards\n\n    @property\n    def cash(self) -> str:\n        \"\"\"The currency of the cashflows.\"\"\"\n        return self._cash\n\n    @property\n    def collateral(self) -> str:\n        \"\"\"The currency of the collateral assuming PAI.\"\"\"\n        return self._collateral\n\n    @property\n    def pair(self) -> str:\n        \"\"\"A pair of currencies representing the cashflow and collateral.\"\"\"\n        return self.cash + self.collateral\n\n    @property\n    def cash_index(self) -> int:\n        \"\"\"The index of the cash currency in the :class:`~rateslib.fx.FXForwards` object.\"\"\"\n        return self.fx_forwards.currencies[self.cash]\n\n    @property\n    def collateral_index(self) -> int:\n        \"\"\"The index of the collateral currency in the :class:`~rateslib.fx.FXForwards` object.\"\"\"\n        return self.fx_forwards.currencies[self.collateral]\n\n    @property\n    def cash_pair(self) -> str:\n        \"\"\"A pair constructed from the cash currency\"\"\"\n        return self.cash + self.cash\n\n    @property\n    def collateral_pair(self) -> str:\n        \"\"\"A pair constructed from the collateral currency\"\"\"\n        return self.collateral + self.collateral\n\n\n@dataclass(frozen=True)\nclass _CurveNodes:\n    \"\"\"\n    An immutable container for the pricing parameters of a :class:`~rateslib.curves._BaseCurve`.\n    \"\"\"\n\n    _nodes: dict[datetime, DualTypes]\n\n    def __post_init__(self) -> None:\n        for idx in range(1, self.n):\n            if self.keys[idx - 1] >= self.keys[idx]:\n                raise ValueError(\n                    \"Curve node dates are not sorted or contain duplicates.\\n\"\n                    \"To sort directly use: `dict(sorted(nodes.items()))`\",\n                )\n\n    @property\n    def nodes(self) -> dict[datetime, DualTypes]:\n        \"\"\"The initial nodes dict passed for construction of this class.\"\"\"\n        return self._nodes\n\n    @cached_property\n    def keys(self) -> list[datetime]:\n        \"\"\"A list of datetime keys in ``nodes``.\"\"\"\n        return list(self._nodes.keys())\n\n    @cached_property\n    def values(self) -> list[DualTypes]:\n        \"\"\"A list of values in ``nodes``.\"\"\"\n        return list(self._nodes.values())\n\n    @property\n    def n(self) -> int:\n        \"\"\"Number of parameters contained in ``nodes``.\"\"\"\n        return len(self.keys)\n\n    @cached_property\n    def posix_keys(self) -> list[float]:\n        \"\"\"A list of the ``keys`` converted to unix timestamps.\"\"\"\n        return [_.replace(tzinfo=UTC).timestamp() for _ in self.keys]\n\n    @property\n    def initial(self) -> datetime:\n        \"\"\"The first node key associated with the *Curve* nodes.\"\"\"\n        return self.keys[0]\n\n    @property\n    def final(self) -> datetime:\n        \"\"\"The last node key associated with the *Curve* nodes.\"\"\"\n        return self.keys[-1]\n\n    def to_json(self) -> str:\n        \"\"\"\n        Serialize this object to JSON format.\n\n        The object can be deserialized using the :meth:`~rateslib.serialization.from_json` method.\n\n        Returns\n        -------\n        str\n        \"\"\"\n\n        obj = dict(\n            PyNative=dict(\n                _CurveNodes=dict(\n                    _nodes={dt.strftime(\"%Y-%m-%d\"): v.real for dt, v in self._nodes.items()},\n                )\n            )\n        )\n        return json.dumps(obj)\n\n    @classmethod\n    def _from_json(cls, loaded_json: dict[str, Any]) -> _CurveNodes:\n        return _CurveNodes(\n            _nodes={datetime.strptime(d, \"%Y-%m-%d\"): v for d, v in loaded_json[\"_nodes\"].items()}\n        )\n\n\ndef average_rate(\n    effective: datetime,\n    termination: datetime,\n    convention: Convention | str,\n    rate: DualTypes,\n    dcf: float,\n) -> tuple[Number, float, float]:\n    \"\"\"\n    Return the geometric, 1-day, average simple rate for a given simple period rate.\n\n    This is used for approximations usually in combination with floating periods.\n\n    Parameters\n    ----------\n    effective : datetime\n        The effective date of the rate.\n    termination : datetime\n        The termination date of the rate.\n    convention : str\n        The day count convention of the curve rate.\n    rate : float, Dual, Dual2\n        The simple period rate to decompose to average, in percentage terms, e.g. 4.00 = 4% rate.\n    dcf : float\n        The day count fraction of the period used to determine daily DCF.\n\n    Returns\n    -------\n    tuple : The simple rate, the 1-day DCF, and the number of relevant days for the convention\n\n    Notes\n    -----\n    This method operates in one of two modes to determine the value, :math:`\\\\bar{r}`.\n\n    - Calendar day basis, where :math:`\\\\tilde{n}` is calendar days in period:\n\n      .. math::\n\n         1+\\\\tilde{n}\\\\bar{d}r = (1 + \\\\bar{d}\\\\bar{r})^{\\\\tilde{n}}\n\n    - Business day basis (if ``convention`` is *'bus252'*), where :math:`n` is business days\n      in period. *n* is approximated by a 252 business days per year rule and does not\n      calculate the exact number of business days from any specific holiday calendar.\n\n      .. math::\n\n         1+n\\\\bar{d}r = (1 + \\\\bar{d}\\\\bar{r})^{n}\n\n    :math:`\\\\bar{d}`, the 1-day DCF is estimated from a ``convention``. For certain conventions,\n    e.g. *'act360'* and *'act365f'* this is explicit and exact, but for others, such as *'30360'*,\n    this function will likely be lesser used and less accurate.\n    \"\"\"\n    convention_ = _get_convention(convention)\n    if convention_ == Convention.Bus252:\n        # business days are used\n        n: float = dcf * 252.0\n        d = 1.0 / 252.0\n    else:  # calendar day mode\n        n = (termination - effective).days\n        d = dcf / n\n\n    _: Number = ((1 + n * d * rate / 100) ** (1 / n) - 1) / d\n    return _ * 100, d, n\n"
  },
  {
    "path": "python/rateslib/data/__instrument_spec.csv",
    "content": "kind,meta,meta,meta,meta,meta,meta,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,base_derivative,fixed,float,float,float,float,fixed,float,float,float,float,float,exchange,exchange,exchange,exchange,exchange,exchange,exchange,exchange,xcs,xcs,xcs,index,index,index,index,index,index,index,index,bond,bond,bond,stir,cds,bondfuture,fx,fx\nleg,meta,meta,meta,meta,meta,meta,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg2,leg1,leg1,leg1,leg1,leg1,leg2,leg2,leg2,leg2,leg2,leg2,leg1,leg1,leg2,leg2,leg1,leg2,leg1,leg2,leg1,leg2,leg2,leg1,leg1,leg1,leg1,leg2,leg2,leg2,leg2,leg1,leg1,leg1,leg1,leg1,leg1,leg1,leg1\nkwarg,currency,instrument,sub_type,bloomberg_ticker,eval,description,effective,termination,frequency,stub,front_stub,back_stub,roll,eom,modifier,calendar,payment_lag,notional,currency,amortization,convention,effective,termination,frequency,stub,front_stub,back_stub,roll,eom,modifier,calendar,payment_lag,notional,currency,amortization,convention,fixed_rate,float_spread,spread_compound_method,rate_fixings,fixing_method,fixed_rate,float_spread,spread_compound_method,rate_fixings,fixing_method,fixing_series,initial_exchange,final_exchange,initial_exchange,final_exchange,fx_fixings,fx_fixings,payment_lag_exchange,payment_lag_exchange,fixed,fixed,mtm,index_method,index_fixings,index_base,index_lag,index_method,index_fixings,index_base,index_lag,settle,ex_div,calc_mode,nominal,premium_accrued,coupon,pair,delivery_lag\ndtype,str,str,str,str,str,str,str,str,str,str,str,str,str,boolean,str,str,Int64,float,str,float,str,str,str,str,str,str,str,str,boolean,str,str,Int64,float,str,float,str,float,float,str,str,str,float,float,str,str,str,str,boolean,boolean,boolean,boolean,str,str,Int64,Int64,boolean,boolean,boolean,str,str,float,Int64,str,str,float,Int64,Int64,str,str,float,boolean,float,str,Int64\ntest,TES,none,none,none,none,A test column,,,m,longfront,,,,FALSE,p,\"nyc,tgt,ldn\",4,,tes,,yearsmonths,,,m,longback,,,1,FALSE,mp,\"nyc,tgt,ldn\",3,,,,one,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\neurusd_call,eurusd,fx_call,,,2b,Currency call option,,,,,,,,,mf,tgt|fed,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurusd,2\nus_ig_cds,usd,cds,,,,,,,q,shortfront,,,20,FALSE,fex,nyc,0,,usd,,act360,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\ninr_ndirs,inr,irs,,irswni,1b,NDIRS vs IN000/N Index,,,s,shortfront,,,,FALSE,mf,mum,0,,usd,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,usdinr,\ninrusd_ndxcs,inrusd_ndxcs,ndxcs,,IRUSON,2b,NDXCS Fixed/Float vs SOFR,,,s,shortfront,,,,FALSE,mf,mum|fed,2,,usd,,act365f,,,,,,,,,,,,,,,act360,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,TRUE,,,,,,,,,,,,,,,,,usdinr,\nmxn_irs,mxn,irs,,MPSWF,2b,F-TIIE OIS,,,28d,shortfront,,,,FALSE,f,mex,2,,mxn,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\nusd_irs,usd,irs,,usosfr,2b,SOFR IRS conventions,,,a,shortfront,,,,FALSE,mf,nyc,2,,usd,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\nusd_irs_lt_2y,usd,irs,,usosfr,2b,SOFR IRS conventions,,,a,shortfront,,,,TRUE,mf,nyc,2,,usd,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\ngbp_irs,gbp,irs,,bpsws,0b,SONIA IRS conventions,,,a,shortfront,,,,TRUE,mf,ldn,0,,gbp,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\neur_irs,eur,irs,,eeswe,2b,ESTR IRS conventions,,,a,shortfront,,,,FALSE,mf,tgt,1,,eur,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\nsek_irs,sek,irs,,sksws,2b,SWESTR IRS conventions,,,a,shortfront,,,,FALSE,mf,stk,1,,sek,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnok_irs,nok,irs,,nks,2b,,,,a,shortfront,,,,FALSE,mf,osl,2,,nok,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\nchf_irs,chf,irs,,sfsnt,2b,,,,a,shortfront,,,,FALSE,mf,zur,2,,chf,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\ncad_irs,cad,irs,,cdso,1b,,,,s,shortfront,,,,FALSE,mf,tro,1,,cad,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\ncad_irs_le_1y,cad,irs,,cdso,1b,,,,a,shortfront,,,,TRUE,mf,tro,1,,cad,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\njpy_irs,jpy,irs,,jyso,2b,,,,a,shortfront,,,,TRUE,mf,tyo,2,,jpy,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnzd_irs3,nzd,irs,,ndswap,2b,,,,s,shortfront,,,,TRUE,mf,wlg,0,,nzd,,act365f,,,q,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnzd_irs6,nzd,irs,,,2b,,,,s,shortfront,,,,TRUE,mf,wlg,0,,nzd,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnzd_irs,nzd,irs,,,2b,,,,a,shortfront,,,,TRUE,mf,wlg,2,,nzd,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\naud_irs6,aud,irs,,adsw,1b,,,,s,shortfront,,,,TRUE,mf,syd,0,,aud,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\naud_irs3,aud,irs,,adsw_q,1b,,,,q,shortfront,,,,TRUE,mf,syd,0,,aud,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\naud_irs3_gt_3y,aud,irs,,,2b,,,,s,shortfront,,,,TRUE,mf,syd,0,,aud,,act365f,,,q,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\naud_irs,aud,irs,,adso,1b,,,,a,shortfront,,,,TRUE,mf,syd,2,,aud,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\neur_irs6,eur,irs,,eusa,2b,,,,a,shortfront,,,,FALSE,mf,tgt,0,,eur,,30e360,,,s,,,,,,,,,,,,act360,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\neur_irs3,eur,irs,,eusw_v3,2b,,,,a,shortfront,,,,FALSE,mf,tgt,0,,eur,,30e360,,,q,,,,,,,,,,,,act360,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\neur_irs1,eur,irs,,,2b,,,,a,shortfront,,,,FALSE,mf,tgt,0,,eur,,30e360,,,m,,,,,,,,,,,,act360,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nsek_irs3,sek,irs,,sksw,2b,,,,a,shortfront,,,,FALSE,mf,stk,0,,sek,,30e360,,,q,,,,,,,,,,,,act360,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnok_irs3,nok,irs,,nksw_v3,2b,,,,a,shortfront,,,,FALSE,mf,osl,0,,nok,,30e360,,,q,,,,,,,,,,,,act360,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnok_irs6,nok,irs,,nksw,2b,,,,a,shortfront,,,,FALSE,mf,osl,0,,nok,,30e360,,,s,,,,,,,,,,,,act360,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\neurusd_xcs,eur/usd,xcs,,euxoqq,2b,,,,q,shortfront,,,,FALSE,mf,\"tgt,nyc\",2,,eur,,act360,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,none_simple,,rfr_payment_delay,,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,eurusd,\ngbpusd_xcs,gbp/usd,xcs,,bpxoqq,2b,,,,q,shortfront,,,,FALSE,mf,\"ldn,nyc\",2,,gbp,,act365f,,,,,,,,,,,,,,,act360,,,none_simple,,rfr_payment_delay,,,none_simple,,rfr_payment_delay,,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,gbpusd,\neurgbp_xcs,eur/gbp,xcs,,ebxoqq,2b,,,,q,shortfront,,,,FALSE,mf,\"tgt,ldn\",2,,eur,,act360,,,,,,,,,,,,,,,act365f,,,none_simple,,rfr_payment_delay,,,none_simple,,rfr_payment_delay,,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,eurgbp,\ngbpeur_xcs,gbp/eur,xcs,,ebxoqq,2b,,,,q,shortfront,,,,FALSE,mf,\"tgt,ldn\",2,,gbp,,act365f,,,,,,,,,,,,,,,act360,,,none_simple,,rfr_payment_delay,,,none_simple,,rfr_payment_delay,,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,eurgbp,\njpyusd_xcs,jpy/usd,xcs,,jybss,2b,,,,q,shortfront,,,,FALSE,mf,\"nyc,tyo\",2,,jpy,,act365f,,,,,,,,,,,,,,,act360,,,none_simple,,rfr_payment_delay,,,none_simple,,rfr_payment_delay,,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,usdjpy,\naudusd_xcs3,aud/usd,xcs,,,,,,,q,shortfront,,,,FALSE,mf,\"nyc,syd\",2,,aud,,act365f,,,,,,,,,,,,,,,act360,,,none_simple,,ibor(0),,,none_simple,,rfr_payment_delay,,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,audusd,\naudusd_xcs,aud/usd,xcs,,,,,,,q,shortfront,,,,FALSE,mf,\"nyc,syd\",2,,aud,,act365f,,,,,,,,,,,,,,,act360,,,none_simple,,rfr_payment_delay,,,none_simple,,rfr_payment_delay,,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,audusd,\nnzdusd_xcs3,nzd/usd,xcs,,,,,,,q,shortfront,,,,FALSE,mf,\"nyc,wlg\",2,,nzd,,act365f,,,,,,,,,,,,,,,act360,,,none_simple,,ibor(0),,,none_simple,,rfr_payment_delay,,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,nzdusd,\nnzdaud_xcs3,nzd/aud,xcs,,,,,,,q,shortfront,,,,FALSE,mf,\"nyc,wlg,syd\",2,,nzd,,act365f,,,,,,,,,,,,,,,act365f,,,none_simple,,ibor(0),,,none_simple,,ibor(0),,,,,,,,0,,FALSE,FALSE,TRUE,,,,,,,,,,,,,,,audnzd,\neur_zcis,eur,zcis,,euswi,2b,,,,a,shortfront,,,,FALSE,mf,tgt,0,,eur,,1+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,monthly,,,3,,,,,,,,\ngbp_zcis,gbp,zcis,,bpswit,0b,,,,a,shortfront,,,,FALSE,mf,ldn,0,,gbp,,1+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,monthly,,,2,,,,,,,,\nusd_zcis,usd,zcis,,usswit,2b,,,,a,shortfront,,,,FALSE,mf,nyc,0,,usd,,1+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,daily,,,3,,,,,,,,\ngbp_zcs,gbp,zcs,,,0b,,,,a,shortfront,,,,TRUE,mf,ldn,0,,gbp,,act365f,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,,,,,,,,,,,,,,,,,,,,,,,,,,,,\nsek_iirs,sek_iirs,iirs,,,2b,,,,a,shortfront,,,,FALSE,none,stk,0,,sek,,actacticma,,,q,,,,,,,,,,,,act360,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,daily,,,3,,,,,,,,,,,,\neur_sbs36,eur,sbs,,,2b,,,,q,shortfront,,,,FALSE,mf,tgt,0,,eur,,act360,,,s,,,,,,,,,,,,,,,none_simple,,ibor(2),,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnok_sbs36,nok,sbs,,,2b,,,,q,shortfront,,,,FALSE,mf,osl,0,,nok,,act360,,,s,,,,,,,,,,,,,,,none_simple,,ibor(2),,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\naud_sbs36,aud,sbs,,,2b,,,,q,shortfront,,,,FALSE,mf,syd,0,,aud,,act365f,,,s,,,,,,,,,,,,,,,none_simple,,ibor(0),,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\naud_sbs31,aud,sbs,,,2b,,,,q,shortfront,,,,FALSE,mf,syd,0,,aud,,act365f,,,m,,,,,,,,,,,,,,,none_simple,,ibor(0),,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnzd_sbs36,nzd,sbs,,,2b,,,,q,shortfront,,,,FALSE,mf,wlg,0,,nzd,,act365f,,,s,,,,,,,,,,,,,,,none_simple,,ibor(0),,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnzd_sbs31,nzd,sbs,,,2b,,,,q,shortfront,,,,FALSE,mf,wlg,0,,nzd,,act365f,,,m,,,,,,,,,,,,,,,none_simple,,ibor(0),,,none_simple,,ibor(0),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nus_gb,usd,frb,,,,,,,s,shortfront,,,,TRUE,none,nyc,0,,usd,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-1b,us_gb,,,,,\nus_gbi,usd,ifrb,,,,,,,s,shortfront,,,,TRUE,none,nyc,0,,usd,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,daily,,,3,,,,,1,-1b,us_gb,,,,,\nus_corp,usd,frb,,,,,,,s,shortfront,,,,TRUE,none,nyc,0,,usd,,30u360,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-1b,us_corp,,,,,\nus_muni,usd,frb,,,,,,,s,shortfront,,,,TRUE,none,nyc,0,,usd,,30u360,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-1b,us_muni,,,,,\nus_gb_tsy,usd,frb,,,,,,,s,shortfront,,,,TRUE,none,nyc,0,,usd,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-1b,us_gb_tsy,,,,,\nuk_gb,gbp,frb,,,,,,,s,longfront,,,,FALSE,none,ldn,0,,gbp,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-7b,uk_gb,,,,,\nau_gb,aud,frb,,,,,,,s,longfront,,,,FALSE,none,syd,0,,aud,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,2,-8d,au_gb,,,,,\nnz_gb,nzd,frb,,,,,,,s,shortfront,,,,FALSE,none,wlg,0,,nzd,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-8b,nz_gb,,,,,\ncn_gb,cny,frb,,,,,,,s,shortfront,,,,FALSE,none,bjs,0,,cny,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-1b,cn_gb,,,,,\nde_gb,eur,frb,,,,,,,a,longfront,,,,FALSE,none,tgt,0,,eur,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,2,-1b,de_gb,,,,,\nfr_gb,eur,frb,,,,,,,a,shortfront,,,,FALSE,none,tgt,0,,eur,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,2,-1b,fr_gb,,,,,\nnl_gb,eur,frb,,,,,,,a,shortfront,,,,FALSE,none,tgt,0,,eur,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,2,-1b,nl_gb,,,,,\nit_gb,eur,frb,,,,,,,s,shortfront,,,,FALSE,none,tgt,0,,eur,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,2,-1b,it_gb,,,,,\nch_gb,chf,frb,,,,,,,a,shortfront,,,,FALSE,none,zur,0,,chf,,30e360,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-1b,ch_gb,,,,,\nse_gb,sek,frb,,,,,,,a,shortfront,,,,FALSE,none,stk,0,,sek,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,2,-5b,se_gb,,,,,\nno_gb,nok,frb,,,,,,,a,shortfront,,,,FALSE,none,osl,0,,nok,,actacticma_stub365f,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-1b,no_gb,,,,,\nca_gb,cad,frb,,,,,,,s,shortfront,,,,FALSE,none,tro,0,,cad,,actacticma_stub365f,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,-1b,ca_gb,,,,,\nca_gbi,cad,ifrb,,,,,,,s,shortfront,,,,FALSE,none,tro,0,,cad,,actacticma_stub365f,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,daily,,,3,,,,,1,-1b,ca_gb,,,,,\nus_gbb,usd,bill,,,,,,,,,,,,TRUE,none,nyc,0,,usd,,act360,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,0b,us_gbb,,,,,\nse_gbb,sek,bill,,,,,,,,,,,,FALSE,none,stk,0,,sek,,act360,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,2,0b,se_gbb,,,,,\nno_gbb,nok,bill,,,,,,,,,,,,FALSE,none,osl,0,,nok,,act365f,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,2,0b,no_gbb,,,,,\nuk_gbb,gbp,bill,,,,,,,,,,,,TRUE,none,ldn,0,,gbp,,act365f,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,,,,,,,1,0b,uk_gbb,,,,,\nuk_gbi,gbp,ifrb,,,,,,,s,shortfront,,,,FALSE,none,ldn,0,,gbp,,actacticma,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,daily,,,3,,,,,1,-7b,uk_gb,,,,,\nsek_fra3,sek,fra,,,,,,3m,q,,,,,FALSE,mf,stk,0,,sek,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\neur_fra3,eur,fra,,,,,,3m,q,,,,,FALSE,mf,tgt,0,,eur,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\neur_fra6,eur,fra,,,,,,6m,s,,,,,FALSE,mf,tgt,0,,eur,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\neur_fra1,eur,fra,,,,,,1m,m,,,,,FALSE,mf,tgt,0,,eur,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnok_fra3,nok,fra,,,,,,3m,q,,,,,FALSE,mf,osl,0,,nok,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nnok_fra6,nok,fra,,,,,,6m,s,,,,,FALSE,mf,osl,0,,nok,,act360,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(2),,,,,,,,,,,,,,,,,,,,,,,,,,,,\nusd_frn5,usd,frn,,,,,,,q,,,,,FALSE,mf,nyc,0,,usd,,act360,,,,,,,,,,,,,,,,,,none_simple,,rfr_observation_shift(5),,,,,,,,,,,,,,,,,,,,,,,,,,1,1b,,,,,,\nusd_stir,usd,stir,,SFR,3m SOFR Futures convention,,,,q,,,,imm,FALSE,mf,nyc,0,,usd,,actacticma,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,usd_rfr,,,,,,,,,,,,,,,,,,,,,,,1000000,,,,\nusd_stir1,usd,stir,,SF1,1m Avergaed SOFR Futures,,,,m,,,,som,FALSE,mf,nyc,0,,usd,,actacticma,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay_avg,usd_rfr,,,,,,,,,,,,,,,,,,,,,,,5000400,,,,\neur_stir,eur,stir,,KTR,3m ESTR Futures,,,,q,,,,imm,FALSE,mf,tgt,0,,eur,,actacticma,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,eur_rfr,,,,,,,,,,,,,,,,,,,,,,,1000000,,,,\neur_stir1,eur,stir,,,1m Averaged ESTR futures,,,,m,,,,som,FALSE,mf,tgt,0,,eur,,actacticma,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay_avg,eur_rfr,,,,,,,,,,,,,,,,,,,,,,,3000000,,,,\neur_stir3,eur,stir,,ER,Euribor 3m Futures,,,,q,,,,imm,FALSE,mf,tgt,0,,eur,,actacticma,,,,,,,,,,,,,,,,,,,,,,,none_simple,,ibor(2),eur_ibor,,,,,,,,,,,,,,,,,,,,,,,1000000,,,,\ngbp_stir,gbp,stir,,SFI,SONIA 3m Futures,,,,q,,,,imm,FALSE,mf,ldn,0,,gbp,,actacticma,,,,,,,,,,,,,,,,,,,,,,,none_simple,,rfr_payment_delay,gbp_rfr,,,,,,,,,,,,,,,,,,,,,,,1000000,,,,\nuk_gb_2y,gbp,bf,,G,Gilt future,,,,,,,,,,,ldn,,,gbp,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ice_gbp,100000,,3,,\nuk_gb_5y,gbp,bf,,G,Gilt future,,,,,,,,,,,ldn,,,gbp,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ice_gbp,100000,,4,,\nuk_gb_10y,gbp,bf,,G,Gilt future,,,,,,,,,,,ldn,,,gbp,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ice_gbp,100000,,4,,\nuk_gb_30y,gbp,bf,,G,Gilt future,,,,,,,,,,,ldn,,,gbp,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ice_gbp,100000,,4,,\nus_gb_2y,usd,bf,,US,US treasury futures,,,,,,,,,,,fed,,,usd,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ust_short,200000,,6,,\nus_gb_3y,usd,bf,,US,US treasury futures,,,,,,,,,,,fed,,,usd,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ust_short,200000,,6,,\nus_gb_5y,usd,bf,,US,US treasury futures,,,,,,,,,,,fed,,,usd,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ust_short,100000,,6,,\nus_gb_10y,usd,bf,,US,US treasury futures,,,,,,,,,,,fed,,,usd,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ust_long,100000,,6,,\nus_gb_30y,usd,bf,,US,US treasury futures,,,,,,,,,,,fed,,,usd,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ust_long,100000,,6,,\nde_gb_2y,eur,bf,,DE,Eurex Germany Futures,,,,,,,,,,,tgt,,,eur,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurex_eur,100000,,6,,\nde_gb_5y,eur,bf,,DE,Eurex Germany Futures,,,,,,,,,,,tgt,,,eur,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurex_eur,100000,,6,,\nde_gb_10y,eur,bf,,DE,Eurex Germany Futures,,,,,,,,,,,tgt,,,eur,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurex_eur,100000,,6,,\nde_gb_30y,eur,bf,,DE,Eurex Germany Futures,,,,,,,,,,,tgt,,,eur,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurex_eur,100000,,4,,\nfr_gb_5y,eur,bf,,FR,Eurex France Futures,,,,,,,,,,,tgt,,,eur,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurex_eur,100000,,6,,\nfr_gb_10y,eur,bf,,FR,Eurex France Futures,,,,,,,,,,,tgt,,,eur,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurex_eur,100000,,6,,\nsp_gb_10y,eur,bf,,SP,Eurex Spain Futures,,,,,,,,,,,tgt,,,eur,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurex_eur,100000,,6,,\nch_gb_10y,chf,bf,,CH,Eurex CHF Futures,,,,,,,,,,,zur,,,chf,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,eurex_chf,100000,,6,,\n"
  },
  {
    "path": "python/rateslib/data/fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport warnings\nfrom abc import ABCMeta, abstractmethod\nfrom datetime import datetime, timedelta\nfrom enum import Enum\nfrom functools import cached_property\nfrom math import prod\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\nimport rateslib.errors as err\nfrom pandas import Series, isna\nfrom rateslib import defaults, fixings\nfrom rateslib.curves.curves import _BaseCurve, _index_value_from_series_no_curve\nfrom rateslib.curves.interpolation import index_left\nfrom rateslib.curves.utils import _CurveType\nfrom rateslib.data.loader import FixingMissingForecasterError, FixingRangeError\nfrom rateslib.dual import Dual, Dual2, Variable\nfrom rateslib.enums.generics import Err, NoInput, Ok, _drb, _validate_obj_not_no_input\nfrom rateslib.enums.parameters import (\n    FloatFixingMethod,\n    SpreadCompoundMethod,\n    SwaptionSettlementMethod,\n    _get_float_fixing_method,\n    _get_index_method,\n    _get_spread_compound_method,\n    _get_swaption_settlement_method,\n)\nfrom rateslib.rs import Adjuster\nfrom rateslib.scheduling.adjuster import _get_adjuster\nfrom rateslib.scheduling.calendars import get_calendar\nfrom rateslib.scheduling.convention import Convention, _get_convention\nfrom rateslib.scheduling.dcfs import dcf\nfrom rateslib.scheduling.frequency import _get_frequency, _get_tenor_from_frequency, add_tenor\nfrom rateslib.scheduling.schedule import Schedule, _get_stub_inference\nfrom rateslib.utils.calendars import _get_first_bus_day\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        IRS,\n        Any,\n        Arr1dF64,\n        Arr1dObj,\n        Cal,\n        CalInput,\n        CalTypes,\n        CurveOption_,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        Frequency,\n        FXForwards,\n        FXForwards_,\n        FXIndex_,\n        IndexMethod,\n        LegFixings,\n        NamedCal,\n        PeriodFixings,\n        Result,\n        StubInference,\n        UnionCal,\n        _BaseCurve_,\n        bool_,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass _BaseFixing(metaclass=ABCMeta):\n    \"\"\"\n    Abstract base class for core financial fixing implementation.\n\n    Parameters\n    ----------\n    date: datetime\n        The date of relevance for the financial fixing, e.g. the publication date for an\n        *IBORFixing* or the reference date for an *IndexFixing*.\n    value: float, Dual, Dual2, Variable, optional\n        The initial value for the fixing to adopt. Most commonly this is not given and it is\n        determined from a timeseries.\n    identifier: str, optional\n        The string name of the timeseries to be loaded by the *Fixings* object.\n    \"\"\"\n\n    _identifier: str_\n    _value: DualTypes_\n    _state: int\n    _date: datetime\n\n    def __init__(\n        self,\n        *,\n        date: datetime,\n        value: DualTypes_ = NoInput(0),\n        identifier: str_ = NoInput(0),\n    ) -> None:\n        self._identifier = identifier if isinstance(identifier, NoInput) else identifier.upper()\n        self._value = value\n        self._state = 0\n        self._date = date\n\n    def reset(self, state: int_ = NoInput(0)) -> None:\n        \"\"\"\n        Sets the ``value`` attribute to :class:`~rateslib.enums.generics.NoInput`, which allows it\n        to be redetermined from a timeseries.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import fixings, dt, NoInput, FXFixing\n           from pandas import Series\n\n        .. ipython:: python\n\n           fx_fixing1 = FXFixing(publication=dt(2021, 1, 1), fx_index=\"eurusd\", identifier=\"A\")\n           fx_fixing2 = FXFixing(publication=dt(2021, 1, 1), fx_index=\"gbpusd\", identifier=\"B\")\n           fixings.add(\"A_eurusd\", Series(index=[dt(2021, 1, 1)], data=[1.1]), state=100)\n           fixings.add(\"B_gbpusd\", Series(index=[dt(2021, 1, 1)], data=[1.4]), state=200)\n\n           # data is populated from the available Series\n           fx_fixing1.value\n           fx_fixing2.value\n\n           # fixings are reset according to the data state\n           fx_fixing1.reset(state=100)\n           fx_fixing2.reset(state=100)\n\n           # only the private data for fixing1 is removed because of its link to the data state\n           fx_fixing1._value\n           fx_fixing2._value\n\n        .. role:: green\n\n        Parameters\n        ----------\n        state: int, :green:`optional`\n            If given only fixings whose state matches this value will be reset. If no state is\n            given then the value will be reset.\n\n        Returns\n        -------\n        None\n        \"\"\"\n        if isinstance(state, NoInput) or self._state == state:\n            self._value = NoInput(0)\n            self._state = 0\n\n    @property\n    def value(self) -> DualTypes_:\n        \"\"\"\n        The fixing value.\n\n        If this value is :class:`rateslib.enums.generics.NoInput`, then each request will attempt a\n        lookup from a timeseries to obtain a new fixing value.\n\n        Once this value is determined it is restated indefinitely, unless :meth:`_BaseFixing.reset`\n        is called.\n        \"\"\"\n        if not isinstance(self._value, NoInput):\n            return self._value\n        else:\n            if isinstance(self._identifier, NoInput):\n                return NoInput(0)\n            else:\n                state, timeseries, bounds = fixings.__getitem__(self._identifier)\n                if state == self._state:\n                    return NoInput(0)\n                else:\n                    self._state = state\n                    v = self._lookup_and_calculate(timeseries, bounds)\n                    self._value = v\n                    return v\n\n    @property\n    def date(self) -> datetime:\n        \"\"\"The date of relevance for the fixing, e.g. the publication date of an IBORFixing.\"\"\"\n        return self._date\n\n    @property\n    def identifier(self) -> str_:\n        \"\"\"The string name of the timeseries to be loaded by the *Fixings* object.\"\"\"\n        return self._identifier\n\n    @abstractmethod\n    def _lookup_and_calculate(\n        self,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        bounds: tuple[datetime, datetime] | None,\n    ) -> DualTypes_:\n        pass\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n\nclass IndexFixing(_BaseFixing):\n    \"\"\"\n    An index fixing value for settlement of indexed cashflows.\n\n    Parameters\n    ----------\n    index_lag: int\n        The number months by which the reference date is lagged to derive an index value.\n    index_method: IndexMethod\n        The method used for calculating the index value. See\n        :class:`~rateslib.enums.parameters.IndexMethod`.\n    date: datetime\n        The date of relevance for the index fixing, which is its **reference value** date.\n    value: float, Dual, Dual2, Variable, optional\n        The initial value for the fixing to adopt. Most commonly this is not given and it is\n        determined from a timeseries of published FX rates.\n    identifier: str, optional\n        The string name of the timeseries to be loaded by the *Fixings* object.\n\n    Examples\n    --------\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.data.fixings import IndexFixing\n       from rateslib.enums.parameters import IndexMethod\n       from rateslib import fixings, dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"UK-CPI\", Series(index=[dt(2000, 1, 1), dt(2000, 2, 1)], data=[100, 110.0]))\n       index_fix = IndexFixing(date=dt(2000, 4, 15), identifier=\"UK-CPI\", index_lag=3, index_method=IndexMethod.Daily)\n       index_fix.value\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"UK-CPI\")\n\n    \"\"\"  # noqa: E501\n\n    _index_lag: int\n    _index_method: IndexMethod\n\n    def __init__(\n        self,\n        *,\n        index_lag: int,\n        index_method: IndexMethod | str,\n        date: datetime,\n        value: DualTypes_ = NoInput(0),\n        identifier: str_ = NoInput(0),\n    ) -> None:\n        super().__init__(date=date, value=value, identifier=identifier)\n\n        self._index_lag = index_lag\n        self._index_method = _get_index_method(index_method)\n\n    @property\n    def index_method(self) -> IndexMethod:\n        \"\"\"The :class:`~rateslib.enums.parameters.IndexMethod` used for calculating the\n        index value.\"\"\"\n        return self._index_method\n\n    @property\n    def index_lag(self) -> int:\n        \"\"\"The number months by which the reference date is lagged to derive an index value.\"\"\"\n        return self._index_lag\n\n    def _lookup_and_calculate(\n        self,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        bounds: tuple[datetime, datetime] | None,\n    ) -> DualTypes_:\n        return self._lookup(\n            index_lag=self.index_lag,\n            index_method=self.index_method,\n            date=self.date,\n            timeseries=timeseries,\n            bounds=bounds,\n        )\n\n    @classmethod\n    def _lookup(\n        cls,\n        index_lag: int,\n        index_method: IndexMethod,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        date: datetime,\n        bounds: tuple[datetime, datetime] | None = None,\n    ) -> DualTypes_:\n        result = _index_value_from_series_no_curve(\n            index_lag=index_lag,\n            index_method=index_method,\n            index_fixings=timeseries,\n            index_date=date,\n            index_fixings_boundary=bounds,\n        )\n        if isinstance(result, Err):\n            if isinstance(result._exception, FixingRangeError):\n                return NoInput(0)\n            result.unwrap()\n        else:\n            return result.unwrap()\n\n\nclass FXIndex:\n    \"\"\"\n    Define the parameters of a specific FX pair and fixing index.\n\n    This object acts as a container to store market conventions for different FX pairs.\n    This allows the determination of dates under different methodologies, e.g. ISDA MTM fixings or\n    spot settlement dates.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.data.fixings import FXIndex\n\n    .. ipython:: python\n\n       fxi = FXIndex(\n           pair=\"eurusd\",\n           calendar=\"tgt|fed\",      # <- Spot FX measures settlement dates according to this calendar\n           settle=2,\n           isda_mtm_calendar=\"ldn\", # <- MTM XCS FX fixing dates are determined according to this calendar\n           isda_mtm_settle=-2,\n       )\n       fxi.delivery(dt(2025, 7, 3))\n       fxi.isda_fixing_date(dt(2025, 7, 3))\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    pair: str, :red:`required`\n        The currency pair of the FX fixing. 6-digit iso code.\n    calendar: Calendar, str, :red:`required`\n        The calendar associated with the FX settlement date determination.\n    settle:  Adjuster, int, str :green:`optional (set by 'defaults')`\n        The delivery lag applied to any FX quotation to adjust 'today' to a delivery date, under\n        the given ``calendar``. If int is assumed to be settleable business days.\n    isda_mtm_calendar: Calendar, str, :green:`optional`\n        The calendar associated with the MTM fixing date determination.\n    isda_mtm_settle: Adjuster, str, int, :green:`optional`,\n        The adjustment applied to determine the MTM fixing date.\n    allow_cross: bool, :green:`optional (set as True)`\n        This allows sub-division of the fixing into its *majors* as defined by WMR\n        benchmark methodology. For an example of using a *cross* see the documentation for\n        an :class:`FXFixing`.\n\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        pair: str,\n        calendar: CalTypes | str,\n        settle: Adjuster | str | int,\n        isda_mtm_calendar: CalInput = NoInput(0),\n        isda_mtm_settle: Adjuster | str | int_ = NoInput(0),\n        allow_cross: bool_ = NoInput(0),\n    ) -> None:\n        self._pair: str = pair.lower()\n        self._calendar: CalTypes = get_calendar(calendar)\n        self._settle: Adjuster = _get_adjuster(settle)\n        self._allow_cross: bool = _drb(True, allow_cross)\n\n        if isinstance(isda_mtm_calendar, NoInput):\n            self._isda_mtm_calendar: CalTypes | NoInput = NoInput(0)\n        else:\n            self._isda_mtm_calendar = get_calendar(isda_mtm_calendar)\n\n        if isinstance(isda_mtm_settle, NoInput):\n            self._isda_mtm_settle: Adjuster | NoInput = NoInput(0)\n        else:\n            self._isda_mtm_settle = _get_adjuster(isda_mtm_settle)\n\n    def __repr__(self) -> str:\n        return f\"<rl.FXIndex:{self.pair} at {id(self)}>\"\n\n    @property\n    def pair(self) -> str:\n        \"\"\"The currency pair of the FX fixing.\"\"\"\n        return self._pair\n\n    @property\n    def calendar(self) -> CalTypes:\n        \"\"\"The calendar associated with the settlement delivery date determination.\"\"\"\n        return self._calendar\n\n    @property\n    def settle(self) -> Adjuster:\n        \"\"\"\n        The :class:`~rateslib.scheduling.Adjuster` associated with determining\n        the settlement delivery date.\n        \"\"\"\n        return self._settle\n\n    @property\n    def isda_mtm_calendar(self) -> CalTypes | NoInput:\n        \"\"\"The calendar associated with the MTM fixing date determination.\"\"\"\n        return self._isda_mtm_calendar\n\n    @property\n    def isda_mtm_settle(self) -> Adjuster | NoInput:\n        \"\"\"\n        The :class:`~rateslib.scheduling.Adjuster` associated with the MTM fixing\n        date determination.\n        \"\"\"\n        return self._isda_mtm_settle\n\n    def isda_fixing_date(self, delivery: datetime) -> datetime:\n        \"\"\"\n        Return the MTM FX fixing date under ISDA conventions.\n\n        Parameters\n        ----------\n        delivery: datetime\n            The delivery date of the notional exchange.\n\n        Returns\n        -------\n        datetime\n\n        Notes\n        -----\n        If ``isda`` attributes are not fully qualified on the object then uses the ``reverse``\n        method to reverse engineer the FX quotation date as a proxy.\n        \"\"\"\n        if isinstance(self.isda_mtm_calendar, NoInput) or isinstance(self.isda_mtm_settle, NoInput):\n            # Fallback method for determining fixing date when ISDA fixing details not available.\n            # This may be due to instruments that only use for non-deliverability as a feature\n            # but do not technically have a published fixing, i.e. a physically settled\n            # FXForward or an FXOption.\n            # In these cases do the best to estimate a respectable date.\n            alternatives: list[datetime] = []\n            counter: int = 0\n            while len(alternatives) == 0:\n                alternatives = self.publications(delivery + timedelta(days=counter))\n                counter += 1\n            return _get_first_bus_day(alternatives, self.calendar)\n        else:\n            return self.isda_mtm_settle.adjust(delivery, self.isda_mtm_calendar)\n\n    def delivery(self, date: datetime) -> datetime:\n        \"\"\"\n        Return the settlement delivery date associated with the publication date.\n\n        Parameters\n        ----------\n        date: datetime\n            The publication date of the quotation.\n\n        Returns\n        -------\n        datetime\n        \"\"\"\n        return self.settle.adjust(date, self.calendar)\n\n    def publications(self, delivery: datetime) -> list[datetime]:\n        \"\"\"\n        Return the potential publication dates that result in a given settlement delivery date.\n\n        Parameters\n        ----------\n        delivery: datetime\n            The settlement delivery date of the publication.\n\n        Returns\n        -------\n        list[datetime]\n        \"\"\"\n        return self.settle.reverse(delivery, self.calendar)\n\n    @property\n    def allow_cross(self) -> bool:\n        \"\"\"Whether to allow FXFixings which sub-divide into USD or EUR crosses.\"\"\"\n        return self._allow_cross\n\n\nclass _FXFixingMajor(_BaseFixing):\n    \"\"\"\n    An FX fixing value for cross currency settlement.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.data.fixings import _FXFixingMajor, FXIndex\n       from rateslib import fixings, dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"Custom_CADSEK\", Series(index=[dt(1999, 12, 29)], data=[8.7]))\n       fxfix = _FXFixingMajor(\n           delivery=dt(2000, 1, 4),\n           fx_index=FXIndex(\n               pair=\"cadsek\",\n               calendar=\"tro,stk|fed\",\n               settle=2,\n               isda_mtm_calendar=\"tro,stk,ldn,nyc\",\n               isda_mtm_settle=-2,\n           ),\n           identifier=\"Custom\"\n       )\n       fxfix.publication  #  <--  derived from isda attributes\n       fxfix.value        #  <--  should be 8.7\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"Custom_CADSEK\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    fx_index: FXIndex, str, :red:`required`\n        The :class:`~rateslib.data.fixings.FXIndex` defining the FX pair and its conventions.\n    publication: datetime, :green:`optional`\n        The publication date of the fixing. If not given, must provide ``delivery`` in order to\n        derive the *publication date*.\n    delivery: datetime, :green:`optional`\n        The settlement delivery date of the cashflow. Can be used to derive the *publication date*.\n        If not given is derived from the ``publication``.\n    value: float, Dual, Dual2, Variable, optional\n        The initial value for the fixing to adopt. Most commonly this is not given and it is\n        determined from a timeseries of published FX rates.\n    identifier: str, optional\n        The string name of the series to be loaded by the *Fixings* object. Will be\n        appended with \"_{pair}\" to derive the full timeseries key.\n\n    Notes\n    ------\n    The *FXFixingMajor* is a class designed to lookup and return FX fixings directly from a\n    Series in either the FX pair directly, or its inverse. This function depends upon what is\n    populated to the datastore. That is, if *'GBPMXN'* is an available dataseries then *'MXNGBP'*\n    would also be calculable as the inverse of *'GBPMXN'*.\n\n    When forecasting the fixing from an :class:`~rateslib.fx.FXForwards` object, the rate pair\n    will be looked up directly according to the ``delivery`` date.\n\n    The use of the name **major**, does not imply that only *FX majors* can be used by this class.\n    I.e. that it is only suitable for *'EURUSD'* and *'EURSEK'*, for example. Rather, the name\n    *major* implies that this object treats the given FX pair as a major and does not perform any\n    type of **cross**. This is, in fact, a sub-component of the more featureful\n    :class:`~rateslib.data.fixings.FXFixing` class which adheres to the ``allow_cross`` argument\n    on the :class:`~rateslib.data.fixings.FXIndex` in order to automatically handle different\n    types of required behaviour.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        fx_index: FXIndex | str,\n        publication: datetime_ = NoInput(0),\n        delivery: datetime_ = NoInput(0),\n        value: DualTypes_ = NoInput(0),\n        identifier: str_ = NoInput(0),\n    ) -> None:\n        self._fx_index = _get_fx_index(fx_index)\n        del fx_index\n\n        if isinstance(delivery, NoInput) and isinstance(publication, NoInput):\n            raise ValueError(\n                \"At least one date; a `delivery` or `publication` is required to derive the \"\n                \"`date` used for the FX fixing.\"\n            )\n        elif isinstance(publication, NoInput) and isinstance(delivery, datetime):\n            # then derive it under conventions\n            date_ = self.fx_index.isda_fixing_date(delivery)\n            self._delivery = delivery\n            self._publication = date_\n        elif isinstance(publication, datetime) and isinstance(delivery, NoInput):\n            date_ = publication\n            self._publication = date_\n            self._delivery = self.fx_index.delivery(date_)\n        elif isinstance(publication, datetime) and isinstance(delivery, datetime):\n            date_ = publication\n            self._publication = date_\n            self._delivery = delivery\n        else:\n            raise TypeError(  # pragma: no cover\n                \"`delivery` and `publication` given as incorrect types.\\n\"\n                f\"Got {type(delivery).__name__} and {type(publication).__name__}.\"\n            )\n\n        super().__init__(date=date_, value=value, identifier=identifier)\n\n    @property\n    def fx_index(self) -> FXIndex:\n        \"\"\"The :class:`FXIndex` for the FX fixing.\"\"\"\n        return self._fx_index\n\n    def _value_from_possible_inversion(self, identifier: str) -> DualTypes_:\n        direct, inverted = self.pair, f\"{self.pair[3:6]}{self.pair[0:3]}\"\n        try:\n            state, timeseries, bounds = fixings.__getitem__(identifier + \"_\" + direct)\n            exponent = 1.0\n        except ValueError as e:\n            try:\n                state, timeseries, bounds = fixings.__getitem__(identifier + \"_\" + inverted)\n                exponent = -1.0\n            except ValueError:\n                raise e\n\n        if state == self._state:\n            return NoInput(0)\n        else:\n            self._state = state\n            v = self._lookup_and_calculate(timeseries, bounds)\n            if isinstance(v, NoInput):\n                return NoInput(0)\n            self._value = v**exponent\n            return self._value\n\n    @property\n    def publication(self) -> datetime:\n        \"\"\"The publication date of the fixing as specified directly, or implied from\n        the :class:`~rateslib.data.fixings.FXIndex`.\"\"\"\n        return self._publication\n\n    @property\n    def delivery(self) -> datetime:\n        \"\"\"The settlement delivery date of the fixing as specified directly, or implied\n        from the :class:`~rateslib.data.fixings.FXIndex`.\"\"\"\n        return self._delivery\n\n    @property\n    def value(self) -> DualTypes_:\n        if not isinstance(self._value, NoInput):\n            return self._value\n        else:\n            if isinstance(self._identifier, NoInput):\n                return NoInput(0)\n            else:\n                return self._value_from_possible_inversion(identifier=self._identifier)\n\n    def _lookup_and_calculate(\n        self, timeseries: Series, bounds: tuple[datetime, datetime] | None\n    ) -> DualTypes_:\n        return self._lookup(timeseries=timeseries, date=self.date, bounds=bounds)\n\n    @classmethod\n    def _lookup(\n        cls,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        date: datetime,\n        bounds: tuple[datetime, datetime] | None = None,\n    ) -> DualTypes_:\n        result = fixings.__base_lookup__(\n            fixing_series=timeseries,\n            lookup_date=date,\n            bounds=bounds,\n        )\n        if isinstance(result, Err):\n            if isinstance(result._exception, FixingRangeError):\n                return NoInput(0)\n            result.unwrap()\n        else:\n            return result.unwrap()\n\n    @property\n    def pair(self) -> str:\n        \"\"\"The currency pair related to the FX fixing.\"\"\"\n        return self.fx_index.pair\n\n    def value_or_forecast(self, fx: FXForwards_) -> DualTypes:\n        \"\"\"\n        Return the determined value of the fixing or forecast it if not available.\n\n        Parameters\n        ----------\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object to forecast the forward FX rate.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        if isinstance(self.value, NoInput):\n            fx_: FXForwards = _validate_obj_not_no_input(fx, \"FXForwards\")\n            return fx_.rate(pair=self.pair, settlement=self.delivery)\n        else:\n            return self.value\n\n    def try_value_or_forecast(self, fx: FXForwards_) -> Result[DualTypes]:\n        \"\"\"\n        Return the determined value of the fixing or forecast it if not available.\n\n        Parameters\n        ----------\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object to forecast the forward FX rate.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        if isinstance(self.value, NoInput):\n            if isinstance(fx, NoInput):\n                return Err(ValueError(\"Must provide `fx` argument to forecast FXFixing.\"))\n            else:\n                return Ok(fx.rate(pair=self.pair, settlement=self.delivery))\n        else:\n            return Ok(self.value)\n\n    def __repr__(self) -> str:\n        return f\"<rl.FXFixingMajor:{self.pair} at {hex(id(self))}>\"\n\n\ndef _clone_isda_mtm(pair: FXIndex | str, isda_index: FXIndex) -> FXIndex:\n    \"\"\"\n    Attempt to lookup the conventions of pair, but maintain the original ISDA index conventions\n    from the given isda_index\n    \"\"\"\n    if isinstance(pair, str):\n        try:\n            fx_index = _get_fx_index(pair)  # lookup the conventions from STATIC directly\n        except ValueError:\n            fx_index = FXIndex(\n                pair=pair,\n                settle=isda_index.settle,\n                calendar=isda_index.calendar,\n            )\n    else:\n        fx_index = pair\n\n    return FXIndex(\n        pair=fx_index.pair,\n        settle=fx_index.settle,\n        calendar=fx_index.calendar,\n        isda_mtm_settle=isda_index.isda_mtm_settle,\n        isda_mtm_calendar=isda_index.isda_mtm_calendar,\n    )\n\n\ndef _fx_index_set_cross(pair: FXIndex, allow_cross: bool) -> FXIndex:\n    return FXIndex(\n        pair=pair.pair,\n        settle=pair.settle,\n        calendar=pair.calendar,\n        isda_mtm_settle=pair.isda_mtm_settle,\n        isda_mtm_calendar=pair.isda_mtm_calendar,\n        allow_cross=allow_cross,\n    )\n\n\nclass _UnitFixing(_BaseFixing):\n    \"\"\"\n    A :class:`~rateslib.data.fixings._BaseFixing` permanently adopting value 1.0.\n    Used as a placeholder.\n    \"\"\"\n\n    def __init__(\n        self, *, date: datetime, value: DualTypes_ = NoInput(0), identifier: str_ = NoInput(0)\n    ) -> None:\n        self._value = 1.0\n        self._state = 0\n        self._date = date\n        self._identifier = identifier\n\n    @property\n    def value(self) -> DualTypes_:\n        \"\"\"Returns 1.0.\"\"\"\n        return 1.0\n\n    def value_or_forecast(self, *args: Any, **kwargs: Any) -> DualTypes:\n        \"\"\"Returns 1.0.\"\"\"\n        return 1.0\n\n    def __repr__(self) -> str:\n        return f\"<rl._UnitFixing at {id(self)}>\"\n\n    def reset(self, *args: Any, **kwargs: Any) -> None:\n        \"\"\"Does nothing.\"\"\"\n        pass\n\n    def _lookup_and_calculate(self, *args: Any, **kwargs: Any) -> DualTypes_:\n        return 1.0\n\n\n_WMR_EUR_BASE = [\"czk\", \"dkk\", \"huf\", \"nok\", \"pln\", \"ron\", \"sek\"]\n_WMR_USD_INVERTED = [\"gbp\", \"eur\", \"aud\", \"nzd\", \"iep\", \"bwp\", \"sbd\", \"top\", \"wst\", \"xeu\"]\n\n\nclass _WMRClassification(Enum):\n    \"\"\"\n    WMR FX Benchmarks classification. Either the currency is USD or EUR or it is a third currency\n    whose base is measured versus USD or EUR\n    \"\"\"\n\n    USD = 0\n    EUR = 1\n    BASE_USD = 2\n    BASE_EUR = 3\n\n    @classmethod\n    def classify(cls, value: str) -> _WMRClassification:\n        if value == \"usd\":\n            return _WMRClassification.USD\n        elif value == \"eur\":\n            return _WMRClassification.EUR\n        elif value in _WMR_EUR_BASE:\n            return _WMRClassification.BASE_EUR\n        else:\n            return _WMRClassification.BASE_USD\n\n\nclass FXFixing(_BaseFixing):\n    \"\"\"\n    An FX fixing value for cross-currency or non-deliverable settlement.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.data.fixings import FXFixing, FXIndex\n       from rateslib import fixings, dt, FXForwards, FXRates, Curve\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"WMR_10AMTYO_USDJPY\", Series(index=[dt(1999, 12, 29)], data=[155.00]))\n       fixings.add(\"WMR_10AMTYO_AUDUSD\", Series(index=[dt(1999, 12, 29)], data=[1.260]))\n       fxfix = FXFixing(\n           delivery=dt(2000, 1, 4),\n           fx_index=FXIndex(\n               pair=\"audjpy\",\n               calendar=\"syd,tyo|fed\",\n               settle=2,\n               isda_mtm_calendar=\"syd,tyo,ldn\",\n               isda_mtm_settle=-2,\n               allow_cross=True,\n           ),\n           identifier=\"WMR_10AMTYO\"\n       )\n       fxfix.publication  #  <--  derived from isda attributes\n       fxfix.value        #  <--  should be from the cross 1.26 * 155 = 195.3\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"WMR_10AMTYO_USDJPY\")\n       fixings.pop(\"WMR_10AMTYO_AUDUSD\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    fx_index: FXIndex, str, :red:`required`\n        The :class:`~rateslib.data.fixings.FXIndex` defining the FX pair and its conventions.\n    publication: datetime, :green:`optional`\n        The publication date of the fixing. If not given, must provide ``delivery`` in order to\n        derive the *publication date*.\n    delivery: datetime, :green:`optional`\n        The settlement delivery date of the cashflow. Can be used to derive the *publication date*.\n        If not given is derived from the ``publication``.\n    value: float, Dual, Dual2, Variable, optional\n        The initial value for the fixing to adopt. Most commonly this is not given and it is\n        determined from a timeseries of published FX rates.\n    identifier: str, optional\n        The string name of the series to be loaded by the *Fixings* object. Will be\n        appended with \"_{pair}\" to derive the full timeseries key.\n\n    Notes\n    -----\n    This object is designed to systematically handle FX fixings across variety of conventions\n    and is typically used for non-deliverable and MTM-XCS settlement.\n\n    If the :class:`~rateslib.data.fixings.FXIndex` is configured to ``allow_cross`` (which is\n    the general default) then it will adopt the `WMR Benchmark Methodology <https://www.lseg.com/content/dam/ftse-russell/en_us/documents/ground-rules/wmr-fx-methodology.pdf>`__\n    and assume cross rates against base USD, except if the currency is one of the European\n    currencies defined as having a base EUR, by that methodology.\n\n    Suppose one transacted a *CADSEK mtm-XCS* with a CAD mtm *Leg*. The ISDA MTM fixing date could\n    be defined as being 2 business days prior to the cashflow under the Stockholm, New York, London\n    and Toronto calendars. Under WMR, CAD has a USD base, and SEK has a EUR base, so the\n    determination of this FX Fixing will be a 3-way cross: CADUSD * USDEUR * EURSEK.\n\n    WMR ignores market settlement convention and local calendars in the determination of the cross.\n    So a cashflow due on 8th Jan '26 will determine a publication date as 5th Jan '26 (since the\n    6th Jan is holiday in Stockholm). All three WMR publication will have different\n    settlement (delivery dates) for the publication date on the 5th Jan:\n\n    .. ipython:: python\n\n       fxfix = FXFixing(\n           fx_index=FXIndex(\n               pair=\"cadsek\",\n               calendar=\"tro,stk|fed\",\n               settle=2,\n               isda_mtm_calendar=\"tro,ldn,stk,nyc\",\n               isda_mtm_settle=-2,\n               allow_cross=True,\n           ),\n           delivery=dt(2026, 1, 8),\n       )\n       fxfix.publication  #  <-- is 5th Jan determnined from the isda specications\n       fxfix.fx_fixing1.delivery  #  <-- USDCAD is T+1 under \"tro|fed\" defined by defaults\n       fxfix.fx_fixing2.delivery  #  <-- EURUSD is T+2 under \"tgt|fed\" defined by defaults\n       fxfix.fx_fixing3.delivery  #  <-- EURSEK is T+2 under \"tgt,stk|fed\" defined by defaults\n\n    This has implications towards the forecasting of these fixing values. In order to properly\n    forecast the above an :class:`~rateslib.fx.FXForwards` with all four currencies is required.\n\n    .. ipython:: python\n       :suppress:\n\n       sek = Curve({dt(2026, 1, 1): 1.0, dt(2027, 1, 1): 0.98})\n       eur = Curve({dt(2026, 1, 1): 1.0, dt(2027, 1, 1): 0.981})\n       cad = Curve({dt(2026, 1, 1): 1.0, dt(2027, 1, 1): 0.97})\n       usd = Curve({dt(2026, 1, 1): 1.0, dt(2027, 1, 1): 0.965})\n\n       fxf = FXForwards(\n           fx_rates=[\n               FXRates({\"usdcad\": 1.38}, settlement=dt(2026, 1, 2)),\n               FXRates({\"eurusd\": 1.165, \"eursek\": 10.75}, settlement=dt(2026, 1, 3))\n           ],\n           fx_curves={\n               \"seksek\": sek, \"sekusd\": sek, \"eureur\": eur, \"eurusd\": eur, \"cadcad\": cad, \"cadusd\": cad, \"usdusd\": usd\n           }\n       )\n\n    .. ipython:: python\n\n       fxfix.value_or_forecast(fx=fxf)  #  <-  FXForwards:usd,cad,eur,sek\n       fxf.rate(\"cadusd\", dt(2026, 1, 6)) * fxf.rate(\"usdeur\", dt(2026, 1, 7)) * fxf.rate(\"eursek\", dt(2026, 1, 8))\n\n    Note that this is different to the **actual** *CADSEK* forecast FX rate and this is due to\n    those milaligned crosses and calendars.\n\n    .. ipython:: python\n\n       fxfix = FXFixing(\n           fx_index=FXIndex(\n               pair=\"cadsek\",\n               calendar=\"tro,stk|fed\",\n               settle=2,\n               isda_mtm_calendar=\"tro,ldn,stk,nyc\",\n               isda_mtm_settle=-2,\n               allow_cross=False,      #  <-  Everything the same except no crossing allowed\n           ),\n           delivery=dt(2026, 1, 8),\n       )\n       fxfix.value_or_forecast(fx=fxf)\n       fxf.rate(\"cadsek\", dt(2026, 1, 8))\n\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        fx_index: FXIndex | str,\n        publication: datetime_ = NoInput(0),\n        delivery: datetime_ = NoInput(0),\n        value: DualTypes_ = NoInput(0),\n        identifier: str_ = NoInput(0),\n    ) -> None:\n        self._fx_index = _get_fx_index(fx_index)\n        del fx_index\n\n        if isinstance(delivery, NoInput) and isinstance(publication, NoInput):\n            raise ValueError(\n                \"At least one date; a `delivery` or `publication` is required to derive the \"\n                \"`date` used for the FX fixing.\"\n            )\n        elif isinstance(publication, NoInput) and isinstance(delivery, datetime):\n            # then derive it under conventions\n            date_ = self.fx_index.isda_fixing_date(delivery)\n            self._delivery = delivery\n            self._publication = date_\n        elif isinstance(publication, datetime) and isinstance(delivery, NoInput):\n            date_ = publication\n            self._publication = date_\n            self._delivery = self.fx_index.delivery(date_)\n        elif isinstance(publication, datetime) and isinstance(delivery, datetime):\n            date_ = publication\n            self._publication = date_\n            self._delivery = delivery\n        else:\n            raise TypeError(  # pragma: no cover\n                \"`delivery` and `publication` given as incorrect types.\\n\"\n                f\"Got {type(delivery).__name__} and {type(publication).__name__}.\"\n            )\n\n        self._identifier = identifier if isinstance(identifier, NoInput) else identifier.upper()\n        self._value = value\n        self._date = date_\n\n        if not self.allow_cross:\n            self._fx_fixing1: _FXFixingMajor = _FXFixingMajor(\n                fx_index=self.fx_index,\n                publication=self.publication,\n                delivery=self.delivery,\n                value=value,\n                identifier=identifier,\n            )\n            self._fx_fixing2: _FXFixingMajor | _UnitFixing = _UnitFixing(\n                date=self.publication, identifier=identifier\n            )\n            self._fx_fixing3: _FXFixingMajor | _UnitFixing = _UnitFixing(\n                date=self.publication, identifier=identifier\n            )\n        else:\n            ccy1, ccy2 = self.fx_index.pair[:3], self.fx_index.pair[3:]\n\n            match (\n                _WMRClassification.classify(self.pair[:3]),\n                _WMRClassification.classify(self.pair[3:]),\n            ):\n                case (_WMRClassification.USD, _WMRClassification.USD):\n                    raise ValueError(\"An FXFixing between 'usd' and 'usd' is not valid.\")\n                case (_WMRClassification.EUR, _WMRClassification.EUR):\n                    raise ValueError(\"An FXFixing between 'eur' and 'eur' is not valid.\")\n                case (\n                    (_WMRClassification.USD, _WMRClassification.EUR)\n                    | (_WMRClassification.EUR, _WMRClassification.USD)\n                    | (_WMRClassification.USD, _WMRClassification.BASE_USD)\n                    | (_WMRClassification.BASE_USD, _WMRClassification.USD)\n                    | (_WMRClassification.EUR, _WMRClassification.BASE_EUR)\n                    | (_WMRClassification.BASE_EUR, _WMRClassification.EUR)\n                ):\n                    # then the pair is a direct major determined by WMR\n                    self._fx_fixing1 = _FXFixingMajor(\n                        fx_index=self.fx_index,\n                        publication=self.publication,\n                        delivery=self.delivery,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing2 = _UnitFixing(date=self.publication, identifier=identifier)\n                    self._fx_fixing3 = _UnitFixing(date=self.publication, identifier=identifier)\n                case (\n                    (_WMRClassification.USD, _WMRClassification.BASE_EUR)\n                    | (_WMRClassification.BASE_EUR, _WMRClassification.USD)\n                    | (_WMRClassification.BASE_EUR, _WMRClassification.BASE_EUR)\n                ):\n                    # then must be a 2 pair cross involving EUR\n                    self._fx_fixing1 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(f\"{ccy1}eur\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing2 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(f\"eur{ccy2}\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing3 = _UnitFixing(date=self.publication, identifier=identifier)\n                case (\n                    (_WMRClassification.BASE_USD, _WMRClassification.EUR)\n                    | (_WMRClassification.EUR, _WMRClassification.BASE_USD)\n                    | (_WMRClassification.BASE_USD, _WMRClassification.BASE_USD)\n                ):\n                    # then must be a 2 pair cross involving USD\n                    self._fx_fixing1 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(f\"{ccy1}usd\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing2 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(f\"usd{ccy2}\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing3 = _UnitFixing(date=self.publication, identifier=identifier)\n                case (_WMRClassification.BASE_USD, _WMRClassification.BASE_EUR):\n                    # then must be a 4 currency cross involving EUR and USD\n                    self._fx_fixing1 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(f\"{ccy1}usd\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing2 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(\"usdeur\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing3 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(f\"eur{ccy2}\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                case (_WMRClassification.BASE_EUR, _WMRClassification.BASE_USD):\n                    # then must be a 4 currency cross involving EUR and USD\n                    self._fx_fixing1 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(f\"{ccy1}eur\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing2 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(\"eurusd\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n                    self._fx_fixing3 = _FXFixingMajor(\n                        fx_index=_clone_isda_mtm(f\"usd{ccy2}\", self.fx_index),\n                        publication=self.publication,\n                        identifier=identifier,\n                    )\n\n    @property\n    def _state(self) -> int:  # type: ignore[override]\n        return hash(self.fx_fixing1._state + self.fx_fixing2._state + self.fx_fixing3._state)\n\n    @property\n    def fx_fixing1(self) -> _FXFixingMajor:\n        \"\"\"\n        The first (or only) :class:`~rateslib.data.fixings._FXFixingMajor` required by the fixing.\n        \"\"\"\n        return self._fx_fixing1\n\n    @property\n    def fx_fixing2(self) -> _FXFixingMajor | _UnitFixing:\n        \"\"\"\n        The second :class:`~rateslib.data.fixings._FXFixingMajor` required by the fixing if crossed.\n        \"\"\"\n        return self._fx_fixing2\n\n    @property\n    def fx_fixing3(self) -> _FXFixingMajor | _UnitFixing:\n        \"\"\"\n        The third :class:`~rateslib.data.fixings._FXFixingMajor` required by the fixing if crossed.\n        \"\"\"\n        return self._fx_fixing3\n\n    @property\n    def allow_cross(self) -> bool:\n        \"\"\"Whether the fixing uses WMR base currencies and majors or directly looks up the given\n        pair.\"\"\"\n        return self.fx_index.allow_cross\n\n    @property\n    def fx_index(self) -> FXIndex:\n        \"\"\"The :class:`FXIndex` for the FX fixing.\"\"\"\n        return self._fx_index\n\n    @property\n    def publication(self) -> datetime:\n        \"\"\"The publication date of the fixing as specified directly, or implied from\n        the :class:`~rateslib.data.fixings.FXIndex`.\"\"\"\n        return self._publication\n\n    @property\n    def delivery(self) -> datetime:\n        \"\"\"The settlement delivery date of the fixing as specified directly, or implied\n        from the :class:`~rateslib.data.fixings.FXIndex`.\"\"\"\n        return self._delivery\n\n    @property\n    def value(self) -> DualTypes_:\n        if not isinstance(self._value, NoInput):\n            return self._value\n        else:\n            if (\n                isinstance(self.fx_fixing1.value, NoInput)\n                or isinstance(self.fx_fixing2.value, NoInput)\n                or isinstance(self.fx_fixing3.value, NoInput)\n            ):\n                return NoInput(0)\n            else:\n                self._value = self.fx_fixing1.value * self.fx_fixing2.value * self.fx_fixing3.value\n                return self._value\n\n    @property\n    def pair(self) -> str:\n        \"\"\"The currency pair related to the FX fixing.\"\"\"\n        return self.fx_index.pair\n\n    def value_or_forecast(self, fx: FXForwards_) -> DualTypes:\n        \"\"\"\n        Return the determined value of the fixing or forecast it if not available.\n\n        Parameters\n        ----------\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object to forecast the forward FX rate.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        if isinstance(self.value, NoInput):\n            fx_: FXForwards = _validate_obj_not_no_input(fx, \"FXForwards\")\n            f1 = self.fx_fixing1.value_or_forecast(fx=fx_)\n            f2 = self.fx_fixing2.value_or_forecast(fx=fx_)\n            f3 = self.fx_fixing3.value_or_forecast(fx=fx_)\n            return f1 * f2 * f3\n        else:\n            return self.value\n\n    def try_value_or_forecast(self, fx: FXForwards_) -> Result[DualTypes]:\n        \"\"\"\n        Return the determined value of the fixing or forecast it if not available.\n\n        Parameters\n        ----------\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object to forecast the forward FX rate.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        if isinstance(self.value, NoInput):\n            if isinstance(fx, NoInput):\n                return Err(ValueError(\"Must provide `fx` argument to forecast FXFixing.\"))\n            else:\n                return Ok(fx.rate(pair=self.pair, settlement=self.delivery))\n        else:\n            return Ok(self.value)\n\n    def _lookup_and_calculate(\n        self, timeseries: Series, bounds: tuple[datetime, datetime] | None\n    ) -> DualTypes_:\n        raise NotImplementedError(\"FXFixing does not support lookup and calculation.\")\n\n    @classmethod\n    def _lookup(\n        cls,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        date: datetime,\n        bounds: tuple[datetime, datetime] | None = None,\n    ) -> DualTypes_:\n        raise NotImplementedError(\"FXFixing does not support lookup.\")\n        result = fixings.__base_lookup__(\n            fixing_series=timeseries,\n            lookup_date=date,\n            bounds=bounds,\n        )\n        if isinstance(result, Err):\n            if isinstance(result._exception, FixingRangeError):\n                return NoInput(0)\n            result.unwrap()\n        else:\n            return result.unwrap()\n\n    def __repr__(self) -> str:\n        _1 = self.fx_fixing1.pair\n        _2 = (\"/\" + self.fx_fixing2.pair) if not isinstance(self.fx_fixing2, _UnitFixing) else \"\"\n        _3 = (\"/\" + self.fx_fixing3.pair) if not isinstance(self.fx_fixing3, _UnitFixing) else \"\"\n        return f\"<rl.FXFixing:{_1}{_2}{_3} at {hex(id(self))}>\"\n\n    def reset(self, state: int_ = NoInput(0)) -> None:\n        if (\n            isinstance(state, NoInput)\n            or self.fx_fixing1._state == state\n            or self.fx_fixing2._state == state\n            or self.fx_fixing3._state == state\n        ):\n            self._value = NoInput(0)\n        self._fx_fixing1.reset(state)\n        self._fx_fixing2.reset(state)\n        self._fx_fixing3.reset(state)\n\n\nclass IRSSeries:\n    \"\"\"\n    Define the parameters of a specific IRS series.\n\n    This object acts as a container to store local conventions for different IRS markets.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.data.fixings import IRSSeries\n\n    .. ipython:: python\n\n       irss = IRSSeries(\n           currency=\"nok\",\n           settle=2,\n           calendar=\"osl\",\n           convention=\"30e360\",\n           leg2_convention=\"act360\",\n           frequency=\"A\",\n           leg2_frequency=\"Q\",\n           leg2_fixing_method=\"Ibor(2)\",\n           eom=False,\n           modifier=\"MF\",\n           payment_lag=0\n       )\n       irss.settle\n\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    currency: str, :red:`required`\n        The currency of the fixing. 3-digit iso code.\n    settle:  Adjuster, int, str :green:`optional (set by 'defaults')`\n        The effective date lag from the fixing date to arrive at the swap effective date,\n        under the given ``calendar``. If int is assumed to be settleable business days.\n    calendar: Calendar, str, :red:`required`\n        The calendar passed to the :class:`~rateslib.instruments.IRS`\n    convention: str, :green:`optional (set by 'defaults')`\n        The convention passed to the :class:`~rateslib.instruments.IRS`\n    leg2_convention: str, :green:`optional (set by 'defaults')`\n        The leg2_convention passed to the :class:`~rateslib.instruments.IRS`\n    frequency: str, :green:`optional (set by 'defaults')`\n        The frequency passed to the :class:`~rateslib.instruments.IRS`\n    leg2_frequency: str, :green:`optional (set by 'defaults')`\n        The leg2_frequency passed to the :class:`~rateslib.instruments.IRS`\n    leg2_fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    eom : bool, :green:`optional`\n        The eom passed to the :class:`~rateslib.instruments.IRS`\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional (set by Default)`\n        The eom passed to the :class:`~rateslib.instruments.IRS`\n    payment_lag: Adjuster, int, :green:`optional`\n        The payment_lag passed to the :class:`~rateslib.instruments.IRS`\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        currency: str,\n        settle: int | Adjuster | str,\n        frequency: Frequency | str,\n        convention: str,\n        calendar: Cal | UnionCal | NamedCal | str,\n        leg2_fixing_method: str | FloatFixingMethod,\n        *,\n        eom: bool_ = NoInput(0),\n        modifier: Adjuster | str_ = NoInput(0),\n        payment_lag: Adjuster | str | int_ = NoInput(0),\n        leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_convention: str_ = NoInput(1),\n    ) -> None:\n        self._currency = currency.lower()\n        self._settle = _get_adjuster(settle)\n        self._calendar = get_calendar(calendar)\n        self._frequency = _get_frequency(frequency, roll=NoInput(0), calendar=self.calendar)\n        self._leg2_frequency = _get_frequency(\n            _drb(self.frequency, leg2_frequency), roll=NoInput(0), calendar=self.calendar\n        )\n        self._convention = _get_convention(convention)\n        self._leg2_convention = _get_convention(_drb(self.convention, leg2_convention))\n        self._eom: bool = _drb(defaults.eom, eom)\n        self._modifier = _get_adjuster(_drb(defaults.modifier, modifier))\n        self._payment_lag = payment_lag\n        self._leg2_fixing_method = _get_float_fixing_method(leg2_fixing_method)\n\n    @property\n    def currency(self) -> str:\n        \"\"\"The currency of the associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._currency\n\n    @property\n    def settle(self) -> Adjuster:\n        \"\"\"The :class:`~rateslib.scheduling.Adjuster` for effective date determination of the\n        associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._settle\n\n    @property\n    def calendar(self) -> Cal | NamedCal | UnionCal:\n        \"\"\"The calendar of the associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._calendar\n\n    @property\n    def frequency(self) -> Frequency:\n        \"\"\"The :class:`~rateslib.scheduling.Frequency` of leg1 of\n        the associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._frequency\n\n    @property\n    def leg2_frequency(self) -> Frequency:\n        \"\"\"The :class:`~rateslib.scheduling.Frequency` of leg2 of\n        the associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._leg2_frequency\n\n    @property\n    def convention(self) -> Convention:\n        \"\"\"The :class:`~rateslib.scheduling.Convention` of leg1 of\n        the associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._convention\n\n    @property\n    def leg2_convention(self) -> Convention:\n        \"\"\"The :class:`~rateslib.scheduling.Convention` of leg2 of\n        the associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._leg2_convention\n\n    @property\n    def modifier(self) -> Adjuster:\n        \"\"\"The :class:`~rateslib.scheduling.Adjuster` for accrual modification\n        of the associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._modifier\n\n    @property\n    def payment_lag(self) -> Adjuster | int | str_:\n        \"\"\"The :class:`~rateslib.scheduling.Adjuster` for payment date modification\n        of the associated :class:`~rateslib.instruments.IRS`\"\"\"\n        return self._payment_lag\n\n    @property\n    def eom(self) -> bool:\n        \"\"\"Whether the roll-day tends to EoM or not.\"\"\"\n        return self._eom\n\n    @property\n    def leg2_fixing_method(self) -> FloatFixingMethod:\n        \"\"\"The :class:`~rateslib.enums.FloatFixingMethod` of the\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        return self._leg2_fixing_method\n\n    def __repr__(self) -> str:\n        return f\"<rl.IRSSeries at {id(self)}>\"\n\n\ndef _get_irs_series(val: IRSSeries | str) -> IRSSeries:\n    if isinstance(val, IRSSeries):\n        return val\n    else:\n        return IRSSeries(**defaults.irs_series[val.lower()])\n\n\nclass IRSFixing(_BaseFixing):\n    \"\"\"\n    An IRS fixing value for the determination of IR Swaptions.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.data.fixings import IRSFixing\n       from rateslib import fixings, dt, Curve\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"ISDA_USD_2Y\", Series(index=[dt(2000, 1, 4)], data=[2.543]))\n       irs_fix = IRSFixing(\n           publication=dt(2000, 1, 4),\n           irs_series=\"usd_irs\",\n           tenor=\"2Y\",\n           identifier=\"ISDA_USD_2Y\",\n       )\n       irs_fix.publication\n       irs_fix.value        #  <--  determined from Series\n\n    .. ipython:: python\n\n       curve = Curve({dt(2000, 1, 4): 1.0, dt(2003, 1, 4): 0.91}, convention=\"Act360\")\n       irs_fix = IRSFixing(\n           publication=dt(2000, 1, 11),\n           irs_series=\"usd_irs\",\n           tenor=\"2Y\",\n           identifier=\"ISDA_USD_2Y\",\n       )\n       irs_fix.publication\n       irs_fix.value_or_forecast(curves=[curve, curve])  #  <-- no Series index available - use Curve\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"ISDA_USD_2Y\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    irs_series: IRSSeries, str, :red:`required`\n        The :class:`~rateslib.data.fixings.IRSSeries` defining the IRS conventions.\n    publication: datetime, :red:`required`\n        The publication date of the fixing.\n    tenor: str, :red:`required`\n        The standard tenor of the underlying :class:`~rateslib.instruments.IRS` of the fixing.\n    value: float, Dual, Dual2, Variable, :green:`optional`\n        The initial value for the fixing to adopt. Most commonly this is not given and it is\n        determined from a timeseries of published rates.\n    identifier: str, :green:`optional`\n        The string name of the series to be loaded by the *Fixings* object.\n\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        irs_series: IRSSeries | str,\n        publication: datetime,\n        tenor: str | datetime,\n        value: DualTypes_ = NoInput(0),\n        identifier: str_ = NoInput(0),\n    ) -> None:\n        self._publication = publication\n        self._tenor = tenor\n        self._irs_series = _get_irs_series(irs_series)\n        super().__init__(identifier=identifier, value=value, date=self.publication)\n\n    @property\n    def tenor(self) -> datetime | str:\n        \"\"\"The tenor of the IRSFixing\"\"\"\n        return self._tenor\n\n    @property\n    def irs_series(self) -> IRSSeries:\n        \"\"\"The :class:`~rateslib.enums.IRSSeries` for the fixing.\"\"\"\n        return self._irs_series\n\n    @cached_property\n    def irs(self) -> IRS:\n        \"\"\"The :class:`~rateslib.instruments.IRS` underlying for the swaptions priced\n        by this *Smile*.\"\"\"\n        from rateslib.instruments.irs import IRS\n\n        return IRS(\n            effective=self.effective,\n            termination=self.tenor,\n            frequency=self.irs_series.frequency,\n            leg2_frequency=self.irs_series.leg2_frequency,\n            convention=self.irs_series.convention,\n            leg2_convention=self.irs_series.leg2_convention,\n            calendar=self.irs_series.calendar,\n            payment_lag=self.irs_series.payment_lag,\n            modifier=self.irs_series.modifier,\n            eom=self.irs_series.eom,\n            notional=1e6,  # default notional to a sized paid IRS\n        )\n\n    def annuity(\n        self,\n        settlement_method: SwaptionSettlementMethod | str,\n        index_curve: _BaseCurve,\n        rate_curve: CurveOption_,\n    ) -> DualTypes:\n        r\"\"\"\n        Return the annuity value used in the determination of the cashflow settlement, scaled to\n        match 1mm notional per bp.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        settlement_method: SwaptionSettlementMethod, str, :red:`required`\n            The :class:`~rateslib.enums.SwaptionSettlementMethod` defining the settlement method.\n        index_curve: _BaseCurve, :green:`optional`\n            The price alignment index (PAI) curve, colloquially known as the discount factor\n            curve for the *IRS* that determines the PV. Required for certain methods.\n        rate_curve: _BaseCurve or dict of such, :green:`optional`\n            The curve used to forecast the floating leg of the\n            underlying :class:`~rateslib.instruments.IRS`.\n\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Notes\n        -----\n        This method branches based on the SwaptionSettlementMethod:\n\n        - **Physical**: only the ``index_curve`` need be provided. In the case of physical\n          settlement this curve is the discount factor curve used to discount the resultant\n          :class:`~rateslib.instruments.IRS`, which is likely to be cleared and hence should\n          typically be a single currency RFR curve, e.g. SOFR or ESTR.\n        - **CashParTenor**: the annuity factor is derived from the *IRSFixing* value itself, using\n          the formula:\n\n          .. math::\n\n             A_R = \\sum_{i=1}^N \\frac{1/f}{(1 + R / f)^{i}}\n\n        - **CashCollateralized**: very similar to the *Physical* settlement, only the\n          ``index_curve`` needs to be provided to derive the annuity. In practice, this *Curve*\n          should be constructed according the ISDA cash collateralized method using the published\n          rates at each tenor for the collateralization, e.g. SOFR swaps or ESTR swaps.\n\n          .. math::\n\n             A_R = \\sum_{i=1}^N d_i v_i\n\n        \"\"\"\n        settlement_method_ = _get_swaption_settlement_method(settlement_method)\n        del settlement_method\n        if settlement_method_ == SwaptionSettlementMethod.Physical:\n            a_r: DualTypes = self.irs.leg1.analytic_delta(  # type: ignore[assignment]\n                disc_curve=index_curve, forward=self.effective, local=False\n            )\n        elif settlement_method_ == SwaptionSettlementMethod.CashParTenor:\n            R = self.value_or_forecast(\n                curves=dict(  # type: ignore[arg-type]\n                    rate_curve=NoInput(0),\n                    disc_curve=index_curve,\n                    leg2_rate_curve=rate_curve,\n                    leg2_disc_curve=index_curve,\n                )\n            )\n            a_r, f = 0.0, self.irs.leg1.schedule.frequency_obj.periods_per_annum()\n            for i, _period in enumerate(self.irs.leg1._regular_periods):\n                a_r += (1 / f) * (1 + R / (f * 100.0)) ** (-i - 1) * 100.0\n        else:  #  settlement_method == SwaptionSettlementMethod.CashCollaterized:\n            a_r = self.irs.leg1.analytic_delta(  # type: ignore[assignment]\n                disc_curve=index_curve,\n                forward=self.effective,\n                local=False,\n            )\n        return a_r\n\n    @property\n    def publication(self) -> datetime:\n        \"\"\"The publication date of the fixing.\"\"\"\n        return self._publication\n\n    @cached_property\n    def effective(self) -> datetime:\n        \"\"\"The effective date of the underlying :class:`~rateslib.instruments.IRS`.\"\"\"\n        return self.irs_series.calendar.adjust(self.publication, self.irs_series.settle)\n\n    @cached_property\n    def termination(self) -> datetime:\n        \"\"\"The termination date of the underlying :class:`~rateslib.instruments.IRS`.\"\"\"\n        if isinstance(self.tenor, datetime):\n            return self.tenor\n        else:\n            schedule = Schedule(\n                effective=self.effective,\n                termination=self.tenor,\n                frequency=self.irs_series.frequency,\n                calendar=self.irs_series.calendar,\n                modifier=self.irs_series.modifier,\n                eom=self.irs_series.eom,\n            )\n            return schedule.aschedule[-1]\n\n    def value_or_forecast(self, curves: CurvesT_) -> DualTypes:\n        \"\"\"\n        Return the determined value of the fixing or forecast it if not available.\n\n        Parameters\n        ----------\n        curves: optional\n            Curves in the pricing format required by :class:`~rateslib.instruments.IRS`.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        if isinstance(self.value, NoInput):\n            rate = self.irs.rate(curves=curves)\n            return rate\n        else:\n            return self.value\n\n    def try_value_or_forecast(self, curves: CurvesT_) -> Result[DualTypes]:\n        \"\"\"\n        Return the determined value of the fixing or forecast it if not available.\n\n        Parameters\n        ----------\n        curves: _Curves,\n            Pricing objects. See **Pricing** on :class:`~rateslib.instruments.IRS`\n            for details of allowed inputs.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        if isinstance(self.value, NoInput):\n            try:\n                return Ok(self.irs.rate(curves=curves))\n            except Exception as e:\n                return Err(e)\n        else:\n            return Ok(self.value)\n\n    def _lookup_and_calculate(\n        self,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        bounds: tuple[datetime, datetime] | None,\n    ) -> DualTypes_:\n        return self._lookup(timeseries=timeseries, bounds=bounds, date=self.date)\n\n    @classmethod\n    def _lookup(\n        cls,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        date: datetime,\n        bounds: tuple[datetime, datetime] | None = None,\n    ) -> DualTypes_:\n        result = fixings.__base_lookup__(\n            fixing_series=timeseries,\n            lookup_date=date,\n            bounds=bounds,\n        )\n        if isinstance(result, Err):\n            if isinstance(result._exception, FixingRangeError):\n                return NoInput(0)\n            result.unwrap()\n        else:\n            return result.unwrap()\n\n    def __repr__(self) -> str:\n        return f\"<rl.IRSFixing at {hex(id(self))}>\"\n\n\ndef _maybe_get_fx_index(val: FXIndex | str_) -> FXIndex_:\n    if isinstance(val, NoInput):\n        return NoInput(0)\n    else:\n        return _get_fx_index(val)\n\n\ndef _get_fx_index(val: FXIndex | str) -> FXIndex:\n    if isinstance(val, FXIndex):\n        return val\n    else:\n        pair = val.lower()\n        try:\n            return FXIndex(**defaults.fx_index[pair])\n        except KeyError:\n            try:\n                reverse_fxi = FXIndex(**defaults.fx_index[f\"{pair[3:]}{pair[:3]}\"])\n                return FXIndex(\n                    pair=pair,\n                    calendar=reverse_fxi.calendar,\n                    settle=reverse_fxi.settle,\n                    isda_mtm_calendar=reverse_fxi.isda_mtm_calendar,\n                    isda_mtm_settle=reverse_fxi.isda_mtm_settle,\n                )\n            except KeyError:\n                raise ValueError(\n                    f\"The FXIndex: '{pair}' was not found in `defaults`.\\n\"\n                    \"To add a default specification for the required FXIndex, for example, use:\\n\"\n                    f\"> defaults.fx_index['{pair}'] = {{ \\n\"\n                    \"      'pair': 'usdsek',\\n\"\n                    \"      'calendar': 'stk|fed',\\n\"\n                    \"      'settle': '2B',\\n\"\n                    \"      'isda_mtm_settle': '-2B',\\n\"\n                    \"      'isda_mtm_calendar': 'stk',\\n\"\n                    \"      'allow_cross': True,\\n\"\n                    f\"  }}\\n\"\n                    \"Alternatively, create an FXIndex directly and supply it to `pair`, \"\n                    \"for example:\\n> pair=FXIndex('usdsek', 'stk|fed\\\\, 2)\"\n                )\n\n\nclass IBORFixing(_BaseFixing):\n    \"\"\"\n    A rate fixing value referencing a tenor-IBOR type calculation.\n\n    Parameters\n    ----------\n    rate_index: FloatRateIndex\n        The parameters associated with the floating rate index.\n    accrual_start: datetime\n        The start accrual date for the period of the floating rate.\n    date: datetime\n        The date of relevance for the fixing, which is its **publication** date. This can\n        be determined by a ``lag`` parameter of the ``rate_index`` measured from the\n        ``accrual_start``.\n    value: float, Dual, Dual2, Variable, optional\n        The initial value for the fixing to adopt. Most commonly this is not given and it is\n        determined from a timeseries of published FX rates.\n    identifier: str, optional\n        The string name of the timeseries to be loaded by the *Fixings* object.\n\n    Examples\n    --------\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.data.fixings import IBORFixing\n       from rateslib.data.fixings import FloatRateIndex\n       from rateslib import fixings, dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"EURIBOR_3M\", Series(index=[dt(2000, 1, 3), dt(2000, 2, 4)], data=[1.651, 1.665]))\n       ibor_fix = IBORFixing(\n           accrual_start=dt(2000, 1, 5),\n           identifier=\"Euribor_3m\",\n           rate_index=FloatRateIndex(frequency=\"Q\", series=\"eur_ibor\")\n       )\n       ibor_fix.date\n       ibor_fix.value\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"Euribor_3m\")\n\n    \"\"\"  # noqa: E501\n\n    _accrual_start: datetime\n    _accrual_end: datetime\n    _rate_index: FloatRateIndex\n\n    def __init__(\n        self,\n        *,\n        rate_index: FloatRateIndex,\n        accrual_start: datetime,\n        date: datetime_ = NoInput(0),\n        value: DualTypes_ = NoInput(0),\n        identifier: str_ = NoInput(0),\n    ) -> None:\n        super().__init__(date=date, value=value, identifier=identifier)  # type: ignore[arg-type]\n        self._accrual_start = accrual_start\n        self._rate_index = rate_index\n        self._date = _drb(\n            self.index.calendar.lag_bus_days(self.accrual_start, -self.index.lag, False),\n            date,\n        )\n        self._accrual_end = add_tenor(\n            start=self.accrual_start,\n            tenor=self.index.frequency,\n            modifier=self.index.modifier,\n            calendar=self.index.calendar,\n        )\n\n    @property\n    def index(self) -> FloatRateIndex:\n        \"\"\"The definitions for the :class:`FloatRateIndex` of the fixing.\"\"\"\n        return self._rate_index\n\n    @property\n    def series(self) -> FloatRateSeries:\n        \"\"\"The :class:`FloatRateSeries` for defining the fixing.\"\"\"\n        return self.index.series\n\n    @property\n    def accrual_start(self) -> datetime:\n        \"\"\"The start accrual date for the defined period of the floating rate.\"\"\"\n        return self._accrual_start\n\n    @property\n    def accrual_end(self) -> datetime:\n        \"\"\"The end accrual date for the defined period of the floating rate.\"\"\"\n        return self._accrual_end\n\n    def _lookup_and_calculate(\n        self,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        bounds: tuple[datetime, datetime] | None,\n    ) -> DualTypes_:\n        return self._lookup(timeseries=timeseries, bounds=bounds, date=self.date)\n\n    @classmethod\n    def _lookup(\n        cls,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        date: datetime,\n        bounds: tuple[datetime, datetime] | None = None,\n    ) -> DualTypes_:\n        result = fixings.__base_lookup__(\n            fixing_series=timeseries,\n            lookup_date=date,\n            bounds=bounds,\n        )\n        if isinstance(result, Err):\n            if isinstance(result._exception, FixingRangeError):\n                return NoInput(0)\n            result.unwrap()\n        else:\n            return result.unwrap()\n\n\nclass IBORStubFixing(_BaseFixing):\n    \"\"\"\n    A rate fixing value referencing an interpolated tenor-IBOR type calculation.\n\n    Parameters\n    ----------\n    rate_series: FloatRateSeries\n        The parameters associated with the floating rate index.\n    accrual_start: datetime\n        The start accrual date for the period.\n    accrual_end: datetime\n        The end accrual date for the period..\n    date: datetime, optional\n        The date of relevance for the fixing, which is its **publication** date. This can\n        be determined by a ``lag`` parameter of the ``rate_series`` measured from the\n        ``accrual_start``.\n    value: float, Dual, Dual2, Variable, optional\n        The initial value for the fixing to adopt. Most commonly this is not given and it is\n        determined from a timeseries of published FX rates.\n    identifier: str, optional\n        The string name of the timeseries to be loaded by the *Fixings* object. This is a\n        *series* identifier, e.g. \"Euribor\", which will be extended to derive the full\n        version, e.g. \"Euribor_3m\" based on available and necessary tenors.\n\n    Notes\n    -----\n    An interpolated tenor-IBOR type calculation depends upon two tenors being determinable from\n    which a rate can be linearly interpolated.\n\n    The ``rate_series`` has a ``tenors`` attribute which will be used in a first instance. If this\n    is empty, i.e. unspecified, then the default tenors of ['1W', '1M', '3M', '6M', '12M']\n    are used in place.\n\n    Examples\n    --------\n\n    This fixing automatically identifies it must be interpolated between the available 3M and 6M\n    tenors.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.data.fixings import IBORStubFixing\n       from rateslib.data.fixings import FloatRateSeries\n       from rateslib import fixings, dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"EURIBOR_1M\", Series(index=[dt(2000, 1, 3), dt(2000, 2, 4)], data=[1.651, 1.665]))\n       fixings.add(\"EURIBOR_2M\", Series(index=[dt(2000, 1, 3), dt(2000, 2, 4)], data=[2.651, 2.665]))\n       fixings.add(\"EURIBOR_3M\", Series(index=[dt(2000, 1, 3), dt(2000, 2, 4)], data=[3.651, 3.665]))\n       fixings.add(\"EURIBOR_6M\", Series(index=[dt(2000, 1, 3), dt(2000, 2, 4)], data=[4.651, 4.665]))\n       ibor_fix = IBORStubFixing(\n           accrual_start=dt(2000, 1, 5),\n           accrual_end=dt(2000, 5, 17),\n           identifier=\"Euribor\",\n           rate_series=FloatRateSeries(\n               lag=2,\n               modifier=\"MF\",\n               calendar=\"tgt\",\n               convention=\"act360\",\n               eom=False,\n               tenors=[\"1M\", \"2M\", \"3M\", \"6M\", \"12M\"],\n           )\n       )\n       ibor_fix.date\n       ibor_fix.value\n\n\n    This fixing can only be determined from a single tenor, which is quite distinct from the\n    12 day period length in this case. In practice this should be avoided.\n\n    .. ipython:: python\n\n       fixings.add(\"NIBOR_6M\", Series(index=[dt(2000, 1, 3), dt(2000, 2, 4)], data=[4.651, 4.665]))\n       ibor_fix = IBORStubFixing(\n           accrual_start=dt(2000, 1, 5),\n           accrual_end=dt(2000, 1, 17),\n           identifier=\"Nibor\",\n           rate_series=FloatRateSeries(\n               lag=2,\n               modifier=\"MF\",\n               calendar=\"osl\",\n               convention=\"act360\",\n               eom=True,\n               tenors=[\"6M\"],\n           )\n       )\n       ibor_fix.date\n       ibor_fix.value\n       ibor_fix.fixing2\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"Euribor_1m\")\n       fixings.pop(\"Euribor_2m\")\n       fixings.pop(\"Euribor_3m\")\n       fixings.pop(\"Euribor_6m\")\n       fixings.pop(\"NIBOR_6M\")\n\n    \"\"\"  # noqa: E501\n\n    _accrual_start: datetime\n    _accrual_end: datetime\n    _series: FloatRateSeries\n    _fixing1: IBORFixing | NoInput\n    _fixing2: IBORFixing | NoInput\n\n    def __init__(\n        self,\n        *,\n        rate_series: FloatRateSeries | str,\n        accrual_start: datetime,\n        accrual_end: datetime,\n        value: DualTypes_ = NoInput(0),\n        identifier: str_ = NoInput(0),\n        date: datetime_ = NoInput(0),\n    ) -> None:\n        super().__init__(value=value, date=date, identifier=identifier)  # type: ignore[arg-type]\n        self._accrual_start = accrual_start\n        self._accrual_end = accrual_end\n        self._series = _get_float_rate_series(rate_series)\n        self._date = _drb(\n            self.series.calendar.lag_bus_days(self.accrual_start, -self.series.lag, False),\n            date,\n        )\n\n        tenors = self._stub_tenors_from_list(\n            tenors=_drb([\"1W\", \"1M\", \"3M\", \"6M\", \"12M\"], self.series.tenors)\n        )\n        self._fixing1 = IBORFixing(\n            rate_index=FloatRateIndex(\n                series=self.series,\n                frequency=_get_frequency(tenors[0][0], NoInput(0), NoInput(0)),\n            ),\n            accrual_start=self.accrual_start,\n            date=date,\n            value=value,\n            identifier=NoInput(0)\n            if isinstance(identifier, NoInput)\n            else identifier + \"_\" + tenors[0][0],\n        )\n        if len(tenors[0]) == 2:\n            self._fixing2 = IBORFixing(\n                rate_index=FloatRateIndex(\n                    series=self._series,\n                    frequency=_get_frequency(tenors[0][1], NoInput(0), NoInput(0)),\n                ),\n                date=date,\n                accrual_start=self.accrual_start,\n                value=value,\n                identifier=NoInput(0)\n                if isinstance(identifier, NoInput)\n                else identifier + \"_\" + tenors[0][1],\n            )\n        else:\n            self._fixing2 = NoInput(0)\n\n        self._value = value\n\n    @property\n    def date(self) -> datetime:\n        \"\"\"The date of relevance for the fixing, which is its **publication** date.\"\"\"\n        return self._date\n\n    @property\n    def fixing1(self) -> IBORFixing | NoInput:\n        \"\"\"The shorter tenor :class:`IBORFixing` making up part of the calculation.\"\"\"\n        return self._fixing1\n\n    @property\n    def fixing2(self) -> IBORFixing | NoInput:\n        \"\"\"The longer tenor :class:`IBORFixing` making up part of the calculation.\"\"\"\n        return self._fixing2\n\n    @property\n    def value(self) -> DualTypes_:\n        if not isinstance(self._value, NoInput):\n            return self._value\n        elif isinstance(self.fixing1, NoInput) or isinstance(self.fixing1.value, NoInput):\n            return NoInput(0)\n        else:\n            if isinstance(self.fixing2, NoInput):\n                self._value = self.fixing1.value\n                return self._value\n            elif isinstance(self.fixing2.value, NoInput):\n                return NoInput(0)\n            else:\n                self._value = (\n                    self.weights[0] * self.fixing1.value + self.weights[1] * self.fixing2.value\n                )\n                return self._value\n\n    def reset(self, state: int_ = NoInput(0)) -> None:\n        if not isinstance(self._fixing1, NoInput):\n            self._fixing1.reset(state=state)\n        if not isinstance(self._fixing2, NoInput):\n            self._fixing2.reset(state=state)\n        self._value = NoInput(0)\n\n    @cached_property\n    def weights(self) -> tuple[float, float]:\n        \"\"\"Scalar multiplier to apply to each tenor fixing for the interpolation.\"\"\"\n        if isinstance(self.fixing2, NoInput):\n            if isinstance(self.fixing1, NoInput):\n                raise ValueError(\n                    \"The IBORStubFixing has no individual IBORFixings to determine weights.\"\n                )\n            return 1.0, 0.0\n        else:\n            e1 = self.fixing1.accrual_end  # type: ignore[union-attr]\n            e2 = self.fixing2.accrual_end\n            e = self.accrual_end\n            return (e2 - e) / (e2 - e1), (e - e1) / (e2 - e1)\n\n    @property\n    def series(self) -> FloatRateSeries:\n        \"\"\"The :class:`FloatRateSeries` for defining the fixing.\"\"\"\n        return self._series\n\n    @property\n    def accrual_start(self) -> datetime:\n        \"\"\"The start accrual date for the defined accrual period.\"\"\"\n        return self._accrual_start\n\n    @property\n    def accrual_end(self) -> datetime:\n        \"\"\"The end accrual date for the defined accrual period.\"\"\"\n        return self._accrual_end\n\n    def _lookup_and_calculate(\n        self,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        bounds: tuple[datetime, datetime] | None,\n    ) -> DualTypes_:\n        raise RuntimeError(\"This method should be unused due to overloaded properties\")\n\n    def _stub_tenors_from_list(self, tenors: list[str]) -> tuple[list[str], list[datetime]]:\n        left: tuple[str | None, datetime] = (None, datetime(1, 1, 1))\n        right: tuple[str | None, datetime] = (None, datetime(9999, 1, 1))\n\n        for tenor in tenors:\n            sample_end = add_tenor(\n                start=self.accrual_start,\n                tenor=tenor,\n                modifier=self.series.modifier,\n                calendar=self.series.calendar,\n            )\n            if sample_end <= self.accrual_end and sample_end > left[1]:\n                left = (tenor, sample_end)\n            if sample_end > self.accrual_end and sample_end < right[1]:\n                right = (tenor, sample_end)\n                break\n\n        ret: tuple[list[str], list[datetime]] = ([], [])\n        if left[0] is not None:\n            ret[0].append(left[0])\n            ret[1].append(left[1])\n        if right[0] is not None:\n            ret[0].append(right[0])\n            ret[1].append(right[1])\n        return ret\n\n    # def _stub_tenors_from_fixings(self) -> tuple[list[str], list[datetime]]:\n    #     \"\"\"\n    #     Return the tenors available in the :class:`~rateslib.defaults.Fixings` object for\n    #     determining an IBOR type stub period.\n    #\n    #     Returns\n    #     -------\n    #     tuple of list[string tenors] and list[evaluated end dates]\n    #     \"\"\"\n    #     from rateslib.scheduling import add_tenor\n    #\n    #     left: tuple[str | None, datetime] = (None, datetime(1, 1, 1))\n    #     right: tuple[str | None, datetime] = (None, datetime(9999, 1, 1))\n    #\n    #     for tenor in [\n    #         \"1D\",\n    #         \"1B\",\n    #         \"2B\",\n    #         \"1W\",\n    #         \"2W\",\n    #         \"3W\",\n    #         \"4W\",\n    #         \"1M\",\n    #         \"2M\",\n    #         \"3M\",\n    #         \"4M\",\n    #         \"5M\",\n    #         \"6M\",\n    #         \"7M\",\n    #         \"8M\",\n    #         \"9M\",\n    #         \"10M\",\n    #         \"11M\",\n    #         \"12M\",\n    #         \"1Y\",\n    #     ]:\n    #         try:\n    #             _ = fixings.__getitem__(f\"{self.identifier}_{tenor}\")\n    #         except Exception:  # noqa: S112\n    #             continue\n    #         else:\n    #             sample_end = add_tenor(\n    #                 start=self.accrual_start,\n    #                 tenor=tenor,\n    #                 modifier=self.series.modifier,\n    #                 calendar=self.series.calendar,\n    #             )\n    #             if sample_end <= self.accrual_end and sample_end > left[1]:\n    #                 left = (tenor, sample_end)\n    #             if sample_end > self.accrual_end and sample_end < right[1]:\n    #                 right = (tenor, sample_end)\n    #                 break\n    #\n    #     ret: tuple[list[str], list[datetime]] = ([], [])\n    #     if left[0] is not None:\n    #         ret[0].append(left[0])\n    #         ret[1].append(left[1])\n    #     if right[0] is not None:\n    #         ret[0].append(right[0])\n    #         ret[1].append(right[1])\n    #     return ret\n\n\nclass RFRFixing(_BaseFixing):\n    \"\"\"\n    A rate fixing value representing an RFR type calculating involving multiple RFR publications.\n\n    Parameters\n    ----------\n    rate_index: FloatRateIndex\n        The parameters associated with the floating rate index.\n    accrual_start: datetime\n        The start accrual date for the period.\n    accrual_end: datetime\n        The end accrual date for the period.\n    value: float, Dual, Dual2, Variable, optional\n        The initial value for the fixing to adopt. Most commonly this is not given and it is\n        determined from a timeseries of published FX rates.\n    identifier: str, optional\n        The string name of the timeseries to be loaded by the *Fixings* object. For alignment with\n        internal structuring these should have the suffix \"_1B\", e.g. \"ESTR_1B\".\n    fixing_method: FloatFixingMethod or str\n        The :class:`FloatFixingMethod` object used to combine multiple RFR fixings.\n    spread_compound_method: SpreadCompoundMethod or str\n        A :class:`SpreadCompoundMethod` object used define the calculation of the addition of the\n        ``float_spread``.\n    float_spread: float, DUal, Dual2, Variable\n        An additional amount added to the calculation to determine the final period rate.\n\n    Examples\n    --------\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.enums.parameters import SpreadCompoundMethod, FloatFixingMethod\n       from rateslib.data.fixings import RFRFixing\n       from rateslib.data.fixings import FloatRateIndex\n       from rateslib import fixings, dt\n       from pandas import Series\n\n    The below is a fully determined *RFRFixing* with populated rates.\n\n    .. ipython:: python\n\n       fixings.add(\"SOFR_1B\", Series(index=[\n            dt(2025, 1, 8), dt(2025, 1, 9), dt(2025, 1, 10), dt(2025, 1, 13), dt(2025, 1, 14)\n          ], data=[1.1, 2.2, 3.3, 4.4, 5.5]))\n\n       rfr_fix = RFRFixing(\n           accrual_start=dt(2025, 1, 9),\n           accrual_end=dt(2025, 1, 15),\n           identifier=\"SOFR_1B\",\n           spread_compound_method=SpreadCompoundMethod.NoneSimple,\n           fixing_method=FloatFixingMethod.RFRPaymentDelay(),\n           float_spread=0.0,\n           rate_index=FloatRateIndex(frequency=\"1B\", series=\"usd_rfr\")\n       )\n       rfr_fix.value\n       rfr_fix.populated\n\n    This second example is a partly undetermined period, and will result in *NoInput* for its\n    value but has recorded partial population of its individual RFRs.\n\n    .. ipython:: python\n\n       rfr_fix2 = RFRFixing(\n           accrual_start=dt(2025, 1, 9),\n           accrual_end=dt(2025, 1, 21),\n           identifier=\"SOFR_1B\",\n           spread_compound_method=\"NoneSimple\",\n           fixing_method=\"RFRPaymentDelay\",\n           float_spread=0.0,\n           rate_index=FloatRateIndex(frequency=\"1B\", series=\"usd_rfr\")\n       )\n       rfr_fix2.value\n       rfr_fix2.populated\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"SOFR_1B\")\n\n    \"\"\"\n\n    _populated: Series[DualTypes]  # type: ignore[type-var]\n    _dates_obs: list[datetime] | None\n    _dates_dcf: list[datetime] | None\n    _float_spread: DualTypes\n    _fixing_index: FloatRateIndex\n    _accrual_start: datetime\n    _accrual_end: datetime\n    _fixing_method: FloatFixingMethod\n    _spread_compound_method: SpreadCompoundMethod\n\n    def __init__(\n        self,\n        *,\n        rate_index: FloatRateIndex,\n        accrual_start: datetime,\n        accrual_end: datetime,\n        fixing_method: FloatFixingMethod | str,\n        spread_compound_method: SpreadCompoundMethod | str,\n        float_spread: DualTypes,\n        value: DualTypes_ = NoInput(0),\n        identifier: str_ = NoInput(0),\n    ):\n        self._identifier = identifier if isinstance(identifier, NoInput) else identifier.upper()\n        self._value = value\n        self._state = 0\n\n        self._float_spread = float_spread\n        self._spread_compound_method = _get_spread_compound_method(spread_compound_method)\n        self._rate_index = rate_index\n        self._value = value\n        self._accrual_start = accrual_start\n        self._accrual_end = accrual_end\n        self._fixing_method = _get_float_fixing_method(fixing_method)\n        self._populated = Series(index=[], data=[], dtype=float)  # type: ignore[assignment]\n\n    def reset(self, state: int_ = NoInput(0)) -> None:\n        if isinstance(state, NoInput) or self._state == state:\n            self._populated = Series(index=[], data=[], dtype=float)  # type: ignore[assignment]\n            self._value = NoInput(0)\n            self._state = 0\n\n    @property\n    def fixing_method(self) -> FloatFixingMethod:\n        \"\"\"The :class:`FloatFixingMethod` object used to combine multiple RFR fixings.\"\"\"\n        return self._fixing_method\n\n    @property\n    def float_spread(self) -> DualTypes:\n        \"\"\"The spread value incorporated into the fixing calculation using the compound method.\"\"\"\n        return self._float_spread\n\n    @property\n    def spread_compound_method(self) -> SpreadCompoundMethod:\n        \"\"\"A :class:`SpreadCompoundMethod` object used define the calculation of the addition of the\n        ``float_spread``.\"\"\"\n        return self._spread_compound_method\n\n    @property\n    def accrual_start(self) -> datetime:\n        \"\"\"The accrual start date for the underlying float rate period.\"\"\"\n        return self._accrual_start\n\n    @property\n    def accrual_end(self) -> datetime:\n        \"\"\"The accrual end date for the underlying float rate period.\"\"\"\n        return self._accrual_end\n\n    @property\n    def value(self) -> DualTypes_:\n        if not isinstance(self._value, NoInput):\n            return self._value\n        else:\n            if isinstance(self._identifier, NoInput):\n                return NoInput(0)\n            else:\n                state, timeseries, bounds = fixings.__getitem__(self._identifier)\n                if state == self._state:\n                    return NoInput(0)\n                else:\n                    self._state = state\n                    v = self._lookup_and_calculate(timeseries, bounds)\n                    self._value = v\n                    return v\n\n    @property\n    def populated(self) -> Series[DualTypes]:  # type: ignore[type-var]\n        \"\"\"The looked up fixings as part of the calculation after a ``value`` calculation.\"\"\"\n        return self._populated\n\n    @property\n    def unpopulated(self) -> Series[DualTypes]:  # type: ignore[type-var]\n        \"\"\"The fixings that are not published but are required to determine the period fixing.\"\"\"\n        return Series(index=self.dates_obs[:-1], data=np.nan, dtype=object).drop(  # type: ignore[return-value]\n            self.populated.index\n        )\n\n    def _lookup_and_calculate(\n        self,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        bounds: tuple[datetime, datetime] | None,\n    ) -> DualTypes_:\n        value, populated = self._lookup(\n            timeseries=timeseries,\n            fixing_method=self.fixing_method,\n            dates_obs=self.dates_obs,\n            dcfs_dcf=self.dcfs_dcf,\n            float_spread=self.float_spread,\n            spread_compound_method=self.spread_compound_method,\n        )\n        self._populated = populated\n        return value\n\n    @classmethod\n    def _lookup(\n        cls,\n        timeseries: Series[DualTypes],  # type: ignore[type-var]\n        # bounds: tuple[datetime, datetime] | None,\n        # accrual_start: datetime,\n        # accrual_end: datetime,\n        fixing_method: FloatFixingMethod,\n        dates_obs: Arr1dObj,\n        # dates_dcf: list[datetime] | None,\n        # dcfs_obs: Arr1dF64,\n        dcfs_dcf: Arr1dF64,\n        float_spread: DualTypes,\n        spread_compound_method: SpreadCompoundMethod,\n    ) -> tuple[DualTypes_, Series[DualTypes]]:  # type: ignore[type-var]\n        fixing_rates: Series[DualTypes] = Series(index=dates_obs[:-1], data=np.nan, dtype=object)  # type: ignore[type-var, assignment]\n        # populate Series with values\n        fixing_rates, populated, unpopulated = (\n            _RFRRate._push_rate_fixings_as_series_to_fixing_rates(\n                fixing_rates=fixing_rates,\n                rate_fixings=timeseries,\n                fixing_method=fixing_method,\n            )\n        )\n        if len(unpopulated) > 0:\n            return NoInput(0), populated\n        else:\n            result = _RFRRate._inefficient_calculation(\n                fixing_rates=fixing_rates,\n                fixing_dcfs=dcfs_dcf,\n                fixing_method=fixing_method,\n                spread_compound_method=spread_compound_method,\n                float_spread=float_spread,\n            )\n            if isinstance(result, Err):\n                result.unwrap()  # will raise\n            return result.unwrap(), populated\n\n    @property\n    def rate_index(self) -> FloatRateIndex:\n        \"\"\"The :class:`FloatRateIndex` defining the parameters of the RFR interest rate index.\"\"\"\n        return self._rate_index\n\n    @cached_property\n    def dates_obs(self) -> Arr1dObj:\n        \"\"\"A sequence of dates defining the individual **observation** rates for the period.\"\"\"\n        start, end = self.bounds[0]\n        return np.array(self.rate_index.calendar.bus_date_range(start, end))\n\n    @cached_property\n    def dates_dcf(self) -> Arr1dObj:\n        \"\"\"A sequence of dates defining the individual **DCF** dates for the period.\"\"\"\n        start, end = self.bounds[1]\n        return np.array(self.rate_index.calendar.bus_date_range(start, end))\n\n    @cached_property\n    def dcfs_obs(self) -> Arr1dF64:\n        \"\"\"A sequence of floats defining the individual **DCF** values associated with\n        the method's **observation** dates.\"\"\"\n        return _RFRRate._get_dcf_values(\n            dcf_dates=self.dates_obs,\n            fixing_convention=self.rate_index.convention,\n            fixing_calendar=self.rate_index.calendar,\n        )\n\n    @cached_property\n    def dcfs_dcf(self) -> Arr1dF64:\n        \"\"\"A sequence of floats defining the individual **DCF** values associated with\n        the **DCF** dates natural to the fixing rates.\"\"\"\n        return _RFRRate._get_dcf_values(\n            dcf_dates=self.dates_dcf,\n            fixing_convention=self.rate_index.convention,\n            fixing_calendar=self.rate_index.calendar,\n        )\n\n    @cached_property\n    def bounds(self) -> tuple[tuple[datetime, datetime], tuple[datetime, datetime]]:\n        \"\"\"The fixing method adjusted start and end dates for the **observation** dates and\n        the **dcf** dates.\"\"\"\n        return self._get_date_bounds(\n            accrual_start=self.accrual_start,\n            accrual_end=self.accrual_end,\n            fixing_method=self.fixing_method,\n            fixing_calendar=self.rate_index.calendar,\n        )\n\n    @staticmethod\n    def _get_date_bounds(\n        accrual_start: datetime,\n        accrual_end: datetime,\n        fixing_method: FloatFixingMethod,\n        fixing_calendar: CalTypes,\n    ) -> tuple[tuple[datetime, datetime], tuple[datetime, datetime]]:\n        \"\"\"\n        For each different RFR fixing method adjust the start and end date of the associated\n        period to return adjusted start and end dates for the fixing set as well as the\n        DCF set.\n\n        For all methods except 'lookback', these dates will align with each other.\n        For 'lookback' the observed RFRs are applied over different DCFs that do not naturally\n        align.\n        \"\"\"\n        # Depending upon method get the observation dates and dcf dates\n        if type(fixing_method) in [\n            FloatFixingMethod.RFRPaymentDelay,\n            FloatFixingMethod.RFRPaymentDelayAverage,\n            FloatFixingMethod.RFRLockout,\n            FloatFixingMethod.RFRLockoutAverage,\n        ]:\n            start_obs, end_obs = accrual_start, accrual_end\n            start_dcf, end_dcf = accrual_start, accrual_end\n        elif type(fixing_method) in [\n            FloatFixingMethod.RFRObservationShift,\n            FloatFixingMethod.RFRObservationShiftAverage,\n        ]:\n            start_obs = fixing_calendar.lag_bus_days(\n                accrual_start, -fixing_method.method_param(), settlement=False\n            )\n            end_obs = fixing_calendar.lag_bus_days(\n                accrual_end, -fixing_method.method_param(), settlement=False\n            )\n            start_dcf, end_dcf = start_obs, end_obs\n        else:\n            # fixing_method in [\n            #    FloatFixingMethod.RFRLookback,\n            #    FloatFixingMethod.RFRLookbackAverage,\n            # ]:\n            start_obs = fixing_calendar.lag_bus_days(\n                accrual_start, -fixing_method.method_param(), settlement=False\n            )\n            end_obs = fixing_calendar.lag_bus_days(\n                accrual_end, -fixing_method.method_param(), settlement=False\n            )\n            start_dcf, end_dcf = accrual_start, accrual_end\n\n        return (start_obs, end_obs), (start_dcf, end_dcf)\n\n\nclass FloatRateIndex:\n    \"\"\"\n    Define the parameters of a specific interest rate index.\n\n    Parameters\n    ----------\n    frequency : Frequency or str\n        The specific tenor of the interest rate index.\n    series : Series or str\n        The general parameters applied to any tenor of this particular interest rate series.\n\n    Examples\n    --------\n    None\n    \"\"\"\n\n    _frequency: Frequency\n    _series: FloatRateSeries\n\n    def __init__(\n        self,\n        frequency: Frequency | str,\n        series: FloatRateSeries | str,\n    ) -> None:\n        self._series = _get_float_rate_series(series)\n        self._frequency = _get_frequency(frequency, NoInput(0), self.calendar)\n\n    @property\n    def frequency(self) -> Frequency:\n        \"\"\"The specific tenor of the interest rate index.\"\"\"\n        return self._frequency\n\n    @property\n    def series(self) -> FloatRateSeries:\n        \"\"\"The general parameters applied to any tenor of this particular interest rate series.\"\"\"\n        return self._series\n\n    @property\n    def lag(self) -> int:\n        \"\"\"The lag for the determining the publication date of the interest rate index.\"\"\"\n        return self.series.lag\n\n    @property\n    def calendar(self) -> CalTypes:\n        \"\"\"The calendar associated with the publication of the interest rate index.\"\"\"\n        return self.series.calendar\n\n    @property\n    def modifier(self) -> Adjuster:\n        \"\"\"The :class:`Adjuster` associated with the end accrual day of the interest rate index.\"\"\"\n        return self.series.modifier\n\n    @property\n    def eom(self) -> bool:\n        \"\"\"Whether the interest rate index adopts an end of month convention.\"\"\"\n        return self.series.eom\n\n    @property\n    def convention(self) -> Convention:\n        \"\"\"The :class:`Convention` associated with the publication of the interest rate index.\"\"\"\n        return self.series.convention\n\n\nclass FloatRateSeries:\n    \"\"\"\n    Define the general parameters of multiple tenors of an interest rate series.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    lag: int, :red:`required`\n        The number of business days by which the fixing date is lagged to the accrual start date.\n    calendar: Calendar, str :red:`required`\n        The calendar associated with the floating rate's date determination.\n    modifier: Adjuster, str, :red:`required`\n        The :class:`~rateslib.scheduling.Adjuster` associated with the end accrual day of the\n        floating rate's date.\n    convention: Convention, str, :red:`required`\n        The day count :class:`~rateslib.scheduling.Convention` associated with the floating rate.\n    eom: bool, :red:`required`\n        Whether the interest rate index natively adopts EoM roll preference or not.\n    tenors: list[str], :green:`optional`\n        The official list of tenor indexes published by this series.\n    zero_float_period_stub: StubInference, str, :green:`optional (set as 'ShortBack')`\n        The stub inference parameter that is used to steer schedule construction when this\n        series is used as part of a :class:`~rateslib.legs.FloatLeg` composed of\n        :class:`~rateslib.periods.ZeroFloatPeriod`.\n\n    \"\"\"\n\n    _lag: int\n    _calendar: CalTypes\n    _modifier: Adjuster\n    _convention: Convention\n    _eom: bool\n    _zero_period_stub: StubInference\n    _tenors: list[str] | NoInput\n\n    def __init__(\n        self,\n        lag: int,\n        calendar: CalTypes | str,\n        modifier: Adjuster | str,\n        convention: Convention | str,\n        eom: bool,\n        zero_period_stub: StubInference | str_ = NoInput(0),\n        tenors: list[str] | NoInput = NoInput(0),\n    ) -> None:\n        self._lag = lag\n        self._calendar = get_calendar(calendar)\n        self._modifier = _get_adjuster(modifier)\n        self._convention = _get_convention(convention)\n        self._eom = eom\n        self._tenors: list[str] = tenors\n        if not isinstance(self.tenors, NoInput) and len(self.tenors) == 0:\n            raise ValueError(\"`tenors` cannot be given as an empty list.\")\n        self._zero_period_stub = _get_stub_inference(\n            _drb(\"ShortBack\", zero_period_stub), NoInput(0), NoInput(0)\n        )\n\n    @property\n    def lag(self) -> int:\n        \"\"\"The number of business days before accrual start that the fixing is published according\n        to ``calendar``.\"\"\"\n        return self._lag\n\n    @property\n    def calendar(self) -> CalTypes:\n        \"\"\"The fixing calendar for the rate series.\"\"\"\n        return self._calendar\n\n    @property\n    def convention(self) -> Convention:\n        \"\"\"The day count :class:`~rateslib.scheduling.Convention` associated with the fixing.\"\"\"\n        return self._convention\n\n    @property\n    def modifier(self) -> Adjuster:\n        \"\"\"The date :class:`~rateslib.scheduling.Adjuster` used for date adjustment of the tenor.\"\"\"\n        return self._modifier\n\n    @property\n    def eom(self) -> bool:\n        \"\"\"Whether end of month date rolling is applied to date calculations for the fixing\n        series.\"\"\"\n        return self._eom\n\n    @property\n    def zero_period_stub(self) -> StubInference:\n        \"\"\":class:`~rateslib.scheduling.StubInference` used when a fixing tenor does not divide\n        into the frequency of a compounded :class:`~rateslib.periods.ZeroFloatPeriod`.\"\"\"\n        return self._zero_period_stub\n\n    @property\n    def tenors(self) -> list[str] | NoInput:\n        \"\"\"\n        A list of tenors that are published by this interest rate series.\n        \"\"\"\n        return self._tenors\n\n\nclass _IBORRate:\n    @staticmethod\n    def _rate(\n        *,\n        rate_curve: _BaseCurve | dict[str, _BaseCurve] | NoInput,\n        rate_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n        start: datetime,\n        end: datetime,\n        lag: int,\n        stub: bool,\n        float_spread: DualTypes,\n        rate_series: FloatRateSeries | NoInput,\n        frequency: Frequency,\n    ) -> Result[DualTypes]:\n        rate_series_ = _maybe_get_rate_series_from_curve(\n            rate_curve=rate_curve,\n            rate_series=rate_series,\n            lag=lag,\n        )\n        fixing_date = rate_series_.calendar.lag_bus_days(start, -rate_series_.lag, settlement=False)\n        if stub:\n            # TODO: pass through tenor convention and modifier to the interpolated stub\n            return _IBORRate._rate_interpolated_stub(\n                rate_curve=rate_curve,\n                rate_fixings=rate_fixings,\n                fixing_date=fixing_date,\n                start=start,\n                end=end,\n                float_spread=float_spread,\n                rate_series=rate_series_,\n            )\n        else:\n            return _IBORRate._rate_single_tenor(\n                rate_curve=rate_curve,\n                rate_fixings=rate_fixings,\n                fixing_date=fixing_date,\n                start=start,\n                end=end,\n                frequency=frequency,\n                float_spread=float_spread,\n            )\n\n    @staticmethod\n    def _rate_interpolated_stub(\n        rate_curve: _BaseCurve | dict[str, _BaseCurve] | NoInput,\n        rate_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n        fixing_date: datetime,\n        start: datetime,\n        end: datetime,\n        float_spread: DualTypes,\n        rate_series: FloatRateSeries,\n    ) -> Result[DualTypes]:\n        if isinstance(rate_fixings, NoInput):\n            # will attempt to forecast stub period from rate_curve\n            if isinstance(rate_curve, dict):\n                return _IBORRate._rate_interpolated_stub_from_curve_dict(\n                    rate_curve=rate_curve,\n                    fixing_date=fixing_date,\n                    start=start,\n                    end=end,\n                    float_spread=float_spread,\n                )\n            else:\n                return _IBORRate._rate_stub_forecast_from_curve(\n                    rate_curve=rate_curve,\n                    fixing_date=fixing_date,\n                    start=start,\n                    end=end,\n                    float_spread=float_spread,\n                )\n        else:\n            # will maybe find relevant fixing values in Series\n            return _IBORRate._rate_interpolated_stub_maybe_from_fixings(\n                rate_curve=rate_curve,\n                rate_fixings=rate_fixings,\n                fixing_date=fixing_date,\n                start=start,\n                end=end,\n                rate_series=rate_series,\n                float_spread=float_spread,\n            )\n\n    @staticmethod\n    def _rate_interpolated_stub_maybe_from_fixings(\n        rate_curve: _BaseCurve_ | dict[str, _BaseCurve],\n        rate_fixings: DualTypes | Series[DualTypes] | str,  # type: ignore[type-var]\n        fixing_date: datetime,\n        start: datetime,\n        end: datetime,\n        float_spread: DualTypes,\n        rate_series: FloatRateSeries,\n    ) -> Result[DualTypes]:\n        if isinstance(rate_fixings, str):\n            tenors, dates, fixings_ = fixings.get_stub_ibor_fixings(\n                value_start_date=start,\n                value_end_date=end,\n                fixing_calendar=rate_series.calendar,\n                fixing_modifier=rate_series.modifier,\n                fixing_identifier=rate_fixings,\n                fixing_date=fixing_date,\n            )\n            if len(tenors) == 0:\n                # nothing found\n                return _IBORRate._rate_interpolated_stub(\n                    rate_curve=rate_curve,\n                    rate_fixings=NoInput(0),  # no fixings are found\n                    fixing_date=fixing_date,\n                    start=start,\n                    end=end,\n                    float_spread=float_spread,\n                    rate_series=rate_series,\n                )\n            elif len(tenors) == 1:\n                if fixings_[0] is None:\n                    return _IBORRate._rate_interpolated_stub(\n                        rate_curve=rate_curve,\n                        rate_fixings=NoInput(0),  # no fixings are found\n                        fixing_date=fixing_date,\n                        start=start,\n                        end=end,\n                        float_spread=float_spread,\n                        rate_series=rate_series,\n                    )\n                return Ok(fixings_[0] + float_spread / 100.0)\n            else:\n                if fixings_[0] is None or fixings_[1] is None:\n                    # missing data exists\n                    return _IBORRate._rate_interpolated_stub(\n                        rate_curve=rate_curve,\n                        rate_fixings=NoInput(0),  # no fixings are found\n                        fixing_date=fixing_date,\n                        start=start,\n                        end=end,\n                        float_spread=float_spread,\n                        rate_series=rate_series,\n                    )\n                return Ok(\n                    _IBORRate._interpolated_stub_rate(\n                        left_date=dates[0],\n                        right_date=dates[1],\n                        left_rate=fixings_[0],\n                        right_rate=fixings_[1],\n                        maturity_date=end,\n                        float_spread=float_spread,\n                    )\n                )\n        elif isinstance(rate_fixings, Series):\n            raise ValueError(err.VE_FIXINGS_BAD_TYPE)\n        else:\n            return Ok(rate_fixings + float_spread / 100.0)\n\n    @staticmethod\n    def _rate_interpolated_stub_from_curve_dict(\n        rate_curve: dict[str, _BaseCurve],\n        fixing_date: datetime,\n        start: datetime,\n        end: datetime,\n        float_spread: DualTypes,\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Get the rate on all available curves in dict and then determine the ones to interpolate.\n        \"\"\"\n\n        def _rate(c: _BaseCurve, tenor: str) -> DualTypes:\n            if c._base_type == _CurveType.dfs:\n                return c._rate_with_raise(start, tenor)\n            else:  # values\n                return c._rate_with_raise(fixing_date, tenor)  # tenor is not used on LineCurve\n\n        try:\n            values = {\n                add_tenor(start, k, v.meta.modifier, v.meta.calendar): _rate(v, k)\n                for k, v in rate_curve.items()\n            }\n        except Exception as e:\n            return Err(e)\n        values = dict(sorted(values.items()))\n        dates, rates = list(values.keys()), list(values.values())\n        if end > dates[-1]:\n            warnings.warn(\n                \"Interpolated stub period has a length longer than the provided \"\n                \"IBOR curve tenors: using the longest IBOR value.\",\n                UserWarning,\n            )\n            ret: DualTypes = rates[-1]\n        elif end < dates[0]:\n            warnings.warn(\n                \"Interpolated stub period has a length shorter than the provided \"\n                \"IBOR curve tenors: using the shortest IBOR value.\",\n                UserWarning,\n            )\n            ret = rates[0]\n        else:\n            i = index_left(dates, len(dates), end)\n            ret = rates[i] + (rates[i + 1] - rates[i]) * (\n                (end - dates[i]).days / (dates[i + 1] - dates[i]).days\n            )\n        return Ok(ret + float_spread / 100.0)\n\n    @staticmethod\n    def _rate_single_tenor(\n        rate_curve: _BaseCurve | dict[str, _BaseCurve] | NoInput,\n        rate_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n        fixing_date: datetime,\n        start: datetime,\n        end: datetime,\n        frequency: Frequency,\n        float_spread: DualTypes,\n    ) -> Result[DualTypes]:\n        if isinstance(rate_fixings, NoInput):\n            return _IBORRate._rate_tenor_forecast_from_curve(\n                rate_curve=rate_curve,\n                fixing_date=fixing_date,\n                start=start,\n                end=end,\n                frequency=frequency,\n                float_spread=float_spread,\n            )\n        else:\n            return _IBORRate._rate_tenor_maybe_from_fixings(\n                rate_curve=rate_curve,\n                rate_fixings=rate_fixings,\n                fixing_date=fixing_date,\n                start=start,\n                end=end,\n                frequency=frequency,\n                float_spread=float_spread,\n            )\n\n    @staticmethod\n    def _rate_tenor_maybe_from_fixings(\n        rate_curve: _BaseCurve_ | dict[str, _BaseCurve],\n        rate_fixings: DualTypes | Series[DualTypes] | str,  # type: ignore[type-var]\n        fixing_date: datetime,\n        start: datetime,\n        end: datetime,\n        frequency: Frequency,\n        float_spread: DualTypes,\n    ) -> Result[DualTypes]:\n        if isinstance(rate_fixings, str | Series):\n            if isinstance(rate_fixings, str):\n                identifier = rate_fixings\n                _, fixings_, bounds = fixings[identifier]\n            else:\n                identifier = \"<SERIES_OBJECT>\"\n                fixings_ = rate_fixings\n                bounds = (rate_fixings.index.min(), rate_fixings.index.max())\n\n            if fixing_date <= bounds[1]:\n                try:\n                    fixing = fixings_.loc[fixing_date]\n                    return Ok(fixing + float_spread / 100.0)\n                except KeyError:\n                    warnings.warn(\n                        f\"Fixings are provided in series: '{identifier}', but the value for \"\n                        f\" date: {fixing_date} is not found.\\nAttempting to forecast from \"\n                        f\"the `rate_curve`.\",\n                    )\n            return _IBORRate._rate_tenor_forecast_from_curve(\n                rate_curve=rate_curve,\n                fixing_date=fixing_date,\n                start=start,\n                end=end,\n                frequency=frequency,\n                float_spread=float_spread,\n            )\n        else:\n            # is just a scalar value so return directly.\n            return Ok(rate_fixings + float_spread / 100.0)\n\n    @staticmethod\n    def _rate_tenor_forecast_from_curve(\n        rate_curve: _BaseCurve_ | dict[str, _BaseCurve],\n        fixing_date: datetime,\n        start: datetime,\n        end: datetime,\n        frequency: Frequency,\n        float_spread: DualTypes,\n    ) -> Result[DualTypes]:\n        tenor = _get_tenor_from_frequency(frequency)\n        if isinstance(rate_curve, NoInput):\n            return Err(ValueError(err.VE_NEEDS_RATE_TO_FORECAST_TENOR_IBOR))\n        elif isinstance(rate_curve, dict):\n            remapped_rate_curve = {k.lower(): v for k, v in rate_curve.items()}\n            rate_curve_ = remapped_rate_curve[tenor.lower()]\n            return _IBORRate._rate_tenor_forecast_from_curve(\n                rate_curve=rate_curve_,\n                fixing_date=fixing_date,\n                start=start,\n                end=end,\n                frequency=frequency,\n                float_spread=float_spread,\n            )\n        else:\n            if rate_curve._base_type == _CurveType.dfs:\n                try:\n                    r = rate_curve._rate_with_raise(start, tenor) + float_spread / 100.0\n                except Exception as e:\n                    return Err(e)\n                else:\n                    return Ok(r)\n            else:\n                try:\n                    r = rate_curve._rate_with_raise(fixing_date, NoInput(0)) + float_spread / 100.0\n                except Exception as e:\n                    return Err(e)\n                else:\n                    return Ok(r)\n\n    @staticmethod\n    def _rate_stub_forecast_from_curve(\n        rate_curve: _BaseCurve_,\n        fixing_date: datetime,\n        start: datetime,\n        end: datetime,\n        float_spread: DualTypes,\n    ) -> Result[DualTypes]:\n        if isinstance(rate_curve, NoInput):\n            return Err(ValueError(err.VE_NEEDS_RATE_TO_FORECAST_STUB_IBOR))\n\n        if rate_curve._base_type == _CurveType.dfs:\n            try:\n                r = rate_curve._rate_with_raise(start, end) + float_spread / 100.0\n            except Exception as e:\n                return Err(e)\n            else:\n                return Ok(r)\n        else:\n            try:\n                r = rate_curve[fixing_date] + float_spread / 100.0\n            except Exception as e:\n                return Err(e)\n            else:\n                return Ok(r)\n\n    @staticmethod\n    def _interpolated_stub_rate(\n        left_date: datetime,\n        right_date: datetime,\n        left_rate: DualTypes,\n        right_rate: DualTypes,\n        maturity_date: datetime,\n        float_spread: DualTypes,\n    ) -> DualTypes:\n        return (\n            left_rate\n            + (maturity_date - left_date).days\n            / (right_date - left_date).days\n            * (right_rate - left_rate)\n            + float_spread / 100.0\n        )\n\n\nclass _RFRRate:\n    \"\"\"\n    Class for maintaining methods related to calculating the period rate for an RFR compounded\n    period. These periods have multiple branches depending upon;\n\n    - which `fixing_method` has been selected.\n    - which `spread_compound_method` has been selected (if the `float_spread` is non-zero).\n    - whether there are any known fixings that must be populated to the calculation or unknown\n      fixings must be forecast by some curve.\n\n    \"\"\"\n\n    @staticmethod\n    def _rate(\n        start: datetime,\n        end: datetime,\n        rate_curve: _BaseCurve_,\n        rate_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n        fixing_method: FloatFixingMethod,\n        spread_compound_method: SpreadCompoundMethod,\n        float_spread: DualTypes,\n        rate_series: FloatRateSeries | NoInput,\n    ) -> Result[  # type: ignore[type-var]\n        tuple[\n            DualTypes,\n            tuple[datetime, datetime] | None,\n            tuple[datetime, datetime] | None,\n            Arr1dObj | None,\n            Arr1dObj | None,\n            Arr1dF64 | None,\n            Arr1dF64 | None,\n            Series[DualTypes] | None,\n            Series[DualTypes] | None,\n            Series[DualTypes] | None,\n        ]\n    ]:\n        \"\"\"\n        To avoid repeated calculation, this function will pass back the data it calculates.\n        In some short-circuited calculation not all data will have been calculated and returns\n        None\n\n        - 0: rate\n        - 1: date_boundary_obs\n        - 2: date_boundary_dcf\n        - 3: dates_obs\n        - 4: dates_dcf\n        - 5: dcfs_obs\n        - 6: dcfs_dcf\n        - 7: fixing_rates\n        - 8: populated\n        - 9: unpopulated\n\n        \"\"\"\n\n        if isinstance(rate_fixings, int | float | Dual | Dual2 | Variable):\n            # a scalar value is assumed to have been pre-computed **including** the float spread\n            # otherwise this information is of no use, since a computation including a\n            # complicated float spread cannot be performed on just a compounded or average rate.\n            return Ok((rate_fixings,) + (None,) * 9)\n\n        rate_series_ = _maybe_get_rate_series_from_curve(\n            rate_curve=rate_curve,\n            rate_series=rate_series,\n            lag=0,\n        )\n\n        bounds_obs, bounds_dcf, is_matching = _RFRRate._adjust_dates(\n            start=start,\n            end=end,\n            fixing_method=fixing_method,\n            fixing_calendar=rate_series_.calendar,\n        )\n\n        # >>> short-circuit here before any complex calculation or date lookup is performed.\n        # EFFICIENT CALCULATION:\n        if _RFRRate._is_rfr_efficient(\n            rate_curve=rate_curve,\n            rate_fixings=rate_fixings,\n            float_spread=float_spread,\n            spread_compound_method=spread_compound_method,\n            fixing_method=fixing_method,\n        ):\n            r_result = _RFRRate._efficient_calculation(\n                rate_curve=rate_curve,  # type: ignore[arg-type]  # is pre-checked\n                bounds_obs=bounds_obs,\n                float_spread=float_spread,\n            )\n            if isinstance(r_result, Err):\n                return r_result\n            else:\n                return Ok((r_result.unwrap(), bounds_obs, bounds_dcf) + (None,) * 7)\n\n        dates_obs, dates_dcf, dcfs_obs, dcfs_dcf, populated, unpopulated, fixing_rates = (\n            _RFRRate._get_dates_and_fixing_rates_from_fixings(\n                rate_series=rate_series_,\n                bounds_obs=bounds_obs,\n                bounds_dcf=bounds_dcf,\n                is_matching=is_matching,\n                rate_fixings=rate_fixings,\n                fixing_method=fixing_method,\n            )\n        )\n\n        # >>> short circuit and perform a semi-efficient calculation splicing fixings with DFs\n        # SEMI-EFFICIENT CALCULATION:\n        if _RFRRate._is_rfr_efficient(\n            rate_curve, NoInput(0), float_spread, spread_compound_method, fixing_method\n        ):\n            r = _RFRRate._semi_efficient_calculation(\n                rate_curve=rate_curve,  # type: ignore[arg-type]  # guaranteed by if statement\n                populated=populated,\n                unpopulated=unpopulated,\n                obs_date_boundary=bounds_obs,\n                float_spread=float_spread,\n                fixing_dcfs=dcfs_dcf,\n            )\n            return Ok(\n                (\n                    r,\n                    bounds_obs,\n                    bounds_dcf,\n                    dates_obs,\n                    dates_dcf,\n                    dcfs_obs,\n                    dcfs_dcf,\n                    fixing_rates,\n                    populated,\n                    unpopulated,\n                )\n            )\n\n        update = _RFRRate._forecast_fixing_rates_from_curve(\n            unpopulated=unpopulated,\n            populated=populated,\n            fixing_rates=fixing_rates,\n            rate_curve=rate_curve,\n            dates_obs=dates_obs,\n            dcfs_obs=dcfs_obs,\n        )\n        if isinstance(update, Err):\n            return update\n\n        # INEFFICIENT CALCULATION having derived all individual fixings.\n        r_result = _RFRRate._inefficient_calculation(\n            fixing_rates=fixing_rates,\n            fixing_dcfs=dcfs_dcf,\n            fixing_method=fixing_method,\n            spread_compound_method=spread_compound_method,\n            float_spread=float_spread,\n        )\n        if isinstance(r_result, Err):\n            return r_result\n        else:\n            return Ok(\n                (\n                    r_result.unwrap(),\n                    bounds_obs,\n                    bounds_dcf,\n                    dates_obs,\n                    dates_dcf,\n                    dcfs_obs,\n                    dcfs_dcf,\n                    fixing_rates,\n                    populated,\n                    unpopulated,\n                )\n            )\n\n    @staticmethod\n    def _efficient_calculation(\n        rate_curve: _BaseCurve,  # discount factors only\n        bounds_obs: tuple[datetime, datetime],\n        float_spread: DualTypes,\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Perform an efficient calculation only after the `_is_rfr_efficient` check is performed.\n\n        This calculation uses only discount factors and does not calculate individual fixing rates.\n        \"\"\"\n        try:\n            r = (\n                rate_curve._rate_with_raise(\n                    effective=bounds_obs[0],\n                    termination=bounds_obs[1],\n                    # no other arguments are necessary following _is_efficient check\n                )\n                + float_spread / 100.0\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(r)\n\n    @staticmethod\n    def _semi_efficient_calculation(\n        rate_curve: _BaseCurve,\n        populated: Series[DualTypes],  # type: ignore[type-var]\n        fixing_dcfs: Arr1dF64,\n        unpopulated: Series[DualTypes],  # type: ignore[type-var]\n        obs_date_boundary: tuple[datetime, datetime],\n        float_spread: DualTypes,\n    ) -> DualTypes:\n        \"\"\"\n        Perform an efficient calculation only after the `_is_rfr_efficient` check is performed.\n\n        This calculation combines some known fixing values with a forecast people calculated\n        using discount factors and not by calculating a number of individual fixing rates.\n        \"\"\"\n        populated_index = prod(\n            [\n                1.0 + d * r / 100.0\n                for r, d in zip(populated, fixing_dcfs[: len(populated)], strict=False)\n            ]\n        )\n        # TODO this is not date safe, i.e. a date maybe before the curve starts and DF is zero.\n        if len(unpopulated) == 0:  # i.e. all fixings are known without needing to forecast\n            unpopulated_index: DualTypes = 1.0\n        else:\n            unpopulated_index = rate_curve[unpopulated.index[0]] / rate_curve[obs_date_boundary[1]]\n        rate: DualTypes = ((populated_index * unpopulated_index) - 1.0) * 100.0 / fixing_dcfs.sum()\n        return rate + float_spread / 100.0\n\n    @staticmethod\n    def _inefficient_calculation(\n        fixing_rates: Series,\n        fixing_dcfs: Arr1dF64,\n        fixing_method: FloatFixingMethod,\n        spread_compound_method: SpreadCompoundMethod,\n        float_spread: DualTypes,\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Perform a full calculation forecasting every individual fixing rate and then compounding\n        or averaging each of them up in turn, combining a float spread if necessary.\n        \"\"\"\n        # overwrite with lockout rates: this is needed if rates have been forecast from curve.\n        if type(fixing_method) in [\n            FloatFixingMethod.RFRLockout,\n            FloatFixingMethod.RFRLockoutAverage,\n        ]:\n            # overwrite fixings\n            method_param = fixing_method.method_param()\n            if method_param >= len(fixing_rates):\n                return Err(\n                    ValueError(err.VE_LOCKOUT_METHOD_PARAM.format(method_param, fixing_rates))\n                )\n            for i in range(1, method_param + 1):\n                fixing_rates.iloc[-i] = fixing_rates.iloc[-(method_param + 1)]\n\n        if type(fixing_method) in [\n            FloatFixingMethod.RFRLockoutAverage,\n            FloatFixingMethod.RFRLookbackAverage,\n            FloatFixingMethod.RFRObservationShiftAverage,\n            FloatFixingMethod.RFRPaymentDelayAverage,\n        ]:\n            return _RFRRate._calculator_rate_rfr_avg_with_spread(\n                float_spread=float_spread,\n                spread_compound_method=spread_compound_method,\n                rates=fixing_rates.to_numpy(),\n                dcf_vals=fixing_dcfs,\n            )\n        else:\n            return _RFRRate._calculator_rate_rfr_isda_compounded_with_spread(\n                float_spread=float_spread,\n                spread_compound_method=spread_compound_method,\n                rates=fixing_rates.to_numpy(),\n                dcf_vals=fixing_dcfs,\n            )\n\n    @staticmethod\n    def _get_dates_and_fixing_rates_from_fixings(\n        rate_series: FloatRateSeries,\n        bounds_obs: tuple[datetime, datetime],\n        bounds_dcf: tuple[datetime, datetime],\n        is_matching: bool,\n        rate_fixings: Series[DualTypes] | str_,  # type: ignore[type-var]\n        fixing_method: FloatFixingMethod,\n    ) -> tuple[  # type: ignore[type-var]\n        Arr1dObj,\n        Arr1dObj,\n        Arr1dF64,\n        Arr1dF64,\n        Series[DualTypes],\n        Series[DualTypes],\n        Series[DualTypes],\n    ]:\n        \"\"\"\n        For an RFR period, construct the necessary fixing dates and DCF schedule.\n        Populate fixings from a Series if any values are available to yield.\n        Return Series objects.\n\n        \"\"\"\n        dates_obs, dates_dcf, fixing_rates = _RFRRate._get_obs_and_dcf_dates(\n            fixing_calendar=rate_series.calendar,\n            fixing_convention=rate_series.convention,\n            obs_date_boundary=bounds_obs,\n            dcf_date_boundary=bounds_dcf,\n            is_matching=is_matching,\n        )\n        dcfs_dcf = _RFRRate._get_dcf_values(\n            dcf_dates=dates_dcf,\n            fixing_convention=rate_series.convention,\n            fixing_calendar=rate_series.calendar,\n        )\n        if is_matching:\n            dcfs_obs = dcfs_dcf.copy()\n        else:\n            dcfs_obs = _RFRRate._get_dcf_values(\n                dcf_dates=dates_obs,\n                fixing_convention=rate_series.convention,\n                fixing_calendar=rate_series.calendar,\n            )\n\n        # populate Series with values\n        if isinstance(rate_fixings, NoInput):\n            populated: Series[DualTypes] = Series(index=[], data=np.nan, dtype=object)  # type: ignore[type-var, assignment]\n            unpopulated: Series[DualTypes] = Series(index=dates_obs[:-1], data=np.nan, dtype=object)  # type: ignore[type-var, assignment]\n        elif isinstance(rate_fixings, str | Series):\n            fixing_rates, populated, unpopulated = (\n                _RFRRate._push_rate_fixings_as_series_to_fixing_rates(\n                    fixing_rates=fixing_rates,\n                    rate_fixings=rate_fixings,\n                    fixing_method=fixing_method,\n                )\n            )\n        else:\n            raise ValueError(err.VE_FIXINGS_BAD_TYPE)  # unknown fixings type fixings runtime issue\n\n        return dates_obs, dates_dcf, dcfs_obs, dcfs_dcf, populated, unpopulated, fixing_rates\n\n    @staticmethod\n    def _forecast_fixing_rates_from_curve(\n        unpopulated: Series[DualTypes],  # type: ignore[type-var]\n        populated: Series[DualTypes],  # type: ignore[type-var]\n        fixing_rates: Series[DualTypes],  # type: ignore[type-var]\n        rate_curve: _BaseCurve_,\n        dates_obs: Arr1dObj,\n        dcfs_obs: Arr1dF64,\n    ) -> Result[None]:\n        # determine unpopulated fixings from the curve\n        if len(unpopulated) > 0 and isinstance(rate_curve, NoInput):\n            return Err(FixingMissingForecasterError())  # missing data - needs a rate_curve\n\n        unpopulated_obs_dates = dates_obs[len(populated) :]\n        if len(unpopulated_obs_dates) > 1:\n            if isinstance(rate_curve, NoInput):\n                return Err(ValueError(err.VE_NEEDS_RATE_TO_FORECAST_RFR))\n\n            if rate_curve._base_type == _CurveType.values:\n                try:\n                    r = [\n                        rate_curve._rate_with_raise(unpopulated_obs_dates[_], NoInput(0))\n                        for _ in range(len(unpopulated))\n                    ]\n                except Exception as e:\n                    return Err(e)\n            else:\n                v = np.array([rate_curve[_] for _ in unpopulated_obs_dates])\n                r = (v[:-1] / v[1:] - 1) * 100 / dcfs_obs[len(populated) :]\n            unpopulated = Series(\n                index=unpopulated.index,\n                data=r,\n            )\n        fixing_rates.update(unpopulated)\n\n        return Ok(None)\n\n    @staticmethod\n    def _push_rate_fixings_as_series_to_fixing_rates(\n        fixing_rates: Series[DualTypes],  # type: ignore[type-var]\n        rate_fixings: str | Series[DualTypes],  # type: ignore[type-var]\n        fixing_method: FloatFixingMethod,\n    ) -> tuple[Series[DualTypes], Series[DualTypes], Series[DualTypes]]:  # type: ignore[type-var]\n        \"\"\"\n        Populates an empty fixings_rates Series with values from a looked up fixings collection.\n        \"\"\"\n        if isinstance(rate_fixings, str):\n            fixing_series = fixings[rate_fixings][1]\n        else:\n            fixing_series = rate_fixings\n        if fixing_rates.index[0] > fixing_series.index[-1]:\n            # then no fixings in scope, so no changes\n            return fixing_rates, Series(index=[], data=np.nan), fixing_rates.copy()  # type: ignore[return-value]\n        else:\n            fixing_rates.update(fixing_series)\n\n        # push lockout rates if they are available\n        if type(fixing_method) in [\n            FloatFixingMethod.RFRLockout,\n            FloatFixingMethod.RFRLockoutAverage,\n        ]:\n            method_param = fixing_method.method_param()\n            if method_param >= len(fixing_rates):\n                raise ValueError(err.VE_LOCKOUT_METHOD_PARAM.format(method_param, fixing_rates))\n            if not isna(fixing_rates.iloc[-(1 + method_param)]):  # type: ignore[arg-type]\n                for i in range(method_param):\n                    fixing_rates.iloc[-(1 + i)] = fixing_rates.iloc[-(1 + method_param)]\n\n        # validate for missing and expected fixings in the fixing Series\n        nans = isna(fixing_rates)\n        populated, unpopulated = fixing_rates[~nans], fixing_rates[nans]\n        if (\n            len(unpopulated) > 0\n            and len(populated) > 0\n            and unpopulated.index[0] < populated.index[-1]\n        ):\n            raise ValueError(\n                err.VE02_5.format(  # there is at least one missing fixing data item\n                    rate_fixings,\n                    fixing_rates[nans].index[0].strftime(\"%d-%m-%Y\"),\n                    fixing_rates[~nans].index[-1].strftime(\"%d-%m-%Y\"),\n                )\n            )\n\n        # validate for unexpected fixings provided in the fixings Series\n        if 0 < len(populated) < len(fixing_series[populated.index[0] : populated.index[-1]]):\n            # then fixing series contains an unexpected fixing.\n            warnings.warn(\n                err.W02_0.format(\n                    rate_fixings,\n                    populated.index[0].strftime(\"%d-%m-%Y\"),\n                    populated.index[-1].strftime(\"%d-%m-%Y\"),\n                ),\n                UserWarning,\n            )\n\n        return fixing_rates, populated, unpopulated\n\n    @staticmethod\n    def _adjust_dates(\n        start: datetime,\n        end: datetime,\n        fixing_method: FloatFixingMethod,\n        fixing_calendar: CalTypes,\n    ) -> tuple[tuple[datetime, datetime], tuple[datetime, datetime], bool]:\n        \"\"\"\n        For each different RFR fixing method adjust the start and end date of the associated\n        period to return adjusted start and end dates for the fixing set as well as the\n        DCF set.\n\n        For all methods except 'lookback', these dates will align with each other.\n        For 'lookback' the observed RFRs are applied over different DCFs that do not naturally\n        align.\n        \"\"\"\n        # Depending upon method get the observation dates and dcf dates\n        if type(fixing_method) in [\n            FloatFixingMethod.RFRPaymentDelay,\n            FloatFixingMethod.RFRPaymentDelayAverage,\n            FloatFixingMethod.RFRLockout,\n            FloatFixingMethod.RFRLockoutAverage,\n        ]:\n            start_obs, end_obs = start, end\n            start_dcf, end_dcf = start, end\n            is_matching = True\n        elif type(fixing_method) in [\n            FloatFixingMethod.RFRObservationShift,\n            FloatFixingMethod.RFRObservationShiftAverage,\n        ]:\n            start_obs = fixing_calendar.lag_bus_days(\n                start, -fixing_method.method_param(), settlement=False\n            )\n            end_obs = fixing_calendar.lag_bus_days(\n                end, -fixing_method.method_param(), settlement=False\n            )\n            start_dcf, end_dcf = start_obs, end_obs\n            is_matching = True\n        else:\n            # fixing_method in [\n            #    FloatFixingMethod.RFRLookback,\n            #    FloatFixingMethod.RFRLookbackAverage,\n            # ]:\n            start_obs = fixing_calendar.lag_bus_days(\n                start, -fixing_method.method_param(), settlement=False\n            )\n            end_obs = fixing_calendar.lag_bus_days(\n                end, -fixing_method.method_param(), settlement=False\n            )\n            start_dcf, end_dcf = start, end\n            is_matching = False\n\n        return (start_obs, end_obs), (start_dcf, end_dcf), is_matching\n\n    @staticmethod\n    def _get_obs_and_dcf_dates(\n        fixing_calendar: CalTypes,\n        fixing_convention: Convention,\n        obs_date_boundary: tuple[datetime, datetime],\n        dcf_date_boundary: tuple[datetime, datetime],\n        is_matching: bool,\n    ) -> tuple[Arr1dObj, Arr1dObj, Series[DualTypes]]:  # type: ignore[type-var]\n        # construct empty Series for rates and DCFs\n        obs_dates = np.array(fixing_calendar.bus_date_range(*obs_date_boundary))\n        fixing_rates: Series[DualTypes] = Series(index=obs_dates[:-1], data=np.nan, dtype=object)  # type: ignore[type-var, assignment]\n        if is_matching:\n            dcf_dates = obs_dates\n        else:\n            dcf_dates = np.array(fixing_calendar.bus_date_range(*dcf_date_boundary))\n        return obs_dates, dcf_dates, fixing_rates\n\n    @staticmethod\n    def _get_dcf_values(\n        dcf_dates: Arr1dObj,\n        fixing_convention: Convention,\n        fixing_calendar: CalTypes,\n    ) -> Arr1dF64:\n        if fixing_convention == Convention.Act365F:\n            days = np.fromiter((_.days for _ in dcf_dates[1:] - dcf_dates[:-1]), float)\n            return days / 365.0\n        elif fixing_convention == Convention.Act360:\n            days = np.fromiter((_.days for _ in dcf_dates[1:] - dcf_dates[:-1]), float)\n            return days / 360.0\n        elif fixing_convention == Convention.Bus252:\n            return np.array([1.0 / 252.0] * (len(dcf_dates) - 1))\n        else:\n            # this is unconventional fixing convention. Should maybe be avoided altogether.\n            return np.array(\n                [\n                    dcf(\n                        start=dcf_dates[i],\n                        end=dcf_dates[i + 1],\n                        convention=fixing_convention,\n                        calendar=fixing_calendar,\n                    )\n                    for i in range(len(dcf_dates) - 1)\n                ]\n            )\n\n    @staticmethod\n    def _is_rfr_efficient(\n        rate_curve: _BaseCurve_,\n        rate_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n        float_spread: DualTypes,\n        spread_compound_method: SpreadCompoundMethod,\n        fixing_method: FloatFixingMethod,\n    ) -> bool:\n        \"\"\"\n        Check all of the conditions to return an RFR rate directly from discount factors.\n\n        - A rate curve must be available and be based on DFs.\n        - There cannot be any known fixings that must be incorporated into the calculation.\n        - Only PaymentDelay and ObservationShift fixing methods are suitable for this calculation.\n        - Only NoneSimple spread compound method is suitable, or the float spread must be 0.0.\n\n        \"\"\"\n        return (\n            isinstance(rate_curve, _BaseCurve)\n            and rate_curve._base_type == _CurveType.dfs\n            and isinstance(rate_fixings, NoInput)\n            and type(fixing_method)\n            in [FloatFixingMethod.RFRPaymentDelay, FloatFixingMethod.RFRObservationShift]\n            and (float_spread == 0.0 or spread_compound_method == SpreadCompoundMethod.NoneSimple)\n        )\n\n    @staticmethod\n    def _calculator_rate_rfr_avg_with_spread(\n        float_spread: DualTypes,\n        spread_compound_method: SpreadCompoundMethod,\n        rates: Arr1dF64,\n        dcf_vals: Arr1dF64,\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Calculate all in rate with float spread under averaging.\n\n        Parameters\n        ----------\n        rates : Series\n            The rates which are expected for each daily period.\n        dcf_vals : Series\n            The weightings which are used for each rate in the compounding formula.\n\n        Returns\n        -------\n        float, Dual, Dual2\n        \"\"\"\n        if spread_compound_method != SpreadCompoundMethod.NoneSimple:\n            return Err(ValueError(err.VE_SPREAD_METHOD_RFR.format(spread_compound_method)))\n        else:\n            _: DualTypes = (dcf_vals * rates).sum() / dcf_vals.sum() + float_spread / 100\n            return Ok(_)\n\n    @staticmethod\n    def _calculator_rate_rfr_isda_compounded_with_spread(\n        float_spread: DualTypes,\n        spread_compound_method: SpreadCompoundMethod,\n        rates: Arr1dObj,\n        dcf_vals: Arr1dF64,\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Calculate all in rates with float spread under different compounding methods.\n\n        Parameters\n        ----------\n        rates : Series\n            The rates which are expected for each daily period.\n        dcf_vals : Series\n            The weightings which are used for each rate in the compounding formula.\n\n        Returns\n        -------\n        float, Dual, Dual2\n        \"\"\"\n        if float_spread == 0 or spread_compound_method == SpreadCompoundMethod.NoneSimple:\n            _: DualTypes = (\n                (1 + dcf_vals * rates / 100).prod() - 1\n            ) * 100 / dcf_vals.sum() + float_spread / 100\n            return Ok(_)\n        elif spread_compound_method == SpreadCompoundMethod.ISDACompounding:\n            _ = (\n                ((1 + dcf_vals * (rates / 100 + float_spread / 10000)).prod() - 1)\n                * 100\n                / dcf_vals.sum()\n            )\n            return Ok(_)\n        else:  # spread_compound_method == SpreadCompoundMethod.ISDAFlatCompounding:\n            sub_cashflows = (rates / 100 + float_spread / 10000) * dcf_vals\n            C_i = 0.0\n            for i in range(1, len(sub_cashflows)):\n                C_i += sub_cashflows[i - 1]\n                sub_cashflows[i] += C_i * rates[i] / 100 * dcf_vals[i]\n            _ = sub_cashflows.sum() * 100 / dcf_vals.sum()\n            return Ok(_)\n\n\ndef _get_float_rate_series(val: FloatRateSeries | str) -> FloatRateSeries:\n    if isinstance(val, FloatRateSeries):\n        return val\n    else:\n        try:\n            return FloatRateSeries(**defaults.float_series[val.lower()])\n        except KeyError:\n            raise ValueError(\n                f\"The FloatRateSeries: '{val.lower()}' was not found in `defaults`.\\n\"\n                \"To add a default specification for a FloatRateSeries, for example, use:\\n\"\n                f\"> defaults.float_series['{val.lower()}'] = {{ \\n\"\n                \"      'lag': 2,\\n\"\n                \"      'calendar': 'nyc',\\n\"\n                \"      'modifier': 'MF',\\n\"\n                \"      'convention': 'Act360',\\n\"\n                \"      'eom': False,\\n\"\n                f\"  }}\"\n            )\n\n\ndef _get_float_rate_series_or_blank(val: FloatRateSeries | str_) -> FloatRateSeries | NoInput:\n    if isinstance(val, NoInput):\n        return val\n    else:\n        return _get_float_rate_series(val)\n\n\ndef _maybe_get_rate_series_from_curve(\n    rate_curve: CurveOption_,\n    rate_series: FloatRateSeries | NoInput,\n    lag: int,\n) -> FloatRateSeries:\n    \"\"\"Get a rate fixing calendar and convention from a Curve or the alternatives if not given.\"\"\"\n\n    if isinstance(rate_curve, NoInput):\n        if isinstance(rate_series, NoInput):\n            raise ValueError(err.VE_NEEDS_CURVE_OR_INDEX)\n        else:\n            # get params from rate_index\n            return rate_series\n    else:\n        if isinstance(rate_curve, dict):\n            cal_ = list(rate_curve.values())[0].meta.calendar\n            conv_ = list(rate_curve.values())[0].meta.convention\n            mod_ = list(rate_curve.values())[0].meta.modifier\n        else:\n            cal_ = rate_curve.meta.calendar\n            conv_ = rate_curve.meta.convention\n            mod_ = rate_curve.meta.modifier\n\n        if isinstance(rate_series, NoInput):\n            # get params from rate_curve\n            return FloatRateSeries(\n                lag=lag,\n                calendar=cal_,\n                convention=conv_,\n                modifier=mod_,\n                eom=False,  # TODO: un hard code this\n            )\n        else:\n            if rate_series.convention != conv_:\n                raise ValueError(\n                    err.MISMATCH_RATE_INDEX_PARAMETERS.format(\n                        \"convention\", conv_, rate_series.convention\n                    )\n                )\n            # dual parameters may be specified\n            # get params from rate_index\n            return rate_series\n\n\ndef _leg_fixings_to_list(rate_fixings: LegFixings, n_periods: int) -> list[PeriodFixings]:\n    \"\"\"Perform a conversion of 'LegRateFixings' into a list of PeriodFixings.\"\"\"\n    if isinstance(rate_fixings, NoInput):\n        # NoInput is converted to a list of NoInputs\n        return [NoInput(0)] * n_periods\n    elif isinstance(rate_fixings, tuple):\n        # A tuple must be a 2-tuple which is converted to a first item and then multiplied.\n        return [rate_fixings[0]] + [rate_fixings[1]] * (n_periods - 1)\n    elif isinstance(rate_fixings, list):\n        # A list is padded with NoInputs\n        return rate_fixings + [NoInput(0)] * (n_periods - len(rate_fixings))\n    elif isinstance(rate_fixings, str | Series):\n        # A string or seried is multiplied\n        return [rate_fixings] * n_periods\n    else:\n        # A scalar value is padded with NoInputs.\n        return [rate_fixings] + [NoInput(0)] * (n_periods - 1)  # type: ignore[return-value]\n\n\n__all__ = [\n    \"FloatRateSeries\",\n    \"FloatRateIndex\",\n    \"IRSSeries\",\n    \"FXIndex\",\n    \"RFRFixing\",\n    \"IBORFixing\",\n    \"IBORStubFixing\",\n    \"IndexFixing\",\n    \"IRSFixing\",\n    \"FXFixing\",\n    \"_FXFixingMajor\",\n    \"_UnitFixing\",\n    \"_BaseFixing\",\n]\n"
  },
  {
    "path": "python/rateslib/data/historical/aud_rfr.csv",
    "content": "﻿reference_date,rate\n04-01-2011,-500\n05-01-2011,-500\n06-01-2011,-500\n07-01-2011,-500\n10-01-2011,-500\n11-01-2011,-500\n12-01-2011,-500\n13-01-2011,-500\n14-01-2011,-500\n17-01-2011,-500\n18-01-2011,-500\n19-01-2011,-500\n20-01-2011,-500\n21-01-2011,-500\n24-01-2011,-500\n25-01-2011,-500\n27-01-2011,-500\n28-01-2011,-500\n31-01-2011,-500\n01-02-2011,-500\n02-02-2011,-500\n03-02-2011,-500\n04-02-2011,-500\n07-02-2011,-500\n08-02-2011,-500\n09-02-2011,-500\n10-02-2011,-500\n11-02-2011,-500\n14-02-2011,-500\n15-02-2011,-500\n16-02-2011,-500\n17-02-2011,-500\n18-02-2011,-500\n21-02-2011,-500\n22-02-2011,-500\n23-02-2011,-500\n24-02-2011,-500\n25-02-2011,-500\n28-02-2011,-500\n01-03-2011,-500\n02-03-2011,-500\n03-03-2011,-500\n04-03-2011,-500\n07-03-2011,-500\n08-03-2011,-500\n09-03-2011,-500\n10-03-2011,-500\n11-03-2011,-500\n14-03-2011,-500\n15-03-2011,-500\n16-03-2011,-500\n17-03-2011,-500\n18-03-2011,-500\n21-03-2011,-500\n22-03-2011,-500\n23-03-2011,-500\n24-03-2011,-500\n25-03-2011,-500\n28-03-2011,-500\n29-03-2011,-500\n30-03-2011,-500\n31-03-2011,-500\n01-04-2011,-500\n04-04-2011,-500\n05-04-2011,-500\n06-04-2011,-500\n07-04-2011,-500\n08-04-2011,-500\n11-04-2011,-500\n12-04-2011,-500\n13-04-2011,-500\n14-04-2011,-500\n15-04-2011,-500\n18-04-2011,-500\n19-04-2011,-500\n20-04-2011,-500\n21-04-2011,-500\n27-04-2011,-500\n28-04-2011,-500\n29-04-2011,-500\n02-05-2011,-500\n03-05-2011,-500\n04-05-2011,-500\n05-05-2011,-500\n06-05-2011,-500\n09-05-2011,-500\n10-05-2011,-500\n11-05-2011,-500\n12-05-2011,-500\n13-05-2011,-500\n16-05-2011,-500\n17-05-2011,-500\n18-05-2011,-500\n19-05-2011,-500\n20-05-2011,-500\n23-05-2011,-500\n24-05-2011,-500\n25-05-2011,-500\n26-05-2011,-500\n27-05-2011,-500\n30-05-2011,-500\n31-05-2011,-500\n01-06-2011,-500\n02-06-2011,-500\n03-06-2011,-500\n06-06-2011,-500\n07-06-2011,-500\n08-06-2011,-500\n09-06-2011,-500\n10-06-2011,-500\n14-06-2011,-500\n15-06-2011,-500\n16-06-2011,-500\n17-06-2011,-500\n20-06-2011,-500\n21-06-2011,-500\n22-06-2011,-500\n23-06-2011,-500\n24-06-2011,-500\n27-06-2011,-500\n28-06-2011,-500\n29-06-2011,-500\n30-06-2011,-500\n01-07-2011,-500\n04-07-2011,-500\n05-07-2011,-500\n06-07-2011,-500\n07-07-2011,-500\n08-07-2011,-500\n11-07-2011,-500\n12-07-2011,-500\n13-07-2011,-500\n14-07-2011,-500\n15-07-2011,-500\n18-07-2011,-500\n19-07-2011,-500\n20-07-2011,-500\n21-07-2011,-500\n22-07-2011,-500\n25-07-2011,-500\n26-07-2011,-500\n27-07-2011,-500\n28-07-2011,-500\n29-07-2011,-500\n01-08-2011,-500\n02-08-2011,-500\n03-08-2011,-500\n04-08-2011,-500\n05-08-2011,-500\n08-08-2011,-500\n09-08-2011,-500\n10-08-2011,-500\n11-08-2011,-500\n12-08-2011,-500\n15-08-2011,-500\n16-08-2011,-500\n17-08-2011,-500\n18-08-2011,-500\n19-08-2011,-500\n22-08-2011,-500\n23-08-2011,-500\n24-08-2011,-500\n25-08-2011,-500\n26-08-2011,-500\n29-08-2011,-500\n30-08-2011,-500\n31-08-2011,-500\n01-09-2011,-500\n02-09-2011,-500\n05-09-2011,-500\n06-09-2011,-500\n07-09-2011,-500\n08-09-2011,-500\n09-09-2011,-500\n12-09-2011,-500\n13-09-2011,-500\n14-09-2011,-500\n15-09-2011,-500\n16-09-2011,-500\n19-09-2011,-500\n20-09-2011,-500\n21-09-2011,-500\n22-09-2011,-500\n23-09-2011,-500\n26-09-2011,-500\n27-09-2011,-500\n28-09-2011,-500\n29-09-2011,-500\n30-09-2011,-500\n03-10-2011,-500\n04-10-2011,-500\n05-10-2011,-500\n06-10-2011,-500\n07-10-2011,-500\n10-10-2011,-500\n11-10-2011,-500\n12-10-2011,-500\n13-10-2011,-500\n14-10-2011,-500\n17-10-2011,-500\n18-10-2011,-500\n19-10-2011,-500\n20-10-2011,-500\n21-10-2011,-500\n24-10-2011,-500\n25-10-2011,-500\n26-10-2011,-500\n27-10-2011,-500\n28-10-2011,-500\n31-10-2011,-500\n01-11-2011,-500\n02-11-2011,-500\n03-11-2011,-500\n04-11-2011,-500\n07-11-2011,-500\n08-11-2011,-500\n09-11-2011,-500\n10-11-2011,-500\n11-11-2011,-500\n14-11-2011,-500\n15-11-2011,-500\n16-11-2011,-500\n17-11-2011,-500\n18-11-2011,-500\n21-11-2011,-500\n22-11-2011,-500\n23-11-2011,-500\n24-11-2011,-500\n25-11-2011,-500\n28-11-2011,-500\n29-11-2011,-500\n30-11-2011,-500\n01-12-2011,-500\n02-12-2011,-500\n05-12-2011,-500\n06-12-2011,-500\n07-12-2011,-500\n08-12-2011,-500\n09-12-2011,-500\n12-12-2011,-500\n13-12-2011,-500\n14-12-2011,-500\n15-12-2011,-500\n16-12-2011,-500\n19-12-2011,-500\n20-12-2011,-500\n21-12-2011,-500\n22-12-2011,-500\n23-12-2011,-500\n28-12-2011,-500\n29-12-2011,-500\n30-12-2011,-500\n03-01-2012,-500\n04-01-2012,-500\n05-01-2012,-500\n06-01-2012,-500\n09-01-2012,-500\n10-01-2012,-500\n11-01-2012,-500\n12-01-2012,-500\n13-01-2012,-500\n16-01-2012,-500\n17-01-2012,-500\n18-01-2012,-500\n19-01-2012,-500\n20-01-2012,-500\n23-01-2012,-500\n24-01-2012,-500\n25-01-2012,-500\n27-01-2012,-500\n30-01-2012,-500\n31-01-2012,-500\n01-02-2012,-500\n02-02-2012,-500\n03-02-2012,-500\n06-02-2012,-500\n07-02-2012,-500\n08-02-2012,-500\n09-02-2012,-500\n10-02-2012,-500\n13-02-2012,-500\n14-02-2012,-500\n15-02-2012,-500\n16-02-2012,-500\n17-02-2012,-500\n20-02-2012,-500\n21-02-2012,-500\n22-02-2012,-500\n23-02-2012,-500\n24-02-2012,-500\n27-02-2012,-500\n28-02-2012,-500\n29-02-2012,-500\n01-03-2012,-500\n02-03-2012,-500\n05-03-2012,-500\n06-03-2012,-500\n07-03-2012,-500\n08-03-2012,-500\n09-03-2012,-500\n12-03-2012,-500\n13-03-2012,-500\n14-03-2012,-500\n15-03-2012,-500\n16-03-2012,-500\n19-03-2012,-500\n20-03-2012,-500\n21-03-2012,-500\n22-03-2012,-500\n23-03-2012,-500\n26-03-2012,-500\n27-03-2012,-500\n28-03-2012,-500\n29-03-2012,-500\n30-03-2012,-500\n02-04-2012,-500\n03-04-2012,-500\n04-04-2012,-500\n05-04-2012,-500\n10-04-2012,-500\n11-04-2012,-500\n12-04-2012,-500\n13-04-2012,-500\n16-04-2012,-500\n17-04-2012,-500\n18-04-2012,-500\n19-04-2012,-500\n20-04-2012,-500\n23-04-2012,-500\n24-04-2012,-500\n26-04-2012,-500\n27-04-2012,-500\n30-04-2012,-500\n01-05-2012,-500\n02-05-2012,-500\n03-05-2012,-500\n04-05-2012,-500\n07-05-2012,-500\n08-05-2012,-500\n09-05-2012,-500\n10-05-2012,-500\n11-05-2012,-500\n14-05-2012,-500\n15-05-2012,-500\n16-05-2012,-500\n17-05-2012,-500\n18-05-2012,-500\n21-05-2012,-500\n22-05-2012,-500\n23-05-2012,-500\n24-05-2012,-500\n25-05-2012,-500\n28-05-2012,-500\n29-05-2012,-500\n30-05-2012,-500\n31-05-2012,-500\n01-06-2012,-500\n04-06-2012,-500\n05-06-2012,-500\n06-06-2012,-500\n07-06-2012,-500\n08-06-2012,-500\n12-06-2012,-500\n13-06-2012,-500\n14-06-2012,-500\n15-06-2012,-500\n18-06-2012,-500\n19-06-2012,-500\n20-06-2012,-500\n21-06-2012,-500\n22-06-2012,-500\n25-06-2012,-500\n26-06-2012,-500\n27-06-2012,-500\n28-06-2012,-500\n29-06-2012,-500\n02-07-2012,-500\n03-07-2012,-500\n04-07-2012,-500\n05-07-2012,-500\n06-07-2012,-500\n09-07-2012,-500\n10-07-2012,-500\n11-07-2012,-500\n12-07-2012,-500\n13-07-2012,-500\n16-07-2012,-500\n17-07-2012,-500\n18-07-2012,-500\n19-07-2012,-500\n20-07-2012,-500\n23-07-2012,-500\n24-07-2012,-500\n25-07-2012,-500\n26-07-2012,-500\n27-07-2012,-500\n30-07-2012,-500\n31-07-2012,-500\n01-08-2012,-500\n02-08-2012,-500\n03-08-2012,-500\n06-08-2012,-500\n07-08-2012,-500\n08-08-2012,-500\n09-08-2012,-500\n10-08-2012,-500\n13-08-2012,-500\n14-08-2012,-500\n15-08-2012,-500\n16-08-2012,-500\n17-08-2012,-500\n20-08-2012,-500\n21-08-2012,-500\n22-08-2012,-500\n23-08-2012,-500\n24-08-2012,-500\n27-08-2012,-500\n28-08-2012,-500\n29-08-2012,-500\n30-08-2012,-500\n31-08-2012,-500\n03-09-2012,-500\n04-09-2012,-500\n05-09-2012,-500\n06-09-2012,-500\n07-09-2012,-500\n10-09-2012,-500\n11-09-2012,-500\n12-09-2012,-500\n13-09-2012,-500\n14-09-2012,-500\n17-09-2012,-500\n18-09-2012,-500\n19-09-2012,-500\n20-09-2012,-500\n21-09-2012,-500\n24-09-2012,-500\n25-09-2012,-500\n26-09-2012,-500\n27-09-2012,-500\n28-09-2012,-500\n01-10-2012,-500\n02-10-2012,-500\n03-10-2012,-500\n04-10-2012,-500\n05-10-2012,-500\n08-10-2012,-500\n09-10-2012,-500\n10-10-2012,-500\n11-10-2012,-500\n12-10-2012,-500\n15-10-2012,-500\n16-10-2012,-500\n17-10-2012,-500\n18-10-2012,-500\n19-10-2012,-500\n22-10-2012,-500\n23-10-2012,-500\n24-10-2012,-500\n25-10-2012,-500\n26-10-2012,-500\n29-10-2012,-500\n30-10-2012,-500\n31-10-2012,-500\n01-11-2012,-500\n02-11-2012,-500\n05-11-2012,-500\n06-11-2012,-500\n07-11-2012,-500\n08-11-2012,-500\n09-11-2012,-500\n12-11-2012,-500\n13-11-2012,-500\n14-11-2012,-500\n15-11-2012,-500\n16-11-2012,-500\n19-11-2012,-500\n20-11-2012,-500\n21-11-2012,-500\n22-11-2012,-500\n23-11-2012,-500\n26-11-2012,-500\n27-11-2012,-500\n28-11-2012,-500\n29-11-2012,-500\n30-11-2012,-500\n03-12-2012,-500\n04-12-2012,-500\n05-12-2012,-500\n06-12-2012,-500\n07-12-2012,-500\n10-12-2012,-500\n11-12-2012,-500\n12-12-2012,-500\n13-12-2012,-500\n14-12-2012,-500\n17-12-2012,-500\n18-12-2012,-500\n19-12-2012,-500\n20-12-2012,-500\n21-12-2012,-500\n24-12-2012,-500\n27-12-2012,-500\n28-12-2012,-500\n31-12-2012,-500\n02-01-2013,-500\n03-01-2013,-500\n04-01-2013,-500\n07-01-2013,-500\n08-01-2013,-500\n09-01-2013,-500\n10-01-2013,-500\n11-01-2013,-500\n14-01-2013,-500\n15-01-2013,-500\n16-01-2013,-500\n17-01-2013,-500\n18-01-2013,-500\n21-01-2013,-500\n22-01-2013,-500\n23-01-2013,-500\n24-01-2013,-500\n25-01-2013,-500\n29-01-2013,-500\n30-01-2013,-500\n31-01-2013,-500\n01-02-2013,-500\n04-02-2013,-500\n05-02-2013,-500\n06-02-2013,-500\n07-02-2013,-500\n08-02-2013,-500\n11-02-2013,-500\n12-02-2013,-500\n13-02-2013,-500\n14-02-2013,-500\n15-02-2013,-500\n18-02-2013,-500\n19-02-2013,-500\n20-02-2013,-500\n21-02-2013,-500\n22-02-2013,-500\n25-02-2013,-500\n26-02-2013,-500\n27-02-2013,-500\n28-02-2013,-500\n01-03-2013,-500\n04-03-2013,-500\n05-03-2013,-500\n06-03-2013,-500\n07-03-2013,-500\n08-03-2013,-500\n11-03-2013,-500\n12-03-2013,-500\n13-03-2013,-500\n14-03-2013,-500\n15-03-2013,-500\n18-03-2013,-500\n19-03-2013,-500\n20-03-2013,-500\n21-03-2013,-500\n22-03-2013,-500\n25-03-2013,-500\n26-03-2013,-500\n27-03-2013,-500\n28-03-2013,-500\n02-04-2013,-500\n03-04-2013,-500\n04-04-2013,-500\n05-04-2013,-500\n08-04-2013,-500\n09-04-2013,-500\n10-04-2013,-500\n11-04-2013,-500\n12-04-2013,-500\n15-04-2013,-500\n16-04-2013,-500\n17-04-2013,-500\n18-04-2013,-500\n19-04-2013,-500\n22-04-2013,-500\n23-04-2013,-500\n24-04-2013,-500\n26-04-2013,-500\n29-04-2013,-500\n30-04-2013,-500\n01-05-2013,-500\n02-05-2013,-500\n03-05-2013,-500\n06-05-2013,-500\n07-05-2013,-500\n08-05-2013,-500\n09-05-2013,-500\n10-05-2013,-500\n13-05-2013,-500\n14-05-2013,-500\n15-05-2013,-500\n16-05-2013,-500\n17-05-2013,-500\n20-05-2013,-500\n21-05-2013,-500\n22-05-2013,-500\n23-05-2013,-500\n24-05-2013,-500\n27-05-2013,-500\n28-05-2013,-500\n29-05-2013,-500\n30-05-2013,-500\n31-05-2013,-500\n03-06-2013,-500\n04-06-2013,-500\n05-06-2013,-500\n06-06-2013,-500\n07-06-2013,-500\n11-06-2013,-500\n12-06-2013,-500\n13-06-2013,-500\n14-06-2013,-500\n17-06-2013,-500\n18-06-2013,-500\n19-06-2013,-500\n20-06-2013,-500\n21-06-2013,-500\n24-06-2013,-500\n25-06-2013,-500\n26-06-2013,-500\n27-06-2013,-500\n28-06-2013,-500\n01-07-2013,-500\n02-07-2013,-500\n03-07-2013,-500\n04-07-2013,-500\n05-07-2013,-500\n08-07-2013,-500\n09-07-2013,-500\n10-07-2013,-500\n11-07-2013,-500\n12-07-2013,-500\n15-07-2013,-500\n16-07-2013,-500\n17-07-2013,-500\n18-07-2013,-500\n19-07-2013,-500\n22-07-2013,-500\n23-07-2013,-500\n24-07-2013,-500\n25-07-2013,-500\n26-07-2013,-500\n29-07-2013,-500\n30-07-2013,-500\n31-07-2013,-500\n01-08-2013,-500\n02-08-2013,-500\n05-08-2013,-500\n06-08-2013,-500\n07-08-2013,-500\n08-08-2013,-500\n09-08-2013,-500\n12-08-2013,-500\n13-08-2013,-500\n14-08-2013,-500\n15-08-2013,-500\n16-08-2013,-500\n19-08-2013,-500\n20-08-2013,-500\n21-08-2013,-500\n22-08-2013,-500\n23-08-2013,-500\n26-08-2013,-500\n27-08-2013,-500\n28-08-2013,-500\n29-08-2013,-500\n30-08-2013,-500\n02-09-2013,-500\n03-09-2013,-500\n04-09-2013,-500\n05-09-2013,-500\n06-09-2013,-500\n09-09-2013,-500\n10-09-2013,-500\n11-09-2013,-500\n12-09-2013,-500\n13-09-2013,-500\n16-09-2013,-500\n17-09-2013,-500\n18-09-2013,-500\n19-09-2013,-500\n20-09-2013,-500\n23-09-2013,-500\n24-09-2013,-500\n25-09-2013,-500\n26-09-2013,-500\n27-09-2013,-500\n30-09-2013,-500\n01-10-2013,-500\n02-10-2013,-500\n03-10-2013,-500\n04-10-2013,-500\n07-10-2013,-500\n08-10-2013,-500\n09-10-2013,-500\n10-10-2013,-500\n11-10-2013,-500\n14-10-2013,-500\n15-10-2013,-500\n16-10-2013,-500\n17-10-2013,-500\n18-10-2013,-500\n21-10-2013,-500\n22-10-2013,-500\n23-10-2013,-500\n24-10-2013,-500\n25-10-2013,-500\n28-10-2013,-500\n29-10-2013,-500\n30-10-2013,-500\n31-10-2013,-500\n01-11-2013,-500\n04-11-2013,-500\n05-11-2013,-500\n06-11-2013,-500\n07-11-2013,-500\n08-11-2013,-500\n11-11-2013,-500\n12-11-2013,-500\n13-11-2013,-500\n14-11-2013,-500\n15-11-2013,-500\n18-11-2013,-500\n19-11-2013,-500\n20-11-2013,-500\n21-11-2013,-500\n22-11-2013,-500\n25-11-2013,-500\n26-11-2013,-500\n27-11-2013,-500\n28-11-2013,-500\n29-11-2013,-500\n02-12-2013,-500\n03-12-2013,-500\n04-12-2013,-500\n05-12-2013,-500\n06-12-2013,-500\n09-12-2013,-500\n10-12-2013,-500\n11-12-2013,-500\n12-12-2013,-500\n13-12-2013,-500\n16-12-2013,-500\n17-12-2013,-500\n18-12-2013,-500\n19-12-2013,-500\n20-12-2013,-500\n23-12-2013,-500\n24-12-2013,-500\n27-12-2013,-500\n30-12-2013,-500\n31-12-2013,-500\n02-01-2014,-500\n03-01-2014,-500\n06-01-2014,-500\n07-01-2014,-500\n08-01-2014,-500\n09-01-2014,-500\n10-01-2014,-500\n13-01-2014,-500\n14-01-2014,-500\n15-01-2014,-500\n16-01-2014,-500\n17-01-2014,-500\n20-01-2014,-500\n21-01-2014,-500\n22-01-2014,-500\n23-01-2014,-500\n24-01-2014,-500\n28-01-2014,-500\n29-01-2014,-500\n30-01-2014,-500\n31-01-2014,-500\n03-02-2014,-500\n04-02-2014,-500\n05-02-2014,-500\n06-02-2014,-500\n07-02-2014,-500\n10-02-2014,-500\n11-02-2014,-500\n12-02-2014,-500\n13-02-2014,-500\n14-02-2014,-500\n17-02-2014,-500\n18-02-2014,-500\n19-02-2014,-500\n20-02-2014,-500\n21-02-2014,-500\n24-02-2014,-500\n25-02-2014,-500\n26-02-2014,-500\n27-02-2014,-500\n28-02-2014,-500\n03-03-2014,-500\n04-03-2014,-500\n05-03-2014,-500\n06-03-2014,-500\n07-03-2014,-500\n10-03-2014,-500\n11-03-2014,-500\n12-03-2014,-500\n13-03-2014,-500\n14-03-2014,-500\n17-03-2014,-500\n18-03-2014,-500\n19-03-2014,-500\n20-03-2014,-500\n21-03-2014,-500\n24-03-2014,-500\n25-03-2014,-500\n26-03-2014,-500\n27-03-2014,-500\n28-03-2014,-500\n31-03-2014,-500\n01-04-2014,-500\n02-04-2014,-500\n03-04-2014,-500\n04-04-2014,-500\n07-04-2014,-500\n08-04-2014,-500\n09-04-2014,-500\n10-04-2014,-500\n11-04-2014,-500\n14-04-2014,-500\n15-04-2014,-500\n16-04-2014,-500\n17-04-2014,-500\n22-04-2014,-500\n23-04-2014,-500\n24-04-2014,-500\n28-04-2014,-500\n29-04-2014,-500\n30-04-2014,-500\n01-05-2014,-500\n02-05-2014,-500\n05-05-2014,-500\n06-05-2014,-500\n07-05-2014,-500\n08-05-2014,-500\n09-05-2014,-500\n12-05-2014,-500\n13-05-2014,-500\n14-05-2014,-500\n15-05-2014,-500\n16-05-2014,-500\n19-05-2014,-500\n20-05-2014,-500\n21-05-2014,-500\n22-05-2014,-500\n23-05-2014,-500\n26-05-2014,-500\n27-05-2014,-500\n28-05-2014,-500\n29-05-2014,-500\n30-05-2014,-500\n02-06-2014,-500\n03-06-2014,-500\n04-06-2014,-500\n05-06-2014,-500\n06-06-2014,-500\n10-06-2014,-500\n11-06-2014,-500\n12-06-2014,-500\n13-06-2014,-500\n16-06-2014,-500\n17-06-2014,-500\n18-06-2014,-500\n19-06-2014,-500\n20-06-2014,-500\n23-06-2014,-500\n24-06-2014,-500\n25-06-2014,-500\n26-06-2014,-500\n27-06-2014,-500\n30-06-2014,-500\n01-07-2014,-500\n02-07-2014,-500\n03-07-2014,-500\n04-07-2014,-500\n07-07-2014,-500\n08-07-2014,-500\n09-07-2014,-500\n10-07-2014,-500\n11-07-2014,-500\n14-07-2014,-500\n15-07-2014,-500\n16-07-2014,-500\n17-07-2014,-500\n18-07-2014,-500\n21-07-2014,-500\n22-07-2014,-500\n23-07-2014,-500\n24-07-2014,-500\n25-07-2014,-500\n28-07-2014,-500\n29-07-2014,-500\n30-07-2014,-500\n31-07-2014,-500\n01-08-2014,-500\n04-08-2014,-500\n05-08-2014,-500\n06-08-2014,-500\n07-08-2014,-500\n08-08-2014,-500\n11-08-2014,-500\n12-08-2014,-500\n13-08-2014,-500\n14-08-2014,-500\n15-08-2014,-500\n18-08-2014,-500\n19-08-2014,-500\n20-08-2014,-500\n21-08-2014,-500\n22-08-2014,-500\n25-08-2014,-500\n26-08-2014,-500\n27-08-2014,-500\n28-08-2014,-500\n29-08-2014,-500\n01-09-2014,-500\n02-09-2014,-500\n03-09-2014,-500\n04-09-2014,-500\n05-09-2014,-500\n08-09-2014,-500\n09-09-2014,-500\n10-09-2014,-500\n11-09-2014,-500\n12-09-2014,-500\n15-09-2014,-500\n16-09-2014,-500\n17-09-2014,-500\n18-09-2014,-500\n19-09-2014,-500\n22-09-2014,-500\n23-09-2014,-500\n24-09-2014,-500\n25-09-2014,-500\n26-09-2014,-500\n29-09-2014,-500\n30-09-2014,-500\n01-10-2014,-500\n02-10-2014,-500\n03-10-2014,-500\n06-10-2014,-500\n07-10-2014,-500\n08-10-2014,-500\n09-10-2014,-500\n10-10-2014,-500\n13-10-2014,-500\n14-10-2014,-500\n15-10-2014,-500\n16-10-2014,-500\n17-10-2014,-500\n20-10-2014,-500\n21-10-2014,-500\n22-10-2014,-500\n23-10-2014,-500\n24-10-2014,-500\n27-10-2014,-500\n28-10-2014,-500\n29-10-2014,-500\n30-10-2014,-500\n31-10-2014,-500\n03-11-2014,-500\n04-11-2014,-500\n05-11-2014,-500\n06-11-2014,-500\n07-11-2014,-500\n10-11-2014,-500\n11-11-2014,-500\n12-11-2014,-500\n13-11-2014,-500\n14-11-2014,-500\n17-11-2014,-500\n18-11-2014,-500\n19-11-2014,-500\n20-11-2014,-500\n21-11-2014,-500\n24-11-2014,-500\n25-11-2014,-500\n26-11-2014,-500\n27-11-2014,-500\n28-11-2014,-500\n01-12-2014,-500\n02-12-2014,-500\n03-12-2014,-500\n04-12-2014,-500\n05-12-2014,-500\n08-12-2014,-500\n09-12-2014,-500\n10-12-2014,-500\n11-12-2014,-500\n12-12-2014,-500\n15-12-2014,-500\n16-12-2014,-500\n17-12-2014,-500\n18-12-2014,-500\n19-12-2014,-500\n22-12-2014,-500\n23-12-2014,-500\n24-12-2014,-500\n29-12-2014,-500\n30-12-2014,-500\n31-12-2014,-500\n02-01-2015,-500\n05-01-2015,-500\n06-01-2015,-500\n07-01-2015,-500\n08-01-2015,-500\n09-01-2015,-500\n12-01-2015,-500\n13-01-2015,-500\n14-01-2015,-500\n15-01-2015,-500\n16-01-2015,-500\n19-01-2015,-500\n20-01-2015,-500\n21-01-2015,-500\n22-01-2015,-500\n23-01-2015,-500\n27-01-2015,-500\n28-01-2015,-500\n29-01-2015,-500\n30-01-2015,-500\n02-02-2015,-500\n03-02-2015,-500\n04-02-2015,-500\n05-02-2015,-500\n06-02-2015,-500\n09-02-2015,-500\n10-02-2015,-500\n11-02-2015,-500\n12-02-2015,-500\n13-02-2015,-500\n16-02-2015,-500\n17-02-2015,-500\n18-02-2015,-500\n19-02-2015,-500\n20-02-2015,-500\n23-02-2015,-500\n24-02-2015,-500\n25-02-2015,-500\n26-02-2015,-500\n27-02-2015,-500\n02-03-2015,-500\n03-03-2015,-500\n04-03-2015,-500\n05-03-2015,-500\n06-03-2015,-500\n09-03-2015,-500\n10-03-2015,-500\n11-03-2015,-500\n12-03-2015,-500\n13-03-2015,-500\n16-03-2015,-500\n17-03-2015,-500\n18-03-2015,-500\n19-03-2015,-500\n20-03-2015,-500\n23-03-2015,-500\n24-03-2015,-500\n25-03-2015,-500\n26-03-2015,-500\n27-03-2015,-500\n30-03-2015,-500\n31-03-2015,-500\n01-04-2015,-500\n02-04-2015,-500\n07-04-2015,-500\n08-04-2015,-500\n09-04-2015,-500\n10-04-2015,-500\n13-04-2015,-500\n14-04-2015,-500\n15-04-2015,-500\n16-04-2015,-500\n17-04-2015,-500\n20-04-2015,-500\n21-04-2015,-500\n22-04-2015,-500\n23-04-2015,-500\n24-04-2015,-500\n27-04-2015,-500\n28-04-2015,-500\n29-04-2015,-500\n30-04-2015,-500\n01-05-2015,-500\n04-05-2015,-500\n05-05-2015,-500\n06-05-2015,-500\n07-05-2015,-500\n08-05-2015,-500\n11-05-2015,-500\n12-05-2015,-500\n13-05-2015,-500\n14-05-2015,-500\n15-05-2015,-500\n18-05-2015,-500\n19-05-2015,-500\n20-05-2015,-500\n21-05-2015,-500\n22-05-2015,-500\n25-05-2015,-500\n26-05-2015,-500\n27-05-2015,-500\n28-05-2015,-500\n29-05-2015,-500\n01-06-2015,-500\n02-06-2015,-500\n03-06-2015,-500\n04-06-2015,-500\n05-06-2015,-500\n09-06-2015,-500\n10-06-2015,-500\n11-06-2015,-500\n12-06-2015,-500\n15-06-2015,-500\n16-06-2015,-500\n17-06-2015,-500\n18-06-2015,-500\n19-06-2015,-500\n22-06-2015,-500\n23-06-2015,-500\n24-06-2015,-500\n25-06-2015,-500\n26-06-2015,-500\n29-06-2015,-500\n30-06-2015,-500\n01-07-2015,-500\n02-07-2015,-500\n03-07-2015,-500\n06-07-2015,-500\n07-07-2015,-500\n08-07-2015,-500\n09-07-2015,-500\n10-07-2015,-500\n13-07-2015,-500\n14-07-2015,-500\n15-07-2015,-500\n16-07-2015,-500\n17-07-2015,-500\n20-07-2015,-500\n21-07-2015,-500\n22-07-2015,-500\n23-07-2015,-500\n24-07-2015,-500\n27-07-2015,-500\n28-07-2015,-500\n29-07-2015,-500\n30-07-2015,-500\n31-07-2015,-500\n03-08-2015,-500\n04-08-2015,-500\n05-08-2015,-500\n06-08-2015,-500\n07-08-2015,-500\n10-08-2015,-500\n11-08-2015,-500\n12-08-2015,-500\n13-08-2015,-500\n14-08-2015,-500\n17-08-2015,-500\n18-08-2015,-500\n19-08-2015,-500\n20-08-2015,-500\n21-08-2015,-500\n24-08-2015,-500\n25-08-2015,-500\n26-08-2015,-500\n27-08-2015,-500\n28-08-2015,-500\n31-08-2015,-500\n01-09-2015,-500\n02-09-2015,-500\n03-09-2015,-500\n04-09-2015,-500\n07-09-2015,-500\n08-09-2015,-500\n09-09-2015,-500\n10-09-2015,-500\n11-09-2015,-500\n14-09-2015,-500\n15-09-2015,-500\n16-09-2015,-500\n17-09-2015,-500\n18-09-2015,-500\n21-09-2015,-500\n22-09-2015,-500\n23-09-2015,-500\n24-09-2015,-500\n25-09-2015,-500\n28-09-2015,-500\n29-09-2015,-500\n30-09-2015,-500\n01-10-2015,-500\n02-10-2015,-500\n05-10-2015,-500\n06-10-2015,-500\n07-10-2015,-500\n08-10-2015,-500\n09-10-2015,-500\n12-10-2015,-500\n13-10-2015,-500\n14-10-2015,-500\n15-10-2015,-500\n16-10-2015,-500\n19-10-2015,-500\n20-10-2015,-500\n21-10-2015,-500\n22-10-2015,-500\n23-10-2015,-500\n26-10-2015,-500\n27-10-2015,-500\n28-10-2015,-500\n29-10-2015,-500\n30-10-2015,-500\n02-11-2015,-500\n03-11-2015,-500\n04-11-2015,-500\n05-11-2015,-500\n06-11-2015,-500\n09-11-2015,-500\n10-11-2015,-500\n11-11-2015,-500\n12-11-2015,-500\n13-11-2015,-500\n16-11-2015,-500\n17-11-2015,-500\n18-11-2015,-500\n19-11-2015,-500\n20-11-2015,-500\n23-11-2015,-500\n24-11-2015,-500\n25-11-2015,-500\n26-11-2015,-500\n27-11-2015,-500\n30-11-2015,-500\n01-12-2015,-500\n02-12-2015,-500\n03-12-2015,-500\n04-12-2015,-500\n07-12-2015,-500\n08-12-2015,-500\n09-12-2015,-500\n10-12-2015,-500\n11-12-2015,-500\n14-12-2015,-500\n15-12-2015,-500\n16-12-2015,-500\n17-12-2015,-500\n18-12-2015,-500\n21-12-2015,-500\n22-12-2015,-500\n23-12-2015,-500\n24-12-2015,-500\n29-12-2015,-500\n30-12-2015,-500\n31-12-2015,-500\n04-01-2016,-500\n05-01-2016,-500\n06-01-2016,-500\n07-01-2016,-500\n08-01-2016,-500\n11-01-2016,-500\n12-01-2016,-500\n13-01-2016,-500\n14-01-2016,-500\n15-01-2016,-500\n18-01-2016,-500\n19-01-2016,-500\n20-01-2016,-500\n21-01-2016,-500\n22-01-2016,-500\n25-01-2016,-500\n27-01-2016,-500\n28-01-2016,-500\n29-01-2016,-500\n01-02-2016,-500\n02-02-2016,-500\n03-02-2016,-500\n04-02-2016,-500\n05-02-2016,-500\n08-02-2016,-500\n09-02-2016,-500\n10-02-2016,-500\n11-02-2016,-500\n12-02-2016,-500\n15-02-2016,-500\n16-02-2016,-500\n17-02-2016,-500\n18-02-2016,-500\n19-02-2016,-500\n22-02-2016,-500\n23-02-2016,-500\n24-02-2016,-500\n25-02-2016,-500\n26-02-2016,-500\n29-02-2016,-500\n01-03-2016,-500\n02-03-2016,-500\n03-03-2016,-500\n04-03-2016,-500\n07-03-2016,-500\n08-03-2016,-500\n09-03-2016,-500\n10-03-2016,-500\n11-03-2016,-500\n14-03-2016,-500\n15-03-2016,-500\n16-03-2016,-500\n17-03-2016,-500\n18-03-2016,-500\n21-03-2016,-500\n22-03-2016,-500\n23-03-2016,-500\n24-03-2016,-500\n29-03-2016,-500\n30-03-2016,-500\n31-03-2016,-500\n01-04-2016,-500\n04-04-2016,-500\n05-04-2016,-500\n06-04-2016,-500\n07-04-2016,-500\n08-04-2016,-500\n11-04-2016,-500\n12-04-2016,-500\n13-04-2016,-500\n14-04-2016,-500\n15-04-2016,-500\n18-04-2016,-500\n19-04-2016,-500\n20-04-2016,-500\n21-04-2016,-500\n22-04-2016,-500\n26-04-2016,-500\n27-04-2016,-500\n28-04-2016,-500\n29-04-2016,-500\n02-05-2016,-500\n03-05-2016,-500\n04-05-2016,-500\n05-05-2016,-500\n06-05-2016,-500\n09-05-2016,-500\n10-05-2016,-500\n11-05-2016,-500\n12-05-2016,-500\n13-05-2016,-500\n16-05-2016,-500\n17-05-2016,-500\n18-05-2016,-500\n19-05-2016,-500\n20-05-2016,-500\n23-05-2016,-500\n24-05-2016,-500\n25-05-2016,-500\n26-05-2016,-500\n27-05-2016,-500\n30-05-2016,-500\n31-05-2016,-500\n01-06-2016,-500\n02-06-2016,-500\n03-06-2016,-500\n06-06-2016,-500\n07-06-2016,-500\n08-06-2016,-500\n09-06-2016,-500\n10-06-2016,-500\n14-06-2016,-500\n15-06-2016,-500\n16-06-2016,-500\n17-06-2016,-500\n20-06-2016,-500\n21-06-2016,-500\n22-06-2016,-500\n23-06-2016,-500\n24-06-2016,-500\n27-06-2016,-500\n28-06-2016,-500\n29-06-2016,-500\n30-06-2016,-500\n01-07-2016,-500\n04-07-2016,-500\n05-07-2016,-500\n06-07-2016,-500\n07-07-2016,-500\n08-07-2016,-500\n11-07-2016,-500\n12-07-2016,-500\n13-07-2016,-500\n14-07-2016,-500\n15-07-2016,-500\n18-07-2016,-500\n19-07-2016,-500\n20-07-2016,-500\n21-07-2016,-500\n22-07-2016,-500\n25-07-2016,-500\n26-07-2016,-500\n27-07-2016,-500\n28-07-2016,-500\n29-07-2016,-500\n01-08-2016,-500\n02-08-2016,-500\n03-08-2016,-500\n04-08-2016,-500\n05-08-2016,-500\n08-08-2016,-500\n09-08-2016,-500\n10-08-2016,-500\n11-08-2016,-500\n12-08-2016,-500\n15-08-2016,-500\n16-08-2016,-500\n17-08-2016,-500\n18-08-2016,-500\n19-08-2016,-500\n22-08-2016,-500\n23-08-2016,-500\n24-08-2016,-500\n25-08-2016,-500\n26-08-2016,-500\n29-08-2016,-500\n30-08-2016,-500\n31-08-2016,-500\n01-09-2016,-500\n02-09-2016,-500\n05-09-2016,-500\n06-09-2016,-500\n07-09-2016,-500\n08-09-2016,-500\n09-09-2016,-500\n12-09-2016,-500\n13-09-2016,-500\n14-09-2016,-500\n15-09-2016,-500\n16-09-2016,-500\n19-09-2016,-500\n20-09-2016,-500\n21-09-2016,-500\n22-09-2016,-500\n23-09-2016,-500\n26-09-2016,-500\n27-09-2016,-500\n28-09-2016,-500\n29-09-2016,-500\n30-09-2016,-500\n03-10-2016,-500\n04-10-2016,-500\n05-10-2016,-500\n06-10-2016,-500\n07-10-2016,-500\n10-10-2016,-500\n11-10-2016,-500\n12-10-2016,-500\n13-10-2016,-500\n14-10-2016,-500\n17-10-2016,-500\n18-10-2016,-500\n19-10-2016,-500\n20-10-2016,-500\n21-10-2016,-500\n24-10-2016,-500\n25-10-2016,-500\n26-10-2016,-500\n27-10-2016,-500\n28-10-2016,-500\n31-10-2016,-500\n01-11-2016,-500\n02-11-2016,-500\n03-11-2016,-500\n04-11-2016,-500\n07-11-2016,-500\n08-11-2016,-500\n09-11-2016,-500\n10-11-2016,-500\n11-11-2016,-500\n14-11-2016,-500\n15-11-2016,-500\n16-11-2016,-500\n17-11-2016,-500\n18-11-2016,-500\n21-11-2016,-500\n22-11-2016,-500\n23-11-2016,-500\n24-11-2016,-500\n25-11-2016,-500\n28-11-2016,-500\n29-11-2016,-500\n30-11-2016,-500\n01-12-2016,-500\n02-12-2016,-500\n05-12-2016,-500\n06-12-2016,-500\n07-12-2016,-500\n08-12-2016,-500\n09-12-2016,-500\n12-12-2016,-500\n13-12-2016,-500\n14-12-2016,-500\n15-12-2016,-500\n16-12-2016,-500\n19-12-2016,-500\n20-12-2016,-500\n21-12-2016,-500\n22-12-2016,-500\n23-12-2016,-500\n28-12-2016,-500\n29-12-2016,-500\n30-12-2016,-500\n03-01-2017,-500\n04-01-2017,-500\n05-01-2017,-500\n06-01-2017,-500\n09-01-2017,-500\n10-01-2017,-500\n11-01-2017,-500\n12-01-2017,-500\n13-01-2017,-500\n16-01-2017,-500\n17-01-2017,-500\n18-01-2017,-500\n19-01-2017,-500\n20-01-2017,-500\n23-01-2017,-500\n24-01-2017,-500\n25-01-2017,-500\n27-01-2017,-500\n30-01-2017,-500\n31-01-2017,-500\n01-02-2017,-500\n02-02-2017,-500\n03-02-2017,-500\n06-02-2017,-500\n07-02-2017,-500\n08-02-2017,-500\n09-02-2017,-500\n10-02-2017,-500\n13-02-2017,-500\n14-02-2017,-500\n15-02-2017,-500\n16-02-2017,-500\n17-02-2017,-500\n20-02-2017,-500\n21-02-2017,-500\n22-02-2017,-500\n23-02-2017,-500\n24-02-2017,-500\n27-02-2017,-500\n28-02-2017,-500\n01-03-2017,-500\n02-03-2017,-500\n03-03-2017,-500\n06-03-2017,-500\n07-03-2017,-500\n08-03-2017,-500\n09-03-2017,-500\n10-03-2017,-500\n13-03-2017,-500\n14-03-2017,-500\n15-03-2017,-500\n16-03-2017,-500\n17-03-2017,-500\n20-03-2017,-500\n21-03-2017,-500\n22-03-2017,-500\n23-03-2017,-500\n24-03-2017,-500\n27-03-2017,-500\n28-03-2017,-500\n29-03-2017,-500\n30-03-2017,-500\n31-03-2017,-500\n03-04-2017,-500\n04-04-2017,-500\n05-04-2017,-500\n06-04-2017,-500\n07-04-2017,-500\n10-04-2017,-500\n11-04-2017,-500\n12-04-2017,-500\n13-04-2017,-500\n18-04-2017,-500\n19-04-2017,-500\n20-04-2017,-500\n21-04-2017,-500\n24-04-2017,-500\n26-04-2017,-500\n27-04-2017,-500\n28-04-2017,-500\n01-05-2017,-500\n02-05-2017,-500\n03-05-2017,-500\n04-05-2017,-500\n05-05-2017,-500\n08-05-2017,-500\n09-05-2017,-500\n10-05-2017,-500\n11-05-2017,-500\n12-05-2017,-500\n15-05-2017,-500\n16-05-2017,-500\n17-05-2017,-500\n18-05-2017,-500\n19-05-2017,-500\n22-05-2017,-500\n23-05-2017,-500\n24-05-2017,-500\n25-05-2017,-500\n26-05-2017,-500\n29-05-2017,-500\n30-05-2017,-500\n31-05-2017,-500\n01-06-2017,-500\n02-06-2017,-500\n05-06-2017,-500\n06-06-2017,-500\n07-06-2017,-500\n08-06-2017,-500\n09-06-2017,-500\n13-06-2017,-500\n14-06-2017,-500\n15-06-2017,-500\n16-06-2017,-500\n19-06-2017,-500\n20-06-2017,-500\n21-06-2017,-500\n22-06-2017,-500\n23-06-2017,-500\n26-06-2017,-500\n27-06-2017,-500\n28-06-2017,-500\n29-06-2017,-500\n30-06-2017,-500\n03-07-2017,-500\n04-07-2017,-500\n05-07-2017,-500\n06-07-2017,-500\n07-07-2017,-500\n10-07-2017,-500\n11-07-2017,-500\n12-07-2017,-500\n13-07-2017,-500\n14-07-2017,-500\n17-07-2017,-500\n18-07-2017,-500\n19-07-2017,-500\n20-07-2017,-500\n21-07-2017,-500\n24-07-2017,-500\n25-07-2017,-500\n26-07-2017,-500\n27-07-2017,-500\n28-07-2017,-500\n31-07-2017,-500\n01-08-2017,-500\n02-08-2017,-500\n03-08-2017,-500\n04-08-2017,-500\n07-08-2017,-500\n08-08-2017,-500\n09-08-2017,-500\n10-08-2017,-500\n11-08-2017,-500\n14-08-2017,-500\n15-08-2017,-500\n16-08-2017,-500\n17-08-2017,-500\n18-08-2017,-500\n21-08-2017,-500\n22-08-2017,-500\n23-08-2017,-500\n24-08-2017,-500\n25-08-2017,-500\n28-08-2017,-500\n29-08-2017,-500\n30-08-2017,-500\n31-08-2017,-500\n01-09-2017,-500\n04-09-2017,-500\n05-09-2017,-500\n06-09-2017,-500\n07-09-2017,-500\n08-09-2017,-500\n11-09-2017,-500\n12-09-2017,-500\n13-09-2017,-500\n14-09-2017,-500\n15-09-2017,-500\n18-09-2017,-500\n19-09-2017,-500\n20-09-2017,-500\n21-09-2017,-500\n22-09-2017,-500\n25-09-2017,-500\n26-09-2017,-500\n27-09-2017,-500\n28-09-2017,-500\n29-09-2017,-500\n02-10-2017,-500\n03-10-2017,-500\n04-10-2017,-500\n05-10-2017,-500\n06-10-2017,-500\n09-10-2017,-500\n10-10-2017,-500\n11-10-2017,-500\n12-10-2017,-500\n13-10-2017,-500\n16-10-2017,-500\n17-10-2017,-500\n18-10-2017,-500\n19-10-2017,-500\n20-10-2017,-500\n23-10-2017,-500\n24-10-2017,-500\n25-10-2017,-500\n26-10-2017,-500\n27-10-2017,-500\n30-10-2017,-500\n31-10-2017,-500\n01-11-2017,-500\n02-11-2017,-500\n03-11-2017,-500\n06-11-2017,-500\n07-11-2017,-500\n08-11-2017,-500\n09-11-2017,-500\n10-11-2017,-500\n13-11-2017,-500\n14-11-2017,-500\n15-11-2017,-500\n16-11-2017,-500\n17-11-2017,-500\n20-11-2017,-500\n21-11-2017,-500\n22-11-2017,-500\n23-11-2017,-500\n24-11-2017,-500\n27-11-2017,-500\n28-11-2017,-500\n29-11-2017,-500\n30-11-2017,-500\n01-12-2017,-500\n04-12-2017,-500\n05-12-2017,-500\n06-12-2017,-500\n07-12-2017,-500\n08-12-2017,-500\n11-12-2017,-500\n12-12-2017,-500\n13-12-2017,-500\n14-12-2017,-500\n15-12-2017,-500\n18-12-2017,-500\n19-12-2017,-500\n20-12-2017,-500\n21-12-2017,-500\n22-12-2017,-500\n27-12-2017,-500\n28-12-2017,-500\n29-12-2017,-500\n02-01-2018,-500\n03-01-2018,-500\n04-01-2018,-500\n05-01-2018,-500\n08-01-2018,-500\n09-01-2018,-500\n10-01-2018,-500\n11-01-2018,-500\n12-01-2018,-500\n15-01-2018,-500\n16-01-2018,-500\n17-01-2018,-500\n18-01-2018,-500\n19-01-2018,-500\n22-01-2018,-500\n23-01-2018,-500\n24-01-2018,-500\n25-01-2018,-500\n29-01-2018,-500\n30-01-2018,-500\n31-01-2018,-500\n01-02-2018,-500\n02-02-2018,-500\n05-02-2018,-500\n06-02-2018,-500\n07-02-2018,-500\n08-02-2018,-500\n09-02-2018,-500\n12-02-2018,-500\n13-02-2018,-500\n14-02-2018,-500\n15-02-2018,-500\n16-02-2018,-500\n19-02-2018,-500\n20-02-2018,-500\n21-02-2018,-500\n22-02-2018,-500\n23-02-2018,-500\n26-02-2018,-500\n27-02-2018,-500\n28-02-2018,-500\n01-03-2018,-500\n02-03-2018,-500\n05-03-2018,-500\n06-03-2018,-500\n07-03-2018,-500\n08-03-2018,-500\n09-03-2018,-500\n12-03-2018,-500\n13-03-2018,-500\n14-03-2018,-500\n15-03-2018,-500\n16-03-2018,-500\n19-03-2018,-500\n20-03-2018,-500\n21-03-2018,-500\n22-03-2018,-500\n23-03-2018,-500\n26-03-2018,-500\n27-03-2018,-500\n28-03-2018,-500\n29-03-2018,-500\n03-04-2018,-500\n04-04-2018,-500\n05-04-2018,-500\n06-04-2018,-500\n09-04-2018,-500\n10-04-2018,-500\n11-04-2018,-500\n12-04-2018,-500\n13-04-2018,-500\n16-04-2018,-500\n17-04-2018,-500\n18-04-2018,-500\n19-04-2018,-500\n20-04-2018,-500\n23-04-2018,-500\n24-04-2018,-500\n26-04-2018,-500\n27-04-2018,-500\n30-04-2018,-500\n01-05-2018,-500\n02-05-2018,-500\n03-05-2018,-500\n04-05-2018,-500\n07-05-2018,-500\n08-05-2018,-500\n09-05-2018,-500\n10-05-2018,-500\n11-05-2018,-500\n14-05-2018,-500\n15-05-2018,-500\n16-05-2018,-500\n17-05-2018,-500\n18-05-2018,-500\n21-05-2018,-500\n22-05-2018,-500\n23-05-2018,-500\n24-05-2018,-500\n25-05-2018,-500\n28-05-2018,-500\n29-05-2018,-500\n30-05-2018,-500\n31-05-2018,-500\n01-06-2018,-500\n04-06-2018,-500\n05-06-2018,-500\n06-06-2018,-500\n07-06-2018,-500\n08-06-2018,-500\n12-06-2018,-500\n13-06-2018,-500\n14-06-2018,-500\n15-06-2018,-500\n18-06-2018,-500\n19-06-2018,-500\n20-06-2018,-500\n21-06-2018,-500\n22-06-2018,-500\n25-06-2018,-500\n26-06-2018,-500\n27-06-2018,-500\n28-06-2018,-500\n29-06-2018,-500\n02-07-2018,-500\n03-07-2018,-500\n04-07-2018,-500\n05-07-2018,-500\n06-07-2018,-500\n09-07-2018,-500\n10-07-2018,-500\n11-07-2018,-500\n12-07-2018,-500\n13-07-2018,-500\n16-07-2018,-500\n17-07-2018,-500\n18-07-2018,-500\n19-07-2018,-500\n20-07-2018,-500\n23-07-2018,-500\n24-07-2018,-500\n25-07-2018,-500\n26-07-2018,-500\n27-07-2018,-500\n30-07-2018,-500\n31-07-2018,-500\n01-08-2018,-500\n02-08-2018,-500\n03-08-2018,-500\n06-08-2018,-500\n07-08-2018,-500\n08-08-2018,-500\n09-08-2018,-500\n10-08-2018,-500\n13-08-2018,-500\n14-08-2018,-500\n15-08-2018,-500\n16-08-2018,-500\n17-08-2018,-500\n20-08-2018,-500\n21-08-2018,-500\n22-08-2018,-500\n23-08-2018,-500\n24-08-2018,-500\n27-08-2018,-500\n28-08-2018,-500\n29-08-2018,-500\n30-08-2018,-500\n31-08-2018,-500\n03-09-2018,-500\n04-09-2018,-500\n05-09-2018,-500\n06-09-2018,-500\n07-09-2018,-500\n10-09-2018,-500\n11-09-2018,-500\n12-09-2018,-500\n13-09-2018,-500\n14-09-2018,-500\n17-09-2018,-500\n18-09-2018,-500\n19-09-2018,-500\n20-09-2018,-500\n21-09-2018,-500\n24-09-2018,-500\n25-09-2018,-500\n26-09-2018,-500\n27-09-2018,-500\n28-09-2018,-500\n01-10-2018,-500\n02-10-2018,-500\n03-10-2018,-500\n04-10-2018,-500\n05-10-2018,-500\n08-10-2018,-500\n09-10-2018,-500\n10-10-2018,-500\n11-10-2018,-500\n12-10-2018,-500\n15-10-2018,-500\n16-10-2018,-500\n17-10-2018,-500\n18-10-2018,-500\n19-10-2018,-500\n22-10-2018,-500\n23-10-2018,-500\n24-10-2018,-500\n25-10-2018,-500\n26-10-2018,-500\n29-10-2018,-500\n30-10-2018,-500\n31-10-2018,-500\n01-11-2018,-500\n02-11-2018,-500\n05-11-2018,-500\n06-11-2018,-500\n07-11-2018,-500\n08-11-2018,-500\n09-11-2018,-500\n12-11-2018,-500\n13-11-2018,-500\n14-11-2018,-500\n15-11-2018,-500\n16-11-2018,-500\n19-11-2018,-500\n20-11-2018,-500\n21-11-2018,-500\n22-11-2018,-500\n23-11-2018,-500\n26-11-2018,-500\n27-11-2018,-500\n28-11-2018,-500\n29-11-2018,-500\n30-11-2018,-500\n03-12-2018,-500\n04-12-2018,-500\n05-12-2018,-500\n06-12-2018,-500\n07-12-2018,-500\n10-12-2018,-500\n11-12-2018,-500\n12-12-2018,-500\n13-12-2018,-500\n14-12-2018,-500\n17-12-2018,-500\n18-12-2018,-500\n19-12-2018,-500\n20-12-2018,-500\n21-12-2018,-500\n24-12-2018,-500\n27-12-2018,-500\n28-12-2018,-500\n31-12-2018,-500\n02-01-2019,-500\n03-01-2019,-500\n04-01-2019,-500\n07-01-2019,-500\n08-01-2019,-500\n09-01-2019,-500\n10-01-2019,-500\n11-01-2019,-500\n14-01-2019,-500\n15-01-2019,-500\n16-01-2019,-500\n17-01-2019,-500\n18-01-2019,-500\n21-01-2019,-500\n22-01-2019,-500\n23-01-2019,-500\n24-01-2019,-500\n25-01-2019,-500\n29-01-2019,-500\n30-01-2019,-500\n31-01-2019,-500\n01-02-2019,-500\n04-02-2019,-500\n05-02-2019,-500\n06-02-2019,-500\n07-02-2019,-500\n08-02-2019,-500\n11-02-2019,-500\n12-02-2019,-500\n13-02-2019,-500\n14-02-2019,-500\n15-02-2019,-500\n18-02-2019,-500\n19-02-2019,-500\n20-02-2019,-500\n21-02-2019,-500\n22-02-2019,-500\n25-02-2019,-500\n26-02-2019,-500\n27-02-2019,-500\n28-02-2019,-500\n01-03-2019,-500\n04-03-2019,-500\n05-03-2019,-500\n06-03-2019,-500\n07-03-2019,-500\n08-03-2019,-500\n11-03-2019,-500\n12-03-2019,-500\n13-03-2019,-500\n14-03-2019,-500\n15-03-2019,-500\n18-03-2019,-500\n19-03-2019,-500\n20-03-2019,-500\n21-03-2019,-500\n22-03-2019,-500\n25-03-2019,-500\n26-03-2019,-500\n27-03-2019,-500\n28-03-2019,-500\n29-03-2019,-500\n01-04-2019,-500\n02-04-2019,-500\n03-04-2019,-500\n04-04-2019,-500\n05-04-2019,-500\n08-04-2019,-500\n09-04-2019,-500\n10-04-2019,-500\n11-04-2019,-500\n12-04-2019,-500\n15-04-2019,-500\n16-04-2019,-500\n17-04-2019,-500\n18-04-2019,-500\n23-04-2019,-500\n24-04-2019,-500\n26-04-2019,-500\n29-04-2019,-500\n30-04-2019,-500\n01-05-2019,-500\n02-05-2019,-500\n03-05-2019,-500\n06-05-2019,-500\n07-05-2019,-500\n08-05-2019,-500\n09-05-2019,-500\n10-05-2019,-500\n13-05-2019,-500\n14-05-2019,-500\n15-05-2019,-500\n16-05-2019,-500\n17-05-2019,-500\n20-05-2019,-500\n21-05-2019,-500\n22-05-2019,-500\n23-05-2019,-500\n24-05-2019,-500\n27-05-2019,-500\n28-05-2019,-500\n29-05-2019,-500\n30-05-2019,-500\n31-05-2019,-500\n03-06-2019,-500\n04-06-2019,-500\n05-06-2019,-500\n06-06-2019,-500\n07-06-2019,-500\n11-06-2019,-500\n12-06-2019,-500\n13-06-2019,-500\n14-06-2019,-500\n17-06-2019,-500\n18-06-2019,-500\n19-06-2019,-500\n20-06-2019,-500\n21-06-2019,-500\n24-06-2019,-500\n25-06-2019,-500\n26-06-2019,-500\n27-06-2019,-500\n28-06-2019,-500\n01-07-2019,-500\n02-07-2019,-500\n03-07-2019,-500\n04-07-2019,-500\n05-07-2019,-500\n08-07-2019,-500\n09-07-2019,-500\n10-07-2019,-500\n11-07-2019,-500\n12-07-2019,-500\n15-07-2019,-500\n16-07-2019,-500\n17-07-2019,-500\n18-07-2019,-500\n19-07-2019,-500\n22-07-2019,-500\n23-07-2019,-500\n24-07-2019,-500\n25-07-2019,-500\n26-07-2019,-500\n29-07-2019,-500\n30-07-2019,-500\n31-07-2019,-500\n01-08-2019,-500\n02-08-2019,-500\n05-08-2019,-500\n06-08-2019,-500\n07-08-2019,-500\n08-08-2019,-500\n09-08-2019,-500\n12-08-2019,-500\n13-08-2019,-500\n14-08-2019,-500\n15-08-2019,-500\n16-08-2019,-500\n19-08-2019,-500\n20-08-2019,-500\n21-08-2019,-500\n22-08-2019,-500\n23-08-2019,-500\n26-08-2019,-500\n27-08-2019,-500\n28-08-2019,-500\n29-08-2019,-500\n30-08-2019,-500\n02-09-2019,-500\n03-09-2019,-500\n04-09-2019,-500\n05-09-2019,-500\n06-09-2019,-500\n09-09-2019,-500\n10-09-2019,-500\n11-09-2019,-500\n12-09-2019,-500\n13-09-2019,-500\n16-09-2019,-500\n17-09-2019,-500\n18-09-2019,-500\n19-09-2019,-500\n20-09-2019,-500\n23-09-2019,-500\n24-09-2019,-500\n25-09-2019,-500\n26-09-2019,-500\n27-09-2019,-500\n30-09-2019,-500\n01-10-2019,-500\n02-10-2019,-500\n03-10-2019,-500\n04-10-2019,-500\n07-10-2019,-500\n08-10-2019,-500\n09-10-2019,-500\n10-10-2019,-500\n11-10-2019,-500\n14-10-2019,-500\n15-10-2019,-500\n16-10-2019,-500\n17-10-2019,-500\n18-10-2019,-500\n21-10-2019,-500\n22-10-2019,-500\n23-10-2019,-500\n24-10-2019,-500\n25-10-2019,-500\n28-10-2019,-500\n29-10-2019,-500\n30-10-2019,-500\n31-10-2019,-500\n01-11-2019,-500\n04-11-2019,-500\n05-11-2019,-500\n06-11-2019,-500\n07-11-2019,-500\n08-11-2019,-500\n11-11-2019,-500\n12-11-2019,-500\n13-11-2019,-500\n14-11-2019,-500\n15-11-2019,-500\n18-11-2019,-500\n19-11-2019,-500\n20-11-2019,-500\n21-11-2019,-500\n22-11-2019,-500\n25-11-2019,-500\n26-11-2019,-500\n27-11-2019,-500\n28-11-2019,-500\n29-11-2019,-500\n02-12-2019,-500\n03-12-2019,-500\n04-12-2019,-500\n05-12-2019,-500\n06-12-2019,-500\n09-12-2019,-500\n10-12-2019,-500\n11-12-2019,-500\n12-12-2019,-500\n13-12-2019,-500\n16-12-2019,-500\n17-12-2019,-500\n18-12-2019,-500\n19-12-2019,-500\n20-12-2019,-500\n23-12-2019,-500\n24-12-2019,-500\n27-12-2019,-500\n30-12-2019,-500\n31-12-2019,-500\n02-01-2020,-500\n03-01-2020,-500\n06-01-2020,-500\n07-01-2020,-500\n08-01-2020,-500\n09-01-2020,-500\n10-01-2020,-500\n13-01-2020,-500\n14-01-2020,-500\n15-01-2020,-500\n16-01-2020,-500\n17-01-2020,-500\n20-01-2020,-500\n21-01-2020,-500\n22-01-2020,-500\n23-01-2020,-500\n24-01-2020,-500\n28-01-2020,-500\n29-01-2020,-500\n30-01-2020,-500\n31-01-2020,-500\n03-02-2020,-500\n04-02-2020,-500\n05-02-2020,-500\n06-02-2020,-500\n07-02-2020,-500\n10-02-2020,-500\n11-02-2020,-500\n12-02-2020,-500\n13-02-2020,-500\n14-02-2020,-500\n17-02-2020,-500\n18-02-2020,-500\n19-02-2020,-500\n20-02-2020,-500\n21-02-2020,-500\n24-02-2020,-500\n25-02-2020,-500\n26-02-2020,-500\n27-02-2020,-500\n28-02-2020,-500\n02-03-2020,-500\n03-03-2020,-500\n04-03-2020,-500\n05-03-2020,-500\n06-03-2020,-500\n09-03-2020,-500\n10-03-2020,-500\n11-03-2020,-500\n12-03-2020,-500\n13-03-2020,-500\n16-03-2020,-500\n17-03-2020,-500\n18-03-2020,-500\n19-03-2020,-500\n20-03-2020,-500\n23-03-2020,-500\n24-03-2020,-500\n25-03-2020,-500\n26-03-2020,-500\n27-03-2020,-500\n30-03-2020,-500\n31-03-2020,-500\n01-04-2020,-500\n02-04-2020,-500\n03-04-2020,-500\n06-04-2020,-500\n07-04-2020,-500\n08-04-2020,-500\n09-04-2020,-500\n14-04-2020,-500\n15-04-2020,-500\n16-04-2020,-500\n17-04-2020,-500\n20-04-2020,-500\n21-04-2020,-500\n22-04-2020,-500\n23-04-2020,-500\n24-04-2020,-500\n27-04-2020,-500\n28-04-2020,-500\n29-04-2020,-500\n30-04-2020,-500\n01-05-2020,-500\n04-05-2020,-500\n05-05-2020,-500\n06-05-2020,-500\n07-05-2020,-500\n08-05-2020,-500\n11-05-2020,-500\n12-05-2020,-500\n13-05-2020,-500\n14-05-2020,-500\n15-05-2020,-500\n18-05-2020,-500\n19-05-2020,-500\n20-05-2020,-500\n21-05-2020,-500\n22-05-2020,-500\n25-05-2020,-500\n26-05-2020,-500\n27-05-2020,-500\n28-05-2020,-500\n29-05-2020,-500\n01-06-2020,-500\n02-06-2020,-500\n03-06-2020,-500\n04-06-2020,-500\n05-06-2020,-500\n09-06-2020,-500\n10-06-2020,-500\n11-06-2020,-500\n12-06-2020,-500\n15-06-2020,-500\n16-06-2020,-500\n17-06-2020,-500\n18-06-2020,-500\n19-06-2020,-500\n22-06-2020,-500\n23-06-2020,-500\n24-06-2020,-500\n25-06-2020,-500\n26-06-2020,-500\n29-06-2020,-500\n30-06-2020,-500\n01-07-2020,-500\n02-07-2020,-500\n03-07-2020,-500\n06-07-2020,-500\n07-07-2020,-500\n08-07-2020,-500\n09-07-2020,-500\n10-07-2020,-500\n13-07-2020,-500\n14-07-2020,-500\n15-07-2020,-500\n16-07-2020,-500\n17-07-2020,-500\n20-07-2020,-500\n21-07-2020,-500\n22-07-2020,-500\n23-07-2020,-500\n24-07-2020,-500\n27-07-2020,-500\n28-07-2020,-500\n29-07-2020,-500\n30-07-2020,-500\n31-07-2020,-500\n03-08-2020,-500\n04-08-2020,-500\n05-08-2020,-500\n06-08-2020,-500\n07-08-2020,-500\n10-08-2020,-500\n11-08-2020,-500\n12-08-2020,-500\n13-08-2020,-500\n14-08-2020,-500\n17-08-2020,-500\n18-08-2020,-500\n19-08-2020,-500\n20-08-2020,-500\n21-08-2020,-500\n24-08-2020,-500\n25-08-2020,-500\n26-08-2020,-500\n27-08-2020,-500\n28-08-2020,-500\n31-08-2020,-500\n01-09-2020,-500\n02-09-2020,-500\n03-09-2020,-500\n04-09-2020,-500\n07-09-2020,-500\n08-09-2020,-500\n09-09-2020,-500\n10-09-2020,-500\n11-09-2020,-500\n14-09-2020,-500\n15-09-2020,-500\n16-09-2020,-500\n17-09-2020,-500\n18-09-2020,-500\n21-09-2020,-500\n22-09-2020,-500\n23-09-2020,-500\n24-09-2020,-500\n25-09-2020,-500\n28-09-2020,-500\n29-09-2020,-500\n30-09-2020,-500\n01-10-2020,-500\n02-10-2020,-500\n05-10-2020,-500\n06-10-2020,-500\n07-10-2020,-500\n08-10-2020,-500\n09-10-2020,-500\n12-10-2020,-500\n13-10-2020,-500\n14-10-2020,-500\n15-10-2020,-500\n16-10-2020,-500\n19-10-2020,-500\n20-10-2020,-500\n21-10-2020,-500\n22-10-2020,-500\n23-10-2020,-500\n26-10-2020,-500\n27-10-2020,-500\n28-10-2020,-500\n29-10-2020,-500\n30-10-2020,-500\n02-11-2020,-500\n03-11-2020,-500\n04-11-2020,-500\n05-11-2020,-500\n06-11-2020,-500\n09-11-2020,-500\n10-11-2020,-500\n11-11-2020,-500\n12-11-2020,-500\n13-11-2020,-500\n16-11-2020,-500\n17-11-2020,-500\n18-11-2020,-500\n19-11-2020,-500\n20-11-2020,-500\n23-11-2020,-500\n24-11-2020,-500\n25-11-2020,-500\n26-11-2020,-500\n27-11-2020,-500\n30-11-2020,-500\n01-12-2020,-500\n02-12-2020,-500\n03-12-2020,-500\n04-12-2020,-500\n07-12-2020,-500\n08-12-2020,-500\n09-12-2020,-500\n10-12-2020,-500\n11-12-2020,-500\n14-12-2020,-500\n15-12-2020,-500\n16-12-2020,-500\n17-12-2020,-500\n18-12-2020,-500\n21-12-2020,-500\n22-12-2020,-500\n23-12-2020,-500\n24-12-2020,-500\n29-12-2020,-500\n30-12-2020,-500\n31-12-2020,-500\n04-01-2021,-500\n05-01-2021,-500\n06-01-2021,-500\n07-01-2021,-500\n08-01-2021,-500\n11-01-2021,-500\n12-01-2021,-500\n13-01-2021,-500\n14-01-2021,-500\n15-01-2021,-500\n18-01-2021,-500\n19-01-2021,-500\n20-01-2021,-500\n21-01-2021,-500\n22-01-2021,-500\n25-01-2021,-500\n27-01-2021,-500\n28-01-2021,-500\n29-01-2021,-500\n01-02-2021,-500\n02-02-2021,-500\n03-02-2021,-500\n04-02-2021,-500\n05-02-2021,-500\n08-02-2021,-500\n09-02-2021,-500\n10-02-2021,-500\n11-02-2021,-500\n12-02-2021,-500\n15-02-2021,-500\n16-02-2021,-500\n17-02-2021,-500\n18-02-2021,-500\n19-02-2021,-500\n22-02-2021,-500\n23-02-2021,-500\n24-02-2021,-500\n25-02-2021,-500\n26-02-2021,-500\n01-03-2021,-500\n02-03-2021,-500\n03-03-2021,-500\n04-03-2021,-500\n05-03-2021,-500\n08-03-2021,-500\n09-03-2021,-500\n10-03-2021,-500\n11-03-2021,-500\n12-03-2021,-500\n15-03-2021,-500\n16-03-2021,-500\n17-03-2021,-500\n18-03-2021,-500\n19-03-2021,-500\n22-03-2021,-500\n23-03-2021,-500\n24-03-2021,-500\n25-03-2021,-500\n26-03-2021,-500\n29-03-2021,-500\n30-03-2021,-500\n31-03-2021,-500\n01-04-2021,-500\n06-04-2021,-500\n07-04-2021,-500\n08-04-2021,-500\n09-04-2021,-500\n12-04-2021,-500\n13-04-2021,-500\n14-04-2021,-500\n15-04-2021,-500\n16-04-2021,-500\n19-04-2021,-500\n20-04-2021,-500\n21-04-2021,-500\n22-04-2021,-500\n23-04-2021,-500\n26-04-2021,-500\n27-04-2021,-500\n28-04-2021,-500\n29-04-2021,-500\n30-04-2021,-500\n03-05-2021,-500\n04-05-2021,-500\n05-05-2021,-500\n06-05-2021,-500\n07-05-2021,-500\n10-05-2021,-500\n11-05-2021,-500\n12-05-2021,-500\n13-05-2021,-500\n14-05-2021,-500\n17-05-2021,-500\n18-05-2021,-500\n19-05-2021,-500\n20-05-2021,-500\n21-05-2021,-500\n24-05-2021,-500\n25-05-2021,-500\n26-05-2021,-500\n27-05-2021,-500\n28-05-2021,-500\n31-05-2021,-500\n01-06-2021,-500\n02-06-2021,-500\n03-06-2021,-500\n04-06-2021,-500\n07-06-2021,-500\n08-06-2021,-500\n09-06-2021,-500\n10-06-2021,-500\n11-06-2021,-500\n15-06-2021,-500\n16-06-2021,-500\n17-06-2021,-500\n18-06-2021,-500\n21-06-2021,-500\n22-06-2021,-500\n23-06-2021,-500\n24-06-2021,-500\n25-06-2021,-500\n28-06-2021,-500\n29-06-2021,-500\n30-06-2021,-500\n01-07-2021,-500\n02-07-2021,-500\n05-07-2021,-500\n06-07-2021,-500\n07-07-2021,-500\n08-07-2021,-500\n09-07-2021,-500\n12-07-2021,-500\n13-07-2021,-500\n14-07-2021,-500\n15-07-2021,-500\n16-07-2021,-500\n19-07-2021,-500\n20-07-2021,-500\n21-07-2021,-500\n22-07-2021,-500\n23-07-2021,-500\n26-07-2021,-500\n27-07-2021,-500\n28-07-2021,-500\n29-07-2021,-500\n30-07-2021,-500\n02-08-2021,-500\n03-08-2021,-500\n04-08-2021,-500\n05-08-2021,-500\n06-08-2021,-500\n09-08-2021,-500\n10-08-2021,-500\n11-08-2021,-500\n12-08-2021,-500\n13-08-2021,-500\n16-08-2021,-500\n17-08-2021,-500\n18-08-2021,-500\n19-08-2021,-500\n20-08-2021,-500\n23-08-2021,-500\n24-08-2021,-500\n25-08-2021,-500\n26-08-2021,-500\n27-08-2021,-500\n30-08-2021,-500\n31-08-2021,-500\n01-09-2021,-500\n02-09-2021,-500\n03-09-2021,-500\n06-09-2021,-500\n07-09-2021,-500\n08-09-2021,-500\n09-09-2021,-500\n10-09-2021,-500\n13-09-2021,-500\n14-09-2021,-500\n15-09-2021,-500\n16-09-2021,-500\n17-09-2021,-500\n20-09-2021,-500\n21-09-2021,-500\n22-09-2021,-500\n23-09-2021,-500\n24-09-2021,-500\n27-09-2021,-500\n28-09-2021,-500\n29-09-2021,-500\n30-09-2021,-500\n01-10-2021,-500\n04-10-2021,-500\n05-10-2021,-500\n06-10-2021,-500\n07-10-2021,-500\n08-10-2021,-500\n11-10-2021,-500\n12-10-2021,-500\n13-10-2021,-500\n14-10-2021,-500\n15-10-2021,-500\n18-10-2021,-500\n19-10-2021,-500\n20-10-2021,-500\n21-10-2021,-500\n22-10-2021,-500\n25-10-2021,-500\n26-10-2021,-500\n27-10-2021,-500\n28-10-2021,-500\n29-10-2021,-500\n01-11-2021,-500\n02-11-2021,-500\n03-11-2021,-500\n04-11-2021,-500\n05-11-2021,-500\n08-11-2021,-500\n09-11-2021,-500\n10-11-2021,-500\n11-11-2021,-500\n12-11-2021,-500\n15-11-2021,-500\n16-11-2021,-500\n17-11-2021,-500\n18-11-2021,-500\n19-11-2021,-500\n22-11-2021,-500\n23-11-2021,-500\n24-11-2021,-500\n25-11-2021,-500\n26-11-2021,-500\n29-11-2021,-500\n30-11-2021,-500\n01-12-2021,-500\n02-12-2021,-500\n03-12-2021,-500\n06-12-2021,-500\n07-12-2021,-500\n08-12-2021,-500\n09-12-2021,-500\n10-12-2021,-500\n13-12-2021,-500\n14-12-2021,-500\n15-12-2021,-500\n16-12-2021,-500\n17-12-2021,-500\n20-12-2021,-500\n21-12-2021,-500\n22-12-2021,-500\n23-12-2021,-500\n24-12-2021,-500\n29-12-2021,-500\n30-12-2021,-500\n31-12-2021,-500\n04-01-2022,-500\n05-01-2022,-500\n06-01-2022,-500\n07-01-2022,-500\n10-01-2022,-500\n11-01-2022,-500\n12-01-2022,-500\n13-01-2022,-500\n14-01-2022,-500\n17-01-2022,-500\n18-01-2022,-500\n19-01-2022,-500\n20-01-2022,-500\n21-01-2022,-500\n24-01-2022,-500\n25-01-2022,-500\n27-01-2022,-500\n28-01-2022,-500\n31-01-2022,-500\n01-02-2022,-500\n02-02-2022,-500\n03-02-2022,-500\n04-02-2022,-500\n07-02-2022,-500\n08-02-2022,-500\n09-02-2022,-500\n10-02-2022,-500\n11-02-2022,-500\n14-02-2022,-500\n15-02-2022,-500\n16-02-2022,-500\n17-02-2022,-500\n18-02-2022,-500\n21-02-2022,-500\n22-02-2022,-500\n23-02-2022,-500\n24-02-2022,-500\n25-02-2022,-500\n28-02-2022,-500\n01-03-2022,-500\n02-03-2022,-500\n03-03-2022,-500\n04-03-2022,-500\n07-03-2022,-500\n08-03-2022,-500\n09-03-2022,-500\n10-03-2022,-500\n11-03-2022,-500\n14-03-2022,-500\n15-03-2022,-500\n16-03-2022,-500\n17-03-2022,-500\n18-03-2022,-500\n21-03-2022,-500\n22-03-2022,-500\n23-03-2022,-500\n24-03-2022,-500\n25-03-2022,-500\n28-03-2022,-500\n29-03-2022,-500\n30-03-2022,-500\n31-03-2022,-500\n01-04-2022,-500\n04-04-2022,-500\n05-04-2022,-500\n06-04-2022,-500\n07-04-2022,-500\n08-04-2022,-500\n11-04-2022,-500\n12-04-2022,-500\n13-04-2022,-500\n14-04-2022,-500\n19-04-2022,-500\n20-04-2022,-500\n21-04-2022,-500\n22-04-2022,-500\n26-04-2022,-500\n27-04-2022,-500\n28-04-2022,-500\n29-04-2022,-500\n02-05-2022,-500\n03-05-2022,-500\n04-05-2022,-500\n05-05-2022,-500\n06-05-2022,-500\n09-05-2022,-500\n10-05-2022,-500\n11-05-2022,-500\n12-05-2022,-500\n13-05-2022,-500\n16-05-2022,-500\n17-05-2022,-500\n18-05-2022,-500\n19-05-2022,-500\n20-05-2022,-500\n23-05-2022,-500\n24-05-2022,-500\n25-05-2022,-500\n26-05-2022,-500\n27-05-2022,-500\n30-05-2022,-500\n31-05-2022,-500\n01-06-2022,-500\n02-06-2022,-500\n03-06-2022,-500\n06-06-2022,-500\n07-06-2022,-500\n08-06-2022,-500\n09-06-2022,-500\n10-06-2022,-500\n14-06-2022,-500\n15-06-2022,-500\n16-06-2022,-500\n17-06-2022,-500\n20-06-2022,-500\n21-06-2022,-500\n22-06-2022,-500\n23-06-2022,-500\n24-06-2022,-500\n27-06-2022,-500\n28-06-2022,-500\n29-06-2022,-500\n30-06-2022,-500\n01-07-2022,-500\n04-07-2022,-500\n05-07-2022,-500\n06-07-2022,-500\n07-07-2022,-500\n08-07-2022,-500\n11-07-2022,-500\n12-07-2022,-500\n13-07-2022,-500\n14-07-2022,-500\n15-07-2022,-500\n18-07-2022,-500\n19-07-2022,-500\n20-07-2022,-500\n21-07-2022,-500\n22-07-2022,-500\n25-07-2022,-500\n26-07-2022,-500\n27-07-2022,-500\n28-07-2022,-500\n29-07-2022,-500\n01-08-2022,-500\n02-08-2022,-500\n03-08-2022,-500\n04-08-2022,-500\n05-08-2022,-500\n08-08-2022,-500\n09-08-2022,-500\n10-08-2022,-500\n11-08-2022,-500\n12-08-2022,-500\n15-08-2022,-500\n16-08-2022,-500\n17-08-2022,-500\n18-08-2022,-500\n19-08-2022,-500\n22-08-2022,-500\n23-08-2022,-500\n24-08-2022,-500\n25-08-2022,-500\n26-08-2022,-500\n29-08-2022,-500\n30-08-2022,-500\n31-08-2022,-500\n01-09-2022,-500\n02-09-2022,-500\n05-09-2022,-500\n06-09-2022,-500\n07-09-2022,-500\n08-09-2022,-500\n09-09-2022,-500\n12-09-2022,-500\n13-09-2022,-500\n14-09-2022,-500\n15-09-2022,-500\n16-09-2022,-500\n19-09-2022,-500\n20-09-2022,-500\n21-09-2022,-500\n23-09-2022,-500\n26-09-2022,-500\n27-09-2022,-500\n28-09-2022,-500\n29-09-2022,-500\n30-09-2022,-500\n03-10-2022,-500\n04-10-2022,-500\n05-10-2022,-500\n06-10-2022,-500\n07-10-2022,-500\n10-10-2022,-500\n11-10-2022,-500\n12-10-2022,-500\n13-10-2022,-500\n14-10-2022,-500\n17-10-2022,-500\n18-10-2022,-500\n19-10-2022,-500\n20-10-2022,-500\n21-10-2022,-500\n24-10-2022,-500\n25-10-2022,-500\n26-10-2022,-500\n27-10-2022,-500\n28-10-2022,-500\n31-10-2022,-500\n01-11-2022,-500\n02-11-2022,-500\n03-11-2022,-500\n04-11-2022,-500\n07-11-2022,-500\n08-11-2022,-500\n09-11-2022,-500\n10-11-2022,-500\n11-11-2022,-500\n14-11-2022,-500\n15-11-2022,-500\n16-11-2022,-500\n17-11-2022,-500\n18-11-2022,-500\n21-11-2022,-500\n22-11-2022,-500\n23-11-2022,-500\n24-11-2022,-500\n25-11-2022,-500\n28-11-2022,-500\n29-11-2022,-500\n30-11-2022,-500\n01-12-2022,-500\n02-12-2022,-500\n05-12-2022,-500\n06-12-2022,-500\n07-12-2022,-500\n08-12-2022,-500\n09-12-2022,-500\n12-12-2022,-500\n13-12-2022,-500\n14-12-2022,-500\n15-12-2022,-500\n16-12-2022,-500\n19-12-2022,-500\n20-12-2022,-500\n21-12-2022,-500\n22-12-2022,-500\n23-12-2022,-500\n28-12-2022,-500\n29-12-2022,-500\n30-12-2022,-500\n03-01-2023,-500\n04-01-2023,-500\n05-01-2023,-500\n06-01-2023,-500\n09-01-2023,-500\n10-01-2023,-500\n11-01-2023,-500\n12-01-2023,-500\n13-01-2023,-500\n16-01-2023,-500\n17-01-2023,-500\n18-01-2023,-500\n19-01-2023,-500\n20-01-2023,-500\n23-01-2023,-500\n24-01-2023,-500\n25-01-2023,-500\n27-01-2023,-500\n30-01-2023,-500\n31-01-2023,-500\n01-02-2023,-500\n02-02-2023,-500\n03-02-2023,-500\n06-02-2023,-500\n07-02-2023,-500\n08-02-2023,-500\n09-02-2023,-500\n10-02-2023,-500\n13-02-2023,-500\n14-02-2023,-500\n15-02-2023,-500\n16-02-2023,-500\n17-02-2023,-500\n20-02-2023,-500\n21-02-2023,-500\n22-02-2023,-500\n23-02-2023,-500\n24-02-2023,-500\n27-02-2023,-500\n28-02-2023,-500\n01-03-2023,-500\n02-03-2023,-500\n03-03-2023,-500\n06-03-2023,-500\n07-03-2023,-500\n08-03-2023,-500\n09-03-2023,-500\n10-03-2023,-500\n13-03-2023,-500\n14-03-2023,-500\n15-03-2023,-500\n16-03-2023,-500\n17-03-2023,-500\n20-03-2023,-500\n21-03-2023,-500\n22-03-2023,-500\n23-03-2023,-500\n24-03-2023,-500\n27-03-2023,-500\n28-03-2023,-500\n29-03-2023,-500\n30-03-2023,-500\n31-03-2023,-500\n03-04-2023,-500\n04-04-2023,-500\n05-04-2023,-500\n06-04-2023,-500\n11-04-2023,-500\n12-04-2023,-500\n13-04-2023,-500\n14-04-2023,-500\n17-04-2023,-500\n18-04-2023,-500\n19-04-2023,-500\n20-04-2023,-500\n21-04-2023,-500\n24-04-2023,-500\n26-04-2023,-500\n27-04-2023,-500\n28-04-2023,-500\n01-05-2023,-500\n02-05-2023,-500\n03-05-2023,-500\n04-05-2023,-500\n05-05-2023,-500\n08-05-2023,-500\n09-05-2023,-500\n10-05-2023,-500\n11-05-2023,-500\n12-05-2023,-500\n15-05-2023,-500\n16-05-2023,-500\n17-05-2023,-500\n18-05-2023,-500\n19-05-2023,-500\n22-05-2023,-500\n23-05-2023,-500\n24-05-2023,-500\n25-05-2023,-500\n26-05-2023,-500\n29-05-2023,-500\n30-05-2023,-500\n31-05-2023,-500\n01-06-2023,-500\n02-06-2023,-500\n05-06-2023,-500\n06-06-2023,-500\n07-06-2023,-500\n08-06-2023,-500\n09-06-2023,-500\n13-06-2023,-500\n14-06-2023,-500\n15-06-2023,-500\n16-06-2023,-500\n19-06-2023,-500\n20-06-2023,-500\n21-06-2023,-500\n22-06-2023,-500\n23-06-2023,-500\n26-06-2023,-500\n27-06-2023,-500\n28-06-2023,-500\n29-06-2023,-500\n30-06-2023,-500\n03-07-2023,-500\n04-07-2023,-500\n05-07-2023,-500\n06-07-2023,-500\n07-07-2023,-500\n10-07-2023,-500\n11-07-2023,-500\n12-07-2023,-500\n13-07-2023,-500\n14-07-2023,-500\n17-07-2023,-500\n18-07-2023,-500\n19-07-2023,-500\n20-07-2023,-500\n21-07-2023,-500\n24-07-2023,-500\n25-07-2023,-500\n26-07-2023,-500\n27-07-2023,-500\n28-07-2023,-500\n31-07-2023,-500\n01-08-2023,-500\n02-08-2023,-500\n03-08-2023,-500\n04-08-2023,-500\n07-08-2023,-500\n08-08-2023,-500\n09-08-2023,-500\n10-08-2023,-500\n11-08-2023,-500\n14-08-2023,-500\n15-08-2023,-500\n16-08-2023,-500\n17-08-2023,-500\n18-08-2023,-500\n21-08-2023,-500\n22-08-2023,-500\n23-08-2023,-500\n24-08-2023,-500\n25-08-2023,-500\n28-08-2023,-500\n29-08-2023,-500\n30-08-2023,-500\n31-08-2023,-500\n01-09-2023,-500\n04-09-2023,-500\n05-09-2023,-500\n06-09-2023,-500\n07-09-2023,-500\n08-09-2023,-500\n11-09-2023,-500\n12-09-2023,-500\n13-09-2023,-500\n14-09-2023,-500\n15-09-2023,-500\n18-09-2023,-500\n19-09-2023,-500\n20-09-2023,-500\n21-09-2023,-500\n22-09-2023,-500\n25-09-2023,-500\n26-09-2023,-500\n27-09-2023,-500\n28-09-2023,-500\n29-09-2023,-500\n02-10-2023,-500\n03-10-2023,-500\n04-10-2023,-500\n05-10-2023,-500\n06-10-2023,-500\n09-10-2023,-500\n10-10-2023,-500\n11-10-2023,-500\n12-10-2023,-500\n13-10-2023,-500\n16-10-2023,-500\n17-10-2023,-500\n18-10-2023,-500\n19-10-2023,-500\n20-10-2023,-500\n23-10-2023,-500\n24-10-2023,-500\n25-10-2023,-500\n26-10-2023,-500\n27-10-2023,-500\n30-10-2023,-500\n31-10-2023,-500\n01-11-2023,-500\n02-11-2023,-500\n03-11-2023,-500\n06-11-2023,-500\n07-11-2023,-500\n08-11-2023,-500\n09-11-2023,-500\n10-11-2023,-500\n13-11-2023,-500\n14-11-2023,-500\n15-11-2023,-500\n16-11-2023,-500\n17-11-2023,-500\n20-11-2023,-500\n21-11-2023,-500\n22-11-2023,-500\n23-11-2023,-500\n24-11-2023,-500\n27-11-2023,-500\n28-11-2023,-500\n29-11-2023,-500\n30-11-2023,-500\n01-12-2023,-500\n04-12-2023,-500\n05-12-2023,-500\n06-12-2023,-500\n07-12-2023,-500\n08-12-2023,-500\n11-12-2023,-500\n12-12-2023,-500\n13-12-2023,-500\n14-12-2023,-500\n15-12-2023,-500\n18-12-2023,-500\n19-12-2023,-500\n20-12-2023,-500\n21-12-2023,-500\n22-12-2023,-500\n27-12-2023,-500\n28-12-2023,-500\n29-12-2023,-500\n02-01-2024,-500\n03-01-2024,-500\n04-01-2024,-500\n05-01-2024,-500\n08-01-2024,-500\n09-01-2024,-500\n10-01-2024,-500\n11-01-2024,-500\n12-01-2024,-500\n15-01-2024,-500\n16-01-2024,-500\n17-01-2024,-500\n18-01-2024,-500\n19-01-2024,-500\n22-01-2024,-500\n23-01-2024,-500\n24-01-2024,-500\n25-01-2024,-500\n29-01-2024,-500\n30-01-2024,-500\n31-01-2024,-500\n01-02-2024,-500\n02-02-2024,-500\n05-02-2024,-500\n06-02-2024,-500\n07-02-2024,-500\n08-02-2024,-500\n09-02-2024,-500\n12-02-2024,-500\n13-02-2024,-500\n14-02-2024,-500\n15-02-2024,-500\n16-02-2024,-500\n19-02-2024,-500\n20-02-2024,-500\n21-02-2024,-500\n22-02-2024,-500\n23-02-2024,-500\n26-02-2024,-500\n27-02-2024,-500\n28-02-2024,-500\n29-02-2024,-500\n01-03-2024,-500\n04-03-2024,-500\n05-03-2024,-500\n06-03-2024,-500\n07-03-2024,-500\n08-03-2024,-500\n11-03-2024,-500\n12-03-2024,-500\n13-03-2024,-500\n14-03-2024,-500\n15-03-2024,-500\n18-03-2024,-500\n19-03-2024,-500\n20-03-2024,-500\n21-03-2024,-500\n22-03-2024,-500\n25-03-2024,-500\n26-03-2024,-500\n27-03-2024,-500\n28-03-2024,-500\n02-04-2024,-500\n03-04-2024,-500\n04-04-2024,-500\n05-04-2024,-500\n08-04-2024,-500\n09-04-2024,-500\n10-04-2024,-500\n11-04-2024,-500\n12-04-2024,-500\n15-04-2024,-500\n16-04-2024,-500\n17-04-2024,-500\n18-04-2024,-500\n19-04-2024,-500\n22-04-2024,-500\n23-04-2024,-500\n24-04-2024,-500\n26-04-2024,-500\n29-04-2024,-500\n30-04-2024,-500\n01-05-2024,-500\n02-05-2024,-500\n03-05-2024,-500\n06-05-2024,-500\n07-05-2024,-500\n08-05-2024,-500\n09-05-2024,-500\n10-05-2024,-500\n13-05-2024,-500\n14-05-2024,-500\n15-05-2024,-500\n16-05-2024,-500\n17-05-2024,-500\n20-05-2024,-500\n21-05-2024,-500\n22-05-2024,-500\n23-05-2024,-500\n24-05-2024,-500\n27-05-2024,-500\n28-05-2024,-500\n29-05-2024,-500\n30-05-2024,-500\n31-05-2024,-500\n03-06-2024,-500\n04-06-2024,-500\n05-06-2024,-500\n06-06-2024,-500\n07-06-2024,-500\n11-06-2024,-500\n12-06-2024,-500\n13-06-2024,-500\n14-06-2024,-500"
  },
  {
    "path": "python/rateslib/data/historical/cad_rfr.csv",
    "content": "﻿reference_date,rate\n12-08-1997,-500\n18-08-1997,-500\n19-08-1997,-500\n20-08-1997,-500\n21-08-1997,-500\n22-08-1997,-500\n25-08-1997,-500\n26-08-1997,-500\n27-08-1997,-500\n28-08-1997,-500\n02-09-1997,-500\n03-09-1997,-500\n04-09-1997,-500\n05-09-1997,-500\n08-09-1997,-500\n09-09-1997,-500\n10-09-1997,-500\n11-09-1997,-500\n12-09-1997,-500\n15-09-1997,-500\n16-09-1997,-500\n17-09-1997,-500\n18-09-1997,-500\n19-09-1997,-500\n22-09-1997,-500\n23-09-1997,-500\n24-09-1997,-500\n25-09-1997,-500\n26-09-1997,-500\n29-09-1997,-500\n30-09-1997,-500\n01-10-1997,-500\n02-10-1997,-500\n03-10-1997,-500\n06-10-1997,-500\n07-10-1997,-500\n08-10-1997,-500\n09-10-1997,-500\n10-10-1997,-500\n14-10-1997,-500\n15-10-1997,-500\n16-10-1997,-500\n17-10-1997,-500\n20-10-1997,-500\n21-10-1997,-500\n22-10-1997,-500\n23-10-1997,-500\n24-10-1997,-500\n27-10-1997,-500\n28-10-1997,-500\n29-10-1997,-500\n30-10-1997,-500\n31-10-1997,-500\n03-11-1997,-500\n04-11-1997,-500\n05-11-1997,-500\n06-11-1997,-500\n07-11-1997,-500\n10-11-1997,-500\n12-11-1997,-500\n13-11-1997,-500\n14-11-1997,-500\n17-11-1997,-500\n18-11-1997,-500\n19-11-1997,-500\n20-11-1997,-500\n21-11-1997,-500\n24-11-1997,-500\n25-11-1997,-500\n26-11-1997,-500\n27-11-1997,-500\n28-11-1997,-500\n01-12-1997,-500\n02-12-1997,-500\n03-12-1997,-500\n04-12-1997,-500\n05-12-1997,-500\n08-12-1997,-500\n09-12-1997,-500\n10-12-1997,-500\n11-12-1997,-500\n12-12-1997,-500\n15-12-1997,-500\n16-12-1997,-500\n17-12-1997,-500\n18-12-1997,-500\n19-12-1997,-500\n23-12-1997,-500\n24-12-1997,-500\n29-12-1997,-500\n30-12-1997,-500\n31-12-1997,-500\n02-01-1998,-500\n05-01-1998,-500\n06-01-1998,-500\n07-01-1998,-500\n08-01-1998,-500\n09-01-1998,-500\n12-01-1998,-500\n13-01-1998,-500\n14-01-1998,-500\n15-01-1998,-500\n16-01-1998,-500\n19-01-1998,-500\n20-01-1998,-500\n21-01-1998,-500\n22-01-1998,-500\n23-01-1998,-500\n26-01-1998,-500\n27-01-1998,-500\n28-01-1998,-500\n29-01-1998,-500\n30-01-1998,-500\n02-02-1998,-500\n03-02-1998,-500\n04-02-1998,-500\n05-02-1998,-500\n06-02-1998,-500\n09-02-1998,-500\n10-02-1998,-500\n11-02-1998,-500\n12-02-1998,-500\n13-02-1998,-500\n16-02-1998,-500\n17-02-1998,-500\n18-02-1998,-500\n19-02-1998,-500\n20-02-1998,-500\n23-02-1998,-500\n24-02-1998,-500\n25-02-1998,-500\n26-02-1998,-500\n27-02-1998,-500\n02-03-1998,-500\n03-03-1998,-500\n04-03-1998,-500\n05-03-1998,-500\n06-03-1998,-500\n09-03-1998,-500\n10-03-1998,-500\n11-03-1998,-500\n12-03-1998,-500\n13-03-1998,-500\n16-03-1998,-500\n17-03-1998,-500\n18-03-1998,-500\n19-03-1998,-500\n20-03-1998,-500\n23-03-1998,-500\n24-03-1998,-500\n25-03-1998,-500\n26-03-1998,-500\n27-03-1998,-500\n30-03-1998,-500\n31-03-1998,-500\n01-04-1998,-500\n02-04-1998,-500\n03-04-1998,-500\n06-04-1998,-500\n07-04-1998,-500\n08-04-1998,-500\n13-04-1998,-500\n14-04-1998,-500\n15-04-1998,-500\n16-04-1998,-500\n17-04-1998,-500\n20-04-1998,-500\n21-04-1998,-500\n22-04-1998,-500\n23-04-1998,-500\n24-04-1998,-500\n27-04-1998,-500\n28-04-1998,-500\n30-04-1998,-500\n01-05-1998,-500\n04-05-1998,-500\n05-05-1998,-500\n06-05-1998,-500\n07-05-1998,-500\n08-05-1998,-500\n11-05-1998,-500\n12-05-1998,-500\n13-05-1998,-500\n14-05-1998,-500\n15-05-1998,-500\n19-05-1998,-500\n20-05-1998,-500\n21-05-1998,-500\n22-05-1998,-500\n25-05-1998,-500\n26-05-1998,-500\n27-05-1998,-500\n28-05-1998,-500\n29-05-1998,-500\n01-06-1998,-500\n02-06-1998,-500\n03-06-1998,-500\n04-06-1998,-500\n05-06-1998,-500\n08-06-1998,-500\n09-06-1998,-500\n10-06-1998,-500\n11-06-1998,-500\n12-06-1998,-500\n15-06-1998,-500\n16-06-1998,-500\n17-06-1998,-500\n18-06-1998,-500\n19-06-1998,-500\n22-06-1998,-500\n23-06-1998,-500\n24-06-1998,-500\n25-06-1998,-500\n26-06-1998,-500\n29-06-1998,-500\n30-06-1998,-500\n02-07-1998,-500\n03-07-1998,-500\n06-07-1998,-500\n07-07-1998,-500\n08-07-1998,-500\n09-07-1998,-500\n10-07-1998,-500\n13-07-1998,-500\n14-07-1998,-500\n15-07-1998,-500\n16-07-1998,-500\n17-07-1998,-500\n20-07-1998,-500\n21-07-1998,-500\n22-07-1998,-500\n23-07-1998,-500\n24-07-1998,-500\n27-07-1998,-500\n28-07-1998,-500\n29-07-1998,-500\n30-07-1998,-500\n31-07-1998,-500\n04-08-1998,-500\n05-08-1998,-500\n06-08-1998,-500\n07-08-1998,-500\n10-08-1998,-500\n11-08-1998,-500\n12-08-1998,-500\n13-08-1998,-500\n14-08-1998,-500\n17-08-1998,-500\n18-08-1998,-500\n19-08-1998,-500\n20-08-1998,-500\n21-08-1998,-500\n24-08-1998,-500\n25-08-1998,-500\n26-08-1998,-500\n27-08-1998,-500\n28-08-1998,-500\n31-08-1998,-500\n01-09-1998,-500\n02-09-1998,-500\n03-09-1998,-500\n04-09-1998,-500\n08-09-1998,-500\n09-09-1998,-500\n10-09-1998,-500\n11-09-1998,-500\n14-09-1998,-500\n15-09-1998,-500\n16-09-1998,-500\n17-09-1998,-500\n18-09-1998,-500\n21-09-1998,-500\n22-09-1998,-500\n23-09-1998,-500\n24-09-1998,-500\n25-09-1998,-500\n28-09-1998,-500\n29-09-1998,-500\n30-09-1998,-500\n01-10-1998,-500\n02-10-1998,-500\n05-10-1998,-500\n06-10-1998,-500\n07-10-1998,-500\n08-10-1998,-500\n09-10-1998,-500\n13-10-1998,-500\n14-10-1998,-500\n15-10-1998,-500\n16-10-1998,-500\n19-10-1998,-500\n20-10-1998,-500\n21-10-1998,-500\n22-10-1998,-500\n23-10-1998,-500\n26-10-1998,-500\n27-10-1998,-500\n28-10-1998,-500\n29-10-1998,-500\n30-10-1998,-500\n02-11-1998,-500\n03-11-1998,-500\n04-11-1998,-500\n05-11-1998,-500\n06-11-1998,-500\n09-11-1998,-500\n10-11-1998,-500\n12-11-1998,-500\n13-11-1998,-500\n16-11-1998,-500\n17-11-1998,-500\n18-11-1998,-500\n19-11-1998,-500\n20-11-1998,-500\n23-11-1998,-500\n24-11-1998,-500\n25-11-1998,-500\n26-11-1998,-500\n27-11-1998,-500\n30-11-1998,-500\n01-12-1998,-500\n02-12-1998,-500\n03-12-1998,-500\n04-12-1998,-500\n07-12-1998,-500\n08-12-1998,-500\n09-12-1998,-500\n10-12-1998,-500\n11-12-1998,-500\n14-12-1998,-500\n15-12-1998,-500\n16-12-1998,-500\n17-12-1998,-500\n18-12-1998,-500\n21-12-1998,-500\n22-12-1998,-500\n23-12-1998,-500\n24-12-1998,-500\n29-12-1998,-500\n30-12-1998,-500\n31-12-1998,-500\n04-01-1999,-500\n05-01-1999,-500\n06-01-1999,-500\n07-01-1999,-500\n08-01-1999,-500\n11-01-1999,-500\n12-01-1999,-500\n13-01-1999,-500\n14-01-1999,-500\n15-01-1999,-500\n18-01-1999,-500\n19-01-1999,-500\n20-01-1999,-500\n21-01-1999,-500\n22-01-1999,-500\n25-01-1999,-500\n26-01-1999,-500\n27-01-1999,-500\n28-01-1999,-500\n29-01-1999,-500\n01-02-1999,-500\n02-02-1999,-500\n03-02-1999,-500\n04-02-1999,-500\n05-02-1999,-500\n08-02-1999,-500\n09-02-1999,-500\n10-02-1999,-500\n11-02-1999,-500\n12-02-1999,-500\n15-02-1999,-500\n16-02-1999,-500\n17-02-1999,-500\n18-02-1999,-500\n19-02-1999,-500\n22-02-1999,-500\n23-02-1999,-500\n24-02-1999,-500\n25-02-1999,-500\n26-02-1999,-500\n01-03-1999,-500\n02-03-1999,-500\n03-03-1999,-500\n04-03-1999,-500\n05-03-1999,-500\n08-03-1999,-500\n09-03-1999,-500\n10-03-1999,-500\n11-03-1999,-500\n12-03-1999,-500\n15-03-1999,-500\n16-03-1999,-500\n17-03-1999,-500\n18-03-1999,-500\n19-03-1999,-500\n22-03-1999,-500\n23-03-1999,-500\n24-03-1999,-500\n25-03-1999,-500\n26-03-1999,-500\n29-03-1999,-500\n30-03-1999,-500\n31-03-1999,-500\n01-04-1999,-500\n05-04-1999,-500\n06-04-1999,-500\n07-04-1999,-500\n08-04-1999,-500\n09-04-1999,-500\n12-04-1999,-500\n13-04-1999,-500\n14-04-1999,-500\n15-04-1999,-500\n16-04-1999,-500\n19-04-1999,-500\n20-04-1999,-500\n21-04-1999,-500\n22-04-1999,-500\n23-04-1999,-500\n26-04-1999,-500\n27-04-1999,-500\n28-04-1999,-500\n29-04-1999,-500\n30-04-1999,-500\n03-05-1999,-500\n04-05-1999,-500\n05-05-1999,-500\n06-05-1999,-500\n07-05-1999,-500\n10-05-1999,-500\n11-05-1999,-500\n12-05-1999,-500\n13-05-1999,-500\n14-05-1999,-500\n17-05-1999,-500\n18-05-1999,-500\n19-05-1999,-500\n20-05-1999,-500\n21-05-1999,-500\n25-05-1999,-500\n26-05-1999,-500\n27-05-1999,-500\n28-05-1999,-500\n31-05-1999,-500\n01-06-1999,-500\n02-06-1999,-500\n03-06-1999,-500\n04-06-1999,-500\n07-06-1999,-500\n08-06-1999,-500\n09-06-1999,-500\n10-06-1999,-500\n11-06-1999,-500\n14-06-1999,-500\n15-06-1999,-500\n16-06-1999,-500\n17-06-1999,-500\n18-06-1999,-500\n21-06-1999,-500\n22-06-1999,-500\n23-06-1999,-500\n24-06-1999,-500\n25-06-1999,-500\n28-06-1999,-500\n29-06-1999,-500\n30-06-1999,-500\n02-07-1999,-500\n05-07-1999,-500\n06-07-1999,-500\n07-07-1999,-500\n08-07-1999,-500\n09-07-1999,-500\n12-07-1999,-500\n13-07-1999,-500\n14-07-1999,-500\n15-07-1999,-500\n16-07-1999,-500\n19-07-1999,-500\n20-07-1999,-500\n21-07-1999,-500\n22-07-1999,-500\n23-07-1999,-500\n26-07-1999,-500\n27-07-1999,-500\n28-07-1999,-500\n29-07-1999,-500\n30-07-1999,-500\n03-08-1999,-500\n04-08-1999,-500\n05-08-1999,-500\n06-08-1999,-500\n09-08-1999,-500\n10-08-1999,-500\n11-08-1999,-500\n12-08-1999,-500\n13-08-1999,-500\n16-08-1999,-500\n17-08-1999,-500\n18-08-1999,-500\n19-08-1999,-500\n20-08-1999,-500\n23-08-1999,-500\n24-08-1999,-500\n25-08-1999,-500\n26-08-1999,-500\n27-08-1999,-500\n30-08-1999,-500\n31-08-1999,-500\n01-09-1999,-500\n02-09-1999,-500\n03-09-1999,-500\n07-09-1999,-500\n08-09-1999,-500\n09-09-1999,-500\n10-09-1999,-500\n13-09-1999,-500\n14-09-1999,-500\n15-09-1999,-500\n16-09-1999,-500\n17-09-1999,-500\n20-09-1999,-500\n21-09-1999,-500\n22-09-1999,-500\n23-09-1999,-500\n24-09-1999,-500\n27-09-1999,-500\n28-09-1999,-500\n29-09-1999,-500\n30-09-1999,-500\n01-10-1999,-500\n04-10-1999,-500\n05-10-1999,-500\n06-10-1999,-500\n07-10-1999,-500\n08-10-1999,-500\n12-10-1999,-500\n13-10-1999,-500\n14-10-1999,-500\n15-10-1999,-500\n18-10-1999,-500\n19-10-1999,-500\n20-10-1999,-500\n21-10-1999,-500\n22-10-1999,-500\n25-10-1999,-500\n26-10-1999,-500\n27-10-1999,-500\n28-10-1999,-500\n29-10-1999,-500\n01-11-1999,-500\n02-11-1999,-500\n03-11-1999,-500\n04-11-1999,-500\n05-11-1999,-500\n08-11-1999,-500\n09-11-1999,-500\n10-11-1999,-500\n12-11-1999,-500\n15-11-1999,-500\n16-11-1999,-500\n17-11-1999,-500\n18-11-1999,-500\n19-11-1999,-500\n22-11-1999,-500\n23-11-1999,-500\n24-11-1999,-500\n25-11-1999,-500\n26-11-1999,-500\n29-11-1999,-500\n30-11-1999,-500\n01-12-1999,-500\n02-12-1999,-500\n03-12-1999,-500\n06-12-1999,-500\n07-12-1999,-500\n08-12-1999,-500\n09-12-1999,-500\n10-12-1999,-500\n13-12-1999,-500\n14-12-1999,-500\n15-12-1999,-500\n16-12-1999,-500\n17-12-1999,-500\n20-12-1999,-500\n21-12-1999,-500\n22-12-1999,-500\n23-12-1999,-500\n24-12-1999,-500\n29-12-1999,-500\n30-12-1999,-500\n31-12-1999,-500\n04-01-2000,-500\n05-01-2000,-500\n06-01-2000,-500\n07-01-2000,-500\n10-01-2000,-500\n11-01-2000,-500\n12-01-2000,-500\n13-01-2000,-500\n14-01-2000,-500\n17-01-2000,-500\n18-01-2000,-500\n19-01-2000,-500\n20-01-2000,-500\n21-01-2000,-500\n24-01-2000,-500\n25-01-2000,-500\n26-01-2000,-500\n27-01-2000,-500\n28-01-2000,-500\n31-01-2000,-500\n01-02-2000,-500\n02-02-2000,-500\n03-02-2000,-500\n04-02-2000,-500\n07-02-2000,-500\n08-02-2000,-500\n09-02-2000,-500\n10-02-2000,-500\n11-02-2000,-500\n14-02-2000,-500\n15-02-2000,-500\n16-02-2000,-500\n17-02-2000,-500\n18-02-2000,-500\n21-02-2000,-500\n22-02-2000,-500\n23-02-2000,-500\n24-02-2000,-500\n25-02-2000,-500\n28-02-2000,-500\n29-02-2000,-500\n01-03-2000,-500\n02-03-2000,-500\n03-03-2000,-500\n06-03-2000,-500\n07-03-2000,-500\n08-03-2000,-500\n09-03-2000,-500\n10-03-2000,-500\n13-03-2000,-500\n14-03-2000,-500\n15-03-2000,-500\n16-03-2000,-500\n17-03-2000,-500\n20-03-2000,-500\n21-03-2000,-500\n22-03-2000,-500\n23-03-2000,-500\n24-03-2000,-500\n27-03-2000,-500\n28-03-2000,-500\n29-03-2000,-500\n30-03-2000,-500\n31-03-2000,-500\n03-04-2000,-500\n04-04-2000,-500\n05-04-2000,-500\n06-04-2000,-500\n07-04-2000,-500\n10-04-2000,-500\n11-04-2000,-500\n12-04-2000,-500\n13-04-2000,-500\n14-04-2000,-500\n17-04-2000,-500\n18-04-2000,-500\n19-04-2000,-500\n20-04-2000,-500\n24-04-2000,-500\n25-04-2000,-500\n26-04-2000,-500\n27-04-2000,-500\n28-04-2000,-500\n01-05-2000,-500\n02-05-2000,-500\n03-05-2000,-500\n04-05-2000,-500\n05-05-2000,-500\n08-05-2000,-500\n09-05-2000,-500\n10-05-2000,-500\n11-05-2000,-500\n12-05-2000,-500\n15-05-2000,-500\n16-05-2000,-500\n17-05-2000,-500\n18-05-2000,-500\n19-05-2000,-500\n23-05-2000,-500\n24-05-2000,-500\n25-05-2000,-500\n26-05-2000,-500\n29-05-2000,-500\n30-05-2000,-500\n31-05-2000,-500\n01-06-2000,-500\n02-06-2000,-500\n05-06-2000,-500\n06-06-2000,-500\n07-06-2000,-500\n08-06-2000,-500\n09-06-2000,-500\n12-06-2000,-500\n13-06-2000,-500\n14-06-2000,-500\n15-06-2000,-500\n16-06-2000,-500\n19-06-2000,-500\n20-06-2000,-500\n21-06-2000,-500\n22-06-2000,-500\n23-06-2000,-500\n26-06-2000,-500\n27-06-2000,-500\n28-06-2000,-500\n29-06-2000,-500\n30-06-2000,-500\n04-07-2000,-500\n05-07-2000,-500\n06-07-2000,-500\n07-07-2000,-500\n10-07-2000,-500\n11-07-2000,-500\n12-07-2000,-500\n13-07-2000,-500\n14-07-2000,-500\n17-07-2000,-500\n18-07-2000,-500\n19-07-2000,-500\n20-07-2000,-500\n21-07-2000,-500\n24-07-2000,-500\n25-07-2000,-500\n26-07-2000,-500\n27-07-2000,-500\n28-07-2000,-500\n31-07-2000,-500\n01-08-2000,-500\n02-08-2000,-500\n03-08-2000,-500\n04-08-2000,-500\n08-08-2000,-500\n09-08-2000,-500\n10-08-2000,-500\n11-08-2000,-500\n14-08-2000,-500\n15-08-2000,-500\n16-08-2000,-500\n17-08-2000,-500\n18-08-2000,-500\n21-08-2000,-500\n22-08-2000,-500\n23-08-2000,-500\n24-08-2000,-500\n25-08-2000,-500\n28-08-2000,-500\n29-08-2000,-500\n30-08-2000,-500\n31-08-2000,-500\n01-09-2000,-500\n05-09-2000,-500\n06-09-2000,-500\n07-09-2000,-500\n08-09-2000,-500\n11-09-2000,-500\n12-09-2000,-500\n13-09-2000,-500\n14-09-2000,-500\n15-09-2000,-500\n18-09-2000,-500\n19-09-2000,-500\n20-09-2000,-500\n21-09-2000,-500\n22-09-2000,-500\n25-09-2000,-500\n26-09-2000,-500\n27-09-2000,-500\n28-09-2000,-500\n29-09-2000,-500\n02-10-2000,-500\n03-10-2000,-500\n04-10-2000,-500\n05-10-2000,-500\n06-10-2000,-500\n10-10-2000,-500\n11-10-2000,-500\n12-10-2000,-500\n13-10-2000,-500\n16-10-2000,-500\n17-10-2000,-500\n18-10-2000,-500\n19-10-2000,-500\n20-10-2000,-500\n23-10-2000,-500\n24-10-2000,-500\n25-10-2000,-500\n26-10-2000,-500\n27-10-2000,-500\n30-10-2000,-500\n31-10-2000,-500\n01-11-2000,-500\n02-11-2000,-500\n03-11-2000,-500\n06-11-2000,-500\n07-11-2000,-500\n08-11-2000,-500\n09-11-2000,-500\n10-11-2000,-500\n14-11-2000,-500\n15-11-2000,-500\n16-11-2000,-500\n17-11-2000,-500\n20-11-2000,-500\n21-11-2000,-500\n22-11-2000,-500\n23-11-2000,-500\n24-11-2000,-500\n27-11-2000,-500\n28-11-2000,-500\n29-11-2000,-500\n30-11-2000,-500\n01-12-2000,-500\n04-12-2000,-500\n05-12-2000,-500\n06-12-2000,-500\n07-12-2000,-500\n08-12-2000,-500\n11-12-2000,-500\n12-12-2000,-500\n13-12-2000,-500\n14-12-2000,-500\n15-12-2000,-500\n18-12-2000,-500\n19-12-2000,-500\n20-12-2000,-500\n21-12-2000,-500\n22-12-2000,-500\n27-12-2000,-500\n28-12-2000,-500\n29-12-2000,-500\n02-01-2001,-500\n03-01-2001,-500\n04-01-2001,-500\n05-01-2001,-500\n08-01-2001,-500\n09-01-2001,-500\n10-01-2001,-500\n11-01-2001,-500\n12-01-2001,-500\n15-01-2001,-500\n16-01-2001,-500\n17-01-2001,-500\n18-01-2001,-500\n19-01-2001,-500\n22-01-2001,-500\n23-01-2001,-500\n24-01-2001,-500\n25-01-2001,-500\n26-01-2001,-500\n29-01-2001,-500\n30-01-2001,-500\n31-01-2001,-500\n01-02-2001,-500\n02-02-2001,-500\n05-02-2001,-500\n06-02-2001,-500\n07-02-2001,-500\n08-02-2001,-500\n09-02-2001,-500\n12-02-2001,-500\n13-02-2001,-500\n14-02-2001,-500\n15-02-2001,-500\n16-02-2001,-500\n19-02-2001,-500\n20-02-2001,-500\n21-02-2001,-500\n22-02-2001,-500\n23-02-2001,-500\n26-02-2001,-500\n27-02-2001,-500\n28-02-2001,-500\n01-03-2001,-500\n02-03-2001,-500\n05-03-2001,-500\n06-03-2001,-500\n07-03-2001,-500\n08-03-2001,-500\n09-03-2001,-500\n12-03-2001,-500\n13-03-2001,-500\n14-03-2001,-500\n15-03-2001,-500\n16-03-2001,-500\n19-03-2001,-500\n20-03-2001,-500\n21-03-2001,-500\n22-03-2001,-500\n23-03-2001,-500\n26-03-2001,-500\n27-03-2001,-500\n28-03-2001,-500\n29-03-2001,-500\n30-03-2001,-500\n02-04-2001,-500\n03-04-2001,-500\n04-04-2001,-500\n05-04-2001,-500\n06-04-2001,-500\n09-04-2001,-500\n10-04-2001,-500\n11-04-2001,-500\n12-04-2001,-500\n16-04-2001,-500\n17-04-2001,-500\n18-04-2001,-500\n19-04-2001,-500\n20-04-2001,-500\n23-04-2001,-500\n24-04-2001,-500\n25-04-2001,-500\n26-04-2001,-500\n27-04-2001,-500\n30-04-2001,-500\n01-05-2001,-500\n02-05-2001,-500\n03-05-2001,-500\n04-05-2001,-500\n07-05-2001,-500\n08-05-2001,-500\n09-05-2001,-500\n10-05-2001,-500\n11-05-2001,-500\n14-05-2001,-500\n15-05-2001,-500\n16-05-2001,-500\n17-05-2001,-500\n18-05-2001,-500\n22-05-2001,-500\n23-05-2001,-500\n24-05-2001,-500\n25-05-2001,-500\n28-05-2001,-500\n29-05-2001,-500\n30-05-2001,-500\n31-05-2001,-500\n01-06-2001,-500\n04-06-2001,-500\n05-06-2001,-500\n06-06-2001,-500\n07-06-2001,-500\n08-06-2001,-500\n11-06-2001,-500\n12-06-2001,-500\n13-06-2001,-500\n14-06-2001,-500\n15-06-2001,-500\n18-06-2001,-500\n19-06-2001,-500\n20-06-2001,-500\n21-06-2001,-500\n22-06-2001,-500\n25-06-2001,-500\n26-06-2001,-500\n27-06-2001,-500\n28-06-2001,-500\n29-06-2001,-500\n03-07-2001,-500\n04-07-2001,-500\n05-07-2001,-500\n06-07-2001,-500\n09-07-2001,-500\n10-07-2001,-500\n11-07-2001,-500\n12-07-2001,-500\n13-07-2001,-500\n16-07-2001,-500\n17-07-2001,-500\n18-07-2001,-500\n19-07-2001,-500\n20-07-2001,-500\n23-07-2001,-500\n24-07-2001,-500\n25-07-2001,-500\n26-07-2001,-500\n27-07-2001,-500\n30-07-2001,-500\n31-07-2001,-500\n01-08-2001,-500\n02-08-2001,-500\n03-08-2001,-500\n07-08-2001,-500\n08-08-2001,-500\n09-08-2001,-500\n10-08-2001,-500\n13-08-2001,-500\n14-08-2001,-500\n15-08-2001,-500\n16-08-2001,-500\n17-08-2001,-500\n20-08-2001,-500\n21-08-2001,-500\n22-08-2001,-500\n23-08-2001,-500\n24-08-2001,-500\n27-08-2001,-500\n28-08-2001,-500\n29-08-2001,-500\n30-08-2001,-500\n31-08-2001,-500\n04-09-2001,-500\n05-09-2001,-500\n06-09-2001,-500\n07-09-2001,-500\n10-09-2001,-500\n11-09-2001,-500\n12-09-2001,-500\n13-09-2001,-500\n14-09-2001,-500\n17-09-2001,-500\n18-09-2001,-500\n19-09-2001,-500\n20-09-2001,-500\n21-09-2001,-500\n24-09-2001,-500\n25-09-2001,-500\n26-09-2001,-500\n27-09-2001,-500\n28-09-2001,-500\n01-10-2001,-500\n02-10-2001,-500\n03-10-2001,-500\n04-10-2001,-500\n05-10-2001,-500\n09-10-2001,-500\n10-10-2001,-500\n11-10-2001,-500\n12-10-2001,-500\n15-10-2001,-500\n16-10-2001,-500\n17-10-2001,-500\n18-10-2001,-500\n19-10-2001,-500\n22-10-2001,-500\n23-10-2001,-500\n24-10-2001,-500\n25-10-2001,-500\n26-10-2001,-500\n29-10-2001,-500\n30-10-2001,-500\n31-10-2001,-500\n01-11-2001,-500\n02-11-2001,-500\n05-11-2001,-500\n06-11-2001,-500\n07-11-2001,-500\n08-11-2001,-500\n09-11-2001,-500\n13-11-2001,-500\n14-11-2001,-500\n15-11-2001,-500\n16-11-2001,-500\n19-11-2001,-500\n20-11-2001,-500\n21-11-2001,-500\n22-11-2001,-500\n23-11-2001,-500\n26-11-2001,-500\n27-11-2001,-500\n28-11-2001,-500\n29-11-2001,-500\n30-11-2001,-500\n03-12-2001,-500\n04-12-2001,-500\n05-12-2001,-500\n06-12-2001,-500\n07-12-2001,-500\n10-12-2001,-500\n11-12-2001,-500\n12-12-2001,-500\n13-12-2001,-500\n14-12-2001,-500\n17-12-2001,-500\n18-12-2001,-500\n19-12-2001,-500\n20-12-2001,-500\n21-12-2001,-500\n24-12-2001,-500\n27-12-2001,-500\n28-12-2001,-500\n31-12-2001,-500\n02-01-2002,-500\n03-01-2002,-500\n04-01-2002,-500\n07-01-2002,-500\n08-01-2002,-500\n09-01-2002,-500\n10-01-2002,-500\n11-01-2002,-500\n14-01-2002,-500\n15-01-2002,-500\n16-01-2002,-500\n17-01-2002,-500\n18-01-2002,-500\n21-01-2002,-500\n22-01-2002,-500\n23-01-2002,-500\n24-01-2002,-500\n25-01-2002,-500\n28-01-2002,-500\n29-01-2002,-500\n30-01-2002,-500\n31-01-2002,-500\n01-02-2002,-500\n04-02-2002,-500\n05-02-2002,-500\n06-02-2002,-500\n07-02-2002,-500\n08-02-2002,-500\n11-02-2002,-500\n12-02-2002,-500\n13-02-2002,-500\n14-02-2002,-500\n15-02-2002,-500\n18-02-2002,-500\n19-02-2002,-500\n20-02-2002,-500\n21-02-2002,-500\n22-02-2002,-500\n25-02-2002,-500\n26-02-2002,-500\n27-02-2002,-500\n28-02-2002,-500\n01-03-2002,-500\n04-03-2002,-500\n05-03-2002,-500\n06-03-2002,-500\n07-03-2002,-500\n08-03-2002,-500\n11-03-2002,-500\n12-03-2002,-500\n13-03-2002,-500\n14-03-2002,-500\n15-03-2002,-500\n18-03-2002,-500\n19-03-2002,-500\n20-03-2002,-500\n21-03-2002,-500\n22-03-2002,-500\n25-03-2002,-500\n26-03-2002,-500\n27-03-2002,-500\n28-03-2002,-500\n01-04-2002,-500\n02-04-2002,-500\n03-04-2002,-500\n04-04-2002,-500\n05-04-2002,-500\n08-04-2002,-500\n09-04-2002,-500\n10-04-2002,-500\n11-04-2002,-500\n12-04-2002,-500\n15-04-2002,-500\n16-04-2002,-500\n17-04-2002,-500\n18-04-2002,-500\n19-04-2002,-500\n22-04-2002,-500\n23-04-2002,-500\n24-04-2002,-500\n25-04-2002,-500\n26-04-2002,-500\n29-04-2002,-500\n30-04-2002,-500\n01-05-2002,-500\n02-05-2002,-500\n03-05-2002,-500\n06-05-2002,-500\n07-05-2002,-500\n08-05-2002,-500\n09-05-2002,-500\n10-05-2002,-500\n13-05-2002,-500\n14-05-2002,-500\n15-05-2002,-500\n16-05-2002,-500\n17-05-2002,-500\n21-05-2002,-500\n22-05-2002,-500\n23-05-2002,-500\n24-05-2002,-500\n27-05-2002,-500\n28-05-2002,-500\n29-05-2002,-500\n30-05-2002,-500\n31-05-2002,-500\n03-06-2002,-500\n04-06-2002,-500\n05-06-2002,-500\n06-06-2002,-500\n07-06-2002,-500\n10-06-2002,-500\n11-06-2002,-500\n12-06-2002,-500\n13-06-2002,-500\n14-06-2002,-500\n17-06-2002,-500\n18-06-2002,-500\n19-06-2002,-500\n20-06-2002,-500\n21-06-2002,-500\n24-06-2002,-500\n25-06-2002,-500\n26-06-2002,-500\n27-06-2002,-500\n28-06-2002,-500\n02-07-2002,-500\n03-07-2002,-500\n04-07-2002,-500\n05-07-2002,-500\n08-07-2002,-500\n09-07-2002,-500\n10-07-2002,-500\n11-07-2002,-500\n12-07-2002,-500\n15-07-2002,-500\n16-07-2002,-500\n17-07-2002,-500\n18-07-2002,-500\n19-07-2002,-500\n22-07-2002,-500\n23-07-2002,-500\n24-07-2002,-500\n25-07-2002,-500\n26-07-2002,-500\n29-07-2002,-500\n30-07-2002,-500\n31-07-2002,-500\n01-08-2002,-500\n02-08-2002,-500\n06-08-2002,-500\n07-08-2002,-500\n08-08-2002,-500\n09-08-2002,-500\n12-08-2002,-500\n13-08-2002,-500\n14-08-2002,-500\n15-08-2002,-500\n16-08-2002,-500\n19-08-2002,-500\n20-08-2002,-500\n21-08-2002,-500\n22-08-2002,-500\n23-08-2002,-500\n26-08-2002,-500\n27-08-2002,-500\n28-08-2002,-500\n29-08-2002,-500\n30-08-2002,-500\n03-09-2002,-500\n04-09-2002,-500\n05-09-2002,-500\n06-09-2002,-500\n09-09-2002,-500\n10-09-2002,-500\n11-09-2002,-500\n12-09-2002,-500\n13-09-2002,-500\n16-09-2002,-500\n17-09-2002,-500\n18-09-2002,-500\n19-09-2002,-500\n20-09-2002,-500\n23-09-2002,-500\n24-09-2002,-500\n25-09-2002,-500\n26-09-2002,-500\n27-09-2002,-500\n30-09-2002,-500\n01-10-2002,-500\n02-10-2002,-500\n03-10-2002,-500\n04-10-2002,-500\n07-10-2002,-500\n08-10-2002,-500\n09-10-2002,-500\n10-10-2002,-500\n11-10-2002,-500\n15-10-2002,-500\n16-10-2002,-500\n17-10-2002,-500\n18-10-2002,-500\n21-10-2002,-500\n22-10-2002,-500\n23-10-2002,-500\n24-10-2002,-500\n25-10-2002,-500\n28-10-2002,-500\n29-10-2002,-500\n30-10-2002,-500\n31-10-2002,-500\n01-11-2002,-500\n04-11-2002,-500\n05-11-2002,-500\n06-11-2002,-500\n07-11-2002,-500\n08-11-2002,-500\n12-11-2002,-500\n13-11-2002,-500\n14-11-2002,-500\n15-11-2002,-500\n18-11-2002,-500\n19-11-2002,-500\n20-11-2002,-500\n21-11-2002,-500\n22-11-2002,-500\n25-11-2002,-500\n26-11-2002,-500\n27-11-2002,-500\n28-11-2002,-500\n29-11-2002,-500\n02-12-2002,-500\n03-12-2002,-500\n04-12-2002,-500\n05-12-2002,-500\n06-12-2002,-500\n09-12-2002,-500\n10-12-2002,-500\n11-12-2002,-500\n12-12-2002,-500\n13-12-2002,-500\n16-12-2002,-500\n17-12-2002,-500\n18-12-2002,-500\n19-12-2002,-500\n20-12-2002,-500\n23-12-2002,-500\n24-12-2002,-500\n27-12-2002,-500\n30-12-2002,-500\n31-12-2002,-500\n02-01-2003,-500\n03-01-2003,-500\n06-01-2003,-500\n07-01-2003,-500\n08-01-2003,-500\n09-01-2003,-500\n10-01-2003,-500\n13-01-2003,-500\n14-01-2003,-500\n15-01-2003,-500\n16-01-2003,-500\n17-01-2003,-500\n20-01-2003,-500\n21-01-2003,-500\n22-01-2003,-500\n23-01-2003,-500\n24-01-2003,-500\n27-01-2003,-500\n28-01-2003,-500\n29-01-2003,-500\n30-01-2003,-500\n31-01-2003,-500\n03-02-2003,-500\n04-02-2003,-500\n05-02-2003,-500\n06-02-2003,-500\n07-02-2003,-500\n10-02-2003,-500\n11-02-2003,-500\n12-02-2003,-500\n13-02-2003,-500\n14-02-2003,-500\n17-02-2003,-500\n18-02-2003,-500\n19-02-2003,-500\n20-02-2003,-500\n21-02-2003,-500\n24-02-2003,-500\n25-02-2003,-500\n26-02-2003,-500\n27-02-2003,-500\n28-02-2003,-500\n03-03-2003,-500\n04-03-2003,-500\n05-03-2003,-500\n06-03-2003,-500\n07-03-2003,-500\n10-03-2003,-500\n11-03-2003,-500\n12-03-2003,-500\n13-03-2003,-500\n14-03-2003,-500\n17-03-2003,-500\n18-03-2003,-500\n19-03-2003,-500\n20-03-2003,-500\n21-03-2003,-500\n24-03-2003,-500\n25-03-2003,-500\n26-03-2003,-500\n27-03-2003,-500\n28-03-2003,-500\n31-03-2003,-500\n01-04-2003,-500\n02-04-2003,-500\n03-04-2003,-500\n04-04-2003,-500\n07-04-2003,-500\n08-04-2003,-500\n09-04-2003,-500\n10-04-2003,-500\n11-04-2003,-500\n14-04-2003,-500\n15-04-2003,-500\n16-04-2003,-500\n17-04-2003,-500\n21-04-2003,-500\n22-04-2003,-500\n23-04-2003,-500\n24-04-2003,-500\n25-04-2003,-500\n28-04-2003,-500\n29-04-2003,-500\n30-04-2003,-500\n01-05-2003,-500\n02-05-2003,-500\n05-05-2003,-500\n06-05-2003,-500\n07-05-2003,-500\n08-05-2003,-500\n09-05-2003,-500\n12-05-2003,-500\n13-05-2003,-500\n14-05-2003,-500\n15-05-2003,-500\n16-05-2003,-500\n20-05-2003,-500\n21-05-2003,-500\n22-05-2003,-500\n23-05-2003,-500\n26-05-2003,-500\n27-05-2003,-500\n28-05-2003,-500\n29-05-2003,-500\n30-05-2003,-500\n02-06-2003,-500\n03-06-2003,-500\n04-06-2003,-500\n05-06-2003,-500\n06-06-2003,-500\n09-06-2003,-500\n10-06-2003,-500\n11-06-2003,-500\n12-06-2003,-500\n13-06-2003,-500\n16-06-2003,-500\n17-06-2003,-500\n18-06-2003,-500\n19-06-2003,-500\n20-06-2003,-500\n23-06-2003,-500\n24-06-2003,-500\n25-06-2003,-500\n26-06-2003,-500\n27-06-2003,-500\n30-06-2003,-500\n02-07-2003,-500\n03-07-2003,-500\n04-07-2003,-500\n07-07-2003,-500\n08-07-2003,-500\n09-07-2003,-500\n10-07-2003,-500\n11-07-2003,-500\n14-07-2003,-500\n15-07-2003,-500\n16-07-2003,-500\n17-07-2003,-500\n18-07-2003,-500\n21-07-2003,-500\n22-07-2003,-500\n23-07-2003,-500\n24-07-2003,-500\n25-07-2003,-500\n28-07-2003,-500\n29-07-2003,-500\n30-07-2003,-500\n31-07-2003,-500\n01-08-2003,-500\n05-08-2003,-500\n06-08-2003,-500\n07-08-2003,-500\n08-08-2003,-500\n11-08-2003,-500\n12-08-2003,-500\n13-08-2003,-500\n14-08-2003,-500\n15-08-2003,-500\n18-08-2003,-500\n19-08-2003,-500\n20-08-2003,-500\n21-08-2003,-500\n22-08-2003,-500\n25-08-2003,-500\n26-08-2003,-500\n27-08-2003,-500\n28-08-2003,-500\n29-08-2003,-500\n02-09-2003,-500\n03-09-2003,-500\n04-09-2003,-500\n05-09-2003,-500\n08-09-2003,-500\n09-09-2003,-500\n10-09-2003,-500\n11-09-2003,-500\n12-09-2003,-500\n15-09-2003,-500\n16-09-2003,-500\n17-09-2003,-500\n18-09-2003,-500\n19-09-2003,-500\n22-09-2003,-500\n23-09-2003,-500\n24-09-2003,-500\n25-09-2003,-500\n26-09-2003,-500\n29-09-2003,-500\n30-09-2003,-500\n01-10-2003,-500\n02-10-2003,-500\n03-10-2003,-500\n06-10-2003,-500\n07-10-2003,-500\n08-10-2003,-500\n09-10-2003,-500\n10-10-2003,-500\n14-10-2003,-500\n15-10-2003,-500\n16-10-2003,-500\n17-10-2003,-500\n20-10-2003,-500\n21-10-2003,-500\n22-10-2003,-500\n23-10-2003,-500\n24-10-2003,-500\n27-10-2003,-500\n28-10-2003,-500\n29-10-2003,-500\n30-10-2003,-500\n31-10-2003,-500\n03-11-2003,-500\n04-11-2003,-500\n05-11-2003,-500\n06-11-2003,-500\n07-11-2003,-500\n10-11-2003,-500\n12-11-2003,-500\n13-11-2003,-500\n14-11-2003,-500\n17-11-2003,-500\n18-11-2003,-500\n19-11-2003,-500\n20-11-2003,-500\n21-11-2003,-500\n24-11-2003,-500\n25-11-2003,-500\n26-11-2003,-500\n27-11-2003,-500\n28-11-2003,-500\n01-12-2003,-500\n02-12-2003,-500\n03-12-2003,-500\n04-12-2003,-500\n05-12-2003,-500\n08-12-2003,-500\n09-12-2003,-500\n10-12-2003,-500\n11-12-2003,-500\n12-12-2003,-500\n15-12-2003,-500\n16-12-2003,-500\n17-12-2003,-500\n18-12-2003,-500\n19-12-2003,-500\n22-12-2003,-500\n23-12-2003,-500\n24-12-2003,-500\n29-12-2003,-500\n30-12-2003,-500\n31-12-2003,-500\n02-01-2004,-500\n05-01-2004,-500\n06-01-2004,-500\n07-01-2004,-500\n08-01-2004,-500\n09-01-2004,-500\n12-01-2004,-500\n13-01-2004,-500\n14-01-2004,-500\n15-01-2004,-500\n16-01-2004,-500\n19-01-2004,-500\n20-01-2004,-500\n21-01-2004,-500\n22-01-2004,-500\n23-01-2004,-500\n26-01-2004,-500\n27-01-2004,-500\n28-01-2004,-500\n29-01-2004,-500\n30-01-2004,-500\n02-02-2004,-500\n03-02-2004,-500\n04-02-2004,-500\n05-02-2004,-500\n06-02-2004,-500\n09-02-2004,-500\n10-02-2004,-500\n11-02-2004,-500\n12-02-2004,-500\n13-02-2004,-500\n16-02-2004,-500\n17-02-2004,-500\n18-02-2004,-500\n19-02-2004,-500\n20-02-2004,-500\n23-02-2004,-500\n24-02-2004,-500\n25-02-2004,-500\n26-02-2004,-500\n27-02-2004,-500\n01-03-2004,-500\n02-03-2004,-500\n03-03-2004,-500\n04-03-2004,-500\n05-03-2004,-500\n08-03-2004,-500\n09-03-2004,-500\n10-03-2004,-500\n11-03-2004,-500\n12-03-2004,-500\n15-03-2004,-500\n16-03-2004,-500\n17-03-2004,-500\n18-03-2004,-500\n19-03-2004,-500\n22-03-2004,-500\n23-03-2004,-500\n24-03-2004,-500\n25-03-2004,-500\n26-03-2004,-500\n29-03-2004,-500\n30-03-2004,-500\n31-03-2004,-500\n01-04-2004,-500\n02-04-2004,-500\n05-04-2004,-500\n06-04-2004,-500\n07-04-2004,-500\n08-04-2004,-500\n12-04-2004,-500\n13-04-2004,-500\n14-04-2004,-500\n15-04-2004,-500\n16-04-2004,-500\n19-04-2004,-500\n20-04-2004,-500\n21-04-2004,-500\n22-04-2004,-500\n23-04-2004,-500\n26-04-2004,-500\n27-04-2004,-500\n28-04-2004,-500\n29-04-2004,-500\n30-04-2004,-500\n03-05-2004,-500\n04-05-2004,-500\n05-05-2004,-500\n06-05-2004,-500\n07-05-2004,-500\n10-05-2004,-500\n11-05-2004,-500\n12-05-2004,-500\n13-05-2004,-500\n14-05-2004,-500\n17-05-2004,-500\n18-05-2004,-500\n19-05-2004,-500\n20-05-2004,-500\n21-05-2004,-500\n25-05-2004,-500\n26-05-2004,-500\n27-05-2004,-500\n28-05-2004,-500\n31-05-2004,-500\n01-06-2004,-500\n02-06-2004,-500\n03-06-2004,-500\n04-06-2004,-500\n07-06-2004,-500\n08-06-2004,-500\n09-06-2004,-500\n10-06-2004,-500\n11-06-2004,-500\n14-06-2004,-500\n15-06-2004,-500\n16-06-2004,-500\n17-06-2004,-500\n18-06-2004,-500\n21-06-2004,-500\n22-06-2004,-500\n23-06-2004,-500\n24-06-2004,-500\n25-06-2004,-500\n28-06-2004,-500\n29-06-2004,-500\n30-06-2004,-500\n02-07-2004,-500\n05-07-2004,-500\n06-07-2004,-500\n07-07-2004,-500\n08-07-2004,-500\n09-07-2004,-500\n12-07-2004,-500\n13-07-2004,-500\n14-07-2004,-500\n15-07-2004,-500\n16-07-2004,-500\n19-07-2004,-500\n20-07-2004,-500\n21-07-2004,-500\n22-07-2004,-500\n23-07-2004,-500\n26-07-2004,-500\n27-07-2004,-500\n28-07-2004,-500\n29-07-2004,-500\n30-07-2004,-500\n03-08-2004,-500\n04-08-2004,-500\n05-08-2004,-500\n06-08-2004,-500\n09-08-2004,-500\n10-08-2004,-500\n11-08-2004,-500\n12-08-2004,-500\n13-08-2004,-500\n16-08-2004,-500\n17-08-2004,-500\n18-08-2004,-500\n19-08-2004,-500\n20-08-2004,-500\n23-08-2004,-500\n24-08-2004,-500\n25-08-2004,-500\n26-08-2004,-500\n27-08-2004,-500\n30-08-2004,-500\n31-08-2004,-500\n01-09-2004,-500\n02-09-2004,-500\n03-09-2004,-500\n07-09-2004,-500\n08-09-2004,-500\n09-09-2004,-500\n10-09-2004,-500\n13-09-2004,-500\n14-09-2004,-500\n15-09-2004,-500\n16-09-2004,-500\n17-09-2004,-500\n20-09-2004,-500\n21-09-2004,-500\n22-09-2004,-500\n23-09-2004,-500\n24-09-2004,-500\n27-09-2004,-500\n28-09-2004,-500\n29-09-2004,-500\n30-09-2004,-500\n01-10-2004,-500\n04-10-2004,-500\n05-10-2004,-500\n06-10-2004,-500\n07-10-2004,-500\n08-10-2004,-500\n12-10-2004,-500\n13-10-2004,-500\n14-10-2004,-500\n15-10-2004,-500\n18-10-2004,-500\n19-10-2004,-500\n20-10-2004,-500\n21-10-2004,-500\n22-10-2004,-500\n25-10-2004,-500\n26-10-2004,-500\n27-10-2004,-500\n28-10-2004,-500\n29-10-2004,-500\n01-11-2004,-500\n02-11-2004,-500\n03-11-2004,-500\n04-11-2004,-500\n05-11-2004,-500\n08-11-2004,-500\n09-11-2004,-500\n10-11-2004,-500\n12-11-2004,-500\n15-11-2004,-500\n16-11-2004,-500\n17-11-2004,-500\n18-11-2004,-500\n19-11-2004,-500\n22-11-2004,-500\n23-11-2004,-500\n24-11-2004,-500\n25-11-2004,-500\n26-11-2004,-500\n29-11-2004,-500\n30-11-2004,-500\n01-12-2004,-500\n02-12-2004,-500\n03-12-2004,-500\n06-12-2004,-500\n07-12-2004,-500\n08-12-2004,-500\n09-12-2004,-500\n10-12-2004,-500\n13-12-2004,-500\n14-12-2004,-500\n15-12-2004,-500\n16-12-2004,-500\n17-12-2004,-500\n20-12-2004,-500\n21-12-2004,-500\n22-12-2004,-500\n23-12-2004,-500\n24-12-2004,-500\n29-12-2004,-500\n30-12-2004,-500\n31-12-2004,-500\n04-01-2005,-500\n05-01-2005,-500\n06-01-2005,-500\n07-01-2005,-500\n10-01-2005,-500\n11-01-2005,-500\n12-01-2005,-500\n13-01-2005,-500\n14-01-2005,-500\n17-01-2005,-500\n18-01-2005,-500\n19-01-2005,-500\n20-01-2005,-500\n21-01-2005,-500\n24-01-2005,-500\n25-01-2005,-500\n26-01-2005,-500\n27-01-2005,-500\n28-01-2005,-500\n31-01-2005,-500\n01-02-2005,-500\n02-02-2005,-500\n03-02-2005,-500\n04-02-2005,-500\n07-02-2005,-500\n08-02-2005,-500\n09-02-2005,-500\n10-02-2005,-500\n11-02-2005,-500\n14-02-2005,-500\n15-02-2005,-500\n16-02-2005,-500\n17-02-2005,-500\n18-02-2005,-500\n21-02-2005,-500\n22-02-2005,-500\n23-02-2005,-500\n24-02-2005,-500\n25-02-2005,-500\n28-02-2005,-500\n01-03-2005,-500\n02-03-2005,-500\n03-03-2005,-500\n04-03-2005,-500\n07-03-2005,-500\n08-03-2005,-500\n09-03-2005,-500\n10-03-2005,-500\n11-03-2005,-500\n14-03-2005,-500\n15-03-2005,-500\n16-03-2005,-500\n17-03-2005,-500\n18-03-2005,-500\n21-03-2005,-500\n22-03-2005,-500\n23-03-2005,-500\n24-03-2005,-500\n28-03-2005,-500\n29-03-2005,-500\n30-03-2005,-500\n31-03-2005,-500\n01-04-2005,-500\n04-04-2005,-500\n05-04-2005,-500\n06-04-2005,-500\n07-04-2005,-500\n08-04-2005,-500\n11-04-2005,-500\n12-04-2005,-500\n13-04-2005,-500\n14-04-2005,-500\n15-04-2005,-500\n18-04-2005,-500\n19-04-2005,-500\n20-04-2005,-500\n21-04-2005,-500\n22-04-2005,-500\n25-04-2005,-500\n26-04-2005,-500\n27-04-2005,-500\n28-04-2005,-500\n29-04-2005,-500\n02-05-2005,-500\n03-05-2005,-500\n04-05-2005,-500\n05-05-2005,-500\n06-05-2005,-500\n09-05-2005,-500\n10-05-2005,-500\n11-05-2005,-500\n12-05-2005,-500\n13-05-2005,-500\n16-05-2005,-500\n17-05-2005,-500\n18-05-2005,-500\n19-05-2005,-500\n20-05-2005,-500\n24-05-2005,-500\n25-05-2005,-500\n26-05-2005,-500\n27-05-2005,-500\n30-05-2005,-500\n31-05-2005,-500\n01-06-2005,-500\n02-06-2005,-500\n03-06-2005,-500\n06-06-2005,-500\n07-06-2005,-500\n08-06-2005,-500\n09-06-2005,-500\n10-06-2005,-500\n13-06-2005,-500\n14-06-2005,-500\n15-06-2005,-500\n16-06-2005,-500\n17-06-2005,-500\n20-06-2005,-500\n21-06-2005,-500\n22-06-2005,-500\n23-06-2005,-500\n24-06-2005,-500\n27-06-2005,-500\n28-06-2005,-500\n29-06-2005,-500\n30-06-2005,-500\n04-07-2005,-500\n05-07-2005,-500\n06-07-2005,-500\n07-07-2005,-500\n08-07-2005,-500\n11-07-2005,-500\n12-07-2005,-500\n13-07-2005,-500\n14-07-2005,-500\n15-07-2005,-500\n18-07-2005,-500\n19-07-2005,-500\n20-07-2005,-500\n21-07-2005,-500\n22-07-2005,-500\n25-07-2005,-500\n26-07-2005,-500\n27-07-2005,-500\n28-07-2005,-500\n29-07-2005,-500\n02-08-2005,-500\n03-08-2005,-500\n04-08-2005,-500\n05-08-2005,-500\n08-08-2005,-500\n09-08-2005,-500\n10-08-2005,-500\n11-08-2005,-500\n12-08-2005,-500\n15-08-2005,-500\n16-08-2005,-500\n17-08-2005,-500\n18-08-2005,-500\n19-08-2005,-500\n22-08-2005,-500\n23-08-2005,-500\n24-08-2005,-500\n25-08-2005,-500\n26-08-2005,-500\n29-08-2005,-500\n30-08-2005,-500\n31-08-2005,-500\n01-09-2005,-500\n02-09-2005,-500\n06-09-2005,-500\n07-09-2005,-500\n08-09-2005,-500\n09-09-2005,-500\n12-09-2005,-500\n13-09-2005,-500\n14-09-2005,-500\n15-09-2005,-500\n16-09-2005,-500\n19-09-2005,-500\n20-09-2005,-500\n21-09-2005,-500\n22-09-2005,-500\n23-09-2005,-500\n26-09-2005,-500\n27-09-2005,-500\n28-09-2005,-500\n29-09-2005,-500\n30-09-2005,-500\n03-10-2005,-500\n04-10-2005,-500\n05-10-2005,-500\n06-10-2005,-500\n07-10-2005,-500\n11-10-2005,-500\n12-10-2005,-500\n13-10-2005,-500\n14-10-2005,-500\n17-10-2005,-500\n18-10-2005,-500\n19-10-2005,-500\n20-10-2005,-500\n21-10-2005,-500\n24-10-2005,-500\n25-10-2005,-500\n26-10-2005,-500\n27-10-2005,-500\n28-10-2005,-500\n31-10-2005,-500\n01-11-2005,-500\n02-11-2005,-500\n03-11-2005,-500\n04-11-2005,-500\n07-11-2005,-500\n08-11-2005,-500\n09-11-2005,-500\n10-11-2005,-500\n14-11-2005,-500\n15-11-2005,-500\n16-11-2005,-500\n17-11-2005,-500\n18-11-2005,-500\n21-11-2005,-500\n22-11-2005,-500\n23-11-2005,-500\n24-11-2005,-500\n25-11-2005,-500\n28-11-2005,-500\n29-11-2005,-500\n30-11-2005,-500\n01-12-2005,-500\n02-12-2005,-500\n05-12-2005,-500\n06-12-2005,-500\n07-12-2005,-500\n08-12-2005,-500\n09-12-2005,-500\n12-12-2005,-500\n13-12-2005,-500\n14-12-2005,-500\n15-12-2005,-500\n16-12-2005,-500\n19-12-2005,-500\n20-12-2005,-500\n21-12-2005,-500\n22-12-2005,-500\n23-12-2005,-500\n28-12-2005,-500\n29-12-2005,-500\n30-12-2005,-500\n03-01-2006,-500\n04-01-2006,-500\n05-01-2006,-500\n06-01-2006,-500\n09-01-2006,-500\n10-01-2006,-500\n11-01-2006,-500\n12-01-2006,-500\n13-01-2006,-500\n16-01-2006,-500\n17-01-2006,-500\n18-01-2006,-500\n19-01-2006,-500\n20-01-2006,-500\n23-01-2006,-500\n24-01-2006,-500\n25-01-2006,-500\n26-01-2006,-500\n27-01-2006,-500\n30-01-2006,-500\n31-01-2006,-500\n01-02-2006,-500\n02-02-2006,-500\n03-02-2006,-500\n06-02-2006,-500\n07-02-2006,-500\n08-02-2006,-500\n09-02-2006,-500\n10-02-2006,-500\n13-02-2006,-500\n14-02-2006,-500\n15-02-2006,-500\n16-02-2006,-500\n17-02-2006,-500\n20-02-2006,-500\n21-02-2006,-500\n22-02-2006,-500\n23-02-2006,-500\n24-02-2006,-500\n27-02-2006,-500\n28-02-2006,-500\n01-03-2006,-500\n02-03-2006,-500\n03-03-2006,-500\n06-03-2006,-500\n07-03-2006,-500\n08-03-2006,-500\n09-03-2006,-500\n10-03-2006,-500\n13-03-2006,-500\n14-03-2006,-500\n15-03-2006,-500\n16-03-2006,-500\n17-03-2006,-500\n20-03-2006,-500\n21-03-2006,-500\n22-03-2006,-500\n23-03-2006,-500\n24-03-2006,-500\n27-03-2006,-500\n28-03-2006,-500\n29-03-2006,-500\n30-03-2006,-500\n31-03-2006,-500\n03-04-2006,-500\n04-04-2006,-500\n05-04-2006,-500\n06-04-2006,-500\n07-04-2006,-500\n10-04-2006,-500\n11-04-2006,-500\n12-04-2006,-500\n13-04-2006,-500\n17-04-2006,-500\n18-04-2006,-500\n19-04-2006,-500\n20-04-2006,-500\n21-04-2006,-500\n24-04-2006,-500\n25-04-2006,-500\n26-04-2006,-500\n27-04-2006,-500\n28-04-2006,-500\n01-05-2006,-500\n02-05-2006,-500\n03-05-2006,-500\n04-05-2006,-500\n05-05-2006,-500\n08-05-2006,-500\n09-05-2006,-500\n10-05-2006,-500\n11-05-2006,-500\n12-05-2006,-500\n15-05-2006,-500\n16-05-2006,-500\n17-05-2006,-500\n18-05-2006,-500\n19-05-2006,-500\n23-05-2006,-500\n24-05-2006,-500\n25-05-2006,-500\n26-05-2006,-500\n29-05-2006,-500\n30-05-2006,-500\n31-05-2006,-500\n01-06-2006,-500\n02-06-2006,-500\n05-06-2006,-500\n06-06-2006,-500\n07-06-2006,-500\n08-06-2006,-500\n09-06-2006,-500\n12-06-2006,-500\n13-06-2006,-500\n14-06-2006,-500\n15-06-2006,-500\n16-06-2006,-500\n19-06-2006,-500\n20-06-2006,-500\n21-06-2006,-500\n22-06-2006,-500\n23-06-2006,-500\n26-06-2006,-500\n27-06-2006,-500\n28-06-2006,-500\n29-06-2006,-500\n30-06-2006,-500\n04-07-2006,-500\n05-07-2006,-500\n06-07-2006,-500\n07-07-2006,-500\n10-07-2006,-500\n11-07-2006,-500\n12-07-2006,-500\n13-07-2006,-500\n14-07-2006,-500\n17-07-2006,-500\n18-07-2006,-500\n19-07-2006,-500\n20-07-2006,-500\n21-07-2006,-500\n24-07-2006,-500\n25-07-2006,-500\n26-07-2006,-500\n27-07-2006,-500\n28-07-2006,-500\n31-07-2006,-500\n01-08-2006,-500\n02-08-2006,-500\n03-08-2006,-500\n04-08-2006,-500\n08-08-2006,-500\n09-08-2006,-500\n10-08-2006,-500\n11-08-2006,-500\n14-08-2006,-500\n15-08-2006,-500\n16-08-2006,-500\n17-08-2006,-500\n18-08-2006,-500\n21-08-2006,-500\n22-08-2006,-500\n23-08-2006,-500\n24-08-2006,-500\n25-08-2006,-500\n28-08-2006,-500\n29-08-2006,-500\n30-08-2006,-500\n31-08-2006,-500\n01-09-2006,-500\n05-09-2006,-500\n06-09-2006,-500\n07-09-2006,-500\n08-09-2006,-500\n11-09-2006,-500\n12-09-2006,-500\n13-09-2006,-500\n14-09-2006,-500\n15-09-2006,-500\n18-09-2006,-500\n19-09-2006,-500\n20-09-2006,-500\n21-09-2006,-500\n22-09-2006,-500\n25-09-2006,-500\n26-09-2006,-500\n27-09-2006,-500\n28-09-2006,-500\n29-09-2006,-500\n02-10-2006,-500\n03-10-2006,-500\n04-10-2006,-500\n05-10-2006,-500\n06-10-2006,-500\n10-10-2006,-500\n11-10-2006,-500\n12-10-2006,-500\n13-10-2006,-500\n16-10-2006,-500\n17-10-2006,-500\n18-10-2006,-500\n19-10-2006,-500\n20-10-2006,-500\n23-10-2006,-500\n24-10-2006,-500\n25-10-2006,-500\n26-10-2006,-500\n27-10-2006,-500\n30-10-2006,-500\n31-10-2006,-500\n01-11-2006,-500\n02-11-2006,-500\n03-11-2006,-500\n06-11-2006,-500\n07-11-2006,-500\n08-11-2006,-500\n09-11-2006,-500\n10-11-2006,-500\n14-11-2006,-500\n15-11-2006,-500\n16-11-2006,-500\n17-11-2006,-500\n20-11-2006,-500\n21-11-2006,-500\n22-11-2006,-500\n23-11-2006,-500\n24-11-2006,-500\n27-11-2006,-500\n28-11-2006,-500\n29-11-2006,-500\n30-11-2006,-500\n01-12-2006,-500\n04-12-2006,-500\n05-12-2006,-500\n06-12-2006,-500\n07-12-2006,-500\n08-12-2006,-500\n11-12-2006,-500\n12-12-2006,-500\n13-12-2006,-500\n14-12-2006,-500\n15-12-2006,-500\n18-12-2006,-500\n19-12-2006,-500\n20-12-2006,-500\n21-12-2006,-500\n22-12-2006,-500\n27-12-2006,-500\n28-12-2006,-500\n29-12-2006,-500\n02-01-2007,-500\n03-01-2007,-500\n04-01-2007,-500\n05-01-2007,-500\n08-01-2007,-500\n09-01-2007,-500\n10-01-2007,-500\n11-01-2007,-500\n12-01-2007,-500\n15-01-2007,-500\n16-01-2007,-500\n17-01-2007,-500\n18-01-2007,-500\n19-01-2007,-500\n22-01-2007,-500\n23-01-2007,-500\n24-01-2007,-500\n25-01-2007,-500\n26-01-2007,-500\n29-01-2007,-500\n30-01-2007,-500\n31-01-2007,-500\n01-02-2007,-500\n02-02-2007,-500\n05-02-2007,-500\n06-02-2007,-500\n07-02-2007,-500\n08-02-2007,-500\n09-02-2007,-500\n12-02-2007,-500\n13-02-2007,-500\n14-02-2007,-500\n15-02-2007,-500\n16-02-2007,-500\n19-02-2007,-500\n20-02-2007,-500\n21-02-2007,-500\n22-02-2007,-500\n23-02-2007,-500\n26-02-2007,-500\n27-02-2007,-500\n28-02-2007,-500\n01-03-2007,-500\n02-03-2007,-500\n05-03-2007,-500\n06-03-2007,-500\n07-03-2007,-500\n08-03-2007,-500\n09-03-2007,-500\n12-03-2007,-500\n13-03-2007,-500\n14-03-2007,-500\n15-03-2007,-500\n16-03-2007,-500\n19-03-2007,-500\n20-03-2007,-500\n21-03-2007,-500\n22-03-2007,-500\n23-03-2007,-500\n26-03-2007,-500\n27-03-2007,-500\n28-03-2007,-500\n29-03-2007,-500\n30-03-2007,-500\n02-04-2007,-500\n03-04-2007,-500\n04-04-2007,-500\n05-04-2007,-500\n09-04-2007,-500\n10-04-2007,-500\n11-04-2007,-500\n12-04-2007,-500\n13-04-2007,-500\n16-04-2007,-500\n17-04-2007,-500\n18-04-2007,-500\n19-04-2007,-500\n20-04-2007,-500\n23-04-2007,-500\n24-04-2007,-500\n25-04-2007,-500\n26-04-2007,-500\n27-04-2007,-500\n30-04-2007,-500\n01-05-2007,-500\n02-05-2007,-500\n03-05-2007,-500\n04-05-2007,-500\n07-05-2007,-500\n08-05-2007,-500\n09-05-2007,-500\n10-05-2007,-500\n11-05-2007,-500\n14-05-2007,-500\n15-05-2007,-500\n16-05-2007,-500\n17-05-2007,-500\n18-05-2007,-500\n22-05-2007,-500\n23-05-2007,-500\n24-05-2007,-500\n25-05-2007,-500\n28-05-2007,-500\n29-05-2007,-500\n30-05-2007,-500\n31-05-2007,-500\n01-06-2007,-500\n04-06-2007,-500\n05-06-2007,-500\n06-06-2007,-500\n07-06-2007,-500\n08-06-2007,-500\n11-06-2007,-500\n12-06-2007,-500\n13-06-2007,-500\n14-06-2007,-500\n15-06-2007,-500\n18-06-2007,-500\n19-06-2007,-500\n20-06-2007,-500\n21-06-2007,-500\n22-06-2007,-500\n25-06-2007,-500\n26-06-2007,-500\n27-06-2007,-500\n28-06-2007,-500\n29-06-2007,-500\n03-07-2007,-500\n04-07-2007,-500\n05-07-2007,-500\n06-07-2007,-500\n09-07-2007,-500\n10-07-2007,-500\n11-07-2007,-500\n12-07-2007,-500\n13-07-2007,-500\n16-07-2007,-500\n17-07-2007,-500\n18-07-2007,-500\n19-07-2007,-500\n20-07-2007,-500\n23-07-2007,-500\n24-07-2007,-500\n25-07-2007,-500\n26-07-2007,-500\n27-07-2007,-500\n30-07-2007,-500\n31-07-2007,-500\n01-08-2007,-500\n02-08-2007,-500\n03-08-2007,-500\n07-08-2007,-500\n08-08-2007,-500\n09-08-2007,-500\n10-08-2007,-500\n13-08-2007,-500\n14-08-2007,-500\n15-08-2007,-500\n16-08-2007,-500\n17-08-2007,-500\n20-08-2007,-500\n21-08-2007,-500\n22-08-2007,-500\n23-08-2007,-500\n24-08-2007,-500\n27-08-2007,-500\n28-08-2007,-500\n29-08-2007,-500\n30-08-2007,-500\n31-08-2007,-500\n04-09-2007,-500\n05-09-2007,-500\n06-09-2007,-500\n07-09-2007,-500\n10-09-2007,-500\n11-09-2007,-500\n12-09-2007,-500\n13-09-2007,-500\n14-09-2007,-500\n17-09-2007,-500\n18-09-2007,-500\n19-09-2007,-500\n20-09-2007,-500\n21-09-2007,-500\n24-09-2007,-500\n25-09-2007,-500\n26-09-2007,-500\n27-09-2007,-500\n28-09-2007,-500\n01-10-2007,-500\n02-10-2007,-500\n03-10-2007,-500\n04-10-2007,-500\n05-10-2007,-500\n09-10-2007,-500\n10-10-2007,-500\n11-10-2007,-500\n12-10-2007,-500\n15-10-2007,-500\n16-10-2007,-500\n17-10-2007,-500\n18-10-2007,-500\n19-10-2007,-500\n22-10-2007,-500\n23-10-2007,-500\n24-10-2007,-500\n25-10-2007,-500\n26-10-2007,-500\n29-10-2007,-500\n30-10-2007,-500\n31-10-2007,-500\n01-11-2007,-500\n02-11-2007,-500\n05-11-2007,-500\n06-11-2007,-500\n07-11-2007,-500\n08-11-2007,-500\n09-11-2007,-500\n13-11-2007,-500\n14-11-2007,-500\n15-11-2007,-500\n16-11-2007,-500\n19-11-2007,-500\n20-11-2007,-500\n21-11-2007,-500\n22-11-2007,-500\n23-11-2007,-500\n26-11-2007,-500\n27-11-2007,-500\n28-11-2007,-500\n29-11-2007,-500\n30-11-2007,-500\n03-12-2007,-500\n04-12-2007,-500\n05-12-2007,-500\n06-12-2007,-500\n07-12-2007,-500\n10-12-2007,-500\n11-12-2007,-500\n12-12-2007,-500\n13-12-2007,-500\n14-12-2007,-500\n17-12-2007,-500\n18-12-2007,-500\n19-12-2007,-500\n20-12-2007,-500\n21-12-2007,-500\n24-12-2007,-500\n27-12-2007,-500\n28-12-2007,-500\n31-12-2007,-500\n02-01-2008,-500\n03-01-2008,-500\n04-01-2008,-500\n07-01-2008,-500\n08-01-2008,-500\n09-01-2008,-500\n10-01-2008,-500\n11-01-2008,-500\n14-01-2008,-500\n15-01-2008,-500\n16-01-2008,-500\n17-01-2008,-500\n18-01-2008,-500\n21-01-2008,-500\n22-01-2008,-500\n23-01-2008,-500\n24-01-2008,-500\n25-01-2008,-500\n28-01-2008,-500\n29-01-2008,-500\n30-01-2008,-500\n31-01-2008,-500\n01-02-2008,-500\n04-02-2008,-500\n05-02-2008,-500\n06-02-2008,-500\n07-02-2008,-500\n08-02-2008,-500\n11-02-2008,-500\n12-02-2008,-500\n13-02-2008,-500\n14-02-2008,-500\n15-02-2008,-500\n19-02-2008,-500\n20-02-2008,-500\n21-02-2008,-500\n22-02-2008,-500\n25-02-2008,-500\n26-02-2008,-500\n27-02-2008,-500\n28-02-2008,-500\n29-02-2008,-500\n03-03-2008,-500\n04-03-2008,-500\n05-03-2008,-500\n06-03-2008,-500\n07-03-2008,-500\n10-03-2008,-500\n11-03-2008,-500\n12-03-2008,-500\n13-03-2008,-500\n14-03-2008,-500\n17-03-2008,-500\n18-03-2008,-500\n19-03-2008,-500\n20-03-2008,-500\n24-03-2008,-500\n25-03-2008,-500\n26-03-2008,-500\n27-03-2008,-500\n28-03-2008,-500\n31-03-2008,-500\n01-04-2008,-500\n02-04-2008,-500\n03-04-2008,-500\n04-04-2008,-500\n07-04-2008,-500\n08-04-2008,-500\n09-04-2008,-500\n10-04-2008,-500\n11-04-2008,-500\n14-04-2008,-500\n15-04-2008,-500\n16-04-2008,-500\n17-04-2008,-500\n18-04-2008,-500\n21-04-2008,-500\n22-04-2008,-500\n23-04-2008,-500\n24-04-2008,-500\n25-04-2008,-500\n28-04-2008,-500\n29-04-2008,-500\n30-04-2008,-500\n01-05-2008,-500\n02-05-2008,-500\n05-05-2008,-500\n06-05-2008,-500\n07-05-2008,-500\n08-05-2008,-500\n09-05-2008,-500\n12-05-2008,-500\n13-05-2008,-500\n14-05-2008,-500\n15-05-2008,-500\n16-05-2008,-500\n20-05-2008,-500\n21-05-2008,-500\n22-05-2008,-500\n23-05-2008,-500\n26-05-2008,-500\n27-05-2008,-500\n28-05-2008,-500\n29-05-2008,-500\n30-05-2008,-500\n02-06-2008,-500\n03-06-2008,-500\n04-06-2008,-500\n05-06-2008,-500\n06-06-2008,-500\n09-06-2008,-500\n10-06-2008,-500\n11-06-2008,-500\n12-06-2008,-500\n13-06-2008,-500\n16-06-2008,-500\n17-06-2008,-500\n18-06-2008,-500\n19-06-2008,-500\n20-06-2008,-500\n23-06-2008,-500\n24-06-2008,-500\n25-06-2008,-500\n26-06-2008,-500\n27-06-2008,-500\n30-06-2008,-500\n02-07-2008,-500\n03-07-2008,-500\n04-07-2008,-500\n07-07-2008,-500\n08-07-2008,-500\n09-07-2008,-500\n10-07-2008,-500\n11-07-2008,-500\n14-07-2008,-500\n15-07-2008,-500\n16-07-2008,-500\n17-07-2008,-500\n18-07-2008,-500\n21-07-2008,-500\n22-07-2008,-500\n23-07-2008,-500\n24-07-2008,-500\n25-07-2008,-500\n28-07-2008,-500\n29-07-2008,-500\n30-07-2008,-500\n31-07-2008,-500\n01-08-2008,-500\n05-08-2008,-500\n06-08-2008,-500\n07-08-2008,-500\n08-08-2008,-500\n11-08-2008,-500\n12-08-2008,-500\n13-08-2008,-500\n14-08-2008,-500\n15-08-2008,-500\n18-08-2008,-500\n19-08-2008,-500\n20-08-2008,-500\n21-08-2008,-500\n22-08-2008,-500\n25-08-2008,-500\n26-08-2008,-500\n27-08-2008,-500\n28-08-2008,-500\n29-08-2008,-500\n02-09-2008,-500\n03-09-2008,-500\n04-09-2008,-500\n05-09-2008,-500\n08-09-2008,-500\n09-09-2008,-500\n10-09-2008,-500\n11-09-2008,-500\n12-09-2008,-500\n15-09-2008,-500\n16-09-2008,-500\n17-09-2008,-500\n18-09-2008,-500\n19-09-2008,-500\n22-09-2008,-500\n23-09-2008,-500\n24-09-2008,-500\n25-09-2008,-500\n26-09-2008,-500\n29-09-2008,-500\n30-09-2008,-500\n01-10-2008,-500\n02-10-2008,-500\n03-10-2008,-500\n06-10-2008,-500\n07-10-2008,-500\n08-10-2008,-500\n09-10-2008,-500\n10-10-2008,-500\n14-10-2008,-500\n15-10-2008,-500\n16-10-2008,-500\n17-10-2008,-500\n20-10-2008,-500\n21-10-2008,-500\n22-10-2008,-500\n23-10-2008,-500\n24-10-2008,-500\n27-10-2008,-500\n28-10-2008,-500\n29-10-2008,-500\n30-10-2008,-500\n31-10-2008,-500\n03-11-2008,-500\n04-11-2008,-500\n05-11-2008,-500\n06-11-2008,-500\n07-11-2008,-500\n10-11-2008,-500\n12-11-2008,-500\n13-11-2008,-500\n14-11-2008,-500\n17-11-2008,-500\n18-11-2008,-500\n19-11-2008,-500\n20-11-2008,-500\n21-11-2008,-500\n24-11-2008,-500\n25-11-2008,-500\n26-11-2008,-500\n27-11-2008,-500\n28-11-2008,-500\n01-12-2008,-500\n02-12-2008,-500\n03-12-2008,-500\n04-12-2008,-500\n05-12-2008,-500\n08-12-2008,-500\n09-12-2008,-500\n10-12-2008,-500\n11-12-2008,-500\n12-12-2008,-500\n15-12-2008,-500\n16-12-2008,-500\n17-12-2008,-500\n18-12-2008,-500\n19-12-2008,-500\n22-12-2008,-500\n23-12-2008,-500\n24-12-2008,-500\n29-12-2008,-500\n30-12-2008,-500\n31-12-2008,-500\n02-01-2009,-500\n05-01-2009,-500\n06-01-2009,-500\n07-01-2009,-500\n08-01-2009,-500\n09-01-2009,-500\n12-01-2009,-500\n13-01-2009,-500\n14-01-2009,-500\n15-01-2009,-500\n16-01-2009,-500\n19-01-2009,-500\n20-01-2009,-500\n21-01-2009,-500\n22-01-2009,-500\n23-01-2009,-500\n26-01-2009,-500\n27-01-2009,-500\n28-01-2009,-500\n29-01-2009,-500\n30-01-2009,-500\n02-02-2009,-500\n03-02-2009,-500\n04-02-2009,-500\n05-02-2009,-500\n06-02-2009,-500\n09-02-2009,-500\n10-02-2009,-500\n11-02-2009,-500\n12-02-2009,-500\n13-02-2009,-500\n17-02-2009,-500\n18-02-2009,-500\n19-02-2009,-500\n20-02-2009,-500\n23-02-2009,-500\n24-02-2009,-500\n25-02-2009,-500\n26-02-2009,-500\n27-02-2009,-500\n02-03-2009,-500\n03-03-2009,-500\n04-03-2009,-500\n05-03-2009,-500\n06-03-2009,-500\n09-03-2009,-500\n10-03-2009,-500\n11-03-2009,-500\n12-03-2009,-500\n13-03-2009,-500\n16-03-2009,-500\n17-03-2009,-500\n18-03-2009,-500\n19-03-2009,-500\n20-03-2009,-500\n23-03-2009,-500\n24-03-2009,-500\n25-03-2009,-500\n26-03-2009,-500\n27-03-2009,-500\n30-03-2009,-500\n31-03-2009,-500\n01-04-2009,-500\n02-04-2009,-500\n03-04-2009,-500\n06-04-2009,-500\n07-04-2009,-500\n08-04-2009,-500\n09-04-2009,-500\n13-04-2009,-500\n14-04-2009,-500\n15-04-2009,-500\n16-04-2009,-500\n17-04-2009,-500\n20-04-2009,-500\n21-04-2009,-500\n22-04-2009,-500\n23-04-2009,-500\n24-04-2009,-500\n27-04-2009,-500\n28-04-2009,-500\n29-04-2009,-500\n30-04-2009,-500\n01-05-2009,-500\n04-05-2009,-500\n05-05-2009,-500\n06-05-2009,-500\n07-05-2009,-500\n08-05-2009,-500\n11-05-2009,-500\n12-05-2009,-500\n13-05-2009,-500\n14-05-2009,-500\n15-05-2009,-500\n19-05-2009,-500\n20-05-2009,-500\n21-05-2009,-500\n22-05-2009,-500\n25-05-2009,-500\n26-05-2009,-500\n27-05-2009,-500\n28-05-2009,-500\n29-05-2009,-500\n01-06-2009,-500\n02-06-2009,-500\n03-06-2009,-500\n04-06-2009,-500\n05-06-2009,-500\n08-06-2009,-500\n09-06-2009,-500\n10-06-2009,-500\n11-06-2009,-500\n12-06-2009,-500\n15-06-2009,-500\n16-06-2009,-500\n17-06-2009,-500\n18-06-2009,-500\n19-06-2009,-500\n22-06-2009,-500\n23-06-2009,-500\n24-06-2009,-500\n25-06-2009,-500\n26-06-2009,-500\n29-06-2009,-500\n30-06-2009,-500\n02-07-2009,-500\n03-07-2009,-500\n06-07-2009,-500\n07-07-2009,-500\n08-07-2009,-500\n09-07-2009,-500\n10-07-2009,-500\n13-07-2009,-500\n14-07-2009,-500\n15-07-2009,-500\n16-07-2009,-500\n17-07-2009,-500\n20-07-2009,-500\n21-07-2009,-500\n22-07-2009,-500\n23-07-2009,-500\n24-07-2009,-500\n27-07-2009,-500\n28-07-2009,-500\n29-07-2009,-500\n30-07-2009,-500\n31-07-2009,-500\n04-08-2009,-500\n05-08-2009,-500\n06-08-2009,-500\n07-08-2009,-500\n10-08-2009,-500\n11-08-2009,-500\n12-08-2009,-500\n13-08-2009,-500\n14-08-2009,-500\n17-08-2009,-500\n18-08-2009,-500\n19-08-2009,-500\n20-08-2009,-500\n21-08-2009,-500\n24-08-2009,-500\n25-08-2009,-500\n26-08-2009,-500\n27-08-2009,-500\n28-08-2009,-500\n31-08-2009,-500\n01-09-2009,-500\n02-09-2009,-500\n03-09-2009,-500\n04-09-2009,-500\n08-09-2009,-500\n09-09-2009,-500\n10-09-2009,-500\n11-09-2009,-500\n14-09-2009,-500\n15-09-2009,-500\n16-09-2009,-500\n17-09-2009,-500\n18-09-2009,-500\n21-09-2009,-500\n22-09-2009,-500\n23-09-2009,-500\n24-09-2009,-500\n25-09-2009,-500\n28-09-2009,-500\n29-09-2009,-500\n30-09-2009,-500\n01-10-2009,-500\n02-10-2009,-500\n05-10-2009,-500\n06-10-2009,-500\n07-10-2009,-500\n08-10-2009,-500\n09-10-2009,-500\n13-10-2009,-500\n14-10-2009,-500\n15-10-2009,-500\n16-10-2009,-500\n19-10-2009,-500\n20-10-2009,-500\n21-10-2009,-500\n22-10-2009,-500\n23-10-2009,-500\n26-10-2009,-500\n27-10-2009,-500\n28-10-2009,-500\n29-10-2009,-500\n30-10-2009,-500\n02-11-2009,-500\n03-11-2009,-500\n04-11-2009,-500\n05-11-2009,-500\n06-11-2009,-500\n09-11-2009,-500\n10-11-2009,-500\n12-11-2009,-500\n13-11-2009,-500\n16-11-2009,-500\n17-11-2009,-500\n18-11-2009,-500\n19-11-2009,-500\n20-11-2009,-500\n23-11-2009,-500\n24-11-2009,-500\n25-11-2009,-500\n26-11-2009,-500\n27-11-2009,-500\n30-11-2009,-500\n01-12-2009,-500\n02-12-2009,-500\n03-12-2009,-500\n04-12-2009,-500\n07-12-2009,-500\n08-12-2009,-500\n09-12-2009,-500\n10-12-2009,-500\n11-12-2009,-500\n14-12-2009,-500\n15-12-2009,-500\n16-12-2009,-500\n17-12-2009,-500\n18-12-2009,-500\n21-12-2009,-500\n22-12-2009,-500\n23-12-2009,-500\n24-12-2009,-500\n29-12-2009,-500\n30-12-2009,-500\n31-12-2009,-500\n04-01-2010,-500\n05-01-2010,-500\n06-01-2010,-500\n07-01-2010,-500\n08-01-2010,-500\n11-01-2010,-500\n12-01-2010,-500\n13-01-2010,-500\n14-01-2010,-500\n15-01-2010,-500\n18-01-2010,-500\n19-01-2010,-500\n20-01-2010,-500\n21-01-2010,-500\n22-01-2010,-500\n25-01-2010,-500\n26-01-2010,-500\n27-01-2010,-500\n28-01-2010,-500\n29-01-2010,-500\n01-02-2010,-500\n02-02-2010,-500\n03-02-2010,-500\n04-02-2010,-500\n05-02-2010,-500\n08-02-2010,-500\n09-02-2010,-500\n10-02-2010,-500\n11-02-2010,-500\n12-02-2010,-500\n16-02-2010,-500\n17-02-2010,-500\n18-02-2010,-500\n19-02-2010,-500\n22-02-2010,-500\n23-02-2010,-500\n24-02-2010,-500\n25-02-2010,-500\n26-02-2010,-500\n01-03-2010,-500\n02-03-2010,-500\n03-03-2010,-500\n04-03-2010,-500\n05-03-2010,-500\n08-03-2010,-500\n09-03-2010,-500\n10-03-2010,-500\n11-03-2010,-500\n12-03-2010,-500\n15-03-2010,-500\n16-03-2010,-500\n17-03-2010,-500\n18-03-2010,-500\n19-03-2010,-500\n22-03-2010,-500\n23-03-2010,-500\n24-03-2010,-500\n25-03-2010,-500\n26-03-2010,-500\n29-03-2010,-500\n30-03-2010,-500\n31-03-2010,-500\n01-04-2010,-500\n05-04-2010,-500\n06-04-2010,-500\n07-04-2010,-500\n08-04-2010,-500\n09-04-2010,-500\n12-04-2010,-500\n13-04-2010,-500\n14-04-2010,-500\n15-04-2010,-500\n16-04-2010,-500\n19-04-2010,-500\n20-04-2010,-500\n21-04-2010,-500\n22-04-2010,-500\n23-04-2010,-500\n26-04-2010,-500\n27-04-2010,-500\n28-04-2010,-500\n29-04-2010,-500\n30-04-2010,-500\n03-05-2010,-500\n04-05-2010,-500\n05-05-2010,-500\n06-05-2010,-500\n07-05-2010,-500\n10-05-2010,-500\n11-05-2010,-500\n12-05-2010,-500\n13-05-2010,-500\n14-05-2010,-500\n17-05-2010,-500\n18-05-2010,-500\n19-05-2010,-500\n20-05-2010,-500\n21-05-2010,-500\n25-05-2010,-500\n26-05-2010,-500\n27-05-2010,-500\n28-05-2010,-500\n31-05-2010,-500\n01-06-2010,-500\n02-06-2010,-500\n03-06-2010,-500\n04-06-2010,-500\n07-06-2010,-500\n08-06-2010,-500\n09-06-2010,-500\n10-06-2010,-500\n11-06-2010,-500\n14-06-2010,-500\n15-06-2010,-500\n16-06-2010,-500\n17-06-2010,-500\n18-06-2010,-500\n21-06-2010,-500\n22-06-2010,-500\n23-06-2010,-500\n24-06-2010,-500\n25-06-2010,-500\n28-06-2010,-500\n29-06-2010,-500\n30-06-2010,-500\n02-07-2010,-500\n05-07-2010,-500\n06-07-2010,-500\n07-07-2010,-500\n08-07-2010,-500\n09-07-2010,-500\n12-07-2010,-500\n13-07-2010,-500\n14-07-2010,-500\n15-07-2010,-500\n16-07-2010,-500\n19-07-2010,-500\n20-07-2010,-500\n21-07-2010,-500\n22-07-2010,-500\n23-07-2010,-500\n26-07-2010,-500\n27-07-2010,-500\n28-07-2010,-500\n29-07-2010,-500\n30-07-2010,-500\n03-08-2010,-500\n04-08-2010,-500\n05-08-2010,-500\n06-08-2010,-500\n09-08-2010,-500\n10-08-2010,-500\n11-08-2010,-500\n12-08-2010,-500\n13-08-2010,-500\n16-08-2010,-500\n17-08-2010,-500\n18-08-2010,-500\n19-08-2010,-500\n20-08-2010,-500\n23-08-2010,-500\n24-08-2010,-500\n25-08-2010,-500\n26-08-2010,-500\n27-08-2010,-500\n30-08-2010,-500\n31-08-2010,-500\n01-09-2010,-500\n02-09-2010,-500\n03-09-2010,-500\n07-09-2010,-500\n08-09-2010,-500\n09-09-2010,-500\n10-09-2010,-500\n13-09-2010,-500\n14-09-2010,-500\n15-09-2010,-500\n16-09-2010,-500\n17-09-2010,-500\n20-09-2010,-500\n21-09-2010,-500\n22-09-2010,-500\n23-09-2010,-500\n24-09-2010,-500\n27-09-2010,-500\n28-09-2010,-500\n29-09-2010,-500\n30-09-2010,-500\n01-10-2010,-500\n04-10-2010,-500\n05-10-2010,-500\n06-10-2010,-500\n07-10-2010,-500\n08-10-2010,-500\n12-10-2010,-500\n13-10-2010,-500\n14-10-2010,-500\n15-10-2010,-500\n18-10-2010,-500\n19-10-2010,-500\n20-10-2010,-500\n21-10-2010,-500\n22-10-2010,-500\n25-10-2010,-500\n26-10-2010,-500\n27-10-2010,-500\n28-10-2010,-500\n29-10-2010,-500\n01-11-2010,-500\n02-11-2010,-500\n03-11-2010,-500\n04-11-2010,-500\n05-11-2010,-500\n08-11-2010,-500\n09-11-2010,-500\n10-11-2010,-500\n12-11-2010,-500\n15-11-2010,-500\n16-11-2010,-500\n17-11-2010,-500\n18-11-2010,-500\n19-11-2010,-500\n22-11-2010,-500\n23-11-2010,-500\n24-11-2010,-500\n25-11-2010,-500\n26-11-2010,-500\n29-11-2010,-500\n30-11-2010,-500\n01-12-2010,-500\n02-12-2010,-500\n03-12-2010,-500\n06-12-2010,-500\n07-12-2010,-500\n08-12-2010,-500\n09-12-2010,-500\n10-12-2010,-500\n13-12-2010,-500\n14-12-2010,-500\n15-12-2010,-500\n16-12-2010,-500\n17-12-2010,-500\n20-12-2010,-500\n21-12-2010,-500\n22-12-2010,-500\n23-12-2010,-500\n24-12-2010,-500\n29-12-2010,-500\n30-12-2010,-500\n31-12-2010,-500\n04-01-2011,-500\n05-01-2011,-500\n06-01-2011,-500\n07-01-2011,-500\n10-01-2011,-500\n11-01-2011,-500\n12-01-2011,-500\n13-01-2011,-500\n14-01-2011,-500\n17-01-2011,-500\n18-01-2011,-500\n19-01-2011,-500\n20-01-2011,-500\n21-01-2011,-500\n24-01-2011,-500\n25-01-2011,-500\n26-01-2011,-500\n27-01-2011,-500\n28-01-2011,-500\n31-01-2011,-500\n01-02-2011,-500\n02-02-2011,-500\n03-02-2011,-500\n04-02-2011,-500\n07-02-2011,-500\n08-02-2011,-500\n09-02-2011,-500\n10-02-2011,-500\n11-02-2011,-500\n14-02-2011,-500\n15-02-2011,-500\n16-02-2011,-500\n17-02-2011,-500\n18-02-2011,-500\n22-02-2011,-500\n23-02-2011,-500\n24-02-2011,-500\n25-02-2011,-500\n28-02-2011,-500\n01-03-2011,-500\n02-03-2011,-500\n03-03-2011,-500\n04-03-2011,-500\n07-03-2011,-500\n08-03-2011,-500\n09-03-2011,-500\n10-03-2011,-500\n11-03-2011,-500\n14-03-2011,-500\n15-03-2011,-500\n16-03-2011,-500\n17-03-2011,-500\n18-03-2011,-500\n21-03-2011,-500\n22-03-2011,-500\n23-03-2011,-500\n24-03-2011,-500\n25-03-2011,-500\n28-03-2011,-500\n29-03-2011,-500\n30-03-2011,-500\n31-03-2011,-500\n01-04-2011,-500\n04-04-2011,-500\n05-04-2011,-500\n06-04-2011,-500\n07-04-2011,-500\n08-04-2011,-500\n11-04-2011,-500\n12-04-2011,-500\n13-04-2011,-500\n14-04-2011,-500\n15-04-2011,-500\n18-04-2011,-500\n19-04-2011,-500\n20-04-2011,-500\n21-04-2011,-500\n25-04-2011,-500\n26-04-2011,-500\n27-04-2011,-500\n28-04-2011,-500\n29-04-2011,-500\n02-05-2011,-500\n03-05-2011,-500\n04-05-2011,-500\n05-05-2011,-500\n06-05-2011,-500\n09-05-2011,-500\n10-05-2011,-500\n11-05-2011,-500\n12-05-2011,-500\n13-05-2011,-500\n16-05-2011,-500\n17-05-2011,-500\n18-05-2011,-500\n19-05-2011,-500\n20-05-2011,-500\n24-05-2011,-500\n25-05-2011,-500\n26-05-2011,-500\n27-05-2011,-500\n30-05-2011,-500\n31-05-2011,-500\n01-06-2011,-500\n02-06-2011,-500\n03-06-2011,-500\n06-06-2011,-500\n07-06-2011,-500\n08-06-2011,-500\n09-06-2011,-500\n10-06-2011,-500\n13-06-2011,-500\n14-06-2011,-500\n15-06-2011,-500\n16-06-2011,-500\n17-06-2011,-500\n20-06-2011,-500\n21-06-2011,-500\n22-06-2011,-500\n23-06-2011,-500\n24-06-2011,-500\n27-06-2011,-500\n28-06-2011,-500\n29-06-2011,-500\n30-06-2011,-500\n04-07-2011,-500\n05-07-2011,-500\n06-07-2011,-500\n07-07-2011,-500\n08-07-2011,-500\n11-07-2011,-500\n12-07-2011,-500\n13-07-2011,-500\n14-07-2011,-500\n15-07-2011,-500\n18-07-2011,-500\n19-07-2011,-500\n20-07-2011,-500\n21-07-2011,-500\n22-07-2011,-500\n25-07-2011,-500\n26-07-2011,-500\n27-07-2011,-500\n28-07-2011,-500\n29-07-2011,-500\n02-08-2011,-500\n03-08-2011,-500\n04-08-2011,-500\n05-08-2011,-500\n08-08-2011,-500\n09-08-2011,-500\n10-08-2011,-500\n11-08-2011,-500\n12-08-2011,-500\n15-08-2011,-500\n16-08-2011,-500\n17-08-2011,-500\n18-08-2011,-500\n19-08-2011,-500\n22-08-2011,-500\n23-08-2011,-500\n24-08-2011,-500\n25-08-2011,-500\n26-08-2011,-500\n29-08-2011,-500\n30-08-2011,-500\n31-08-2011,-500\n01-09-2011,-500\n02-09-2011,-500\n06-09-2011,-500\n07-09-2011,-500\n08-09-2011,-500\n09-09-2011,-500\n12-09-2011,-500\n13-09-2011,-500\n14-09-2011,-500\n15-09-2011,-500\n16-09-2011,-500\n19-09-2011,-500\n20-09-2011,-500\n21-09-2011,-500\n22-09-2011,-500\n23-09-2011,-500\n26-09-2011,-500\n27-09-2011,-500\n28-09-2011,-500\n29-09-2011,-500\n30-09-2011,-500\n03-10-2011,-500\n04-10-2011,-500\n05-10-2011,-500\n06-10-2011,-500\n07-10-2011,-500\n11-10-2011,-500\n12-10-2011,-500\n13-10-2011,-500\n14-10-2011,-500\n17-10-2011,-500\n18-10-2011,-500\n19-10-2011,-500\n20-10-2011,-500\n21-10-2011,-500\n24-10-2011,-500\n25-10-2011,-500\n26-10-2011,-500\n27-10-2011,-500\n28-10-2011,-500\n31-10-2011,-500\n01-11-2011,-500\n02-11-2011,-500\n03-11-2011,-500\n04-11-2011,-500\n07-11-2011,-500\n08-11-2011,-500\n09-11-2011,-500\n10-11-2011,-500\n14-11-2011,-500\n15-11-2011,-500\n16-11-2011,-500\n17-11-2011,-500\n18-11-2011,-500\n21-11-2011,-500\n22-11-2011,-500\n23-11-2011,-500\n24-11-2011,-500\n25-11-2011,-500\n28-11-2011,-500\n29-11-2011,-500\n30-11-2011,-500\n01-12-2011,-500\n02-12-2011,-500\n05-12-2011,-500\n06-12-2011,-500\n07-12-2011,-500\n08-12-2011,-500\n09-12-2011,-500\n12-12-2011,-500\n13-12-2011,-500\n14-12-2011,-500\n15-12-2011,-500\n16-12-2011,-500\n19-12-2011,-500\n20-12-2011,-500\n21-12-2011,-500\n22-12-2011,-500\n23-12-2011,-500\n28-12-2011,-500\n29-12-2011,-500\n30-12-2011,-500\n03-01-2012,-500\n04-01-2012,-500\n05-01-2012,-500\n06-01-2012,-500\n09-01-2012,-500\n10-01-2012,-500\n11-01-2012,-500\n12-01-2012,-500\n13-01-2012,-500\n16-01-2012,-500\n17-01-2012,-500\n18-01-2012,-500\n19-01-2012,-500\n20-01-2012,-500\n23-01-2012,-500\n24-01-2012,-500\n25-01-2012,-500\n26-01-2012,-500\n27-01-2012,-500\n30-01-2012,-500\n31-01-2012,-500\n01-02-2012,-500\n02-02-2012,-500\n03-02-2012,-500\n06-02-2012,-500\n07-02-2012,-500\n08-02-2012,-500\n09-02-2012,-500\n10-02-2012,-500\n13-02-2012,-500\n14-02-2012,-500\n15-02-2012,-500\n16-02-2012,-500\n17-02-2012,-500\n21-02-2012,-500\n22-02-2012,-500\n23-02-2012,-500\n24-02-2012,-500\n27-02-2012,-500\n28-02-2012,-500\n29-02-2012,-500\n01-03-2012,-500\n02-03-2012,-500\n05-03-2012,-500\n06-03-2012,-500\n07-03-2012,-500\n08-03-2012,-500\n09-03-2012,-500\n12-03-2012,-500\n13-03-2012,-500\n14-03-2012,-500\n15-03-2012,-500\n16-03-2012,-500\n19-03-2012,-500\n20-03-2012,-500\n21-03-2012,-500\n22-03-2012,-500\n23-03-2012,-500\n26-03-2012,-500\n27-03-2012,-500\n28-03-2012,-500\n29-03-2012,-500\n30-03-2012,-500\n02-04-2012,-500\n03-04-2012,-500\n04-04-2012,-500\n05-04-2012,-500\n09-04-2012,-500\n10-04-2012,-500\n11-04-2012,-500\n12-04-2012,-500\n13-04-2012,-500\n16-04-2012,-500\n17-04-2012,-500\n18-04-2012,-500\n19-04-2012,-500\n20-04-2012,-500\n23-04-2012,-500\n24-04-2012,-500\n25-04-2012,-500\n26-04-2012,-500\n27-04-2012,-500\n30-04-2012,-500\n01-05-2012,-500\n02-05-2012,-500\n03-05-2012,-500\n04-05-2012,-500\n07-05-2012,-500\n08-05-2012,-500\n09-05-2012,-500\n10-05-2012,-500\n11-05-2012,-500\n14-05-2012,-500\n15-05-2012,-500\n16-05-2012,-500\n17-05-2012,-500\n18-05-2012,-500\n22-05-2012,-500\n23-05-2012,-500\n24-05-2012,-500\n25-05-2012,-500\n28-05-2012,-500\n29-05-2012,-500\n30-05-2012,-500\n31-05-2012,-500\n01-06-2012,-500\n04-06-2012,-500\n05-06-2012,-500\n06-06-2012,-500\n07-06-2012,-500\n08-06-2012,-500\n11-06-2012,-500\n12-06-2012,-500\n13-06-2012,-500\n14-06-2012,-500\n15-06-2012,-500\n18-06-2012,-500\n19-06-2012,-500\n20-06-2012,-500\n21-06-2012,-500\n22-06-2012,-500\n25-06-2012,-500\n26-06-2012,-500\n27-06-2012,-500\n28-06-2012,-500\n29-06-2012,-500\n03-07-2012,-500\n04-07-2012,-500\n05-07-2012,-500\n06-07-2012,-500\n09-07-2012,-500\n10-07-2012,-500\n11-07-2012,-500\n12-07-2012,-500\n13-07-2012,-500\n16-07-2012,-500\n17-07-2012,-500\n18-07-2012,-500\n19-07-2012,-500\n20-07-2012,-500\n23-07-2012,-500\n24-07-2012,-500\n25-07-2012,-500\n26-07-2012,-500\n27-07-2012,-500\n30-07-2012,-500\n31-07-2012,-500\n01-08-2012,-500\n02-08-2012,-500\n03-08-2012,-500\n07-08-2012,-500\n08-08-2012,-500\n09-08-2012,-500\n10-08-2012,-500\n13-08-2012,-500\n14-08-2012,-500\n15-08-2012,-500\n16-08-2012,-500\n17-08-2012,-500\n20-08-2012,-500\n21-08-2012,-500\n22-08-2012,-500\n23-08-2012,-500\n24-08-2012,-500\n27-08-2012,-500\n28-08-2012,-500\n29-08-2012,-500\n30-08-2012,-500\n31-08-2012,-500\n04-09-2012,-500\n05-09-2012,-500\n06-09-2012,-500\n07-09-2012,-500\n10-09-2012,-500\n11-09-2012,-500\n12-09-2012,-500\n13-09-2012,-500\n14-09-2012,-500\n17-09-2012,-500\n18-09-2012,-500\n19-09-2012,-500\n20-09-2012,-500\n21-09-2012,-500\n24-09-2012,-500\n25-09-2012,-500\n26-09-2012,-500\n27-09-2012,-500\n28-09-2012,-500\n01-10-2012,-500\n02-10-2012,-500\n03-10-2012,-500\n04-10-2012,-500\n05-10-2012,-500\n09-10-2012,-500\n10-10-2012,-500\n11-10-2012,-500\n12-10-2012,-500\n15-10-2012,-500\n16-10-2012,-500\n17-10-2012,-500\n18-10-2012,-500\n19-10-2012,-500\n22-10-2012,-500\n23-10-2012,-500\n24-10-2012,-500\n25-10-2012,-500\n26-10-2012,-500\n29-10-2012,-500\n30-10-2012,-500\n31-10-2012,-500\n01-11-2012,-500\n02-11-2012,-500\n05-11-2012,-500\n06-11-2012,-500\n07-11-2012,-500\n08-11-2012,-500\n09-11-2012,-500\n13-11-2012,-500\n14-11-2012,-500\n15-11-2012,-500\n16-11-2012,-500\n19-11-2012,-500\n20-11-2012,-500\n21-11-2012,-500\n22-11-2012,-500\n23-11-2012,-500\n26-11-2012,-500\n27-11-2012,-500\n28-11-2012,-500\n29-11-2012,-500\n30-11-2012,-500\n03-12-2012,-500\n04-12-2012,-500\n05-12-2012,-500\n06-12-2012,-500\n07-12-2012,-500\n10-12-2012,-500\n11-12-2012,-500\n12-12-2012,-500\n13-12-2012,-500\n14-12-2012,-500\n17-12-2012,-500\n18-12-2012,-500\n19-12-2012,-500\n20-12-2012,-500\n21-12-2012,-500\n24-12-2012,-500\n27-12-2012,-500\n28-12-2012,-500\n31-12-2012,-500\n02-01-2013,-500\n03-01-2013,-500\n04-01-2013,-500\n07-01-2013,-500\n08-01-2013,-500\n09-01-2013,-500\n10-01-2013,-500\n11-01-2013,-500\n14-01-2013,-500\n15-01-2013,-500\n16-01-2013,-500\n17-01-2013,-500\n18-01-2013,-500\n21-01-2013,-500\n22-01-2013,-500\n23-01-2013,-500\n24-01-2013,-500\n25-01-2013,-500\n28-01-2013,-500\n29-01-2013,-500\n30-01-2013,-500\n31-01-2013,-500\n01-02-2013,-500\n04-02-2013,-500\n05-02-2013,-500\n06-02-2013,-500\n07-02-2013,-500\n08-02-2013,-500\n11-02-2013,-500\n12-02-2013,-500\n13-02-2013,-500\n14-02-2013,-500\n15-02-2013,-500\n19-02-2013,-500\n20-02-2013,-500\n21-02-2013,-500\n22-02-2013,-500\n25-02-2013,-500\n26-02-2013,-500\n27-02-2013,-500\n28-02-2013,-500\n01-03-2013,-500\n04-03-2013,-500\n05-03-2013,-500\n06-03-2013,-500\n07-03-2013,-500\n08-03-2013,-500\n11-03-2013,-500\n12-03-2013,-500\n13-03-2013,-500\n14-03-2013,-500\n15-03-2013,-500\n18-03-2013,-500\n19-03-2013,-500\n20-03-2013,-500\n21-03-2013,-500\n22-03-2013,-500\n25-03-2013,-500\n26-03-2013,-500\n27-03-2013,-500\n28-03-2013,-500\n01-04-2013,-500\n02-04-2013,-500\n03-04-2013,-500\n04-04-2013,-500\n05-04-2013,-500\n08-04-2013,-500\n09-04-2013,-500\n10-04-2013,-500\n11-04-2013,-500\n12-04-2013,-500\n15-04-2013,-500\n16-04-2013,-500\n17-04-2013,-500\n18-04-2013,-500\n19-04-2013,-500\n22-04-2013,-500\n23-04-2013,-500\n24-04-2013,-500\n25-04-2013,-500\n26-04-2013,-500\n29-04-2013,-500\n30-04-2013,-500\n01-05-2013,-500\n02-05-2013,-500\n03-05-2013,-500\n06-05-2013,-500\n07-05-2013,-500\n08-05-2013,-500\n09-05-2013,-500\n10-05-2013,-500\n13-05-2013,-500\n14-05-2013,-500\n15-05-2013,-500\n16-05-2013,-500\n17-05-2013,-500\n21-05-2013,-500\n22-05-2013,-500\n23-05-2013,-500\n24-05-2013,-500\n27-05-2013,-500\n28-05-2013,-500\n29-05-2013,-500\n30-05-2013,-500\n31-05-2013,-500\n03-06-2013,-500\n04-06-2013,-500\n05-06-2013,-500\n06-06-2013,-500\n07-06-2013,-500\n10-06-2013,-500\n11-06-2013,-500\n12-06-2013,-500\n13-06-2013,-500\n14-06-2013,-500\n17-06-2013,-500\n18-06-2013,-500\n19-06-2013,-500\n20-06-2013,-500\n21-06-2013,-500\n24-06-2013,-500\n25-06-2013,-500\n26-06-2013,-500\n27-06-2013,-500\n28-06-2013,-500\n02-07-2013,-500\n03-07-2013,-500\n04-07-2013,-500\n05-07-2013,-500\n08-07-2013,-500\n09-07-2013,-500\n10-07-2013,-500\n11-07-2013,-500\n12-07-2013,-500\n15-07-2013,-500\n16-07-2013,-500\n17-07-2013,-500\n18-07-2013,-500\n19-07-2013,-500\n22-07-2013,-500\n23-07-2013,-500\n24-07-2013,-500\n25-07-2013,-500\n26-07-2013,-500\n29-07-2013,-500\n30-07-2013,-500\n31-07-2013,-500\n01-08-2013,-500\n02-08-2013,-500\n06-08-2013,-500\n07-08-2013,-500\n08-08-2013,-500\n09-08-2013,-500\n12-08-2013,-500\n13-08-2013,-500\n14-08-2013,-500\n15-08-2013,-500\n16-08-2013,-500\n19-08-2013,-500\n20-08-2013,-500\n21-08-2013,-500\n22-08-2013,-500\n23-08-2013,-500\n26-08-2013,-500\n27-08-2013,-500\n28-08-2013,-500\n29-08-2013,-500\n30-08-2013,-500\n03-09-2013,-500\n04-09-2013,-500\n05-09-2013,-500\n06-09-2013,-500\n09-09-2013,-500\n10-09-2013,-500\n11-09-2013,-500\n12-09-2013,-500\n13-09-2013,-500\n16-09-2013,-500\n17-09-2013,-500\n18-09-2013,-500\n19-09-2013,-500\n20-09-2013,-500\n23-09-2013,-500\n24-09-2013,-500\n25-09-2013,-500\n26-09-2013,-500\n27-09-2013,-500\n30-09-2013,-500\n01-10-2013,-500\n02-10-2013,-500\n03-10-2013,-500\n04-10-2013,-500\n07-10-2013,-500\n08-10-2013,-500\n09-10-2013,-500\n10-10-2013,-500\n11-10-2013,-500\n15-10-2013,-500\n16-10-2013,-500\n17-10-2013,-500\n18-10-2013,-500\n21-10-2013,-500\n22-10-2013,-500\n23-10-2013,-500\n24-10-2013,-500\n25-10-2013,-500\n28-10-2013,-500\n29-10-2013,-500\n30-10-2013,-500\n31-10-2013,-500\n01-11-2013,-500\n04-11-2013,-500\n05-11-2013,-500\n06-11-2013,-500\n07-11-2013,-500\n08-11-2013,-500\n12-11-2013,-500\n13-11-2013,-500\n14-11-2013,-500\n15-11-2013,-500\n18-11-2013,-500\n19-11-2013,-500\n20-11-2013,-500\n21-11-2013,-500\n22-11-2013,-500\n25-11-2013,-500\n26-11-2013,-500\n27-11-2013,-500\n28-11-2013,-500\n29-11-2013,-500\n02-12-2013,-500\n03-12-2013,-500\n04-12-2013,-500\n05-12-2013,-500\n06-12-2013,-500\n09-12-2013,-500\n10-12-2013,-500\n11-12-2013,-500\n12-12-2013,-500\n13-12-2013,-500\n16-12-2013,-500\n17-12-2013,-500\n18-12-2013,-500\n19-12-2013,-500\n20-12-2013,-500\n23-12-2013,-500\n24-12-2013,-500\n27-12-2013,-500\n30-12-2013,-500\n31-12-2013,-500\n02-01-2014,-500\n03-01-2014,-500\n06-01-2014,-500\n07-01-2014,-500\n08-01-2014,-500\n09-01-2014,-500\n10-01-2014,-500\n13-01-2014,-500\n14-01-2014,-500\n15-01-2014,-500\n16-01-2014,-500\n17-01-2014,-500\n20-01-2014,-500\n21-01-2014,-500\n22-01-2014,-500\n23-01-2014,-500\n24-01-2014,-500\n27-01-2014,-500\n28-01-2014,-500\n29-01-2014,-500\n30-01-2014,-500\n31-01-2014,-500\n03-02-2014,-500\n04-02-2014,-500\n05-02-2014,-500\n06-02-2014,-500\n07-02-2014,-500\n10-02-2014,-500\n11-02-2014,-500\n12-02-2014,-500\n13-02-2014,-500\n14-02-2014,-500\n18-02-2014,-500\n19-02-2014,-500\n20-02-2014,-500\n21-02-2014,-500\n24-02-2014,-500\n25-02-2014,-500\n26-02-2014,-500\n27-02-2014,-500\n28-02-2014,-500\n03-03-2014,-500\n04-03-2014,-500\n05-03-2014,-500\n06-03-2014,-500\n07-03-2014,-500\n10-03-2014,-500\n11-03-2014,-500\n12-03-2014,-500\n13-03-2014,-500\n14-03-2014,-500\n17-03-2014,-500\n18-03-2014,-500\n19-03-2014,-500\n20-03-2014,-500\n21-03-2014,-500\n24-03-2014,-500\n25-03-2014,-500\n26-03-2014,-500\n27-03-2014,-500\n28-03-2014,-500\n31-03-2014,-500\n01-04-2014,-500\n02-04-2014,-500\n03-04-2014,-500\n04-04-2014,-500\n07-04-2014,-500\n08-04-2014,-500\n09-04-2014,-500\n10-04-2014,-500\n11-04-2014,-500\n14-04-2014,-500\n15-04-2014,-500\n16-04-2014,-500\n17-04-2014,-500\n21-04-2014,-500\n22-04-2014,-500\n23-04-2014,-500\n24-04-2014,-500\n25-04-2014,-500\n28-04-2014,-500\n29-04-2014,-500\n30-04-2014,-500\n01-05-2014,-500\n02-05-2014,-500\n05-05-2014,-500\n06-05-2014,-500\n07-05-2014,-500\n08-05-2014,-500\n09-05-2014,-500\n12-05-2014,-500\n13-05-2014,-500\n14-05-2014,-500\n15-05-2014,-500\n16-05-2014,-500\n20-05-2014,-500\n21-05-2014,-500\n22-05-2014,-500\n23-05-2014,-500\n26-05-2014,-500\n27-05-2014,-500\n28-05-2014,-500\n29-05-2014,-500\n30-05-2014,-500\n02-06-2014,-500\n03-06-2014,-500\n04-06-2014,-500\n05-06-2014,-500\n06-06-2014,-500\n09-06-2014,-500\n10-06-2014,-500\n11-06-2014,-500\n12-06-2014,-500\n13-06-2014,-500\n16-06-2014,-500\n17-06-2014,-500\n18-06-2014,-500\n19-06-2014,-500\n20-06-2014,-500\n23-06-2014,-500\n24-06-2014,-500\n25-06-2014,-500\n26-06-2014,-500\n27-06-2014,-500\n30-06-2014,-500\n02-07-2014,-500\n03-07-2014,-500\n04-07-2014,-500\n07-07-2014,-500\n08-07-2014,-500\n09-07-2014,-500\n10-07-2014,-500\n11-07-2014,-500\n14-07-2014,-500\n15-07-2014,-500\n16-07-2014,-500\n17-07-2014,-500\n18-07-2014,-500\n21-07-2014,-500\n22-07-2014,-500\n23-07-2014,-500\n24-07-2014,-500\n25-07-2014,-500\n28-07-2014,-500\n29-07-2014,-500\n30-07-2014,-500\n31-07-2014,-500\n01-08-2014,-500\n05-08-2014,-500\n06-08-2014,-500\n07-08-2014,-500\n08-08-2014,-500\n11-08-2014,-500\n12-08-2014,-500\n13-08-2014,-500\n14-08-2014,-500\n15-08-2014,-500\n18-08-2014,-500\n19-08-2014,-500\n20-08-2014,-500\n21-08-2014,-500\n22-08-2014,-500\n25-08-2014,-500\n26-08-2014,-500\n27-08-2014,-500\n28-08-2014,-500\n29-08-2014,-500\n02-09-2014,-500\n03-09-2014,-500\n04-09-2014,-500\n05-09-2014,-500\n08-09-2014,-500\n09-09-2014,-500\n10-09-2014,-500\n11-09-2014,-500\n12-09-2014,-500\n15-09-2014,-500\n16-09-2014,-500\n17-09-2014,-500\n18-09-2014,-500\n19-09-2014,-500\n22-09-2014,-500\n23-09-2014,-500\n24-09-2014,-500\n25-09-2014,-500\n26-09-2014,-500\n29-09-2014,-500\n30-09-2014,-500\n01-10-2014,-500\n02-10-2014,-500\n03-10-2014,-500\n06-10-2014,-500\n07-10-2014,-500\n08-10-2014,-500\n09-10-2014,-500\n10-10-2014,-500\n14-10-2014,-500\n15-10-2014,-500\n16-10-2014,-500\n17-10-2014,-500\n20-10-2014,-500\n21-10-2014,-500\n22-10-2014,-500\n23-10-2014,-500\n24-10-2014,-500\n27-10-2014,-500\n28-10-2014,-500\n29-10-2014,-500\n30-10-2014,-500\n31-10-2014,-500\n03-11-2014,-500\n04-11-2014,-500\n05-11-2014,-500\n06-11-2014,-500\n07-11-2014,-500\n10-11-2014,-500\n12-11-2014,-500\n13-11-2014,-500\n14-11-2014,-500\n17-11-2014,-500\n18-11-2014,-500\n19-11-2014,-500\n20-11-2014,-500\n21-11-2014,-500\n24-11-2014,-500\n25-11-2014,-500\n26-11-2014,-500\n27-11-2014,-500\n28-11-2014,-500\n01-12-2014,-500\n02-12-2014,-500\n03-12-2014,-500\n04-12-2014,-500\n05-12-2014,-500\n08-12-2014,-500\n09-12-2014,-500\n10-12-2014,-500\n11-12-2014,-500\n12-12-2014,-500\n15-12-2014,-500\n16-12-2014,-500\n17-12-2014,-500\n18-12-2014,-500\n19-12-2014,-500\n22-12-2014,-500\n23-12-2014,-500\n24-12-2014,-500\n29-12-2014,-500\n30-12-2014,-500\n31-12-2014,-500\n02-01-2015,-500\n05-01-2015,-500\n06-01-2015,-500\n07-01-2015,-500\n08-01-2015,-500\n09-01-2015,-500\n12-01-2015,-500\n13-01-2015,-500\n14-01-2015,-500\n15-01-2015,-500\n16-01-2015,-500\n19-01-2015,-500\n20-01-2015,-500\n21-01-2015,-500\n22-01-2015,-500\n23-01-2015,-500\n26-01-2015,-500\n27-01-2015,-500\n28-01-2015,-500\n29-01-2015,-500\n30-01-2015,-500\n02-02-2015,-500\n03-02-2015,-500\n04-02-2015,-500\n05-02-2015,-500\n06-02-2015,-500\n09-02-2015,-500\n10-02-2015,-500\n11-02-2015,-500\n12-02-2015,-500\n13-02-2015,-500\n17-02-2015,-500\n18-02-2015,-500\n19-02-2015,-500\n20-02-2015,-500\n23-02-2015,-500\n24-02-2015,-500\n25-02-2015,-500\n26-02-2015,-500\n27-02-2015,-500\n02-03-2015,-500\n03-03-2015,-500\n04-03-2015,-500\n05-03-2015,-500\n06-03-2015,-500\n09-03-2015,-500\n10-03-2015,-500\n11-03-2015,-500\n12-03-2015,-500\n13-03-2015,-500\n16-03-2015,-500\n17-03-2015,-500\n18-03-2015,-500\n19-03-2015,-500\n20-03-2015,-500\n23-03-2015,-500\n24-03-2015,-500\n25-03-2015,-500\n26-03-2015,-500\n27-03-2015,-500\n30-03-2015,-500\n31-03-2015,-500\n01-04-2015,-500\n02-04-2015,-500\n06-04-2015,-500\n07-04-2015,-500\n08-04-2015,-500\n09-04-2015,-500\n10-04-2015,-500\n13-04-2015,-500\n14-04-2015,-500\n15-04-2015,-500\n16-04-2015,-500\n17-04-2015,-500\n20-04-2015,-500\n21-04-2015,-500\n22-04-2015,-500\n23-04-2015,-500\n24-04-2015,-500\n27-04-2015,-500\n28-04-2015,-500\n29-04-2015,-500\n30-04-2015,-500\n01-05-2015,-500\n04-05-2015,-500\n05-05-2015,-500\n06-05-2015,-500\n07-05-2015,-500\n08-05-2015,-500\n11-05-2015,-500\n12-05-2015,-500\n13-05-2015,-500\n14-05-2015,-500\n15-05-2015,-500\n19-05-2015,-500\n20-05-2015,-500\n21-05-2015,-500\n22-05-2015,-500\n25-05-2015,-500\n26-05-2015,-500\n27-05-2015,-500\n28-05-2015,-500\n29-05-2015,-500\n01-06-2015,-500\n02-06-2015,-500\n03-06-2015,-500\n04-06-2015,-500\n05-06-2015,-500\n08-06-2015,-500\n09-06-2015,-500\n10-06-2015,-500\n11-06-2015,-500\n12-06-2015,-500\n15-06-2015,-500\n16-06-2015,-500\n17-06-2015,-500\n18-06-2015,-500\n19-06-2015,-500\n22-06-2015,-500\n23-06-2015,-500\n24-06-2015,-500\n25-06-2015,-500\n26-06-2015,-500\n29-06-2015,-500\n30-06-2015,-500\n02-07-2015,-500\n03-07-2015,-500\n06-07-2015,-500\n07-07-2015,-500\n08-07-2015,-500\n09-07-2015,-500\n10-07-2015,-500\n13-07-2015,-500\n14-07-2015,-500\n15-07-2015,-500\n16-07-2015,-500\n17-07-2015,-500\n20-07-2015,-500\n21-07-2015,-500\n22-07-2015,-500\n23-07-2015,-500\n24-07-2015,-500\n27-07-2015,-500\n28-07-2015,-500\n29-07-2015,-500\n30-07-2015,-500\n31-07-2015,-500\n04-08-2015,-500\n05-08-2015,-500\n06-08-2015,-500\n07-08-2015,-500\n10-08-2015,-500\n11-08-2015,-500\n12-08-2015,-500\n13-08-2015,-500\n14-08-2015,-500\n17-08-2015,-500\n18-08-2015,-500\n19-08-2015,-500\n20-08-2015,-500\n21-08-2015,-500\n24-08-2015,-500\n25-08-2015,-500\n26-08-2015,-500\n27-08-2015,-500\n28-08-2015,-500\n31-08-2015,-500\n01-09-2015,-500\n02-09-2015,-500\n03-09-2015,-500\n04-09-2015,-500\n08-09-2015,-500\n09-09-2015,-500\n10-09-2015,-500\n11-09-2015,-500\n14-09-2015,-500\n15-09-2015,-500\n16-09-2015,-500\n17-09-2015,-500\n18-09-2015,-500\n21-09-2015,-500\n22-09-2015,-500\n23-09-2015,-500\n24-09-2015,-500\n25-09-2015,-500\n28-09-2015,-500\n29-09-2015,-500\n30-09-2015,-500\n01-10-2015,-500\n02-10-2015,-500\n05-10-2015,-500\n06-10-2015,-500\n07-10-2015,-500\n08-10-2015,-500\n09-10-2015,-500\n13-10-2015,-500\n14-10-2015,-500\n15-10-2015,-500\n16-10-2015,-500\n19-10-2015,-500\n20-10-2015,-500\n21-10-2015,-500\n22-10-2015,-500\n23-10-2015,-500\n26-10-2015,-500\n27-10-2015,-500\n28-10-2015,-500\n29-10-2015,-500\n30-10-2015,-500\n02-11-2015,-500\n03-11-2015,-500\n04-11-2015,-500\n05-11-2015,-500\n06-11-2015,-500\n09-11-2015,-500\n10-11-2015,-500\n12-11-2015,-500\n13-11-2015,-500\n16-11-2015,-500\n17-11-2015,-500\n18-11-2015,-500\n19-11-2015,-500\n20-11-2015,-500\n23-11-2015,-500\n24-11-2015,-500\n25-11-2015,-500\n26-11-2015,-500\n27-11-2015,-500\n30-11-2015,-500\n01-12-2015,-500\n02-12-2015,-500\n03-12-2015,-500\n04-12-2015,-500\n07-12-2015,-500\n08-12-2015,-500\n09-12-2015,-500\n10-12-2015,-500\n11-12-2015,-500\n14-12-2015,-500\n15-12-2015,-500\n16-12-2015,-500\n17-12-2015,-500\n18-12-2015,-500\n21-12-2015,-500\n22-12-2015,-500\n23-12-2015,-500\n24-12-2015,-500\n29-12-2015,-500\n30-12-2015,-500\n31-12-2015,-500\n04-01-2016,-500\n05-01-2016,-500\n06-01-2016,-500\n07-01-2016,-500\n08-01-2016,-500\n11-01-2016,-500\n12-01-2016,-500\n13-01-2016,-500\n14-01-2016,-500\n15-01-2016,-500\n18-01-2016,-500\n19-01-2016,-500\n20-01-2016,-500\n21-01-2016,-500\n22-01-2016,-500\n25-01-2016,-500\n26-01-2016,-500\n27-01-2016,-500\n28-01-2016,-500\n29-01-2016,-500\n01-02-2016,-500\n02-02-2016,-500\n03-02-2016,-500\n04-02-2016,-500\n05-02-2016,-500\n08-02-2016,-500\n09-02-2016,-500\n10-02-2016,-500\n11-02-2016,-500\n12-02-2016,-500\n16-02-2016,-500\n17-02-2016,-500\n18-02-2016,-500\n19-02-2016,-500\n22-02-2016,-500\n23-02-2016,-500\n24-02-2016,-500\n25-02-2016,-500\n26-02-2016,-500\n29-02-2016,-500\n01-03-2016,-500\n02-03-2016,-500\n03-03-2016,-500\n04-03-2016,-500\n07-03-2016,-500\n08-03-2016,-500\n09-03-2016,-500\n10-03-2016,-500\n11-03-2016,-500\n14-03-2016,-500\n15-03-2016,-500\n16-03-2016,-500\n17-03-2016,-500\n18-03-2016,-500\n21-03-2016,-500\n22-03-2016,-500\n23-03-2016,-500\n24-03-2016,-500\n28-03-2016,-500\n29-03-2016,-500\n30-03-2016,-500\n31-03-2016,-500\n01-04-2016,-500\n04-04-2016,-500\n05-04-2016,-500\n06-04-2016,-500\n07-04-2016,-500\n08-04-2016,-500\n11-04-2016,-500\n12-04-2016,-500\n13-04-2016,-500\n14-04-2016,-500\n15-04-2016,-500\n18-04-2016,-500\n19-04-2016,-500\n20-04-2016,-500\n21-04-2016,-500\n22-04-2016,-500\n25-04-2016,-500\n26-04-2016,-500\n27-04-2016,-500\n28-04-2016,-500\n29-04-2016,-500\n02-05-2016,-500\n03-05-2016,-500\n04-05-2016,-500\n05-05-2016,-500\n06-05-2016,-500\n09-05-2016,-500\n10-05-2016,-500\n11-05-2016,-500\n12-05-2016,-500\n13-05-2016,-500\n16-05-2016,-500\n17-05-2016,-500\n18-05-2016,-500\n19-05-2016,-500\n20-05-2016,-500\n24-05-2016,-500\n25-05-2016,-500\n26-05-2016,-500\n27-05-2016,-500\n30-05-2016,-500\n31-05-2016,-500\n01-06-2016,-500\n02-06-2016,-500\n03-06-2016,-500\n06-06-2016,-500\n07-06-2016,-500\n08-06-2016,-500\n09-06-2016,-500\n10-06-2016,-500\n13-06-2016,-500\n14-06-2016,-500\n15-06-2016,-500\n16-06-2016,-500\n17-06-2016,-500\n20-06-2016,-500\n21-06-2016,-500\n22-06-2016,-500\n23-06-2016,-500\n24-06-2016,-500\n27-06-2016,-500\n28-06-2016,-500\n29-06-2016,-500\n30-06-2016,-500\n04-07-2016,-500\n05-07-2016,-500\n06-07-2016,-500\n07-07-2016,-500\n08-07-2016,-500\n11-07-2016,-500\n12-07-2016,-500\n13-07-2016,-500\n14-07-2016,-500\n15-07-2016,-500\n18-07-2016,-500\n19-07-2016,-500\n20-07-2016,-500\n21-07-2016,-500\n22-07-2016,-500\n25-07-2016,-500\n26-07-2016,-500\n27-07-2016,-500\n28-07-2016,-500\n29-07-2016,-500\n02-08-2016,-500\n03-08-2016,-500\n04-08-2016,-500\n05-08-2016,-500\n08-08-2016,-500\n09-08-2016,-500\n10-08-2016,-500\n11-08-2016,-500\n12-08-2016,-500\n15-08-2016,-500\n16-08-2016,-500\n17-08-2016,-500\n18-08-2016,-500\n19-08-2016,-500\n22-08-2016,-500\n23-08-2016,-500\n24-08-2016,-500\n25-08-2016,-500\n26-08-2016,-500\n29-08-2016,-500\n30-08-2016,-500\n31-08-2016,-500\n01-09-2016,-500\n02-09-2016,-500\n06-09-2016,-500\n07-09-2016,-500\n08-09-2016,-500\n09-09-2016,-500\n12-09-2016,-500\n13-09-2016,-500\n14-09-2016,-500\n15-09-2016,-500\n16-09-2016,-500\n19-09-2016,-500\n20-09-2016,-500\n21-09-2016,-500\n22-09-2016,-500\n23-09-2016,-500\n26-09-2016,-500\n27-09-2016,-500\n28-09-2016,-500\n29-09-2016,-500\n30-09-2016,-500\n03-10-2016,-500\n04-10-2016,-500\n05-10-2016,-500\n06-10-2016,-500\n07-10-2016,-500\n11-10-2016,-500\n12-10-2016,-500\n13-10-2016,-500\n14-10-2016,-500\n17-10-2016,-500\n18-10-2016,-500\n19-10-2016,-500\n20-10-2016,-500\n21-10-2016,-500\n24-10-2016,-500\n25-10-2016,-500\n26-10-2016,-500\n27-10-2016,-500\n28-10-2016,-500\n31-10-2016,-500\n01-11-2016,-500\n02-11-2016,-500\n03-11-2016,-500\n04-11-2016,-500\n07-11-2016,-500\n08-11-2016,-500\n09-11-2016,-500\n10-11-2016,-500\n14-11-2016,-500\n15-11-2016,-500\n16-11-2016,-500\n17-11-2016,-500\n18-11-2016,-500\n21-11-2016,-500\n22-11-2016,-500\n23-11-2016,-500\n24-11-2016,-500\n25-11-2016,-500\n28-11-2016,-500\n29-11-2016,-500\n30-11-2016,-500\n01-12-2016,-500\n02-12-2016,-500\n05-12-2016,-500\n06-12-2016,-500\n07-12-2016,-500\n08-12-2016,-500\n09-12-2016,-500\n12-12-2016,-500\n13-12-2016,-500\n14-12-2016,-500\n15-12-2016,-500\n16-12-2016,-500\n19-12-2016,-500\n20-12-2016,-500\n21-12-2016,-500\n22-12-2016,-500\n23-12-2016,-500\n28-12-2016,-500\n29-12-2016,-500\n30-12-2016,-500\n03-01-2017,-500\n04-01-2017,-500\n05-01-2017,-500\n06-01-2017,-500\n09-01-2017,-500\n10-01-2017,-500\n11-01-2017,-500\n12-01-2017,-500\n13-01-2017,-500\n16-01-2017,-500\n17-01-2017,-500\n18-01-2017,-500\n19-01-2017,-500\n20-01-2017,-500\n23-01-2017,-500\n24-01-2017,-500\n25-01-2017,-500\n26-01-2017,-500\n27-01-2017,-500\n30-01-2017,-500\n31-01-2017,-500\n01-02-2017,-500\n02-02-2017,-500\n03-02-2017,-500\n06-02-2017,-500\n07-02-2017,-500\n08-02-2017,-500\n09-02-2017,-500\n10-02-2017,-500\n13-02-2017,-500\n14-02-2017,-500\n15-02-2017,-500\n16-02-2017,-500\n17-02-2017,-500\n21-02-2017,-500\n22-02-2017,-500\n23-02-2017,-500\n24-02-2017,-500\n27-02-2017,-500\n28-02-2017,-500\n01-03-2017,-500\n02-03-2017,-500\n03-03-2017,-500\n06-03-2017,-500\n07-03-2017,-500\n08-03-2017,-500\n09-03-2017,-500\n10-03-2017,-500\n13-03-2017,-500\n14-03-2017,-500\n15-03-2017,-500\n16-03-2017,-500\n17-03-2017,-500\n20-03-2017,-500\n21-03-2017,-500\n22-03-2017,-500\n23-03-2017,-500\n24-03-2017,-500\n27-03-2017,-500\n28-03-2017,-500\n29-03-2017,-500\n30-03-2017,-500\n31-03-2017,-500\n03-04-2017,-500\n04-04-2017,-500\n05-04-2017,-500\n06-04-2017,-500\n07-04-2017,-500\n10-04-2017,-500\n11-04-2017,-500\n12-04-2017,-500\n13-04-2017,-500\n17-04-2017,-500\n18-04-2017,-500\n19-04-2017,-500\n20-04-2017,-500\n21-04-2017,-500\n24-04-2017,-500\n25-04-2017,-500\n26-04-2017,-500\n27-04-2017,-500\n28-04-2017,-500\n01-05-2017,-500\n02-05-2017,-500\n03-05-2017,-500\n04-05-2017,-500\n05-05-2017,-500\n08-05-2017,-500\n09-05-2017,-500\n10-05-2017,-500\n11-05-2017,-500\n12-05-2017,-500\n15-05-2017,-500\n16-05-2017,-500\n17-05-2017,-500\n18-05-2017,-500\n19-05-2017,-500\n23-05-2017,-500\n24-05-2017,-500\n25-05-2017,-500\n26-05-2017,-500\n29-05-2017,-500\n30-05-2017,-500\n31-05-2017,-500\n01-06-2017,-500\n02-06-2017,-500\n05-06-2017,-500\n06-06-2017,-500\n07-06-2017,-500\n08-06-2017,-500\n09-06-2017,-500\n12-06-2017,-500\n13-06-2017,-500\n14-06-2017,-500\n15-06-2017,-500\n16-06-2017,-500\n19-06-2017,-500\n20-06-2017,-500\n21-06-2017,-500\n22-06-2017,-500\n23-06-2017,-500\n26-06-2017,-500\n27-06-2017,-500\n28-06-2017,-500\n29-06-2017,-500\n30-06-2017,-500\n04-07-2017,-500\n05-07-2017,-500\n06-07-2017,-500\n07-07-2017,-500\n10-07-2017,-500\n11-07-2017,-500\n12-07-2017,-500\n13-07-2017,-500\n14-07-2017,-500\n17-07-2017,-500\n18-07-2017,-500\n19-07-2017,-500\n20-07-2017,-500\n21-07-2017,-500\n24-07-2017,-500\n25-07-2017,-500\n26-07-2017,-500\n27-07-2017,-500\n28-07-2017,-500\n31-07-2017,-500\n01-08-2017,-500\n02-08-2017,-500\n03-08-2017,-500\n04-08-2017,-500\n08-08-2017,-500\n09-08-2017,-500\n10-08-2017,-500\n11-08-2017,-500\n14-08-2017,-500\n15-08-2017,-500\n16-08-2017,-500\n17-08-2017,-500\n18-08-2017,-500\n21-08-2017,-500\n22-08-2017,-500\n23-08-2017,-500\n24-08-2017,-500\n25-08-2017,-500\n28-08-2017,-500\n29-08-2017,-500\n30-08-2017,-500\n31-08-2017,-500\n01-09-2017,-500\n05-09-2017,-500\n06-09-2017,-500\n07-09-2017,-500\n08-09-2017,-500\n11-09-2017,-500\n12-09-2017,-500\n13-09-2017,-500\n14-09-2017,-500\n15-09-2017,-500\n18-09-2017,-500\n19-09-2017,-500\n20-09-2017,-500\n21-09-2017,-500\n22-09-2017,-500\n25-09-2017,-500\n26-09-2017,-500\n27-09-2017,-500\n28-09-2017,-500\n29-09-2017,-500\n02-10-2017,-500\n03-10-2017,-500\n04-10-2017,-500\n05-10-2017,-500\n06-10-2017,-500\n10-10-2017,-500\n11-10-2017,-500\n12-10-2017,-500\n13-10-2017,-500\n16-10-2017,-500\n17-10-2017,-500\n18-10-2017,-500\n19-10-2017,-500\n20-10-2017,-500\n23-10-2017,-500\n24-10-2017,-500\n25-10-2017,-500\n26-10-2017,-500\n27-10-2017,-500\n30-10-2017,-500\n31-10-2017,-500\n01-11-2017,-500\n02-11-2017,-500\n03-11-2017,-500\n06-11-2017,-500\n07-11-2017,-500\n08-11-2017,-500\n09-11-2017,-500\n10-11-2017,-500\n14-11-2017,-500\n15-11-2017,-500\n16-11-2017,-500\n17-11-2017,-500\n20-11-2017,-500\n21-11-2017,-500\n22-11-2017,-500\n23-11-2017,-500\n24-11-2017,-500\n27-11-2017,-500\n28-11-2017,-500\n29-11-2017,-500\n30-11-2017,-500\n01-12-2017,-500\n04-12-2017,-500\n05-12-2017,-500\n06-12-2017,-500\n07-12-2017,-500\n08-12-2017,-500\n11-12-2017,-500\n12-12-2017,-500\n13-12-2017,-500\n14-12-2017,-500\n15-12-2017,-500\n18-12-2017,-500\n19-12-2017,-500\n20-12-2017,-500\n21-12-2017,-500\n22-12-2017,-500\n27-12-2017,-500\n28-12-2017,-500\n29-12-2017,-500\n02-01-2018,-500\n03-01-2018,-500\n04-01-2018,-500\n05-01-2018,-500\n08-01-2018,-500\n09-01-2018,-500\n10-01-2018,-500\n11-01-2018,-500\n12-01-2018,-500\n15-01-2018,-500\n16-01-2018,-500\n17-01-2018,-500\n18-01-2018,-500\n19-01-2018,-500\n22-01-2018,-500\n23-01-2018,-500\n24-01-2018,-500\n25-01-2018,-500\n26-01-2018,-500\n29-01-2018,-500\n30-01-2018,-500\n31-01-2018,-500\n01-02-2018,-500\n02-02-2018,-500\n05-02-2018,-500\n06-02-2018,-500\n07-02-2018,-500\n08-02-2018,-500\n09-02-2018,-500\n12-02-2018,-500\n13-02-2018,-500\n14-02-2018,-500\n15-02-2018,-500\n16-02-2018,-500\n20-02-2018,-500\n21-02-2018,-500\n22-02-2018,-500\n23-02-2018,-500\n26-02-2018,-500\n27-02-2018,-500\n28-02-2018,-500\n01-03-2018,-500\n02-03-2018,-500\n05-03-2018,-500\n06-03-2018,-500\n07-03-2018,-500\n08-03-2018,-500\n09-03-2018,-500\n12-03-2018,-500\n13-03-2018,-500\n14-03-2018,-500\n15-03-2018,-500\n16-03-2018,-500\n19-03-2018,-500\n20-03-2018,-500\n21-03-2018,-500\n22-03-2018,-500\n23-03-2018,-500\n26-03-2018,-500\n27-03-2018,-500\n28-03-2018,-500\n29-03-2018,-500\n02-04-2018,-500\n03-04-2018,-500\n04-04-2018,-500\n05-04-2018,-500\n06-04-2018,-500\n09-04-2018,-500\n10-04-2018,-500\n11-04-2018,-500\n12-04-2018,-500\n13-04-2018,-500\n16-04-2018,-500\n17-04-2018,-500\n18-04-2018,-500\n19-04-2018,-500\n20-04-2018,-500\n23-04-2018,-500\n24-04-2018,-500\n25-04-2018,-500\n26-04-2018,-500\n27-04-2018,-500\n30-04-2018,-500\n01-05-2018,-500\n02-05-2018,-500\n03-05-2018,-500\n04-05-2018,-500\n07-05-2018,-500\n08-05-2018,-500\n09-05-2018,-500\n10-05-2018,-500\n11-05-2018,-500\n14-05-2018,-500\n15-05-2018,-500\n16-05-2018,-500\n17-05-2018,-500\n18-05-2018,-500\n22-05-2018,-500\n23-05-2018,-500\n24-05-2018,-500\n25-05-2018,-500\n28-05-2018,-500\n29-05-2018,-500\n30-05-2018,-500\n31-05-2018,-500\n01-06-2018,-500\n04-06-2018,-500\n05-06-2018,-500\n06-06-2018,-500\n07-06-2018,-500\n08-06-2018,-500\n11-06-2018,-500\n12-06-2018,-500\n13-06-2018,-500\n14-06-2018,-500\n15-06-2018,-500\n18-06-2018,-500\n19-06-2018,-500\n20-06-2018,-500\n21-06-2018,-500\n22-06-2018,-500\n25-06-2018,-500\n26-06-2018,-500\n27-06-2018,-500\n28-06-2018,-500\n29-06-2018,-500\n03-07-2018,-500\n04-07-2018,-500\n05-07-2018,-500\n06-07-2018,-500\n09-07-2018,-500\n10-07-2018,-500\n11-07-2018,-500\n12-07-2018,-500\n13-07-2018,-500\n16-07-2018,-500\n17-07-2018,-500\n18-07-2018,-500\n19-07-2018,-500\n20-07-2018,-500\n23-07-2018,-500\n24-07-2018,-500\n25-07-2018,-500\n26-07-2018,-500\n27-07-2018,-500\n30-07-2018,-500\n31-07-2018,-500\n01-08-2018,-500\n02-08-2018,-500\n03-08-2018,-500\n07-08-2018,-500\n08-08-2018,-500\n09-08-2018,-500\n10-08-2018,-500\n13-08-2018,-500\n14-08-2018,-500\n15-08-2018,-500\n16-08-2018,-500\n17-08-2018,-500\n20-08-2018,-500\n21-08-2018,-500\n22-08-2018,-500\n23-08-2018,-500\n24-08-2018,-500\n27-08-2018,-500\n28-08-2018,-500\n29-08-2018,-500\n30-08-2018,-500\n31-08-2018,-500\n04-09-2018,-500\n05-09-2018,-500\n06-09-2018,-500\n07-09-2018,-500\n10-09-2018,-500\n11-09-2018,-500\n12-09-2018,-500\n13-09-2018,-500\n14-09-2018,-500\n17-09-2018,-500\n18-09-2018,-500\n19-09-2018,-500\n20-09-2018,-500\n21-09-2018,-500\n24-09-2018,-500\n25-09-2018,-500\n26-09-2018,-500\n27-09-2018,-500\n28-09-2018,-500\n01-10-2018,-500\n02-10-2018,-500\n03-10-2018,-500\n04-10-2018,-500\n05-10-2018,-500\n09-10-2018,-500\n10-10-2018,-500\n11-10-2018,-500\n12-10-2018,-500\n15-10-2018,-500\n16-10-2018,-500\n17-10-2018,-500\n18-10-2018,-500\n19-10-2018,-500\n22-10-2018,-500\n23-10-2018,-500\n24-10-2018,-500\n25-10-2018,-500\n26-10-2018,-500\n29-10-2018,-500\n30-10-2018,-500\n31-10-2018,-500\n01-11-2018,-500\n02-11-2018,-500\n05-11-2018,-500\n06-11-2018,-500\n07-11-2018,-500\n08-11-2018,-500\n09-11-2018,-500\n13-11-2018,-500\n14-11-2018,-500\n15-11-2018,-500\n16-11-2018,-500\n19-11-2018,-500\n20-11-2018,-500\n21-11-2018,-500\n22-11-2018,-500\n23-11-2018,-500\n26-11-2018,-500\n27-11-2018,-500\n28-11-2018,-500\n29-11-2018,-500\n30-11-2018,-500\n03-12-2018,-500\n04-12-2018,-500\n05-12-2018,-500\n06-12-2018,-500\n07-12-2018,-500\n10-12-2018,-500\n11-12-2018,-500\n12-12-2018,-500\n13-12-2018,-500\n14-12-2018,-500\n17-12-2018,-500\n18-12-2018,-500\n19-12-2018,-500\n20-12-2018,-500\n21-12-2018,-500\n24-12-2018,-500\n27-12-2018,-500\n28-12-2018,-500\n31-12-2018,-500\n02-01-2019,-500\n03-01-2019,-500\n04-01-2019,-500\n07-01-2019,-500\n08-01-2019,-500\n09-01-2019,-500\n10-01-2019,-500\n11-01-2019,-500\n14-01-2019,-500\n15-01-2019,-500\n16-01-2019,-500\n17-01-2019,-500\n18-01-2019,-500\n21-01-2019,-500\n22-01-2019,-500\n23-01-2019,-500\n24-01-2019,-500\n25-01-2019,-500\n28-01-2019,-500\n29-01-2019,-500\n30-01-2019,-500\n31-01-2019,-500\n01-02-2019,-500\n04-02-2019,-500\n05-02-2019,-500\n06-02-2019,-500\n07-02-2019,-500\n08-02-2019,-500\n11-02-2019,-500\n12-02-2019,-500\n13-02-2019,-500\n14-02-2019,-500\n15-02-2019,-500\n19-02-2019,-500\n20-02-2019,-500\n21-02-2019,-500\n22-02-2019,-500\n25-02-2019,-500\n26-02-2019,-500\n27-02-2019,-500\n28-02-2019,-500\n01-03-2019,-500\n04-03-2019,-500\n05-03-2019,-500\n06-03-2019,-500\n07-03-2019,-500\n08-03-2019,-500\n11-03-2019,-500\n12-03-2019,-500\n13-03-2019,-500\n14-03-2019,-500\n15-03-2019,-500\n18-03-2019,-500\n19-03-2019,-500\n20-03-2019,-500\n21-03-2019,-500\n22-03-2019,-500\n25-03-2019,-500\n26-03-2019,-500\n27-03-2019,-500\n28-03-2019,-500\n29-03-2019,-500\n01-04-2019,-500\n02-04-2019,-500\n03-04-2019,-500\n04-04-2019,-500\n05-04-2019,-500\n08-04-2019,-500\n09-04-2019,-500\n10-04-2019,-500\n11-04-2019,-500\n12-04-2019,-500\n15-04-2019,-500\n16-04-2019,-500\n17-04-2019,-500\n18-04-2019,-500\n22-04-2019,-500\n23-04-2019,-500\n24-04-2019,-500\n25-04-2019,-500\n26-04-2019,-500\n29-04-2019,-500\n30-04-2019,-500\n01-05-2019,-500\n02-05-2019,-500\n03-05-2019,-500\n06-05-2019,-500\n07-05-2019,-500\n08-05-2019,-500\n09-05-2019,-500\n10-05-2019,-500\n13-05-2019,-500\n14-05-2019,-500\n15-05-2019,-500\n16-05-2019,-500\n17-05-2019,-500\n21-05-2019,-500\n22-05-2019,-500\n23-05-2019,-500\n24-05-2019,-500\n27-05-2019,-500\n28-05-2019,-500\n29-05-2019,-500\n30-05-2019,-500\n31-05-2019,-500\n03-06-2019,-500\n04-06-2019,-500\n05-06-2019,-500\n06-06-2019,-500\n07-06-2019,-500\n10-06-2019,-500\n11-06-2019,-500\n12-06-2019,-500\n13-06-2019,-500\n14-06-2019,-500\n17-06-2019,-500\n18-06-2019,-500\n19-06-2019,-500\n20-06-2019,-500\n21-06-2019,-500\n24-06-2019,-500\n25-06-2019,-500\n26-06-2019,-500\n27-06-2019,-500\n28-06-2019,-500\n02-07-2019,-500\n03-07-2019,-500\n04-07-2019,-500\n05-07-2019,-500\n08-07-2019,-500\n09-07-2019,-500\n10-07-2019,-500\n11-07-2019,-500\n12-07-2019,-500\n15-07-2019,-500\n16-07-2019,-500\n17-07-2019,-500\n18-07-2019,-500\n19-07-2019,-500\n22-07-2019,-500\n23-07-2019,-500\n24-07-2019,-500\n25-07-2019,-500\n26-07-2019,-500\n29-07-2019,-500\n30-07-2019,-500\n31-07-2019,-500\n01-08-2019,-500\n02-08-2019,-500\n06-08-2019,-500\n07-08-2019,-500\n08-08-2019,-500\n09-08-2019,-500\n12-08-2019,-500\n13-08-2019,-500\n14-08-2019,-500\n15-08-2019,-500\n16-08-2019,-500\n19-08-2019,-500\n20-08-2019,-500\n21-08-2019,-500\n22-08-2019,-500\n23-08-2019,-500\n26-08-2019,-500\n27-08-2019,-500\n28-08-2019,-500\n29-08-2019,-500\n30-08-2019,-500\n03-09-2019,-500\n04-09-2019,-500\n05-09-2019,-500\n06-09-2019,-500\n09-09-2019,-500\n10-09-2019,-500\n11-09-2019,-500\n12-09-2019,-500\n13-09-2019,-500\n16-09-2019,-500\n17-09-2019,-500\n18-09-2019,-500\n19-09-2019,-500\n20-09-2019,-500\n23-09-2019,-500\n24-09-2019,-500\n25-09-2019,-500\n26-09-2019,-500\n27-09-2019,-500\n30-09-2019,-500\n01-10-2019,-500\n02-10-2019,-500\n03-10-2019,-500\n04-10-2019,-500\n07-10-2019,-500\n08-10-2019,-500\n09-10-2019,-500\n10-10-2019,-500\n11-10-2019,-500\n15-10-2019,-500\n16-10-2019,-500\n17-10-2019,-500\n18-10-2019,-500\n21-10-2019,-500\n22-10-2019,-500\n23-10-2019,-500\n24-10-2019,-500\n25-10-2019,-500\n28-10-2019,-500\n29-10-2019,-500\n30-10-2019,-500\n31-10-2019,-500\n01-11-2019,-500\n04-11-2019,-500\n05-11-2019,-500\n06-11-2019,-500\n07-11-2019,-500\n08-11-2019,-500\n12-11-2019,-500\n13-11-2019,-500\n14-11-2019,-500\n15-11-2019,-500\n18-11-2019,-500\n19-11-2019,-500\n20-11-2019,-500\n21-11-2019,-500\n22-11-2019,-500\n25-11-2019,-500\n26-11-2019,-500\n27-11-2019,-500\n28-11-2019,-500\n29-11-2019,-500\n02-12-2019,-500\n03-12-2019,-500\n04-12-2019,-500\n05-12-2019,-500\n06-12-2019,-500\n09-12-2019,-500\n10-12-2019,-500\n11-12-2019,-500\n12-12-2019,-500\n13-12-2019,-500\n16-12-2019,-500\n17-12-2019,-500\n18-12-2019,-500\n19-12-2019,-500\n20-12-2019,-500\n23-12-2019,-500\n24-12-2019,-500\n27-12-2019,-500\n30-12-2019,-500\n31-12-2019,-500\n02-01-2020,-500\n03-01-2020,-500\n06-01-2020,-500\n07-01-2020,-500\n08-01-2020,-500\n09-01-2020,-500\n10-01-2020,-500\n13-01-2020,-500\n14-01-2020,-500\n15-01-2020,-500\n16-01-2020,-500\n17-01-2020,-500\n20-01-2020,-500\n21-01-2020,-500\n22-01-2020,-500\n23-01-2020,-500\n24-01-2020,-500\n27-01-2020,-500\n28-01-2020,-500\n29-01-2020,-500\n30-01-2020,-500\n31-01-2020,-500\n03-02-2020,-500\n04-02-2020,-500\n05-02-2020,-500\n06-02-2020,-500\n07-02-2020,-500\n10-02-2020,-500\n11-02-2020,-500\n12-02-2020,-500\n13-02-2020,-500\n14-02-2020,-500\n18-02-2020,-500\n19-02-2020,-500\n20-02-2020,-500\n21-02-2020,-500\n24-02-2020,-500\n25-02-2020,-500\n26-02-2020,-500\n27-02-2020,-500\n28-02-2020,-500\n02-03-2020,-500\n03-03-2020,-500\n04-03-2020,-500\n05-03-2020,-500\n06-03-2020,-500\n09-03-2020,-500\n10-03-2020,-500\n11-03-2020,-500\n12-03-2020,-500\n13-03-2020,-500\n16-03-2020,-500\n17-03-2020,-500\n18-03-2020,-500\n19-03-2020,-500\n20-03-2020,-500\n23-03-2020,-500\n24-03-2020,-500\n25-03-2020,-500\n26-03-2020,-500\n27-03-2020,-500\n30-03-2020,-500\n31-03-2020,-500\n01-04-2020,-500\n02-04-2020,-500\n03-04-2020,-500\n06-04-2020,-500\n07-04-2020,-500\n08-04-2020,-500\n09-04-2020,-500\n13-04-2020,-500\n14-04-2020,-500\n15-04-2020,-500\n16-04-2020,-500\n17-04-2020,-500\n20-04-2020,-500\n21-04-2020,-500\n22-04-2020,-500\n23-04-2020,-500\n24-04-2020,-500\n27-04-2020,-500\n28-04-2020,-500\n29-04-2020,-500\n30-04-2020,-500\n01-05-2020,-500\n04-05-2020,-500\n05-05-2020,-500\n06-05-2020,-500\n07-05-2020,-500\n08-05-2020,-500\n11-05-2020,-500\n12-05-2020,-500\n13-05-2020,-500\n14-05-2020,-500\n15-05-2020,-500\n19-05-2020,-500\n20-05-2020,-500\n21-05-2020,-500\n22-05-2020,-500\n25-05-2020,-500\n26-05-2020,-500\n27-05-2020,-500\n28-05-2020,-500\n29-05-2020,-500\n01-06-2020,-500\n02-06-2020,-500\n03-06-2020,-500\n04-06-2020,-500\n05-06-2020,-500\n08-06-2020,-500\n09-06-2020,-500\n10-06-2020,-500\n11-06-2020,-500\n12-06-2020,-500\n15-06-2020,-500\n16-06-2020,-500\n17-06-2020,-500\n18-06-2020,-500\n19-06-2020,-500\n22-06-2020,-500\n23-06-2020,-500\n24-06-2020,-500\n25-06-2020,-500\n26-06-2020,-500\n29-06-2020,-500\n30-06-2020,-500\n02-07-2020,-500\n03-07-2020,-500\n06-07-2020,-500\n07-07-2020,-500\n08-07-2020,-500\n09-07-2020,-500\n10-07-2020,-500\n13-07-2020,-500\n14-07-2020,-500\n15-07-2020,-500\n16-07-2020,-500\n17-07-2020,-500\n20-07-2020,-500\n21-07-2020,-500\n22-07-2020,-500\n23-07-2020,-500\n24-07-2020,-500\n27-07-2020,-500\n28-07-2020,-500\n29-07-2020,-500\n30-07-2020,-500\n31-07-2020,-500\n04-08-2020,-500\n05-08-2020,-500\n06-08-2020,-500\n07-08-2020,-500\n10-08-2020,-500\n11-08-2020,-500\n12-08-2020,-500\n13-08-2020,-500\n14-08-2020,-500\n17-08-2020,-500\n18-08-2020,-500\n19-08-2020,-500\n20-08-2020,-500\n21-08-2020,-500\n24-08-2020,-500\n25-08-2020,-500\n26-08-2020,-500\n27-08-2020,-500\n28-08-2020,-500\n31-08-2020,-500\n01-09-2020,-500\n02-09-2020,-500\n03-09-2020,-500\n04-09-2020,-500\n08-09-2020,-500\n09-09-2020,-500\n10-09-2020,-500\n11-09-2020,-500\n14-09-2020,-500\n15-09-2020,-500\n16-09-2020,-500\n17-09-2020,-500\n18-09-2020,-500\n21-09-2020,-500\n22-09-2020,-500\n23-09-2020,-500\n24-09-2020,-500\n25-09-2020,-500\n28-09-2020,-500\n29-09-2020,-500\n30-09-2020,-500\n01-10-2020,-500\n02-10-2020,-500\n05-10-2020,-500\n06-10-2020,-500\n07-10-2020,-500\n08-10-2020,-500\n09-10-2020,-500\n13-10-2020,-500\n14-10-2020,-500\n15-10-2020,-500\n16-10-2020,-500\n19-10-2020,-500\n20-10-2020,-500\n21-10-2020,-500\n22-10-2020,-500\n23-10-2020,-500\n26-10-2020,-500\n27-10-2020,-500\n28-10-2020,-500\n29-10-2020,-500\n30-10-2020,-500\n02-11-2020,-500\n03-11-2020,-500\n04-11-2020,-500\n05-11-2020,-500\n06-11-2020,-500\n09-11-2020,-500\n10-11-2020,-500\n12-11-2020,-500\n13-11-2020,-500\n16-11-2020,-500\n17-11-2020,-500\n18-11-2020,-500\n19-11-2020,-500\n20-11-2020,-500\n23-11-2020,-500\n24-11-2020,-500\n25-11-2020,-500\n26-11-2020,-500\n27-11-2020,-500\n30-11-2020,-500\n01-12-2020,-500\n02-12-2020,-500\n03-12-2020,-500\n04-12-2020,-500\n07-12-2020,-500\n08-12-2020,-500\n09-12-2020,-500\n10-12-2020,-500\n11-12-2020,-500\n14-12-2020,-500\n15-12-2020,-500\n16-12-2020,-500\n17-12-2020,-500\n18-12-2020,-500\n21-12-2020,-500\n22-12-2020,-500\n23-12-2020,-500\n24-12-2020,-500\n29-12-2020,-500\n30-12-2020,-500\n31-12-2020,-500\n04-01-2021,-500\n05-01-2021,-500\n06-01-2021,-500\n07-01-2021,-500\n08-01-2021,-500\n11-01-2021,-500\n12-01-2021,-500\n13-01-2021,-500\n14-01-2021,-500\n15-01-2021,-500\n18-01-2021,-500\n19-01-2021,-500\n20-01-2021,-500\n21-01-2021,-500\n22-01-2021,-500\n25-01-2021,-500\n26-01-2021,-500\n27-01-2021,-500\n28-01-2021,-500\n29-01-2021,-500\n01-02-2021,-500\n02-02-2021,-500\n03-02-2021,-500\n04-02-2021,-500\n05-02-2021,-500\n08-02-2021,-500\n09-02-2021,-500\n10-02-2021,-500\n11-02-2021,-500\n12-02-2021,-500\n16-02-2021,-500\n17-02-2021,-500\n18-02-2021,-500\n19-02-2021,-500\n22-02-2021,-500\n23-02-2021,-500\n24-02-2021,-500\n25-02-2021,-500\n26-02-2021,-500\n01-03-2021,-500\n02-03-2021,-500\n03-03-2021,-500\n04-03-2021,-500\n05-03-2021,-500\n08-03-2021,-500\n09-03-2021,-500\n10-03-2021,-500\n11-03-2021,-500\n12-03-2021,-500\n15-03-2021,-500\n16-03-2021,-500\n17-03-2021,-500\n18-03-2021,-500\n19-03-2021,-500\n22-03-2021,-500\n23-03-2021,-500\n24-03-2021,-500\n25-03-2021,-500\n26-03-2021,-500\n29-03-2021,-500\n30-03-2021,-500\n31-03-2021,-500\n01-04-2021,-500\n05-04-2021,-500\n06-04-2021,-500\n07-04-2021,-500\n08-04-2021,-500\n09-04-2021,-500\n12-04-2021,-500\n13-04-2021,-500\n14-04-2021,-500\n15-04-2021,-500\n16-04-2021,-500\n19-04-2021,-500\n20-04-2021,-500\n21-04-2021,-500\n22-04-2021,-500\n23-04-2021,-500\n26-04-2021,-500\n27-04-2021,-500\n28-04-2021,-500\n29-04-2021,-500\n30-04-2021,-500\n03-05-2021,-500\n04-05-2021,-500\n05-05-2021,-500\n06-05-2021,-500\n07-05-2021,-500\n10-05-2021,-500\n11-05-2021,-500\n12-05-2021,-500\n13-05-2021,-500\n14-05-2021,-500\n17-05-2021,-500\n18-05-2021,-500\n19-05-2021,-500\n20-05-2021,-500\n21-05-2021,-500\n25-05-2021,-500\n26-05-2021,-500\n27-05-2021,-500\n28-05-2021,-500\n31-05-2021,-500\n01-06-2021,-500\n02-06-2021,-500\n03-06-2021,-500\n04-06-2021,-500\n07-06-2021,-500\n08-06-2021,-500\n09-06-2021,-500\n10-06-2021,-500\n11-06-2021,-500\n14-06-2021,-500\n15-06-2021,-500\n16-06-2021,-500\n17-06-2021,-500\n18-06-2021,-500\n21-06-2021,-500\n22-06-2021,-500\n23-06-2021,-500\n24-06-2021,-500\n25-06-2021,-500\n28-06-2021,-500\n29-06-2021,-500\n30-06-2021,-500\n02-07-2021,-500\n05-07-2021,-500\n06-07-2021,-500\n07-07-2021,-500\n08-07-2021,-500\n09-07-2021,-500\n12-07-2021,-500\n13-07-2021,-500\n14-07-2021,-500\n15-07-2021,-500\n16-07-2021,-500\n19-07-2021,-500\n20-07-2021,-500\n21-07-2021,-500\n22-07-2021,-500\n23-07-2021,-500\n26-07-2021,-500\n27-07-2021,-500\n28-07-2021,-500\n29-07-2021,-500\n30-07-2021,-500\n03-08-2021,-500\n04-08-2021,-500\n05-08-2021,-500\n06-08-2021,-500\n09-08-2021,-500\n10-08-2021,-500\n11-08-2021,-500\n12-08-2021,-500\n13-08-2021,-500\n16-08-2021,-500\n17-08-2021,-500\n18-08-2021,-500\n19-08-2021,-500\n20-08-2021,-500\n23-08-2021,-500\n24-08-2021,-500\n25-08-2021,-500\n26-08-2021,-500\n27-08-2021,-500\n30-08-2021,-500\n31-08-2021,-500\n01-09-2021,-500\n02-09-2021,-500\n03-09-2021,-500\n07-09-2021,-500\n08-09-2021,-500\n09-09-2021,-500\n10-09-2021,-500\n13-09-2021,-500\n14-09-2021,-500\n15-09-2021,-500\n16-09-2021,-500\n17-09-2021,-500\n20-09-2021,-500\n21-09-2021,-500\n22-09-2021,-500\n23-09-2021,-500\n24-09-2021,-500\n27-09-2021,-500\n28-09-2021,-500\n29-09-2021,-500\n01-10-2021,-500\n04-10-2021,-500\n05-10-2021,-500\n06-10-2021,-500\n07-10-2021,-500\n08-10-2021,-500\n12-10-2021,-500\n13-10-2021,-500\n14-10-2021,-500\n15-10-2021,-500\n18-10-2021,-500\n19-10-2021,-500\n20-10-2021,-500\n21-10-2021,-500\n22-10-2021,-500\n25-10-2021,-500\n26-10-2021,-500\n27-10-2021,-500\n28-10-2021,-500\n29-10-2021,-500\n01-11-2021,-500\n02-11-2021,-500\n03-11-2021,-500\n04-11-2021,-500\n05-11-2021,-500\n08-11-2021,-500\n09-11-2021,-500\n10-11-2021,-500\n12-11-2021,-500\n15-11-2021,-500\n16-11-2021,-500\n17-11-2021,-500\n18-11-2021,-500\n19-11-2021,-500\n22-11-2021,-500\n23-11-2021,-500\n24-11-2021,-500\n25-11-2021,-500\n26-11-2021,-500\n29-11-2021,-500\n30-11-2021,-500\n01-12-2021,-500\n02-12-2021,-500\n03-12-2021,-500\n06-12-2021,-500\n07-12-2021,-500\n08-12-2021,-500\n09-12-2021,-500\n10-12-2021,-500\n13-12-2021,-500\n14-12-2021,-500\n15-12-2021,-500\n16-12-2021,-500\n17-12-2021,-500\n20-12-2021,-500\n21-12-2021,-500\n22-12-2021,-500\n23-12-2021,-500\n24-12-2021,-500\n29-12-2021,-500\n30-12-2021,-500\n31-12-2021,-500\n04-01-2022,-500\n05-01-2022,-500\n06-01-2022,-500\n07-01-2022,-500\n10-01-2022,-500\n11-01-2022,-500\n12-01-2022,-500\n13-01-2022,-500\n14-01-2022,-500\n17-01-2022,-500\n18-01-2022,-500\n19-01-2022,-500\n20-01-2022,-500\n21-01-2022,-500\n24-01-2022,-500\n25-01-2022,-500\n26-01-2022,-500\n27-01-2022,-500\n28-01-2022,-500\n31-01-2022,-500\n01-02-2022,-500\n02-02-2022,-500\n03-02-2022,-500\n04-02-2022,-500\n07-02-2022,-500\n08-02-2022,-500\n09-02-2022,-500\n10-02-2022,-500\n11-02-2022,-500\n14-02-2022,-500\n15-02-2022,-500\n16-02-2022,-500\n17-02-2022,-500\n18-02-2022,-500\n22-02-2022,-500\n23-02-2022,-500\n24-02-2022,-500\n25-02-2022,-500\n28-02-2022,-500\n01-03-2022,-500\n02-03-2022,-500\n03-03-2022,-500\n04-03-2022,-500\n07-03-2022,-500\n08-03-2022,-500\n09-03-2022,-500\n10-03-2022,-500\n11-03-2022,-500\n14-03-2022,-500\n15-03-2022,-500\n16-03-2022,-500\n17-03-2022,-500\n18-03-2022,-500\n21-03-2022,-500\n22-03-2022,-500\n23-03-2022,-500\n24-03-2022,-500\n25-03-2022,-500\n28-03-2022,-500\n29-03-2022,-500\n30-03-2022,-500\n31-03-2022,-500\n01-04-2022,-500\n04-04-2022,-500\n05-04-2022,-500\n06-04-2022,-500\n07-04-2022,-500\n08-04-2022,-500\n11-04-2022,-500\n12-04-2022,-500\n13-04-2022,-500\n14-04-2022,-500\n18-04-2022,-500\n19-04-2022,-500\n20-04-2022,-500\n21-04-2022,-500\n22-04-2022,-500\n25-04-2022,-500\n26-04-2022,-500\n27-04-2022,-500\n28-04-2022,-500\n29-04-2022,-500\n02-05-2022,-500\n03-05-2022,-500\n04-05-2022,-500\n05-05-2022,-500\n06-05-2022,-500\n09-05-2022,-500\n10-05-2022,-500\n11-05-2022,-500\n12-05-2022,-500\n13-05-2022,-500\n16-05-2022,-500\n17-05-2022,-500\n18-05-2022,-500\n19-05-2022,-500\n20-05-2022,-500\n24-05-2022,-500\n25-05-2022,-500\n26-05-2022,-500\n27-05-2022,-500\n30-05-2022,-500\n31-05-2022,-500\n01-06-2022,-500\n02-06-2022,-500\n03-06-2022,-500\n06-06-2022,-500\n07-06-2022,-500\n08-06-2022,-500\n09-06-2022,-500\n10-06-2022,-500\n13-06-2022,-500\n14-06-2022,-500\n15-06-2022,-500\n16-06-2022,-500\n17-06-2022,-500\n20-06-2022,-500\n21-06-2022,-500\n22-06-2022,-500\n23-06-2022,-500\n24-06-2022,-500\n27-06-2022,-500\n28-06-2022,-500\n29-06-2022,-500\n30-06-2022,-500\n04-07-2022,-500\n05-07-2022,-500\n06-07-2022,-500\n07-07-2022,-500\n08-07-2022,-500\n11-07-2022,-500\n12-07-2022,-500\n13-07-2022,-500\n14-07-2022,-500\n15-07-2022,-500\n18-07-2022,-500\n19-07-2022,-500\n20-07-2022,-500\n21-07-2022,-500\n22-07-2022,-500\n25-07-2022,-500\n26-07-2022,-500\n27-07-2022,-500\n28-07-2022,-500\n29-07-2022,-500\n02-08-2022,-500\n03-08-2022,-500\n04-08-2022,-500\n05-08-2022,-500\n08-08-2022,-500\n09-08-2022,-500\n10-08-2022,-500\n11-08-2022,-500\n12-08-2022,-500\n15-08-2022,-500\n16-08-2022,-500\n17-08-2022,-500\n18-08-2022,-500\n19-08-2022,-500\n22-08-2022,-500\n23-08-2022,-500\n24-08-2022,-500\n25-08-2022,-500\n26-08-2022,-500\n29-08-2022,-500\n30-08-2022,-500\n31-08-2022,-500\n01-09-2022,-500\n02-09-2022,-500\n06-09-2022,-500\n07-09-2022,-500\n08-09-2022,-500\n09-09-2022,-500\n12-09-2022,-500\n13-09-2022,-500\n14-09-2022,-500\n15-09-2022,-500\n16-09-2022,-500\n19-09-2022,-500\n20-09-2022,-500\n21-09-2022,-500\n22-09-2022,-500\n23-09-2022,-500\n26-09-2022,-500\n27-09-2022,-500\n28-09-2022,-500\n29-09-2022,-500\n03-10-2022,-500\n04-10-2022,-500\n05-10-2022,-500\n06-10-2022,-500\n07-10-2022,-500\n11-10-2022,-500\n12-10-2022,-500\n13-10-2022,-500\n14-10-2022,-500\n17-10-2022,-500\n18-10-2022,-500\n19-10-2022,-500\n20-10-2022,-500\n21-10-2022,-500\n24-10-2022,-500\n25-10-2022,-500\n26-10-2022,-500\n27-10-2022,-500\n28-10-2022,-500\n31-10-2022,-500\n01-11-2022,-500\n02-11-2022,-500\n03-11-2022,-500\n04-11-2022,-500\n07-11-2022,-500\n08-11-2022,-500\n09-11-2022,-500\n10-11-2022,-500\n14-11-2022,-500\n15-11-2022,-500\n16-11-2022,-500\n17-11-2022,-500\n18-11-2022,-500\n21-11-2022,-500\n22-11-2022,-500\n23-11-2022,-500\n24-11-2022,-500\n25-11-2022,-500\n28-11-2022,-500\n29-11-2022,-500\n30-11-2022,-500\n01-12-2022,-500\n02-12-2022,-500\n05-12-2022,-500\n06-12-2022,-500\n07-12-2022,-500\n08-12-2022,-500\n09-12-2022,-500\n12-12-2022,-500\n13-12-2022,-500\n14-12-2022,-500\n15-12-2022,-500\n16-12-2022,-500\n19-12-2022,-500\n20-12-2022,-500\n21-12-2022,-500\n22-12-2022,-500\n23-12-2022,-500\n28-12-2022,-500\n29-12-2022,-500\n30-12-2022,-500\n03-01-2023,-500\n04-01-2023,-500\n05-01-2023,-500\n06-01-2023,-500\n09-01-2023,-500\n10-01-2023,-500\n11-01-2023,-500\n12-01-2023,-500\n13-01-2023,-500\n16-01-2023,-500\n17-01-2023,-500\n18-01-2023,-500\n19-01-2023,-500\n20-01-2023,-500\n23-01-2023,-500\n24-01-2023,-500\n25-01-2023,-500\n26-01-2023,-500\n27-01-2023,-500\n30-01-2023,-500\n31-01-2023,-500\n01-02-2023,-500\n02-02-2023,-500\n03-02-2023,-500\n06-02-2023,-500\n07-02-2023,-500\n08-02-2023,-500\n09-02-2023,-500\n10-02-2023,-500\n13-02-2023,-500\n14-02-2023,-500\n15-02-2023,-500\n16-02-2023,-500\n17-02-2023,-500\n21-02-2023,-500\n22-02-2023,-500\n23-02-2023,-500\n24-02-2023,-500\n27-02-2023,-500\n28-02-2023,-500\n01-03-2023,-500\n02-03-2023,-500\n03-03-2023,-500\n06-03-2023,-500\n07-03-2023,-500\n08-03-2023,-500\n09-03-2023,-500\n10-03-2023,-500\n13-03-2023,-500\n14-03-2023,-500\n15-03-2023,-500\n16-03-2023,-500\n17-03-2023,-500\n20-03-2023,-500\n21-03-2023,-500\n22-03-2023,-500\n23-03-2023,-500\n24-03-2023,-500\n27-03-2023,-500\n28-03-2023,-500\n29-03-2023,-500\n30-03-2023,-500\n31-03-2023,-500\n03-04-2023,-500\n04-04-2023,-500\n05-04-2023,-500\n06-04-2023,-500\n10-04-2023,-500\n11-04-2023,-500\n12-04-2023,-500\n13-04-2023,-500\n14-04-2023,-500\n17-04-2023,-500\n18-04-2023,-500\n19-04-2023,-500\n20-04-2023,-500\n21-04-2023,-500\n24-04-2023,-500\n25-04-2023,-500\n26-04-2023,-500\n27-04-2023,-500\n28-04-2023,-500\n01-05-2023,-500\n02-05-2023,-500\n03-05-2023,-500\n04-05-2023,-500\n05-05-2023,-500\n08-05-2023,-500\n09-05-2023,-500\n10-05-2023,-500\n11-05-2023,-500\n12-05-2023,-500\n15-05-2023,-500\n16-05-2023,-500\n17-05-2023,-500\n18-05-2023,-500\n19-05-2023,-500\n23-05-2023,-500\n24-05-2023,-500\n25-05-2023,-500\n26-05-2023,-500\n29-05-2023,-500\n30-05-2023,-500\n31-05-2023,-500\n01-06-2023,-500\n02-06-2023,-500\n05-06-2023,-500\n06-06-2023,-500\n07-06-2023,-500\n08-06-2023,-500\n09-06-2023,-500\n12-06-2023,-500\n13-06-2023,-500\n14-06-2023,-500\n15-06-2023,-500\n16-06-2023,-500\n19-06-2023,-500\n20-06-2023,-500\n21-06-2023,-500\n22-06-2023,-500\n23-06-2023,-500\n26-06-2023,-500\n27-06-2023,-500\n28-06-2023,-500\n29-06-2023,-500\n30-06-2023,-500\n04-07-2023,-500\n05-07-2023,-500\n06-07-2023,-500\n07-07-2023,-500\n10-07-2023,-500\n11-07-2023,-500\n12-07-2023,-500\n13-07-2023,-500\n14-07-2023,-500\n17-07-2023,-500\n18-07-2023,-500\n19-07-2023,-500\n20-07-2023,-500\n21-07-2023,-500\n24-07-2023,-500\n25-07-2023,-500\n26-07-2023,-500\n27-07-2023,-500\n28-07-2023,-500\n31-07-2023,-500\n01-08-2023,-500\n02-08-2023,-500\n03-08-2023,-500\n04-08-2023,-500\n08-08-2023,-500\n09-08-2023,-500\n10-08-2023,-500\n11-08-2023,-500\n14-08-2023,-500\n15-08-2023,-500\n16-08-2023,-500\n17-08-2023,-500\n18-08-2023,-500\n21-08-2023,-500\n22-08-2023,-500\n23-08-2023,-500\n24-08-2023,-500\n25-08-2023,-500\n28-08-2023,-500\n29-08-2023,-500\n30-08-2023,-500\n31-08-2023,-500\n01-09-2023,-500\n05-09-2023,-500\n06-09-2023,-500\n07-09-2023,-500"
  },
  {
    "path": "python/rateslib/data/historical/corra.csv",
    "content": "﻿reference_date,rate\n12-08-1997,-500\n18-08-1997,-500\n19-08-1997,-500\n20-08-1997,-500\n21-08-1997,-500\n22-08-1997,-500\n25-08-1997,-500\n26-08-1997,-500\n27-08-1997,-500\n28-08-1997,-500\n02-09-1997,-500\n03-09-1997,-500\n04-09-1997,-500\n05-09-1997,-500\n08-09-1997,-500\n09-09-1997,-500\n10-09-1997,-500\n11-09-1997,-500\n12-09-1997,-500\n15-09-1997,-500\n16-09-1997,-500\n17-09-1997,-500\n18-09-1997,-500\n19-09-1997,-500\n22-09-1997,-500\n23-09-1997,-500\n24-09-1997,-500\n25-09-1997,-500\n26-09-1997,-500\n29-09-1997,-500\n30-09-1997,-500\n01-10-1997,-500\n02-10-1997,-500\n03-10-1997,-500\n06-10-1997,-500\n07-10-1997,-500\n08-10-1997,-500\n09-10-1997,-500\n10-10-1997,-500\n14-10-1997,-500\n15-10-1997,-500\n16-10-1997,-500\n17-10-1997,-500\n20-10-1997,-500\n21-10-1997,-500\n22-10-1997,-500\n23-10-1997,-500\n24-10-1997,-500\n27-10-1997,-500\n28-10-1997,-500\n29-10-1997,-500\n30-10-1997,-500\n31-10-1997,-500\n03-11-1997,-500\n04-11-1997,-500\n05-11-1997,-500\n06-11-1997,-500\n07-11-1997,-500\n10-11-1997,-500\n12-11-1997,-500\n13-11-1997,-500\n14-11-1997,-500\n17-11-1997,-500\n18-11-1997,-500\n19-11-1997,-500\n20-11-1997,-500\n21-11-1997,-500\n24-11-1997,-500\n25-11-1997,-500\n26-11-1997,-500\n27-11-1997,-500\n28-11-1997,-500\n01-12-1997,-500\n02-12-1997,-500\n03-12-1997,-500\n04-12-1997,-500\n05-12-1997,-500\n08-12-1997,-500\n09-12-1997,-500\n10-12-1997,-500\n11-12-1997,-500\n12-12-1997,-500\n15-12-1997,-500\n16-12-1997,-500\n17-12-1997,-500\n18-12-1997,-500\n19-12-1997,-500\n23-12-1997,-500\n24-12-1997,-500\n29-12-1997,-500\n30-12-1997,-500\n31-12-1997,-500\n02-01-1998,-500\n05-01-1998,-500\n06-01-1998,-500\n07-01-1998,-500\n08-01-1998,-500\n09-01-1998,-500\n12-01-1998,-500\n13-01-1998,-500\n14-01-1998,-500\n15-01-1998,-500\n16-01-1998,-500\n19-01-1998,-500\n20-01-1998,-500\n21-01-1998,-500\n22-01-1998,-500\n23-01-1998,-500\n26-01-1998,-500\n27-01-1998,-500\n28-01-1998,-500\n29-01-1998,-500\n30-01-1998,-500\n02-02-1998,-500\n03-02-1998,-500\n04-02-1998,-500\n05-02-1998,-500\n06-02-1998,-500\n09-02-1998,-500\n10-02-1998,-500\n11-02-1998,-500\n12-02-1998,-500\n13-02-1998,-500\n16-02-1998,-500\n17-02-1998,-500\n18-02-1998,-500\n19-02-1998,-500\n20-02-1998,-500\n23-02-1998,-500\n24-02-1998,-500\n25-02-1998,-500\n26-02-1998,-500\n27-02-1998,-500\n02-03-1998,-500\n03-03-1998,-500\n04-03-1998,-500\n05-03-1998,-500\n06-03-1998,-500\n09-03-1998,-500\n10-03-1998,-500\n11-03-1998,-500\n12-03-1998,-500\n13-03-1998,-500\n16-03-1998,-500\n17-03-1998,-500\n18-03-1998,-500\n19-03-1998,-500\n20-03-1998,-500\n23-03-1998,-500\n24-03-1998,-500\n25-03-1998,-500\n26-03-1998,-500\n27-03-1998,-500\n30-03-1998,-500\n31-03-1998,-500\n01-04-1998,-500\n02-04-1998,-500\n03-04-1998,-500\n06-04-1998,-500\n07-04-1998,-500\n08-04-1998,-500\n13-04-1998,-500\n14-04-1998,-500\n15-04-1998,-500\n16-04-1998,-500\n17-04-1998,-500\n20-04-1998,-500\n21-04-1998,-500\n22-04-1998,-500\n23-04-1998,-500\n24-04-1998,-500\n27-04-1998,-500\n28-04-1998,-500\n30-04-1998,-500\n01-05-1998,-500\n04-05-1998,-500\n05-05-1998,-500\n06-05-1998,-500\n07-05-1998,-500\n08-05-1998,-500\n11-05-1998,-500\n12-05-1998,-500\n13-05-1998,-500\n14-05-1998,-500\n15-05-1998,-500\n19-05-1998,-500\n20-05-1998,-500\n21-05-1998,-500\n22-05-1998,-500\n25-05-1998,-500\n26-05-1998,-500\n27-05-1998,-500\n28-05-1998,-500\n29-05-1998,-500\n01-06-1998,-500\n02-06-1998,-500\n03-06-1998,-500\n04-06-1998,-500\n05-06-1998,-500\n08-06-1998,-500\n09-06-1998,-500\n10-06-1998,-500\n11-06-1998,-500\n12-06-1998,-500\n15-06-1998,-500\n16-06-1998,-500\n17-06-1998,-500\n18-06-1998,-500\n19-06-1998,-500\n22-06-1998,-500\n23-06-1998,-500\n24-06-1998,-500\n25-06-1998,-500\n26-06-1998,-500\n29-06-1998,-500\n30-06-1998,-500\n02-07-1998,-500\n03-07-1998,-500\n06-07-1998,-500\n07-07-1998,-500\n08-07-1998,-500\n09-07-1998,-500\n10-07-1998,-500\n13-07-1998,-500\n14-07-1998,-500\n15-07-1998,-500\n16-07-1998,-500\n17-07-1998,-500\n20-07-1998,-500\n21-07-1998,-500\n22-07-1998,-500\n23-07-1998,-500\n24-07-1998,-500\n27-07-1998,-500\n28-07-1998,-500\n29-07-1998,-500\n30-07-1998,-500\n31-07-1998,-500\n04-08-1998,-500\n05-08-1998,-500\n06-08-1998,-500\n07-08-1998,-500\n10-08-1998,-500\n11-08-1998,-500\n12-08-1998,-500\n13-08-1998,-500\n14-08-1998,-500\n17-08-1998,-500\n18-08-1998,-500\n19-08-1998,-500\n20-08-1998,-500\n21-08-1998,-500\n24-08-1998,-500\n25-08-1998,-500\n26-08-1998,-500\n27-08-1998,-500\n28-08-1998,-500\n31-08-1998,-500\n01-09-1998,-500\n02-09-1998,-500\n03-09-1998,-500\n04-09-1998,-500\n08-09-1998,-500\n09-09-1998,-500\n10-09-1998,-500\n11-09-1998,-500\n14-09-1998,-500\n15-09-1998,-500\n16-09-1998,-500\n17-09-1998,-500\n18-09-1998,-500\n21-09-1998,-500\n22-09-1998,-500\n23-09-1998,-500\n24-09-1998,-500\n25-09-1998,-500\n28-09-1998,-500\n29-09-1998,-500\n30-09-1998,-500\n01-10-1998,-500\n02-10-1998,-500\n05-10-1998,-500\n06-10-1998,-500\n07-10-1998,-500\n08-10-1998,-500\n09-10-1998,-500\n13-10-1998,-500\n14-10-1998,-500\n15-10-1998,-500\n16-10-1998,-500\n19-10-1998,-500\n20-10-1998,-500\n21-10-1998,-500\n22-10-1998,-500\n23-10-1998,-500\n26-10-1998,-500\n27-10-1998,-500\n28-10-1998,-500\n29-10-1998,-500\n30-10-1998,-500\n02-11-1998,-500\n03-11-1998,-500\n04-11-1998,-500\n05-11-1998,-500\n06-11-1998,-500\n09-11-1998,-500\n10-11-1998,-500\n12-11-1998,-500\n13-11-1998,-500\n16-11-1998,-500\n17-11-1998,-500\n18-11-1998,-500\n19-11-1998,-500\n20-11-1998,-500\n23-11-1998,-500\n24-11-1998,-500\n25-11-1998,-500\n26-11-1998,-500\n27-11-1998,-500\n30-11-1998,-500\n01-12-1998,-500\n02-12-1998,-500\n03-12-1998,-500\n04-12-1998,-500\n07-12-1998,-500\n08-12-1998,-500\n09-12-1998,-500\n10-12-1998,-500\n11-12-1998,-500\n14-12-1998,-500\n15-12-1998,-500\n16-12-1998,-500\n17-12-1998,-500\n18-12-1998,-500\n21-12-1998,-500\n22-12-1998,-500\n23-12-1998,-500\n24-12-1998,-500\n29-12-1998,-500\n30-12-1998,-500\n31-12-1998,-500\n04-01-1999,-500\n05-01-1999,-500\n06-01-1999,-500\n07-01-1999,-500\n08-01-1999,-500\n11-01-1999,-500\n12-01-1999,-500\n13-01-1999,-500\n14-01-1999,-500\n15-01-1999,-500\n18-01-1999,-500\n19-01-1999,-500\n20-01-1999,-500\n21-01-1999,-500\n22-01-1999,-500\n25-01-1999,-500\n26-01-1999,-500\n27-01-1999,-500\n28-01-1999,-500\n29-01-1999,-500\n01-02-1999,-500\n02-02-1999,-500\n03-02-1999,-500\n04-02-1999,-500\n05-02-1999,-500\n08-02-1999,-500\n09-02-1999,-500\n10-02-1999,-500\n11-02-1999,-500\n12-02-1999,-500\n15-02-1999,-500\n16-02-1999,-500\n17-02-1999,-500\n18-02-1999,-500\n19-02-1999,-500\n22-02-1999,-500\n23-02-1999,-500\n24-02-1999,-500\n25-02-1999,-500\n26-02-1999,-500\n01-03-1999,-500\n02-03-1999,-500\n03-03-1999,-500\n04-03-1999,-500\n05-03-1999,-500\n08-03-1999,-500\n09-03-1999,-500\n10-03-1999,-500\n11-03-1999,-500\n12-03-1999,-500\n15-03-1999,-500\n16-03-1999,-500\n17-03-1999,-500\n18-03-1999,-500\n19-03-1999,-500\n22-03-1999,-500\n23-03-1999,-500\n24-03-1999,-500\n25-03-1999,-500\n26-03-1999,-500\n29-03-1999,-500\n30-03-1999,-500\n31-03-1999,-500\n01-04-1999,-500\n05-04-1999,-500\n06-04-1999,-500\n07-04-1999,-500\n08-04-1999,-500\n09-04-1999,-500\n12-04-1999,-500\n13-04-1999,-500\n14-04-1999,-500\n15-04-1999,-500\n16-04-1999,-500\n19-04-1999,-500\n20-04-1999,-500\n21-04-1999,-500\n22-04-1999,-500\n23-04-1999,-500\n26-04-1999,-500\n27-04-1999,-500\n28-04-1999,-500\n29-04-1999,-500\n30-04-1999,-500\n03-05-1999,-500\n04-05-1999,-500\n05-05-1999,-500\n06-05-1999,-500\n07-05-1999,-500\n10-05-1999,-500\n11-05-1999,-500\n12-05-1999,-500\n13-05-1999,-500\n14-05-1999,-500\n17-05-1999,-500\n18-05-1999,-500\n19-05-1999,-500\n20-05-1999,-500\n21-05-1999,-500\n25-05-1999,-500\n26-05-1999,-500\n27-05-1999,-500\n28-05-1999,-500\n31-05-1999,-500\n01-06-1999,-500\n02-06-1999,-500\n03-06-1999,-500\n04-06-1999,-500\n07-06-1999,-500\n08-06-1999,-500\n09-06-1999,-500\n10-06-1999,-500\n11-06-1999,-500\n14-06-1999,-500\n15-06-1999,-500\n16-06-1999,-500\n17-06-1999,-500\n18-06-1999,-500\n21-06-1999,-500\n22-06-1999,-500\n23-06-1999,-500\n24-06-1999,-500\n25-06-1999,-500\n28-06-1999,-500\n29-06-1999,-500\n30-06-1999,-500\n02-07-1999,-500\n05-07-1999,-500\n06-07-1999,-500\n07-07-1999,-500\n08-07-1999,-500\n09-07-1999,-500\n12-07-1999,-500\n13-07-1999,-500\n14-07-1999,-500\n15-07-1999,-500\n16-07-1999,-500\n19-07-1999,-500\n20-07-1999,-500\n21-07-1999,-500\n22-07-1999,-500\n23-07-1999,-500\n26-07-1999,-500\n27-07-1999,-500\n28-07-1999,-500\n29-07-1999,-500\n30-07-1999,-500\n03-08-1999,-500\n04-08-1999,-500\n05-08-1999,-500\n06-08-1999,-500\n09-08-1999,-500\n10-08-1999,-500\n11-08-1999,-500\n12-08-1999,-500\n13-08-1999,-500\n16-08-1999,-500\n17-08-1999,-500\n18-08-1999,-500\n19-08-1999,-500\n20-08-1999,-500\n23-08-1999,-500\n24-08-1999,-500\n25-08-1999,-500\n26-08-1999,-500\n27-08-1999,-500\n30-08-1999,-500\n31-08-1999,-500\n01-09-1999,-500\n02-09-1999,-500\n03-09-1999,-500\n07-09-1999,-500\n08-09-1999,-500\n09-09-1999,-500\n10-09-1999,-500\n13-09-1999,-500\n14-09-1999,-500\n15-09-1999,-500\n16-09-1999,-500\n17-09-1999,-500\n20-09-1999,-500\n21-09-1999,-500\n22-09-1999,-500\n23-09-1999,-500\n24-09-1999,-500\n27-09-1999,-500\n28-09-1999,-500\n29-09-1999,-500\n30-09-1999,-500\n01-10-1999,-500\n04-10-1999,-500\n05-10-1999,-500\n06-10-1999,-500\n07-10-1999,-500\n08-10-1999,-500\n12-10-1999,-500\n13-10-1999,-500\n14-10-1999,-500\n15-10-1999,-500\n18-10-1999,-500\n19-10-1999,-500\n20-10-1999,-500\n21-10-1999,-500\n22-10-1999,-500\n25-10-1999,-500\n26-10-1999,-500\n27-10-1999,-500\n28-10-1999,-500\n29-10-1999,-500\n01-11-1999,-500\n02-11-1999,-500\n03-11-1999,-500\n04-11-1999,-500\n05-11-1999,-500\n08-11-1999,-500\n09-11-1999,-500\n10-11-1999,-500\n12-11-1999,-500\n15-11-1999,-500\n16-11-1999,-500\n17-11-1999,-500\n18-11-1999,-500\n19-11-1999,-500\n22-11-1999,-500\n23-11-1999,-500\n24-11-1999,-500\n25-11-1999,-500\n26-11-1999,-500\n29-11-1999,-500\n30-11-1999,-500\n01-12-1999,-500\n02-12-1999,-500\n03-12-1999,-500\n06-12-1999,-500\n07-12-1999,-500\n08-12-1999,-500\n09-12-1999,-500\n10-12-1999,-500\n13-12-1999,-500\n14-12-1999,-500\n15-12-1999,-500\n16-12-1999,-500\n17-12-1999,-500\n20-12-1999,-500\n21-12-1999,-500\n22-12-1999,-500\n23-12-1999,-500\n24-12-1999,-500\n29-12-1999,-500\n30-12-1999,-500\n31-12-1999,-500\n04-01-2000,-500\n05-01-2000,-500\n06-01-2000,-500\n07-01-2000,-500\n10-01-2000,-500\n11-01-2000,-500\n12-01-2000,-500\n13-01-2000,-500\n14-01-2000,-500\n17-01-2000,-500\n18-01-2000,-500\n19-01-2000,-500\n20-01-2000,-500\n21-01-2000,-500\n24-01-2000,-500\n25-01-2000,-500\n26-01-2000,-500\n27-01-2000,-500\n28-01-2000,-500\n31-01-2000,-500\n01-02-2000,-500\n02-02-2000,-500\n03-02-2000,-500\n04-02-2000,-500\n07-02-2000,-500\n08-02-2000,-500\n09-02-2000,-500\n10-02-2000,-500\n11-02-2000,-500\n14-02-2000,-500\n15-02-2000,-500\n16-02-2000,-500\n17-02-2000,-500\n18-02-2000,-500\n21-02-2000,-500\n22-02-2000,-500\n23-02-2000,-500\n24-02-2000,-500\n25-02-2000,-500\n28-02-2000,-500\n29-02-2000,-500\n01-03-2000,-500\n02-03-2000,-500\n03-03-2000,-500\n06-03-2000,-500\n07-03-2000,-500\n08-03-2000,-500\n09-03-2000,-500\n10-03-2000,-500\n13-03-2000,-500\n14-03-2000,-500\n15-03-2000,-500\n16-03-2000,-500\n17-03-2000,-500\n20-03-2000,-500\n21-03-2000,-500\n22-03-2000,-500\n23-03-2000,-500\n24-03-2000,-500\n27-03-2000,-500\n28-03-2000,-500\n29-03-2000,-500\n30-03-2000,-500\n31-03-2000,-500\n03-04-2000,-500\n04-04-2000,-500\n05-04-2000,-500\n06-04-2000,-500\n07-04-2000,-500\n10-04-2000,-500\n11-04-2000,-500\n12-04-2000,-500\n13-04-2000,-500\n14-04-2000,-500\n17-04-2000,-500\n18-04-2000,-500\n19-04-2000,-500\n20-04-2000,-500\n24-04-2000,-500\n25-04-2000,-500\n26-04-2000,-500\n27-04-2000,-500\n28-04-2000,-500\n01-05-2000,-500\n02-05-2000,-500\n03-05-2000,-500\n04-05-2000,-500\n05-05-2000,-500\n08-05-2000,-500\n09-05-2000,-500\n10-05-2000,-500\n11-05-2000,-500\n12-05-2000,-500\n15-05-2000,-500\n16-05-2000,-500\n17-05-2000,-500\n18-05-2000,-500\n19-05-2000,-500\n23-05-2000,-500\n24-05-2000,-500\n25-05-2000,-500\n26-05-2000,-500\n29-05-2000,-500\n30-05-2000,-500\n31-05-2000,-500\n01-06-2000,-500\n02-06-2000,-500\n05-06-2000,-500\n06-06-2000,-500\n07-06-2000,-500\n08-06-2000,-500\n09-06-2000,-500\n12-06-2000,-500\n13-06-2000,-500\n14-06-2000,-500\n15-06-2000,-500\n16-06-2000,-500\n19-06-2000,-500\n20-06-2000,-500\n21-06-2000,-500\n22-06-2000,-500\n23-06-2000,-500\n26-06-2000,-500\n27-06-2000,-500\n28-06-2000,-500\n29-06-2000,-500\n30-06-2000,-500\n04-07-2000,-500\n05-07-2000,-500\n06-07-2000,-500\n07-07-2000,-500\n10-07-2000,-500\n11-07-2000,-500\n12-07-2000,-500\n13-07-2000,-500\n14-07-2000,-500\n17-07-2000,-500\n18-07-2000,-500\n19-07-2000,-500\n20-07-2000,-500\n21-07-2000,-500\n24-07-2000,-500\n25-07-2000,-500\n26-07-2000,-500\n27-07-2000,-500\n28-07-2000,-500\n31-07-2000,-500\n01-08-2000,-500\n02-08-2000,-500\n03-08-2000,-500\n04-08-2000,-500\n08-08-2000,-500\n09-08-2000,-500\n10-08-2000,-500\n11-08-2000,-500\n14-08-2000,-500\n15-08-2000,-500\n16-08-2000,-500\n17-08-2000,-500\n18-08-2000,-500\n21-08-2000,-500\n22-08-2000,-500\n23-08-2000,-500\n24-08-2000,-500\n25-08-2000,-500\n28-08-2000,-500\n29-08-2000,-500\n30-08-2000,-500\n31-08-2000,-500\n01-09-2000,-500\n05-09-2000,-500\n06-09-2000,-500\n07-09-2000,-500\n08-09-2000,-500\n11-09-2000,-500\n12-09-2000,-500\n13-09-2000,-500\n14-09-2000,-500\n15-09-2000,-500\n18-09-2000,-500\n19-09-2000,-500\n20-09-2000,-500\n21-09-2000,-500\n22-09-2000,-500\n25-09-2000,-500\n26-09-2000,-500\n27-09-2000,-500\n28-09-2000,-500\n29-09-2000,-500\n02-10-2000,-500\n03-10-2000,-500\n04-10-2000,-500\n05-10-2000,-500\n06-10-2000,-500\n10-10-2000,-500\n11-10-2000,-500\n12-10-2000,-500\n13-10-2000,-500\n16-10-2000,-500\n17-10-2000,-500\n18-10-2000,-500\n19-10-2000,-500\n20-10-2000,-500\n23-10-2000,-500\n24-10-2000,-500\n25-10-2000,-500\n26-10-2000,-500\n27-10-2000,-500\n30-10-2000,-500\n31-10-2000,-500\n01-11-2000,-500\n02-11-2000,-500\n03-11-2000,-500\n06-11-2000,-500\n07-11-2000,-500\n08-11-2000,-500\n09-11-2000,-500\n10-11-2000,-500\n14-11-2000,-500\n15-11-2000,-500\n16-11-2000,-500\n17-11-2000,-500\n20-11-2000,-500\n21-11-2000,-500\n22-11-2000,-500\n23-11-2000,-500\n24-11-2000,-500\n27-11-2000,-500\n28-11-2000,-500\n29-11-2000,-500\n30-11-2000,-500\n01-12-2000,-500\n04-12-2000,-500\n05-12-2000,-500\n06-12-2000,-500\n07-12-2000,-500\n08-12-2000,-500\n11-12-2000,-500\n12-12-2000,-500\n13-12-2000,-500\n14-12-2000,-500\n15-12-2000,-500\n18-12-2000,-500\n19-12-2000,-500\n20-12-2000,-500\n21-12-2000,-500\n22-12-2000,-500\n27-12-2000,-500\n28-12-2000,-500\n29-12-2000,-500\n02-01-2001,-500\n03-01-2001,-500\n04-01-2001,-500\n05-01-2001,-500\n08-01-2001,-500\n09-01-2001,-500\n10-01-2001,-500\n11-01-2001,-500\n12-01-2001,-500\n15-01-2001,-500\n16-01-2001,-500\n17-01-2001,-500\n18-01-2001,-500\n19-01-2001,-500\n22-01-2001,-500\n23-01-2001,-500\n24-01-2001,-500\n25-01-2001,-500\n26-01-2001,-500\n29-01-2001,-500\n30-01-2001,-500\n31-01-2001,-500\n01-02-2001,-500\n02-02-2001,-500\n05-02-2001,-500\n06-02-2001,-500\n07-02-2001,-500\n08-02-2001,-500\n09-02-2001,-500\n12-02-2001,-500\n13-02-2001,-500\n14-02-2001,-500\n15-02-2001,-500\n16-02-2001,-500\n19-02-2001,-500\n20-02-2001,-500\n21-02-2001,-500\n22-02-2001,-500\n23-02-2001,-500\n26-02-2001,-500\n27-02-2001,-500\n28-02-2001,-500\n01-03-2001,-500\n02-03-2001,-500\n05-03-2001,-500\n06-03-2001,-500\n07-03-2001,-500\n08-03-2001,-500\n09-03-2001,-500\n12-03-2001,-500\n13-03-2001,-500\n14-03-2001,-500\n15-03-2001,-500\n16-03-2001,-500\n19-03-2001,-500\n20-03-2001,-500\n21-03-2001,-500\n22-03-2001,-500\n23-03-2001,-500\n26-03-2001,-500\n27-03-2001,-500\n28-03-2001,-500\n29-03-2001,-500\n30-03-2001,-500\n02-04-2001,-500\n03-04-2001,-500\n04-04-2001,-500\n05-04-2001,-500\n06-04-2001,-500\n09-04-2001,-500\n10-04-2001,-500\n11-04-2001,-500\n12-04-2001,-500\n16-04-2001,-500\n17-04-2001,-500\n18-04-2001,-500\n19-04-2001,-500\n20-04-2001,-500\n23-04-2001,-500\n24-04-2001,-500\n25-04-2001,-500\n26-04-2001,-500\n27-04-2001,-500\n30-04-2001,-500\n01-05-2001,-500\n02-05-2001,-500\n03-05-2001,-500\n04-05-2001,-500\n07-05-2001,-500\n08-05-2001,-500\n09-05-2001,-500\n10-05-2001,-500\n11-05-2001,-500\n14-05-2001,-500\n15-05-2001,-500\n16-05-2001,-500\n17-05-2001,-500\n18-05-2001,-500\n22-05-2001,-500\n23-05-2001,-500\n24-05-2001,-500\n25-05-2001,-500\n28-05-2001,-500\n29-05-2001,-500\n30-05-2001,-500\n31-05-2001,-500\n01-06-2001,-500\n04-06-2001,-500\n05-06-2001,-500\n06-06-2001,-500\n07-06-2001,-500\n08-06-2001,-500\n11-06-2001,-500\n12-06-2001,-500\n13-06-2001,-500\n14-06-2001,-500\n15-06-2001,-500\n18-06-2001,-500\n19-06-2001,-500\n20-06-2001,-500\n21-06-2001,-500\n22-06-2001,-500\n25-06-2001,-500\n26-06-2001,-500\n27-06-2001,-500\n28-06-2001,-500\n29-06-2001,-500\n03-07-2001,-500\n04-07-2001,-500\n05-07-2001,-500\n06-07-2001,-500\n09-07-2001,-500\n10-07-2001,-500\n11-07-2001,-500\n12-07-2001,-500\n13-07-2001,-500\n16-07-2001,-500\n17-07-2001,-500\n18-07-2001,-500\n19-07-2001,-500\n20-07-2001,-500\n23-07-2001,-500\n24-07-2001,-500\n25-07-2001,-500\n26-07-2001,-500\n27-07-2001,-500\n30-07-2001,-500\n31-07-2001,-500\n01-08-2001,-500\n02-08-2001,-500\n03-08-2001,-500\n07-08-2001,-500\n08-08-2001,-500\n09-08-2001,-500\n10-08-2001,-500\n13-08-2001,-500\n14-08-2001,-500\n15-08-2001,-500\n16-08-2001,-500\n17-08-2001,-500\n20-08-2001,-500\n21-08-2001,-500\n22-08-2001,-500\n23-08-2001,-500\n24-08-2001,-500\n27-08-2001,-500\n28-08-2001,-500\n29-08-2001,-500\n30-08-2001,-500\n31-08-2001,-500\n04-09-2001,-500\n05-09-2001,-500\n06-09-2001,-500\n07-09-2001,-500\n10-09-2001,-500\n11-09-2001,-500\n12-09-2001,-500\n13-09-2001,-500\n14-09-2001,-500\n17-09-2001,-500\n18-09-2001,-500\n19-09-2001,-500\n20-09-2001,-500\n21-09-2001,-500\n24-09-2001,-500\n25-09-2001,-500\n26-09-2001,-500\n27-09-2001,-500\n28-09-2001,-500\n01-10-2001,-500\n02-10-2001,-500\n03-10-2001,-500\n04-10-2001,-500\n05-10-2001,-500\n09-10-2001,-500\n10-10-2001,-500\n11-10-2001,-500\n12-10-2001,-500\n15-10-2001,-500\n16-10-2001,-500\n17-10-2001,-500\n18-10-2001,-500\n19-10-2001,-500\n22-10-2001,-500\n23-10-2001,-500\n24-10-2001,-500\n25-10-2001,-500\n26-10-2001,-500\n29-10-2001,-500\n30-10-2001,-500\n31-10-2001,-500\n01-11-2001,-500\n02-11-2001,-500\n05-11-2001,-500\n06-11-2001,-500\n07-11-2001,-500\n08-11-2001,-500\n09-11-2001,-500\n13-11-2001,-500\n14-11-2001,-500\n15-11-2001,-500\n16-11-2001,-500\n19-11-2001,-500\n20-11-2001,-500\n21-11-2001,-500\n22-11-2001,-500\n23-11-2001,-500\n26-11-2001,-500\n27-11-2001,-500\n28-11-2001,-500\n29-11-2001,-500\n30-11-2001,-500\n03-12-2001,-500\n04-12-2001,-500\n05-12-2001,-500\n06-12-2001,-500\n07-12-2001,-500\n10-12-2001,-500\n11-12-2001,-500\n12-12-2001,-500\n13-12-2001,-500\n14-12-2001,-500\n17-12-2001,-500\n18-12-2001,-500\n19-12-2001,-500\n20-12-2001,-500\n21-12-2001,-500\n24-12-2001,-500\n27-12-2001,-500\n28-12-2001,-500\n31-12-2001,-500\n02-01-2002,-500\n03-01-2002,-500\n04-01-2002,-500\n07-01-2002,-500\n08-01-2002,-500\n09-01-2002,-500\n10-01-2002,-500\n11-01-2002,-500\n14-01-2002,-500\n15-01-2002,-500\n16-01-2002,-500\n17-01-2002,-500\n18-01-2002,-500\n21-01-2002,-500\n22-01-2002,-500\n23-01-2002,-500\n24-01-2002,-500\n25-01-2002,-500\n28-01-2002,-500\n29-01-2002,-500\n30-01-2002,-500\n31-01-2002,-500\n01-02-2002,-500\n04-02-2002,-500\n05-02-2002,-500\n06-02-2002,-500\n07-02-2002,-500\n08-02-2002,-500\n11-02-2002,-500\n12-02-2002,-500\n13-02-2002,-500\n14-02-2002,-500\n15-02-2002,-500\n18-02-2002,-500\n19-02-2002,-500\n20-02-2002,-500\n21-02-2002,-500\n22-02-2002,-500\n25-02-2002,-500\n26-02-2002,-500\n27-02-2002,-500\n28-02-2002,-500\n01-03-2002,-500\n04-03-2002,-500\n05-03-2002,-500\n06-03-2002,-500\n07-03-2002,-500\n08-03-2002,-500\n11-03-2002,-500\n12-03-2002,-500\n13-03-2002,-500\n14-03-2002,-500\n15-03-2002,-500\n18-03-2002,-500\n19-03-2002,-500\n20-03-2002,-500\n21-03-2002,-500\n22-03-2002,-500\n25-03-2002,-500\n26-03-2002,-500\n27-03-2002,-500\n28-03-2002,-500\n01-04-2002,-500\n02-04-2002,-500\n03-04-2002,-500\n04-04-2002,-500\n05-04-2002,-500\n08-04-2002,-500\n09-04-2002,-500\n10-04-2002,-500\n11-04-2002,-500\n12-04-2002,-500\n15-04-2002,-500\n16-04-2002,-500\n17-04-2002,-500\n18-04-2002,-500\n19-04-2002,-500\n22-04-2002,-500\n23-04-2002,-500\n24-04-2002,-500\n25-04-2002,-500\n26-04-2002,-500\n29-04-2002,-500\n30-04-2002,-500\n01-05-2002,-500\n02-05-2002,-500\n03-05-2002,-500\n06-05-2002,-500\n07-05-2002,-500\n08-05-2002,-500\n09-05-2002,-500\n10-05-2002,-500\n13-05-2002,-500\n14-05-2002,-500\n15-05-2002,-500\n16-05-2002,-500\n17-05-2002,-500\n21-05-2002,-500\n22-05-2002,-500\n23-05-2002,-500\n24-05-2002,-500\n27-05-2002,-500\n28-05-2002,-500\n29-05-2002,-500\n30-05-2002,-500\n31-05-2002,-500\n03-06-2002,-500\n04-06-2002,-500\n05-06-2002,-500\n06-06-2002,-500\n07-06-2002,-500\n10-06-2002,-500\n11-06-2002,-500\n12-06-2002,-500\n13-06-2002,-500\n14-06-2002,-500\n17-06-2002,-500\n18-06-2002,-500\n19-06-2002,-500\n20-06-2002,-500\n21-06-2002,-500\n24-06-2002,-500\n25-06-2002,-500\n26-06-2002,-500\n27-06-2002,-500\n28-06-2002,-500\n02-07-2002,-500\n03-07-2002,-500\n04-07-2002,-500\n05-07-2002,-500\n08-07-2002,-500\n09-07-2002,-500\n10-07-2002,-500\n11-07-2002,-500\n12-07-2002,-500\n15-07-2002,-500\n16-07-2002,-500\n17-07-2002,-500\n18-07-2002,-500\n19-07-2002,-500\n22-07-2002,-500\n23-07-2002,-500\n24-07-2002,-500\n25-07-2002,-500\n26-07-2002,-500\n29-07-2002,-500\n30-07-2002,-500\n31-07-2002,-500\n01-08-2002,-500\n02-08-2002,-500\n06-08-2002,-500\n07-08-2002,-500\n08-08-2002,-500\n09-08-2002,-500\n12-08-2002,-500\n13-08-2002,-500\n14-08-2002,-500\n15-08-2002,-500\n16-08-2002,-500\n19-08-2002,-500\n20-08-2002,-500\n21-08-2002,-500\n22-08-2002,-500\n23-08-2002,-500\n26-08-2002,-500\n27-08-2002,-500\n28-08-2002,-500\n29-08-2002,-500\n30-08-2002,-500\n03-09-2002,-500\n04-09-2002,-500\n05-09-2002,-500\n06-09-2002,-500\n09-09-2002,-500\n10-09-2002,-500\n11-09-2002,-500\n12-09-2002,-500\n13-09-2002,-500\n16-09-2002,-500\n17-09-2002,-500\n18-09-2002,-500\n19-09-2002,-500\n20-09-2002,-500\n23-09-2002,-500\n24-09-2002,-500\n25-09-2002,-500\n26-09-2002,-500\n27-09-2002,-500\n30-09-2002,-500\n01-10-2002,-500\n02-10-2002,-500\n03-10-2002,-500\n04-10-2002,-500\n07-10-2002,-500\n08-10-2002,-500\n09-10-2002,-500\n10-10-2002,-500\n11-10-2002,-500\n15-10-2002,-500\n16-10-2002,-500\n17-10-2002,-500\n18-10-2002,-500\n21-10-2002,-500\n22-10-2002,-500\n23-10-2002,-500\n24-10-2002,-500\n25-10-2002,-500\n28-10-2002,-500\n29-10-2002,-500\n30-10-2002,-500\n31-10-2002,-500\n01-11-2002,-500\n04-11-2002,-500\n05-11-2002,-500\n06-11-2002,-500\n07-11-2002,-500\n08-11-2002,-500\n12-11-2002,-500\n13-11-2002,-500\n14-11-2002,-500\n15-11-2002,-500\n18-11-2002,-500\n19-11-2002,-500\n20-11-2002,-500\n21-11-2002,-500\n22-11-2002,-500\n25-11-2002,-500\n26-11-2002,-500\n27-11-2002,-500\n28-11-2002,-500\n29-11-2002,-500\n02-12-2002,-500\n03-12-2002,-500\n04-12-2002,-500\n05-12-2002,-500\n06-12-2002,-500\n09-12-2002,-500\n10-12-2002,-500\n11-12-2002,-500\n12-12-2002,-500\n13-12-2002,-500\n16-12-2002,-500\n17-12-2002,-500\n18-12-2002,-500\n19-12-2002,-500\n20-12-2002,-500\n23-12-2002,-500\n24-12-2002,-500\n27-12-2002,-500\n30-12-2002,-500\n31-12-2002,-500\n02-01-2003,-500\n03-01-2003,-500\n06-01-2003,-500\n07-01-2003,-500\n08-01-2003,-500\n09-01-2003,-500\n10-01-2003,-500\n13-01-2003,-500\n14-01-2003,-500\n15-01-2003,-500\n16-01-2003,-500\n17-01-2003,-500\n20-01-2003,-500\n21-01-2003,-500\n22-01-2003,-500\n23-01-2003,-500\n24-01-2003,-500\n27-01-2003,-500\n28-01-2003,-500\n29-01-2003,-500\n30-01-2003,-500\n31-01-2003,-500\n03-02-2003,-500\n04-02-2003,-500\n05-02-2003,-500\n06-02-2003,-500\n07-02-2003,-500\n10-02-2003,-500\n11-02-2003,-500\n12-02-2003,-500\n13-02-2003,-500\n14-02-2003,-500\n17-02-2003,-500\n18-02-2003,-500\n19-02-2003,-500\n20-02-2003,-500\n21-02-2003,-500\n24-02-2003,-500\n25-02-2003,-500\n26-02-2003,-500\n27-02-2003,-500\n28-02-2003,-500\n03-03-2003,-500\n04-03-2003,-500\n05-03-2003,-500\n06-03-2003,-500\n07-03-2003,-500\n10-03-2003,-500\n11-03-2003,-500\n12-03-2003,-500\n13-03-2003,-500\n14-03-2003,-500\n17-03-2003,-500\n18-03-2003,-500\n19-03-2003,-500\n20-03-2003,-500\n21-03-2003,-500\n24-03-2003,-500\n25-03-2003,-500\n26-03-2003,-500\n27-03-2003,-500\n28-03-2003,-500\n31-03-2003,-500\n01-04-2003,-500\n02-04-2003,-500\n03-04-2003,-500\n04-04-2003,-500\n07-04-2003,-500\n08-04-2003,-500\n09-04-2003,-500\n10-04-2003,-500\n11-04-2003,-500\n14-04-2003,-500\n15-04-2003,-500\n16-04-2003,-500\n17-04-2003,-500\n21-04-2003,-500\n22-04-2003,-500\n23-04-2003,-500\n24-04-2003,-500\n25-04-2003,-500\n28-04-2003,-500\n29-04-2003,-500\n30-04-2003,-500\n01-05-2003,-500\n02-05-2003,-500\n05-05-2003,-500\n06-05-2003,-500\n07-05-2003,-500\n08-05-2003,-500\n09-05-2003,-500\n12-05-2003,-500\n13-05-2003,-500\n14-05-2003,-500\n15-05-2003,-500\n16-05-2003,-500\n20-05-2003,-500\n21-05-2003,-500\n22-05-2003,-500\n23-05-2003,-500\n26-05-2003,-500\n27-05-2003,-500\n28-05-2003,-500\n29-05-2003,-500\n30-05-2003,-500\n02-06-2003,-500\n03-06-2003,-500\n04-06-2003,-500\n05-06-2003,-500\n06-06-2003,-500\n09-06-2003,-500\n10-06-2003,-500\n11-06-2003,-500\n12-06-2003,-500\n13-06-2003,-500\n16-06-2003,-500\n17-06-2003,-500\n18-06-2003,-500\n19-06-2003,-500\n20-06-2003,-500\n23-06-2003,-500\n24-06-2003,-500\n25-06-2003,-500\n26-06-2003,-500\n27-06-2003,-500\n30-06-2003,-500\n02-07-2003,-500\n03-07-2003,-500\n04-07-2003,-500\n07-07-2003,-500\n08-07-2003,-500\n09-07-2003,-500\n10-07-2003,-500\n11-07-2003,-500\n14-07-2003,-500\n15-07-2003,-500\n16-07-2003,-500\n17-07-2003,-500\n18-07-2003,-500\n21-07-2003,-500\n22-07-2003,-500\n23-07-2003,-500\n24-07-2003,-500\n25-07-2003,-500\n28-07-2003,-500\n29-07-2003,-500\n30-07-2003,-500\n31-07-2003,-500\n01-08-2003,-500\n05-08-2003,-500\n06-08-2003,-500\n07-08-2003,-500\n08-08-2003,-500\n11-08-2003,-500\n12-08-2003,-500\n13-08-2003,-500\n14-08-2003,-500\n15-08-2003,-500\n18-08-2003,-500\n19-08-2003,-500\n20-08-2003,-500\n21-08-2003,-500\n22-08-2003,-500\n25-08-2003,-500\n26-08-2003,-500\n27-08-2003,-500\n28-08-2003,-500\n29-08-2003,-500\n02-09-2003,-500\n03-09-2003,-500\n04-09-2003,-500\n05-09-2003,-500\n08-09-2003,-500\n09-09-2003,-500\n10-09-2003,-500\n11-09-2003,-500\n12-09-2003,-500\n15-09-2003,-500\n16-09-2003,-500\n17-09-2003,-500\n18-09-2003,-500\n19-09-2003,-500\n22-09-2003,-500\n23-09-2003,-500\n24-09-2003,-500\n25-09-2003,-500\n26-09-2003,-500\n29-09-2003,-500\n30-09-2003,-500\n01-10-2003,-500\n02-10-2003,-500\n03-10-2003,-500\n06-10-2003,-500\n07-10-2003,-500\n08-10-2003,-500\n09-10-2003,-500\n10-10-2003,-500\n14-10-2003,-500\n15-10-2003,-500\n16-10-2003,-500\n17-10-2003,-500\n20-10-2003,-500\n21-10-2003,-500\n22-10-2003,-500\n23-10-2003,-500\n24-10-2003,-500\n27-10-2003,-500\n28-10-2003,-500\n29-10-2003,-500\n30-10-2003,-500\n31-10-2003,-500\n03-11-2003,-500\n04-11-2003,-500\n05-11-2003,-500\n06-11-2003,-500\n07-11-2003,-500\n10-11-2003,-500\n12-11-2003,-500\n13-11-2003,-500\n14-11-2003,-500\n17-11-2003,-500\n18-11-2003,-500\n19-11-2003,-500\n20-11-2003,-500\n21-11-2003,-500\n24-11-2003,-500\n25-11-2003,-500\n26-11-2003,-500\n27-11-2003,-500\n28-11-2003,-500\n01-12-2003,-500\n02-12-2003,-500\n03-12-2003,-500\n04-12-2003,-500\n05-12-2003,-500\n08-12-2003,-500\n09-12-2003,-500\n10-12-2003,-500\n11-12-2003,-500\n12-12-2003,-500\n15-12-2003,-500\n16-12-2003,-500\n17-12-2003,-500\n18-12-2003,-500\n19-12-2003,-500\n22-12-2003,-500\n23-12-2003,-500\n24-12-2003,-500\n29-12-2003,-500\n30-12-2003,-500\n31-12-2003,-500\n02-01-2004,-500\n05-01-2004,-500\n06-01-2004,-500\n07-01-2004,-500\n08-01-2004,-500\n09-01-2004,-500\n12-01-2004,-500\n13-01-2004,-500\n14-01-2004,-500\n15-01-2004,-500\n16-01-2004,-500\n19-01-2004,-500\n20-01-2004,-500\n21-01-2004,-500\n22-01-2004,-500\n23-01-2004,-500\n26-01-2004,-500\n27-01-2004,-500\n28-01-2004,-500\n29-01-2004,-500\n30-01-2004,-500\n02-02-2004,-500\n03-02-2004,-500\n04-02-2004,-500\n05-02-2004,-500\n06-02-2004,-500\n09-02-2004,-500\n10-02-2004,-500\n11-02-2004,-500\n12-02-2004,-500\n13-02-2004,-500\n16-02-2004,-500\n17-02-2004,-500\n18-02-2004,-500\n19-02-2004,-500\n20-02-2004,-500\n23-02-2004,-500\n24-02-2004,-500\n25-02-2004,-500\n26-02-2004,-500\n27-02-2004,-500\n01-03-2004,-500\n02-03-2004,-500\n03-03-2004,-500\n04-03-2004,-500\n05-03-2004,-500\n08-03-2004,-500\n09-03-2004,-500\n10-03-2004,-500\n11-03-2004,-500\n12-03-2004,-500\n15-03-2004,-500\n16-03-2004,-500\n17-03-2004,-500\n18-03-2004,-500\n19-03-2004,-500\n22-03-2004,-500\n23-03-2004,-500\n24-03-2004,-500\n25-03-2004,-500\n26-03-2004,-500\n29-03-2004,-500\n30-03-2004,-500\n31-03-2004,-500\n01-04-2004,-500\n02-04-2004,-500\n05-04-2004,-500\n06-04-2004,-500\n07-04-2004,-500\n08-04-2004,-500\n12-04-2004,-500\n13-04-2004,-500\n14-04-2004,-500\n15-04-2004,-500\n16-04-2004,-500\n19-04-2004,-500\n20-04-2004,-500\n21-04-2004,-500\n22-04-2004,-500\n23-04-2004,-500\n26-04-2004,-500\n27-04-2004,-500\n28-04-2004,-500\n29-04-2004,-500\n30-04-2004,-500\n03-05-2004,-500\n04-05-2004,-500\n05-05-2004,-500\n06-05-2004,-500\n07-05-2004,-500\n10-05-2004,-500\n11-05-2004,-500\n12-05-2004,-500\n13-05-2004,-500\n14-05-2004,-500\n17-05-2004,-500\n18-05-2004,-500\n19-05-2004,-500\n20-05-2004,-500\n21-05-2004,-500\n25-05-2004,-500\n26-05-2004,-500\n27-05-2004,-500\n28-05-2004,-500\n31-05-2004,-500\n01-06-2004,-500\n02-06-2004,-500\n03-06-2004,-500\n04-06-2004,-500\n07-06-2004,-500\n08-06-2004,-500\n09-06-2004,-500\n10-06-2004,-500\n11-06-2004,-500\n14-06-2004,-500\n15-06-2004,-500\n16-06-2004,-500\n17-06-2004,-500\n18-06-2004,-500\n21-06-2004,-500\n22-06-2004,-500\n23-06-2004,-500\n24-06-2004,-500\n25-06-2004,-500\n28-06-2004,-500\n29-06-2004,-500\n30-06-2004,-500\n02-07-2004,-500\n05-07-2004,-500\n06-07-2004,-500\n07-07-2004,-500\n08-07-2004,-500\n09-07-2004,-500\n12-07-2004,-500\n13-07-2004,-500\n14-07-2004,-500\n15-07-2004,-500\n16-07-2004,-500\n19-07-2004,-500\n20-07-2004,-500\n21-07-2004,-500\n22-07-2004,-500\n23-07-2004,-500\n26-07-2004,-500\n27-07-2004,-500\n28-07-2004,-500\n29-07-2004,-500\n30-07-2004,-500\n03-08-2004,-500\n04-08-2004,-500\n05-08-2004,-500\n06-08-2004,-500\n09-08-2004,-500\n10-08-2004,-500\n11-08-2004,-500\n12-08-2004,-500\n13-08-2004,-500\n16-08-2004,-500\n17-08-2004,-500\n18-08-2004,-500\n19-08-2004,-500\n20-08-2004,-500\n23-08-2004,-500\n24-08-2004,-500\n25-08-2004,-500\n26-08-2004,-500\n27-08-2004,-500\n30-08-2004,-500\n31-08-2004,-500\n01-09-2004,-500\n02-09-2004,-500\n03-09-2004,-500\n07-09-2004,-500\n08-09-2004,-500\n09-09-2004,-500\n10-09-2004,-500\n13-09-2004,-500\n14-09-2004,-500\n15-09-2004,-500\n16-09-2004,-500\n17-09-2004,-500\n20-09-2004,-500\n21-09-2004,-500\n22-09-2004,-500\n23-09-2004,-500\n24-09-2004,-500\n27-09-2004,-500\n28-09-2004,-500\n29-09-2004,-500\n30-09-2004,-500\n01-10-2004,-500\n04-10-2004,-500\n05-10-2004,-500\n06-10-2004,-500\n07-10-2004,-500\n08-10-2004,-500\n12-10-2004,-500\n13-10-2004,-500\n14-10-2004,-500\n15-10-2004,-500\n18-10-2004,-500\n19-10-2004,-500\n20-10-2004,-500\n21-10-2004,-500\n22-10-2004,-500\n25-10-2004,-500\n26-10-2004,-500\n27-10-2004,-500\n28-10-2004,-500\n29-10-2004,-500\n01-11-2004,-500\n02-11-2004,-500\n03-11-2004,-500\n04-11-2004,-500\n05-11-2004,-500\n08-11-2004,-500\n09-11-2004,-500\n10-11-2004,-500\n12-11-2004,-500\n15-11-2004,-500\n16-11-2004,-500\n17-11-2004,-500\n18-11-2004,-500\n19-11-2004,-500\n22-11-2004,-500\n23-11-2004,-500\n24-11-2004,-500\n25-11-2004,-500\n26-11-2004,-500\n29-11-2004,-500\n30-11-2004,-500\n01-12-2004,-500\n02-12-2004,-500\n03-12-2004,-500\n06-12-2004,-500\n07-12-2004,-500\n08-12-2004,-500\n09-12-2004,-500\n10-12-2004,-500\n13-12-2004,-500\n14-12-2004,-500\n15-12-2004,-500\n16-12-2004,-500\n17-12-2004,-500\n20-12-2004,-500\n21-12-2004,-500\n22-12-2004,-500\n23-12-2004,-500\n24-12-2004,-500\n29-12-2004,-500\n30-12-2004,-500\n31-12-2004,-500\n04-01-2005,-500\n05-01-2005,-500\n06-01-2005,-500\n07-01-2005,-500\n10-01-2005,-500\n11-01-2005,-500\n12-01-2005,-500\n13-01-2005,-500\n14-01-2005,-500\n17-01-2005,-500\n18-01-2005,-500\n19-01-2005,-500\n20-01-2005,-500\n21-01-2005,-500\n24-01-2005,-500\n25-01-2005,-500\n26-01-2005,-500\n27-01-2005,-500\n28-01-2005,-500\n31-01-2005,-500\n01-02-2005,-500\n02-02-2005,-500\n03-02-2005,-500\n04-02-2005,-500\n07-02-2005,-500\n08-02-2005,-500\n09-02-2005,-500\n10-02-2005,-500\n11-02-2005,-500\n14-02-2005,-500\n15-02-2005,-500\n16-02-2005,-500\n17-02-2005,-500\n18-02-2005,-500\n21-02-2005,-500\n22-02-2005,-500\n23-02-2005,-500\n24-02-2005,-500\n25-02-2005,-500\n28-02-2005,-500\n01-03-2005,-500\n02-03-2005,-500\n03-03-2005,-500\n04-03-2005,-500\n07-03-2005,-500\n08-03-2005,-500\n09-03-2005,-500\n10-03-2005,-500\n11-03-2005,-500\n14-03-2005,-500\n15-03-2005,-500\n16-03-2005,-500\n17-03-2005,-500\n18-03-2005,-500\n21-03-2005,-500\n22-03-2005,-500\n23-03-2005,-500\n24-03-2005,-500\n28-03-2005,-500\n29-03-2005,-500\n30-03-2005,-500\n31-03-2005,-500\n01-04-2005,-500\n04-04-2005,-500\n05-04-2005,-500\n06-04-2005,-500\n07-04-2005,-500\n08-04-2005,-500\n11-04-2005,-500\n12-04-2005,-500\n13-04-2005,-500\n14-04-2005,-500\n15-04-2005,-500\n18-04-2005,-500\n19-04-2005,-500\n20-04-2005,-500\n21-04-2005,-500\n22-04-2005,-500\n25-04-2005,-500\n26-04-2005,-500\n27-04-2005,-500\n28-04-2005,-500\n29-04-2005,-500\n02-05-2005,-500\n03-05-2005,-500\n04-05-2005,-500\n05-05-2005,-500\n06-05-2005,-500\n09-05-2005,-500\n10-05-2005,-500\n11-05-2005,-500\n12-05-2005,-500\n13-05-2005,-500\n16-05-2005,-500\n17-05-2005,-500\n18-05-2005,-500\n19-05-2005,-500\n20-05-2005,-500\n24-05-2005,-500\n25-05-2005,-500\n26-05-2005,-500\n27-05-2005,-500\n30-05-2005,-500\n31-05-2005,-500\n01-06-2005,-500\n02-06-2005,-500\n03-06-2005,-500\n06-06-2005,-500\n07-06-2005,-500\n08-06-2005,-500\n09-06-2005,-500\n10-06-2005,-500\n13-06-2005,-500\n14-06-2005,-500\n15-06-2005,-500\n16-06-2005,-500\n17-06-2005,-500\n20-06-2005,-500\n21-06-2005,-500\n22-06-2005,-500\n23-06-2005,-500\n24-06-2005,-500\n27-06-2005,-500\n28-06-2005,-500\n29-06-2005,-500\n30-06-2005,-500\n04-07-2005,-500\n05-07-2005,-500\n06-07-2005,-500\n07-07-2005,-500\n08-07-2005,-500\n11-07-2005,-500\n12-07-2005,-500\n13-07-2005,-500\n14-07-2005,-500\n15-07-2005,-500\n18-07-2005,-500\n19-07-2005,-500\n20-07-2005,-500\n21-07-2005,-500\n22-07-2005,-500\n25-07-2005,-500\n26-07-2005,-500\n27-07-2005,-500\n28-07-2005,-500\n29-07-2005,-500\n02-08-2005,-500\n03-08-2005,-500\n04-08-2005,-500\n05-08-2005,-500\n08-08-2005,-500\n09-08-2005,-500\n10-08-2005,-500\n11-08-2005,-500\n12-08-2005,-500\n15-08-2005,-500\n16-08-2005,-500\n17-08-2005,-500\n18-08-2005,-500\n19-08-2005,-500\n22-08-2005,-500\n23-08-2005,-500\n24-08-2005,-500\n25-08-2005,-500\n26-08-2005,-500\n29-08-2005,-500\n30-08-2005,-500\n31-08-2005,-500\n01-09-2005,-500\n02-09-2005,-500\n06-09-2005,-500\n07-09-2005,-500\n08-09-2005,-500\n09-09-2005,-500\n12-09-2005,-500\n13-09-2005,-500\n14-09-2005,-500\n15-09-2005,-500\n16-09-2005,-500\n19-09-2005,-500\n20-09-2005,-500\n21-09-2005,-500\n22-09-2005,-500\n23-09-2005,-500\n26-09-2005,-500\n27-09-2005,-500\n28-09-2005,-500\n29-09-2005,-500\n30-09-2005,-500\n03-10-2005,-500\n04-10-2005,-500\n05-10-2005,-500\n06-10-2005,-500\n07-10-2005,-500\n11-10-2005,-500\n12-10-2005,-500\n13-10-2005,-500\n14-10-2005,-500\n17-10-2005,-500\n18-10-2005,-500\n19-10-2005,-500\n20-10-2005,-500\n21-10-2005,-500\n24-10-2005,-500\n25-10-2005,-500\n26-10-2005,-500\n27-10-2005,-500\n28-10-2005,-500\n31-10-2005,-500\n01-11-2005,-500\n02-11-2005,-500\n03-11-2005,-500\n04-11-2005,-500\n07-11-2005,-500\n08-11-2005,-500\n09-11-2005,-500\n10-11-2005,-500\n14-11-2005,-500\n15-11-2005,-500\n16-11-2005,-500\n17-11-2005,-500\n18-11-2005,-500\n21-11-2005,-500\n22-11-2005,-500\n23-11-2005,-500\n24-11-2005,-500\n25-11-2005,-500\n28-11-2005,-500\n29-11-2005,-500\n30-11-2005,-500\n01-12-2005,-500\n02-12-2005,-500\n05-12-2005,-500\n06-12-2005,-500\n07-12-2005,-500\n08-12-2005,-500\n09-12-2005,-500\n12-12-2005,-500\n13-12-2005,-500\n14-12-2005,-500\n15-12-2005,-500\n16-12-2005,-500\n19-12-2005,-500\n20-12-2005,-500\n21-12-2005,-500\n22-12-2005,-500\n23-12-2005,-500\n28-12-2005,-500\n29-12-2005,-500\n30-12-2005,-500\n03-01-2006,-500\n04-01-2006,-500\n05-01-2006,-500\n06-01-2006,-500\n09-01-2006,-500\n10-01-2006,-500\n11-01-2006,-500\n12-01-2006,-500\n13-01-2006,-500\n16-01-2006,-500\n17-01-2006,-500\n18-01-2006,-500\n19-01-2006,-500\n20-01-2006,-500\n23-01-2006,-500\n24-01-2006,-500\n25-01-2006,-500\n26-01-2006,-500\n27-01-2006,-500\n30-01-2006,-500\n31-01-2006,-500\n01-02-2006,-500\n02-02-2006,-500\n03-02-2006,-500\n06-02-2006,-500\n07-02-2006,-500\n08-02-2006,-500\n09-02-2006,-500\n10-02-2006,-500\n13-02-2006,-500\n14-02-2006,-500\n15-02-2006,-500\n16-02-2006,-500\n17-02-2006,-500\n20-02-2006,-500\n21-02-2006,-500\n22-02-2006,-500\n23-02-2006,-500\n24-02-2006,-500\n27-02-2006,-500\n28-02-2006,-500\n01-03-2006,-500\n02-03-2006,-500\n03-03-2006,-500\n06-03-2006,-500\n07-03-2006,-500\n08-03-2006,-500\n09-03-2006,-500\n10-03-2006,-500\n13-03-2006,-500\n14-03-2006,-500\n15-03-2006,-500\n16-03-2006,-500\n17-03-2006,-500\n20-03-2006,-500\n21-03-2006,-500\n22-03-2006,-500\n23-03-2006,-500\n24-03-2006,-500\n27-03-2006,-500\n28-03-2006,-500\n29-03-2006,-500\n30-03-2006,-500\n31-03-2006,-500\n03-04-2006,-500\n04-04-2006,-500\n05-04-2006,-500\n06-04-2006,-500\n07-04-2006,-500\n10-04-2006,-500\n11-04-2006,-500\n12-04-2006,-500\n13-04-2006,-500\n17-04-2006,-500\n18-04-2006,-500\n19-04-2006,-500\n20-04-2006,-500\n21-04-2006,-500\n24-04-2006,-500\n25-04-2006,-500\n26-04-2006,-500\n27-04-2006,-500\n28-04-2006,-500\n01-05-2006,-500\n02-05-2006,-500\n03-05-2006,-500\n04-05-2006,-500\n05-05-2006,-500\n08-05-2006,-500\n09-05-2006,-500\n10-05-2006,-500\n11-05-2006,-500\n12-05-2006,-500\n15-05-2006,-500\n16-05-2006,-500\n17-05-2006,-500\n18-05-2006,-500\n19-05-2006,-500\n23-05-2006,-500\n24-05-2006,-500\n25-05-2006,-500\n26-05-2006,-500\n29-05-2006,-500\n30-05-2006,-500\n31-05-2006,-500\n01-06-2006,-500\n02-06-2006,-500\n05-06-2006,-500\n06-06-2006,-500\n07-06-2006,-500\n08-06-2006,-500\n09-06-2006,-500\n12-06-2006,-500\n13-06-2006,-500\n14-06-2006,-500\n15-06-2006,-500\n16-06-2006,-500\n19-06-2006,-500\n20-06-2006,-500\n21-06-2006,-500\n22-06-2006,-500\n23-06-2006,-500\n26-06-2006,-500\n27-06-2006,-500\n28-06-2006,-500\n29-06-2006,-500\n30-06-2006,-500\n04-07-2006,-500\n05-07-2006,-500\n06-07-2006,-500\n07-07-2006,-500\n10-07-2006,-500\n11-07-2006,-500\n12-07-2006,-500\n13-07-2006,-500\n14-07-2006,-500\n17-07-2006,-500\n18-07-2006,-500\n19-07-2006,-500\n20-07-2006,-500\n21-07-2006,-500\n24-07-2006,-500\n25-07-2006,-500\n26-07-2006,-500\n27-07-2006,-500\n28-07-2006,-500\n31-07-2006,-500\n01-08-2006,-500\n02-08-2006,-500\n03-08-2006,-500\n04-08-2006,-500\n08-08-2006,-500\n09-08-2006,-500\n10-08-2006,-500\n11-08-2006,-500\n14-08-2006,-500\n15-08-2006,-500\n16-08-2006,-500\n17-08-2006,-500\n18-08-2006,-500\n21-08-2006,-500\n22-08-2006,-500\n23-08-2006,-500\n24-08-2006,-500\n25-08-2006,-500\n28-08-2006,-500\n29-08-2006,-500\n30-08-2006,-500\n31-08-2006,-500\n01-09-2006,-500\n05-09-2006,-500\n06-09-2006,-500\n07-09-2006,-500\n08-09-2006,-500\n11-09-2006,-500\n12-09-2006,-500\n13-09-2006,-500\n14-09-2006,-500\n15-09-2006,-500\n18-09-2006,-500\n19-09-2006,-500\n20-09-2006,-500\n21-09-2006,-500\n22-09-2006,-500\n25-09-2006,-500\n26-09-2006,-500\n27-09-2006,-500\n28-09-2006,-500\n29-09-2006,-500\n02-10-2006,-500\n03-10-2006,-500\n04-10-2006,-500\n05-10-2006,-500\n06-10-2006,-500\n10-10-2006,-500\n11-10-2006,-500\n12-10-2006,-500\n13-10-2006,-500\n16-10-2006,-500\n17-10-2006,-500\n18-10-2006,-500\n19-10-2006,-500\n20-10-2006,-500\n23-10-2006,-500\n24-10-2006,-500\n25-10-2006,-500\n26-10-2006,-500\n27-10-2006,-500\n30-10-2006,-500\n31-10-2006,-500\n01-11-2006,-500\n02-11-2006,-500\n03-11-2006,-500\n06-11-2006,-500\n07-11-2006,-500\n08-11-2006,-500\n09-11-2006,-500\n10-11-2006,-500\n14-11-2006,-500\n15-11-2006,-500\n16-11-2006,-500\n17-11-2006,-500\n20-11-2006,-500\n21-11-2006,-500\n22-11-2006,-500\n23-11-2006,-500\n24-11-2006,-500\n27-11-2006,-500\n28-11-2006,-500\n29-11-2006,-500\n30-11-2006,-500\n01-12-2006,-500\n04-12-2006,-500\n05-12-2006,-500\n06-12-2006,-500\n07-12-2006,-500\n08-12-2006,-500\n11-12-2006,-500\n12-12-2006,-500\n13-12-2006,-500\n14-12-2006,-500\n15-12-2006,-500\n18-12-2006,-500\n19-12-2006,-500\n20-12-2006,-500\n21-12-2006,-500\n22-12-2006,-500\n27-12-2006,-500\n28-12-2006,-500\n29-12-2006,-500\n02-01-2007,-500\n03-01-2007,-500\n04-01-2007,-500\n05-01-2007,-500\n08-01-2007,-500\n09-01-2007,-500\n10-01-2007,-500\n11-01-2007,-500\n12-01-2007,-500\n15-01-2007,-500\n16-01-2007,-500\n17-01-2007,-500\n18-01-2007,-500\n19-01-2007,-500\n22-01-2007,-500\n23-01-2007,-500\n24-01-2007,-500\n25-01-2007,-500\n26-01-2007,-500\n29-01-2007,-500\n30-01-2007,-500\n31-01-2007,-500\n01-02-2007,-500\n02-02-2007,-500\n05-02-2007,-500\n06-02-2007,-500\n07-02-2007,-500\n08-02-2007,-500\n09-02-2007,-500\n12-02-2007,-500\n13-02-2007,-500\n14-02-2007,-500\n15-02-2007,-500\n16-02-2007,-500\n19-02-2007,-500\n20-02-2007,-500\n21-02-2007,-500\n22-02-2007,-500\n23-02-2007,-500\n26-02-2007,-500\n27-02-2007,-500\n28-02-2007,-500\n01-03-2007,-500\n02-03-2007,-500\n05-03-2007,-500\n06-03-2007,-500\n07-03-2007,-500\n08-03-2007,-500\n09-03-2007,-500\n12-03-2007,-500\n13-03-2007,-500\n14-03-2007,-500\n15-03-2007,-500\n16-03-2007,-500\n19-03-2007,-500\n20-03-2007,-500\n21-03-2007,-500\n22-03-2007,-500\n23-03-2007,-500\n26-03-2007,-500\n27-03-2007,-500\n28-03-2007,-500\n29-03-2007,-500\n30-03-2007,-500\n02-04-2007,-500\n03-04-2007,-500\n04-04-2007,-500\n05-04-2007,-500\n09-04-2007,-500\n10-04-2007,-500\n11-04-2007,-500\n12-04-2007,-500\n13-04-2007,-500\n16-04-2007,-500\n17-04-2007,-500\n18-04-2007,-500\n19-04-2007,-500\n20-04-2007,-500\n23-04-2007,-500\n24-04-2007,-500\n25-04-2007,-500\n26-04-2007,-500\n27-04-2007,-500\n30-04-2007,-500\n01-05-2007,-500\n02-05-2007,-500\n03-05-2007,-500\n04-05-2007,-500\n07-05-2007,-500\n08-05-2007,-500\n09-05-2007,-500\n10-05-2007,-500\n11-05-2007,-500\n14-05-2007,-500\n15-05-2007,-500\n16-05-2007,-500\n17-05-2007,-500\n18-05-2007,-500\n22-05-2007,-500\n23-05-2007,-500\n24-05-2007,-500\n25-05-2007,-500\n28-05-2007,-500\n29-05-2007,-500\n30-05-2007,-500\n31-05-2007,-500\n01-06-2007,-500\n04-06-2007,-500\n05-06-2007,-500\n06-06-2007,-500\n07-06-2007,-500\n08-06-2007,-500\n11-06-2007,-500\n12-06-2007,-500\n13-06-2007,-500\n14-06-2007,-500\n15-06-2007,-500\n18-06-2007,-500\n19-06-2007,-500\n20-06-2007,-500\n21-06-2007,-500\n22-06-2007,-500\n25-06-2007,-500\n26-06-2007,-500\n27-06-2007,-500\n28-06-2007,-500\n29-06-2007,-500\n03-07-2007,-500\n04-07-2007,-500\n05-07-2007,-500\n06-07-2007,-500\n09-07-2007,-500\n10-07-2007,-500\n11-07-2007,-500\n12-07-2007,-500\n13-07-2007,-500\n16-07-2007,-500\n17-07-2007,-500\n18-07-2007,-500\n19-07-2007,-500\n20-07-2007,-500\n23-07-2007,-500\n24-07-2007,-500\n25-07-2007,-500\n26-07-2007,-500\n27-07-2007,-500\n30-07-2007,-500\n31-07-2007,-500\n01-08-2007,-500\n02-08-2007,-500\n03-08-2007,-500\n07-08-2007,-500\n08-08-2007,-500\n09-08-2007,-500\n10-08-2007,-500\n13-08-2007,-500\n14-08-2007,-500\n15-08-2007,-500\n16-08-2007,-500\n17-08-2007,-500\n20-08-2007,-500\n21-08-2007,-500\n22-08-2007,-500\n23-08-2007,-500\n24-08-2007,-500\n27-08-2007,-500\n28-08-2007,-500\n29-08-2007,-500\n30-08-2007,-500\n31-08-2007,-500\n04-09-2007,-500\n05-09-2007,-500\n06-09-2007,-500\n07-09-2007,-500\n10-09-2007,-500\n11-09-2007,-500\n12-09-2007,-500\n13-09-2007,-500\n14-09-2007,-500\n17-09-2007,-500\n18-09-2007,-500\n19-09-2007,-500\n20-09-2007,-500\n21-09-2007,-500\n24-09-2007,-500\n25-09-2007,-500\n26-09-2007,-500\n27-09-2007,-500\n28-09-2007,-500\n01-10-2007,-500\n02-10-2007,-500\n03-10-2007,-500\n04-10-2007,-500\n05-10-2007,-500\n09-10-2007,-500\n10-10-2007,-500\n11-10-2007,-500\n12-10-2007,-500\n15-10-2007,-500\n16-10-2007,-500\n17-10-2007,-500\n18-10-2007,-500\n19-10-2007,-500\n22-10-2007,-500\n23-10-2007,-500\n24-10-2007,-500\n25-10-2007,-500\n26-10-2007,-500\n29-10-2007,-500\n30-10-2007,-500\n31-10-2007,-500\n01-11-2007,-500\n02-11-2007,-500\n05-11-2007,-500\n06-11-2007,-500\n07-11-2007,-500\n08-11-2007,-500\n09-11-2007,-500\n13-11-2007,-500\n14-11-2007,-500\n15-11-2007,-500\n16-11-2007,-500\n19-11-2007,-500\n20-11-2007,-500\n21-11-2007,-500\n22-11-2007,-500\n23-11-2007,-500\n26-11-2007,-500\n27-11-2007,-500\n28-11-2007,-500\n29-11-2007,-500\n30-11-2007,-500\n03-12-2007,-500\n04-12-2007,-500\n05-12-2007,-500\n06-12-2007,-500\n07-12-2007,-500\n10-12-2007,-500\n11-12-2007,-500\n12-12-2007,-500\n13-12-2007,-500\n14-12-2007,-500\n17-12-2007,-500\n18-12-2007,-500\n19-12-2007,-500\n20-12-2007,-500\n21-12-2007,-500\n24-12-2007,-500\n27-12-2007,-500\n28-12-2007,-500\n31-12-2007,-500\n02-01-2008,-500\n03-01-2008,-500\n04-01-2008,-500\n07-01-2008,-500\n08-01-2008,-500\n09-01-2008,-500\n10-01-2008,-500\n11-01-2008,-500\n14-01-2008,-500\n15-01-2008,-500\n16-01-2008,-500\n17-01-2008,-500\n18-01-2008,-500\n21-01-2008,-500\n22-01-2008,-500\n23-01-2008,-500\n24-01-2008,-500\n25-01-2008,-500\n28-01-2008,-500\n29-01-2008,-500\n30-01-2008,-500\n31-01-2008,-500\n01-02-2008,-500\n04-02-2008,-500\n05-02-2008,-500\n06-02-2008,-500\n07-02-2008,-500\n08-02-2008,-500\n11-02-2008,-500\n12-02-2008,-500\n13-02-2008,-500\n14-02-2008,-500\n15-02-2008,-500\n19-02-2008,-500\n20-02-2008,-500\n21-02-2008,-500\n22-02-2008,-500\n25-02-2008,-500\n26-02-2008,-500\n27-02-2008,-500\n28-02-2008,-500\n29-02-2008,-500\n03-03-2008,-500\n04-03-2008,-500\n05-03-2008,-500\n06-03-2008,-500\n07-03-2008,-500\n10-03-2008,-500\n11-03-2008,-500\n12-03-2008,-500\n13-03-2008,-500\n14-03-2008,-500\n17-03-2008,-500\n18-03-2008,-500\n19-03-2008,-500\n20-03-2008,-500\n24-03-2008,-500\n25-03-2008,-500\n26-03-2008,-500\n27-03-2008,-500\n28-03-2008,-500\n31-03-2008,-500\n01-04-2008,-500\n02-04-2008,-500\n03-04-2008,-500\n04-04-2008,-500\n07-04-2008,-500\n08-04-2008,-500\n09-04-2008,-500\n10-04-2008,-500\n11-04-2008,-500\n14-04-2008,-500\n15-04-2008,-500\n16-04-2008,-500\n17-04-2008,-500\n18-04-2008,-500\n21-04-2008,-500\n22-04-2008,-500\n23-04-2008,-500\n24-04-2008,-500\n25-04-2008,-500\n28-04-2008,-500\n29-04-2008,-500\n30-04-2008,-500\n01-05-2008,-500\n02-05-2008,-500\n05-05-2008,-500\n06-05-2008,-500\n07-05-2008,-500\n08-05-2008,-500\n09-05-2008,-500\n12-05-2008,-500\n13-05-2008,-500\n14-05-2008,-500\n15-05-2008,-500\n16-05-2008,-500\n20-05-2008,-500\n21-05-2008,-500\n22-05-2008,-500\n23-05-2008,-500\n26-05-2008,-500\n27-05-2008,-500\n28-05-2008,-500\n29-05-2008,-500\n30-05-2008,-500\n02-06-2008,-500\n03-06-2008,-500\n04-06-2008,-500\n05-06-2008,-500\n06-06-2008,-500\n09-06-2008,-500\n10-06-2008,-500\n11-06-2008,-500\n12-06-2008,-500\n13-06-2008,-500\n16-06-2008,-500\n17-06-2008,-500\n18-06-2008,-500\n19-06-2008,-500\n20-06-2008,-500\n23-06-2008,-500\n24-06-2008,-500\n25-06-2008,-500\n26-06-2008,-500\n27-06-2008,-500\n30-06-2008,-500\n02-07-2008,-500\n03-07-2008,-500\n04-07-2008,-500\n07-07-2008,-500\n08-07-2008,-500\n09-07-2008,-500\n10-07-2008,-500\n11-07-2008,-500\n14-07-2008,-500\n15-07-2008,-500\n16-07-2008,-500\n17-07-2008,-500\n18-07-2008,-500\n21-07-2008,-500\n22-07-2008,-500\n23-07-2008,-500\n24-07-2008,-500\n25-07-2008,-500\n28-07-2008,-500\n29-07-2008,-500\n30-07-2008,-500\n31-07-2008,-500\n01-08-2008,-500\n05-08-2008,-500\n06-08-2008,-500\n07-08-2008,-500\n08-08-2008,-500\n11-08-2008,-500\n12-08-2008,-500\n13-08-2008,-500\n14-08-2008,-500\n15-08-2008,-500\n18-08-2008,-500\n19-08-2008,-500\n20-08-2008,-500\n21-08-2008,-500\n22-08-2008,-500\n25-08-2008,-500\n26-08-2008,-500\n27-08-2008,-500\n28-08-2008,-500\n29-08-2008,-500\n02-09-2008,-500\n03-09-2008,-500\n04-09-2008,-500\n05-09-2008,-500\n08-09-2008,-500\n09-09-2008,-500\n10-09-2008,-500\n11-09-2008,-500\n12-09-2008,-500\n15-09-2008,-500\n16-09-2008,-500\n17-09-2008,-500\n18-09-2008,-500\n19-09-2008,-500\n22-09-2008,-500\n23-09-2008,-500\n24-09-2008,-500\n25-09-2008,-500\n26-09-2008,-500\n29-09-2008,-500\n30-09-2008,-500\n01-10-2008,-500\n02-10-2008,-500\n03-10-2008,-500\n06-10-2008,-500\n07-10-2008,-500\n08-10-2008,-500\n09-10-2008,-500\n10-10-2008,-500\n14-10-2008,-500\n15-10-2008,-500\n16-10-2008,-500\n17-10-2008,-500\n20-10-2008,-500\n21-10-2008,-500\n22-10-2008,-500\n23-10-2008,-500\n24-10-2008,-500\n27-10-2008,-500\n28-10-2008,-500\n29-10-2008,-500\n30-10-2008,-500\n31-10-2008,-500\n03-11-2008,-500\n04-11-2008,-500\n05-11-2008,-500\n06-11-2008,-500\n07-11-2008,-500\n10-11-2008,-500\n12-11-2008,-500\n13-11-2008,-500\n14-11-2008,-500\n17-11-2008,-500\n18-11-2008,-500\n19-11-2008,-500\n20-11-2008,-500\n21-11-2008,-500\n24-11-2008,-500\n25-11-2008,-500\n26-11-2008,-500\n27-11-2008,-500\n28-11-2008,-500\n01-12-2008,-500\n02-12-2008,-500\n03-12-2008,-500\n04-12-2008,-500\n05-12-2008,-500\n08-12-2008,-500\n09-12-2008,-500\n10-12-2008,-500\n11-12-2008,-500\n12-12-2008,-500\n15-12-2008,-500\n16-12-2008,-500\n17-12-2008,-500\n18-12-2008,-500\n19-12-2008,-500\n22-12-2008,-500\n23-12-2008,-500\n24-12-2008,-500\n29-12-2008,-500\n30-12-2008,-500\n31-12-2008,-500\n02-01-2009,-500\n05-01-2009,-500\n06-01-2009,-500\n07-01-2009,-500\n08-01-2009,-500\n09-01-2009,-500\n12-01-2009,-500\n13-01-2009,-500\n14-01-2009,-500\n15-01-2009,-500\n16-01-2009,-500\n19-01-2009,-500\n20-01-2009,-500\n21-01-2009,-500\n22-01-2009,-500\n23-01-2009,-500\n26-01-2009,-500\n27-01-2009,-500\n28-01-2009,-500\n29-01-2009,-500\n30-01-2009,-500\n02-02-2009,-500\n03-02-2009,-500\n04-02-2009,-500\n05-02-2009,-500\n06-02-2009,-500\n09-02-2009,-500\n10-02-2009,-500\n11-02-2009,-500\n12-02-2009,-500\n13-02-2009,-500\n17-02-2009,-500\n18-02-2009,-500\n19-02-2009,-500\n20-02-2009,-500\n23-02-2009,-500\n24-02-2009,-500\n25-02-2009,-500\n26-02-2009,-500\n27-02-2009,-500\n02-03-2009,-500\n03-03-2009,-500\n04-03-2009,-500\n05-03-2009,-500\n06-03-2009,-500\n09-03-2009,-500\n10-03-2009,-500\n11-03-2009,-500\n12-03-2009,-500\n13-03-2009,-500\n16-03-2009,-500\n17-03-2009,-500\n18-03-2009,-500\n19-03-2009,-500\n20-03-2009,-500\n23-03-2009,-500\n24-03-2009,-500\n25-03-2009,-500\n26-03-2009,-500\n27-03-2009,-500\n30-03-2009,-500\n31-03-2009,-500\n01-04-2009,-500\n02-04-2009,-500\n03-04-2009,-500\n06-04-2009,-500\n07-04-2009,-500\n08-04-2009,-500\n09-04-2009,-500\n13-04-2009,-500\n14-04-2009,-500\n15-04-2009,-500\n16-04-2009,-500\n17-04-2009,-500\n20-04-2009,-500\n21-04-2009,-500\n22-04-2009,-500\n23-04-2009,-500\n24-04-2009,-500\n27-04-2009,-500\n28-04-2009,-500\n29-04-2009,-500\n30-04-2009,-500\n01-05-2009,-500\n04-05-2009,-500\n05-05-2009,-500\n06-05-2009,-500\n07-05-2009,-500\n08-05-2009,-500\n11-05-2009,-500\n12-05-2009,-500\n13-05-2009,-500\n14-05-2009,-500\n15-05-2009,-500\n19-05-2009,-500\n20-05-2009,-500\n21-05-2009,-500\n22-05-2009,-500\n25-05-2009,-500\n26-05-2009,-500\n27-05-2009,-500\n28-05-2009,-500\n29-05-2009,-500\n01-06-2009,-500\n02-06-2009,-500\n03-06-2009,-500\n04-06-2009,-500\n05-06-2009,-500\n08-06-2009,-500\n09-06-2009,-500\n10-06-2009,-500\n11-06-2009,-500\n12-06-2009,-500\n15-06-2009,-500\n16-06-2009,-500\n17-06-2009,-500\n18-06-2009,-500\n19-06-2009,-500\n22-06-2009,-500\n23-06-2009,-500\n24-06-2009,-500\n25-06-2009,-500\n26-06-2009,-500\n29-06-2009,-500\n30-06-2009,-500\n02-07-2009,-500\n03-07-2009,-500\n06-07-2009,-500\n07-07-2009,-500\n08-07-2009,-500\n09-07-2009,-500\n10-07-2009,-500\n13-07-2009,-500\n14-07-2009,-500\n15-07-2009,-500\n16-07-2009,-500\n17-07-2009,-500\n20-07-2009,-500\n21-07-2009,-500\n22-07-2009,-500\n23-07-2009,-500\n24-07-2009,-500\n27-07-2009,-500\n28-07-2009,-500\n29-07-2009,-500\n30-07-2009,-500\n31-07-2009,-500\n04-08-2009,-500\n05-08-2009,-500\n06-08-2009,-500\n07-08-2009,-500\n10-08-2009,-500\n11-08-2009,-500\n12-08-2009,-500\n13-08-2009,-500\n14-08-2009,-500\n17-08-2009,-500\n18-08-2009,-500\n19-08-2009,-500\n20-08-2009,-500\n21-08-2009,-500\n24-08-2009,-500\n25-08-2009,-500\n26-08-2009,-500\n27-08-2009,-500\n28-08-2009,-500\n31-08-2009,-500\n01-09-2009,-500\n02-09-2009,-500\n03-09-2009,-500\n04-09-2009,-500\n08-09-2009,-500\n09-09-2009,-500\n10-09-2009,-500\n11-09-2009,-500\n14-09-2009,-500\n15-09-2009,-500\n16-09-2009,-500\n17-09-2009,-500\n18-09-2009,-500\n21-09-2009,-500\n22-09-2009,-500\n23-09-2009,-500\n24-09-2009,-500\n25-09-2009,-500\n28-09-2009,-500\n29-09-2009,-500\n30-09-2009,-500\n01-10-2009,-500\n02-10-2009,-500\n05-10-2009,-500\n06-10-2009,-500\n07-10-2009,-500\n08-10-2009,-500\n09-10-2009,-500\n13-10-2009,-500\n14-10-2009,-500\n15-10-2009,-500\n16-10-2009,-500\n19-10-2009,-500\n20-10-2009,-500\n21-10-2009,-500\n22-10-2009,-500\n23-10-2009,-500\n26-10-2009,-500\n27-10-2009,-500\n28-10-2009,-500\n29-10-2009,-500\n30-10-2009,-500\n02-11-2009,-500\n03-11-2009,-500\n04-11-2009,-500\n05-11-2009,-500\n06-11-2009,-500\n09-11-2009,-500\n10-11-2009,-500\n12-11-2009,-500\n13-11-2009,-500\n16-11-2009,-500\n17-11-2009,-500\n18-11-2009,-500\n19-11-2009,-500\n20-11-2009,-500\n23-11-2009,-500\n24-11-2009,-500\n25-11-2009,-500\n26-11-2009,-500\n27-11-2009,-500\n30-11-2009,-500\n01-12-2009,-500\n02-12-2009,-500\n03-12-2009,-500\n04-12-2009,-500\n07-12-2009,-500\n08-12-2009,-500\n09-12-2009,-500\n10-12-2009,-500\n11-12-2009,-500\n14-12-2009,-500\n15-12-2009,-500\n16-12-2009,-500\n17-12-2009,-500\n18-12-2009,-500\n21-12-2009,-500\n22-12-2009,-500\n23-12-2009,-500\n24-12-2009,-500\n29-12-2009,-500\n30-12-2009,-500\n31-12-2009,-500\n04-01-2010,-500\n05-01-2010,-500\n06-01-2010,-500\n07-01-2010,-500\n08-01-2010,-500\n11-01-2010,-500\n12-01-2010,-500\n13-01-2010,-500\n14-01-2010,-500\n15-01-2010,-500\n18-01-2010,-500\n19-01-2010,-500\n20-01-2010,-500\n21-01-2010,-500\n22-01-2010,-500\n25-01-2010,-500\n26-01-2010,-500\n27-01-2010,-500\n28-01-2010,-500\n29-01-2010,-500\n01-02-2010,-500\n02-02-2010,-500\n03-02-2010,-500\n04-02-2010,-500\n05-02-2010,-500\n08-02-2010,-500\n09-02-2010,-500\n10-02-2010,-500\n11-02-2010,-500\n12-02-2010,-500\n16-02-2010,-500\n17-02-2010,-500\n18-02-2010,-500\n19-02-2010,-500\n22-02-2010,-500\n23-02-2010,-500\n24-02-2010,-500\n25-02-2010,-500\n26-02-2010,-500\n01-03-2010,-500\n02-03-2010,-500\n03-03-2010,-500\n04-03-2010,-500\n05-03-2010,-500\n08-03-2010,-500\n09-03-2010,-500\n10-03-2010,-500\n11-03-2010,-500\n12-03-2010,-500\n15-03-2010,-500\n16-03-2010,-500\n17-03-2010,-500\n18-03-2010,-500\n19-03-2010,-500\n22-03-2010,-500\n23-03-2010,-500\n24-03-2010,-500\n25-03-2010,-500\n26-03-2010,-500\n29-03-2010,-500\n30-03-2010,-500\n31-03-2010,-500\n01-04-2010,-500\n05-04-2010,-500\n06-04-2010,-500\n07-04-2010,-500\n08-04-2010,-500\n09-04-2010,-500\n12-04-2010,-500\n13-04-2010,-500\n14-04-2010,-500\n15-04-2010,-500\n16-04-2010,-500\n19-04-2010,-500\n20-04-2010,-500\n21-04-2010,-500\n22-04-2010,-500\n23-04-2010,-500\n26-04-2010,-500\n27-04-2010,-500\n28-04-2010,-500\n29-04-2010,-500\n30-04-2010,-500\n03-05-2010,-500\n04-05-2010,-500\n05-05-2010,-500\n06-05-2010,-500\n07-05-2010,-500\n10-05-2010,-500\n11-05-2010,-500\n12-05-2010,-500\n13-05-2010,-500\n14-05-2010,-500\n17-05-2010,-500\n18-05-2010,-500\n19-05-2010,-500\n20-05-2010,-500\n21-05-2010,-500\n25-05-2010,-500\n26-05-2010,-500\n27-05-2010,-500\n28-05-2010,-500\n31-05-2010,-500\n01-06-2010,-500\n02-06-2010,-500\n03-06-2010,-500\n04-06-2010,-500\n07-06-2010,-500\n08-06-2010,-500\n09-06-2010,-500\n10-06-2010,-500\n11-06-2010,-500\n14-06-2010,-500\n15-06-2010,-500\n16-06-2010,-500\n17-06-2010,-500\n18-06-2010,-500\n21-06-2010,-500\n22-06-2010,-500\n23-06-2010,-500\n24-06-2010,-500\n25-06-2010,-500\n28-06-2010,-500\n29-06-2010,-500\n30-06-2010,-500\n02-07-2010,-500\n05-07-2010,-500\n06-07-2010,-500\n07-07-2010,-500\n08-07-2010,-500\n09-07-2010,-500\n12-07-2010,-500\n13-07-2010,-500\n14-07-2010,-500\n15-07-2010,-500\n16-07-2010,-500\n19-07-2010,-500\n20-07-2010,-500\n21-07-2010,-500\n22-07-2010,-500\n23-07-2010,-500\n26-07-2010,-500\n27-07-2010,-500\n28-07-2010,-500\n29-07-2010,-500\n30-07-2010,-500\n03-08-2010,-500\n04-08-2010,-500\n05-08-2010,-500\n06-08-2010,-500\n09-08-2010,-500\n10-08-2010,-500\n11-08-2010,-500\n12-08-2010,-500\n13-08-2010,-500\n16-08-2010,-500\n17-08-2010,-500\n18-08-2010,-500\n19-08-2010,-500\n20-08-2010,-500\n23-08-2010,-500\n24-08-2010,-500\n25-08-2010,-500\n26-08-2010,-500\n27-08-2010,-500\n30-08-2010,-500\n31-08-2010,-500\n01-09-2010,-500\n02-09-2010,-500\n03-09-2010,-500\n07-09-2010,-500\n08-09-2010,-500\n09-09-2010,-500\n10-09-2010,-500\n13-09-2010,-500\n14-09-2010,-500\n15-09-2010,-500\n16-09-2010,-500\n17-09-2010,-500\n20-09-2010,-500\n21-09-2010,-500\n22-09-2010,-500\n23-09-2010,-500\n24-09-2010,-500\n27-09-2010,-500\n28-09-2010,-500\n29-09-2010,-500\n30-09-2010,-500\n01-10-2010,-500\n04-10-2010,-500\n05-10-2010,-500\n06-10-2010,-500\n07-10-2010,-500\n08-10-2010,-500\n12-10-2010,-500\n13-10-2010,-500\n14-10-2010,-500\n15-10-2010,-500\n18-10-2010,-500\n19-10-2010,-500\n20-10-2010,-500\n21-10-2010,-500\n22-10-2010,-500\n25-10-2010,-500\n26-10-2010,-500\n27-10-2010,-500\n28-10-2010,-500\n29-10-2010,-500\n01-11-2010,-500\n02-11-2010,-500\n03-11-2010,-500\n04-11-2010,-500\n05-11-2010,-500\n08-11-2010,-500\n09-11-2010,-500\n10-11-2010,-500\n12-11-2010,-500\n15-11-2010,-500\n16-11-2010,-500\n17-11-2010,-500\n18-11-2010,-500\n19-11-2010,-500\n22-11-2010,-500\n23-11-2010,-500\n24-11-2010,-500\n25-11-2010,-500\n26-11-2010,-500\n29-11-2010,-500\n30-11-2010,-500\n01-12-2010,-500\n02-12-2010,-500\n03-12-2010,-500\n06-12-2010,-500\n07-12-2010,-500\n08-12-2010,-500\n09-12-2010,-500\n10-12-2010,-500\n13-12-2010,-500\n14-12-2010,-500\n15-12-2010,-500\n16-12-2010,-500\n17-12-2010,-500\n20-12-2010,-500\n21-12-2010,-500\n22-12-2010,-500\n23-12-2010,-500\n24-12-2010,-500\n29-12-2010,-500\n30-12-2010,-500\n31-12-2010,-500\n04-01-2011,-500\n05-01-2011,-500\n06-01-2011,-500\n07-01-2011,-500\n10-01-2011,-500\n11-01-2011,-500\n12-01-2011,-500\n13-01-2011,-500\n14-01-2011,-500\n17-01-2011,-500\n18-01-2011,-500\n19-01-2011,-500\n20-01-2011,-500\n21-01-2011,-500\n24-01-2011,-500\n25-01-2011,-500\n26-01-2011,-500\n27-01-2011,-500\n28-01-2011,-500\n31-01-2011,-500\n01-02-2011,-500\n02-02-2011,-500\n03-02-2011,-500\n04-02-2011,-500\n07-02-2011,-500\n08-02-2011,-500\n09-02-2011,-500\n10-02-2011,-500\n11-02-2011,-500\n14-02-2011,-500\n15-02-2011,-500\n16-02-2011,-500\n17-02-2011,-500\n18-02-2011,-500\n22-02-2011,-500\n23-02-2011,-500\n24-02-2011,-500\n25-02-2011,-500\n28-02-2011,-500\n01-03-2011,-500\n02-03-2011,-500\n03-03-2011,-500\n04-03-2011,-500\n07-03-2011,-500\n08-03-2011,-500\n09-03-2011,-500\n10-03-2011,-500\n11-03-2011,-500\n14-03-2011,-500\n15-03-2011,-500\n16-03-2011,-500\n17-03-2011,-500\n18-03-2011,-500\n21-03-2011,-500\n22-03-2011,-500\n23-03-2011,-500\n24-03-2011,-500\n25-03-2011,-500\n28-03-2011,-500\n29-03-2011,-500\n30-03-2011,-500\n31-03-2011,-500\n01-04-2011,-500\n04-04-2011,-500\n05-04-2011,-500\n06-04-2011,-500\n07-04-2011,-500\n08-04-2011,-500\n11-04-2011,-500\n12-04-2011,-500\n13-04-2011,-500\n14-04-2011,-500\n15-04-2011,-500\n18-04-2011,-500\n19-04-2011,-500\n20-04-2011,-500\n21-04-2011,-500\n25-04-2011,-500\n26-04-2011,-500\n27-04-2011,-500\n28-04-2011,-500\n29-04-2011,-500\n02-05-2011,-500\n03-05-2011,-500\n04-05-2011,-500\n05-05-2011,-500\n06-05-2011,-500\n09-05-2011,-500\n10-05-2011,-500\n11-05-2011,-500\n12-05-2011,-500\n13-05-2011,-500\n16-05-2011,-500\n17-05-2011,-500\n18-05-2011,-500\n19-05-2011,-500\n20-05-2011,-500\n24-05-2011,-500\n25-05-2011,-500\n26-05-2011,-500\n27-05-2011,-500\n30-05-2011,-500\n31-05-2011,-500\n01-06-2011,-500\n02-06-2011,-500\n03-06-2011,-500\n06-06-2011,-500\n07-06-2011,-500\n08-06-2011,-500\n09-06-2011,-500\n10-06-2011,-500\n13-06-2011,-500\n14-06-2011,-500\n15-06-2011,-500\n16-06-2011,-500\n17-06-2011,-500\n20-06-2011,-500\n21-06-2011,-500\n22-06-2011,-500\n23-06-2011,-500\n24-06-2011,-500\n27-06-2011,-500\n28-06-2011,-500\n29-06-2011,-500\n30-06-2011,-500\n04-07-2011,-500\n05-07-2011,-500\n06-07-2011,-500\n07-07-2011,-500\n08-07-2011,-500\n11-07-2011,-500\n12-07-2011,-500\n13-07-2011,-500\n14-07-2011,-500\n15-07-2011,-500\n18-07-2011,-500\n19-07-2011,-500\n20-07-2011,-500\n21-07-2011,-500\n22-07-2011,-500\n25-07-2011,-500\n26-07-2011,-500\n27-07-2011,-500\n28-07-2011,-500\n29-07-2011,-500\n02-08-2011,-500\n03-08-2011,-500\n04-08-2011,-500\n05-08-2011,-500\n08-08-2011,-500\n09-08-2011,-500\n10-08-2011,-500\n11-08-2011,-500\n12-08-2011,-500\n15-08-2011,-500\n16-08-2011,-500\n17-08-2011,-500\n18-08-2011,-500\n19-08-2011,-500\n22-08-2011,-500\n23-08-2011,-500\n24-08-2011,-500\n25-08-2011,-500\n26-08-2011,-500\n29-08-2011,-500\n30-08-2011,-500\n31-08-2011,-500\n01-09-2011,-500\n02-09-2011,-500\n06-09-2011,-500\n07-09-2011,-500\n08-09-2011,-500\n09-09-2011,-500\n12-09-2011,-500\n13-09-2011,-500\n14-09-2011,-500\n15-09-2011,-500\n16-09-2011,-500\n19-09-2011,-500\n20-09-2011,-500\n21-09-2011,-500\n22-09-2011,-500\n23-09-2011,-500\n26-09-2011,-500\n27-09-2011,-500\n28-09-2011,-500\n29-09-2011,-500\n30-09-2011,-500\n03-10-2011,-500\n04-10-2011,-500\n05-10-2011,-500\n06-10-2011,-500\n07-10-2011,-500\n11-10-2011,-500\n12-10-2011,-500\n13-10-2011,-500\n14-10-2011,-500\n17-10-2011,-500\n18-10-2011,-500\n19-10-2011,-500\n20-10-2011,-500\n21-10-2011,-500\n24-10-2011,-500\n25-10-2011,-500\n26-10-2011,-500\n27-10-2011,-500\n28-10-2011,-500\n31-10-2011,-500\n01-11-2011,-500\n02-11-2011,-500\n03-11-2011,-500\n04-11-2011,-500\n07-11-2011,-500\n08-11-2011,-500\n09-11-2011,-500\n10-11-2011,-500\n14-11-2011,-500\n15-11-2011,-500\n16-11-2011,-500\n17-11-2011,-500\n18-11-2011,-500\n21-11-2011,-500\n22-11-2011,-500\n23-11-2011,-500\n24-11-2011,-500\n25-11-2011,-500\n28-11-2011,-500\n29-11-2011,-500\n30-11-2011,-500\n01-12-2011,-500\n02-12-2011,-500\n05-12-2011,-500\n06-12-2011,-500\n07-12-2011,-500\n08-12-2011,-500\n09-12-2011,-500\n12-12-2011,-500\n13-12-2011,-500\n14-12-2011,-500\n15-12-2011,-500\n16-12-2011,-500\n19-12-2011,-500\n20-12-2011,-500\n21-12-2011,-500\n22-12-2011,-500\n23-12-2011,-500\n28-12-2011,-500\n29-12-2011,-500\n30-12-2011,-500\n03-01-2012,-500\n04-01-2012,-500\n05-01-2012,-500\n06-01-2012,-500\n09-01-2012,-500\n10-01-2012,-500\n11-01-2012,-500\n12-01-2012,-500\n13-01-2012,-500\n16-01-2012,-500\n17-01-2012,-500\n18-01-2012,-500\n19-01-2012,-500\n20-01-2012,-500\n23-01-2012,-500\n24-01-2012,-500\n25-01-2012,-500\n26-01-2012,-500\n27-01-2012,-500\n30-01-2012,-500\n31-01-2012,-500\n01-02-2012,-500\n02-02-2012,-500\n03-02-2012,-500\n06-02-2012,-500\n07-02-2012,-500\n08-02-2012,-500\n09-02-2012,-500\n10-02-2012,-500\n13-02-2012,-500\n14-02-2012,-500\n15-02-2012,-500\n16-02-2012,-500\n17-02-2012,-500\n21-02-2012,-500\n22-02-2012,-500\n23-02-2012,-500\n24-02-2012,-500\n27-02-2012,-500\n28-02-2012,-500\n29-02-2012,-500\n01-03-2012,-500\n02-03-2012,-500\n05-03-2012,-500\n06-03-2012,-500\n07-03-2012,-500\n08-03-2012,-500\n09-03-2012,-500\n12-03-2012,-500\n13-03-2012,-500\n14-03-2012,-500\n15-03-2012,-500\n16-03-2012,-500\n19-03-2012,-500\n20-03-2012,-500\n21-03-2012,-500\n22-03-2012,-500\n23-03-2012,-500\n26-03-2012,-500\n27-03-2012,-500\n28-03-2012,-500\n29-03-2012,-500\n30-03-2012,-500\n02-04-2012,-500\n03-04-2012,-500\n04-04-2012,-500\n05-04-2012,-500\n09-04-2012,-500\n10-04-2012,-500\n11-04-2012,-500\n12-04-2012,-500\n13-04-2012,-500\n16-04-2012,-500\n17-04-2012,-500\n18-04-2012,-500\n19-04-2012,-500\n20-04-2012,-500\n23-04-2012,-500\n24-04-2012,-500\n25-04-2012,-500\n26-04-2012,-500\n27-04-2012,-500\n30-04-2012,-500\n01-05-2012,-500\n02-05-2012,-500\n03-05-2012,-500\n04-05-2012,-500\n07-05-2012,-500\n08-05-2012,-500\n09-05-2012,-500\n10-05-2012,-500\n11-05-2012,-500\n14-05-2012,-500\n15-05-2012,-500\n16-05-2012,-500\n17-05-2012,-500\n18-05-2012,-500\n22-05-2012,-500\n23-05-2012,-500\n24-05-2012,-500\n25-05-2012,-500\n28-05-2012,-500\n29-05-2012,-500\n30-05-2012,-500\n31-05-2012,-500\n01-06-2012,-500\n04-06-2012,-500\n05-06-2012,-500\n06-06-2012,-500\n07-06-2012,-500\n08-06-2012,-500\n11-06-2012,-500\n12-06-2012,-500\n13-06-2012,-500\n14-06-2012,-500\n15-06-2012,-500\n18-06-2012,-500\n19-06-2012,-500\n20-06-2012,-500\n21-06-2012,-500\n22-06-2012,-500\n25-06-2012,-500\n26-06-2012,-500\n27-06-2012,-500\n28-06-2012,-500\n29-06-2012,-500\n03-07-2012,-500\n04-07-2012,-500\n05-07-2012,-500\n06-07-2012,-500\n09-07-2012,-500\n10-07-2012,-500\n11-07-2012,-500\n12-07-2012,-500\n13-07-2012,-500\n16-07-2012,-500\n17-07-2012,-500\n18-07-2012,-500\n19-07-2012,-500\n20-07-2012,-500\n23-07-2012,-500\n24-07-2012,-500\n25-07-2012,-500\n26-07-2012,-500\n27-07-2012,-500\n30-07-2012,-500\n31-07-2012,-500\n01-08-2012,-500\n02-08-2012,-500\n03-08-2012,-500\n07-08-2012,-500\n08-08-2012,-500\n09-08-2012,-500\n10-08-2012,-500\n13-08-2012,-500\n14-08-2012,-500\n15-08-2012,-500\n16-08-2012,-500\n17-08-2012,-500\n20-08-2012,-500\n21-08-2012,-500\n22-08-2012,-500\n23-08-2012,-500\n24-08-2012,-500\n27-08-2012,-500\n28-08-2012,-500\n29-08-2012,-500\n30-08-2012,-500\n31-08-2012,-500\n04-09-2012,-500\n05-09-2012,-500\n06-09-2012,-500\n07-09-2012,-500\n10-09-2012,-500\n11-09-2012,-500\n12-09-2012,-500\n13-09-2012,-500\n14-09-2012,-500\n17-09-2012,-500\n18-09-2012,-500\n19-09-2012,-500\n20-09-2012,-500\n21-09-2012,-500\n24-09-2012,-500\n25-09-2012,-500\n26-09-2012,-500\n27-09-2012,-500\n28-09-2012,-500\n01-10-2012,-500\n02-10-2012,-500\n03-10-2012,-500\n04-10-2012,-500\n05-10-2012,-500\n09-10-2012,-500\n10-10-2012,-500\n11-10-2012,-500\n12-10-2012,-500\n15-10-2012,-500\n16-10-2012,-500\n17-10-2012,-500\n18-10-2012,-500\n19-10-2012,-500\n22-10-2012,-500\n23-10-2012,-500\n24-10-2012,-500\n25-10-2012,-500\n26-10-2012,-500\n29-10-2012,-500\n30-10-2012,-500\n31-10-2012,-500\n01-11-2012,-500\n02-11-2012,-500\n05-11-2012,-500\n06-11-2012,-500\n07-11-2012,-500\n08-11-2012,-500\n09-11-2012,-500\n13-11-2012,-500\n14-11-2012,-500\n15-11-2012,-500\n16-11-2012,-500\n19-11-2012,-500\n20-11-2012,-500\n21-11-2012,-500\n22-11-2012,-500\n23-11-2012,-500\n26-11-2012,-500\n27-11-2012,-500\n28-11-2012,-500\n29-11-2012,-500\n30-11-2012,-500\n03-12-2012,-500\n04-12-2012,-500\n05-12-2012,-500\n06-12-2012,-500\n07-12-2012,-500\n10-12-2012,-500\n11-12-2012,-500\n12-12-2012,-500\n13-12-2012,-500\n14-12-2012,-500\n17-12-2012,-500\n18-12-2012,-500\n19-12-2012,-500\n20-12-2012,-500\n21-12-2012,-500\n24-12-2012,-500\n27-12-2012,-500\n28-12-2012,-500\n31-12-2012,-500\n02-01-2013,-500\n03-01-2013,-500\n04-01-2013,-500\n07-01-2013,-500\n08-01-2013,-500\n09-01-2013,-500\n10-01-2013,-500\n11-01-2013,-500\n14-01-2013,-500\n15-01-2013,-500\n16-01-2013,-500\n17-01-2013,-500\n18-01-2013,-500\n21-01-2013,-500\n22-01-2013,-500\n23-01-2013,-500\n24-01-2013,-500\n25-01-2013,-500\n28-01-2013,-500\n29-01-2013,-500\n30-01-2013,-500\n31-01-2013,-500\n01-02-2013,-500\n04-02-2013,-500\n05-02-2013,-500\n06-02-2013,-500\n07-02-2013,-500\n08-02-2013,-500\n11-02-2013,-500\n12-02-2013,-500\n13-02-2013,-500\n14-02-2013,-500\n15-02-2013,-500\n19-02-2013,-500\n20-02-2013,-500\n21-02-2013,-500\n22-02-2013,-500\n25-02-2013,-500\n26-02-2013,-500\n27-02-2013,-500\n28-02-2013,-500\n01-03-2013,-500\n04-03-2013,-500\n05-03-2013,-500\n06-03-2013,-500\n07-03-2013,-500\n08-03-2013,-500\n11-03-2013,-500\n12-03-2013,-500\n13-03-2013,-500\n14-03-2013,-500\n15-03-2013,-500\n18-03-2013,-500\n19-03-2013,-500\n20-03-2013,-500\n21-03-2013,-500\n22-03-2013,-500\n25-03-2013,-500\n26-03-2013,-500\n27-03-2013,-500\n28-03-2013,-500\n01-04-2013,-500\n02-04-2013,-500\n03-04-2013,-500\n04-04-2013,-500\n05-04-2013,-500\n08-04-2013,-500\n09-04-2013,-500\n10-04-2013,-500\n11-04-2013,-500\n12-04-2013,-500\n15-04-2013,-500\n16-04-2013,-500\n17-04-2013,-500\n18-04-2013,-500\n19-04-2013,-500\n22-04-2013,-500\n23-04-2013,-500\n24-04-2013,-500\n25-04-2013,-500\n26-04-2013,-500\n29-04-2013,-500\n30-04-2013,-500\n01-05-2013,-500\n02-05-2013,-500\n03-05-2013,-500\n06-05-2013,-500\n07-05-2013,-500\n08-05-2013,-500\n09-05-2013,-500\n10-05-2013,-500\n13-05-2013,-500\n14-05-2013,-500\n15-05-2013,-500\n16-05-2013,-500\n17-05-2013,-500\n21-05-2013,-500\n22-05-2013,-500\n23-05-2013,-500\n24-05-2013,-500\n27-05-2013,-500\n28-05-2013,-500\n29-05-2013,-500\n30-05-2013,-500\n31-05-2013,-500\n03-06-2013,-500\n04-06-2013,-500\n05-06-2013,-500\n06-06-2013,-500\n07-06-2013,-500\n10-06-2013,-500\n11-06-2013,-500\n12-06-2013,-500\n13-06-2013,-500\n14-06-2013,-500\n17-06-2013,-500\n18-06-2013,-500\n19-06-2013,-500\n20-06-2013,-500\n21-06-2013,-500\n24-06-2013,-500\n25-06-2013,-500\n26-06-2013,-500\n27-06-2013,-500\n28-06-2013,-500\n02-07-2013,-500\n03-07-2013,-500\n04-07-2013,-500\n05-07-2013,-500\n08-07-2013,-500\n09-07-2013,-500\n10-07-2013,-500\n11-07-2013,-500\n12-07-2013,-500\n15-07-2013,-500\n16-07-2013,-500\n17-07-2013,-500\n18-07-2013,-500\n19-07-2013,-500\n22-07-2013,-500\n23-07-2013,-500\n24-07-2013,-500\n25-07-2013,-500\n26-07-2013,-500\n29-07-2013,-500\n30-07-2013,-500\n31-07-2013,-500\n01-08-2013,-500\n02-08-2013,-500\n06-08-2013,-500\n07-08-2013,-500\n08-08-2013,-500\n09-08-2013,-500\n12-08-2013,-500\n13-08-2013,-500\n14-08-2013,-500\n15-08-2013,-500\n16-08-2013,-500\n19-08-2013,-500\n20-08-2013,-500\n21-08-2013,-500\n22-08-2013,-500\n23-08-2013,-500\n26-08-2013,-500\n27-08-2013,-500\n28-08-2013,-500\n29-08-2013,-500\n30-08-2013,-500\n03-09-2013,-500\n04-09-2013,-500\n05-09-2013,-500\n06-09-2013,-500\n09-09-2013,-500\n10-09-2013,-500\n11-09-2013,-500\n12-09-2013,-500\n13-09-2013,-500\n16-09-2013,-500\n17-09-2013,-500\n18-09-2013,-500\n19-09-2013,-500\n20-09-2013,-500\n23-09-2013,-500\n24-09-2013,-500\n25-09-2013,-500\n26-09-2013,-500\n27-09-2013,-500\n30-09-2013,-500\n01-10-2013,-500\n02-10-2013,-500\n03-10-2013,-500\n04-10-2013,-500\n07-10-2013,-500\n08-10-2013,-500\n09-10-2013,-500\n10-10-2013,-500\n11-10-2013,-500\n15-10-2013,-500\n16-10-2013,-500\n17-10-2013,-500\n18-10-2013,-500\n21-10-2013,-500\n22-10-2013,-500\n23-10-2013,-500\n24-10-2013,-500\n25-10-2013,-500\n28-10-2013,-500\n29-10-2013,-500\n30-10-2013,-500\n31-10-2013,-500\n01-11-2013,-500\n04-11-2013,-500\n05-11-2013,-500\n06-11-2013,-500\n07-11-2013,-500\n08-11-2013,-500\n12-11-2013,-500\n13-11-2013,-500\n14-11-2013,-500\n15-11-2013,-500\n18-11-2013,-500\n19-11-2013,-500\n20-11-2013,-500\n21-11-2013,-500\n22-11-2013,-500\n25-11-2013,-500\n26-11-2013,-500\n27-11-2013,-500\n28-11-2013,-500\n29-11-2013,-500\n02-12-2013,-500\n03-12-2013,-500\n04-12-2013,-500\n05-12-2013,-500\n06-12-2013,-500\n09-12-2013,-500\n10-12-2013,-500\n11-12-2013,-500\n12-12-2013,-500\n13-12-2013,-500\n16-12-2013,-500\n17-12-2013,-500\n18-12-2013,-500\n19-12-2013,-500\n20-12-2013,-500\n23-12-2013,-500\n24-12-2013,-500\n27-12-2013,-500\n30-12-2013,-500\n31-12-2013,-500\n02-01-2014,-500\n03-01-2014,-500\n06-01-2014,-500\n07-01-2014,-500\n08-01-2014,-500\n09-01-2014,-500\n10-01-2014,-500\n13-01-2014,-500\n14-01-2014,-500\n15-01-2014,-500\n16-01-2014,-500\n17-01-2014,-500\n20-01-2014,-500\n21-01-2014,-500\n22-01-2014,-500\n23-01-2014,-500\n24-01-2014,-500\n27-01-2014,-500\n28-01-2014,-500\n29-01-2014,-500\n30-01-2014,-500\n31-01-2014,-500\n03-02-2014,-500\n04-02-2014,-500\n05-02-2014,-500\n06-02-2014,-500\n07-02-2014,-500\n10-02-2014,-500\n11-02-2014,-500\n12-02-2014,-500\n13-02-2014,-500\n14-02-2014,-500\n18-02-2014,-500\n19-02-2014,-500\n20-02-2014,-500\n21-02-2014,-500\n24-02-2014,-500\n25-02-2014,-500\n26-02-2014,-500\n27-02-2014,-500\n28-02-2014,-500\n03-03-2014,-500\n04-03-2014,-500\n05-03-2014,-500\n06-03-2014,-500\n07-03-2014,-500\n10-03-2014,-500\n11-03-2014,-500\n12-03-2014,-500\n13-03-2014,-500\n14-03-2014,-500\n17-03-2014,-500\n18-03-2014,-500\n19-03-2014,-500\n20-03-2014,-500\n21-03-2014,-500\n24-03-2014,-500\n25-03-2014,-500\n26-03-2014,-500\n27-03-2014,-500\n28-03-2014,-500\n31-03-2014,-500\n01-04-2014,-500\n02-04-2014,-500\n03-04-2014,-500\n04-04-2014,-500\n07-04-2014,-500\n08-04-2014,-500\n09-04-2014,-500\n10-04-2014,-500\n11-04-2014,-500\n14-04-2014,-500\n15-04-2014,-500\n16-04-2014,-500\n17-04-2014,-500\n21-04-2014,-500\n22-04-2014,-500\n23-04-2014,-500\n24-04-2014,-500\n25-04-2014,-500\n28-04-2014,-500\n29-04-2014,-500\n30-04-2014,-500\n01-05-2014,-500\n02-05-2014,-500\n05-05-2014,-500\n06-05-2014,-500\n07-05-2014,-500\n08-05-2014,-500\n09-05-2014,-500\n12-05-2014,-500\n13-05-2014,-500\n14-05-2014,-500\n15-05-2014,-500\n16-05-2014,-500\n20-05-2014,-500\n21-05-2014,-500\n22-05-2014,-500\n23-05-2014,-500\n26-05-2014,-500\n27-05-2014,-500\n28-05-2014,-500\n29-05-2014,-500\n30-05-2014,-500\n02-06-2014,-500\n03-06-2014,-500\n04-06-2014,-500\n05-06-2014,-500\n06-06-2014,-500\n09-06-2014,-500\n10-06-2014,-500\n11-06-2014,-500\n12-06-2014,-500\n13-06-2014,-500\n16-06-2014,-500\n17-06-2014,-500\n18-06-2014,-500\n19-06-2014,-500\n20-06-2014,-500\n23-06-2014,-500\n24-06-2014,-500\n25-06-2014,-500\n26-06-2014,-500\n27-06-2014,-500\n30-06-2014,-500\n02-07-2014,-500\n03-07-2014,-500\n04-07-2014,-500\n07-07-2014,-500\n08-07-2014,-500\n09-07-2014,-500\n10-07-2014,-500\n11-07-2014,-500\n14-07-2014,-500\n15-07-2014,-500\n16-07-2014,-500\n17-07-2014,-500\n18-07-2014,-500\n21-07-2014,-500\n22-07-2014,-500\n23-07-2014,-500\n24-07-2014,-500\n25-07-2014,-500\n28-07-2014,-500\n29-07-2014,-500\n30-07-2014,-500\n31-07-2014,-500\n01-08-2014,-500\n05-08-2014,-500\n06-08-2014,-500\n07-08-2014,-500\n08-08-2014,-500\n11-08-2014,-500\n12-08-2014,-500\n13-08-2014,-500\n14-08-2014,-500\n15-08-2014,-500\n18-08-2014,-500\n19-08-2014,-500\n20-08-2014,-500\n21-08-2014,-500\n22-08-2014,-500\n25-08-2014,-500\n26-08-2014,-500\n27-08-2014,-500\n28-08-2014,-500\n29-08-2014,-500\n02-09-2014,-500\n03-09-2014,-500\n04-09-2014,-500\n05-09-2014,-500\n08-09-2014,-500\n09-09-2014,-500\n10-09-2014,-500\n11-09-2014,-500\n12-09-2014,-500\n15-09-2014,-500\n16-09-2014,-500\n17-09-2014,-500\n18-09-2014,-500\n19-09-2014,-500\n22-09-2014,-500\n23-09-2014,-500\n24-09-2014,-500\n25-09-2014,-500\n26-09-2014,-500\n29-09-2014,-500\n30-09-2014,-500\n01-10-2014,-500\n02-10-2014,-500\n03-10-2014,-500\n06-10-2014,-500\n07-10-2014,-500\n08-10-2014,-500\n09-10-2014,-500\n10-10-2014,-500\n14-10-2014,-500\n15-10-2014,-500\n16-10-2014,-500\n17-10-2014,-500\n20-10-2014,-500\n21-10-2014,-500\n22-10-2014,-500\n23-10-2014,-500\n24-10-2014,-500\n27-10-2014,-500\n28-10-2014,-500\n29-10-2014,-500\n30-10-2014,-500\n31-10-2014,-500\n03-11-2014,-500\n04-11-2014,-500\n05-11-2014,-500\n06-11-2014,-500\n07-11-2014,-500\n10-11-2014,-500\n12-11-2014,-500\n13-11-2014,-500\n14-11-2014,-500\n17-11-2014,-500\n18-11-2014,-500\n19-11-2014,-500\n20-11-2014,-500\n21-11-2014,-500\n24-11-2014,-500\n25-11-2014,-500\n26-11-2014,-500\n27-11-2014,-500\n28-11-2014,-500\n01-12-2014,-500\n02-12-2014,-500\n03-12-2014,-500\n04-12-2014,-500\n05-12-2014,-500\n08-12-2014,-500\n09-12-2014,-500\n10-12-2014,-500\n11-12-2014,-500\n12-12-2014,-500\n15-12-2014,-500\n16-12-2014,-500\n17-12-2014,-500\n18-12-2014,-500\n19-12-2014,-500\n22-12-2014,-500\n23-12-2014,-500\n24-12-2014,-500\n29-12-2014,-500\n30-12-2014,-500\n31-12-2014,-500\n02-01-2015,-500\n05-01-2015,-500\n06-01-2015,-500\n07-01-2015,-500\n08-01-2015,-500\n09-01-2015,-500\n12-01-2015,-500\n13-01-2015,-500\n14-01-2015,-500\n15-01-2015,-500\n16-01-2015,-500\n19-01-2015,-500\n20-01-2015,-500\n21-01-2015,-500\n22-01-2015,-500\n23-01-2015,-500\n26-01-2015,-500\n27-01-2015,-500\n28-01-2015,-500\n29-01-2015,-500\n30-01-2015,-500\n02-02-2015,-500\n03-02-2015,-500\n04-02-2015,-500\n05-02-2015,-500\n06-02-2015,-500\n09-02-2015,-500\n10-02-2015,-500\n11-02-2015,-500\n12-02-2015,-500\n13-02-2015,-500\n17-02-2015,-500\n18-02-2015,-500\n19-02-2015,-500\n20-02-2015,-500\n23-02-2015,-500\n24-02-2015,-500\n25-02-2015,-500\n26-02-2015,-500\n27-02-2015,-500\n02-03-2015,-500\n03-03-2015,-500\n04-03-2015,-500\n05-03-2015,-500\n06-03-2015,-500\n09-03-2015,-500\n10-03-2015,-500\n11-03-2015,-500\n12-03-2015,-500\n13-03-2015,-500\n16-03-2015,-500\n17-03-2015,-500\n18-03-2015,-500\n19-03-2015,-500\n20-03-2015,-500\n23-03-2015,-500\n24-03-2015,-500\n25-03-2015,-500\n26-03-2015,-500\n27-03-2015,-500\n30-03-2015,-500\n31-03-2015,-500\n01-04-2015,-500\n02-04-2015,-500\n06-04-2015,-500\n07-04-2015,-500\n08-04-2015,-500\n09-04-2015,-500\n10-04-2015,-500\n13-04-2015,-500\n14-04-2015,-500\n15-04-2015,-500\n16-04-2015,-500\n17-04-2015,-500\n20-04-2015,-500\n21-04-2015,-500\n22-04-2015,-500\n23-04-2015,-500\n24-04-2015,-500\n27-04-2015,-500\n28-04-2015,-500\n29-04-2015,-500\n30-04-2015,-500\n01-05-2015,-500\n04-05-2015,-500\n05-05-2015,-500\n06-05-2015,-500\n07-05-2015,-500\n08-05-2015,-500\n11-05-2015,-500\n12-05-2015,-500\n13-05-2015,-500\n14-05-2015,-500\n15-05-2015,-500\n19-05-2015,-500\n20-05-2015,-500\n21-05-2015,-500\n22-05-2015,-500\n25-05-2015,-500\n26-05-2015,-500\n27-05-2015,-500\n28-05-2015,-500\n29-05-2015,-500\n01-06-2015,-500\n02-06-2015,-500\n03-06-2015,-500\n04-06-2015,-500\n05-06-2015,-500\n08-06-2015,-500\n09-06-2015,-500\n10-06-2015,-500\n11-06-2015,-500\n12-06-2015,-500\n15-06-2015,-500\n16-06-2015,-500\n17-06-2015,-500\n18-06-2015,-500\n19-06-2015,-500\n22-06-2015,-500\n23-06-2015,-500\n24-06-2015,-500\n25-06-2015,-500\n26-06-2015,-500\n29-06-2015,-500\n30-06-2015,-500\n02-07-2015,-500\n03-07-2015,-500\n06-07-2015,-500\n07-07-2015,-500\n08-07-2015,-500\n09-07-2015,-500\n10-07-2015,-500\n13-07-2015,-500\n14-07-2015,-500\n15-07-2015,-500\n16-07-2015,-500\n17-07-2015,-500\n20-07-2015,-500\n21-07-2015,-500\n22-07-2015,-500\n23-07-2015,-500\n24-07-2015,-500\n27-07-2015,-500\n28-07-2015,-500\n29-07-2015,-500\n30-07-2015,-500\n31-07-2015,-500\n04-08-2015,-500\n05-08-2015,-500\n06-08-2015,-500\n07-08-2015,-500\n10-08-2015,-500\n11-08-2015,-500\n12-08-2015,-500\n13-08-2015,-500\n14-08-2015,-500\n17-08-2015,-500\n18-08-2015,-500\n19-08-2015,-500\n20-08-2015,-500\n21-08-2015,-500\n24-08-2015,-500\n25-08-2015,-500\n26-08-2015,-500\n27-08-2015,-500\n28-08-2015,-500\n31-08-2015,-500\n01-09-2015,-500\n02-09-2015,-500\n03-09-2015,-500\n04-09-2015,-500\n08-09-2015,-500\n09-09-2015,-500\n10-09-2015,-500\n11-09-2015,-500\n14-09-2015,-500\n15-09-2015,-500\n16-09-2015,-500\n17-09-2015,-500\n18-09-2015,-500\n21-09-2015,-500\n22-09-2015,-500\n23-09-2015,-500\n24-09-2015,-500\n25-09-2015,-500\n28-09-2015,-500\n29-09-2015,-500\n30-09-2015,-500\n01-10-2015,-500\n02-10-2015,-500\n05-10-2015,-500\n06-10-2015,-500\n07-10-2015,-500\n08-10-2015,-500\n09-10-2015,-500\n13-10-2015,-500\n14-10-2015,-500\n15-10-2015,-500\n16-10-2015,-500\n19-10-2015,-500\n20-10-2015,-500\n21-10-2015,-500\n22-10-2015,-500\n23-10-2015,-500\n26-10-2015,-500\n27-10-2015,-500\n28-10-2015,-500\n29-10-2015,-500\n30-10-2015,-500\n02-11-2015,-500\n03-11-2015,-500\n04-11-2015,-500\n05-11-2015,-500\n06-11-2015,-500\n09-11-2015,-500\n10-11-2015,-500\n12-11-2015,-500\n13-11-2015,-500\n16-11-2015,-500\n17-11-2015,-500\n18-11-2015,-500\n19-11-2015,-500\n20-11-2015,-500\n23-11-2015,-500\n24-11-2015,-500\n25-11-2015,-500\n26-11-2015,-500\n27-11-2015,-500\n30-11-2015,-500\n01-12-2015,-500\n02-12-2015,-500\n03-12-2015,-500\n04-12-2015,-500\n07-12-2015,-500\n08-12-2015,-500\n09-12-2015,-500\n10-12-2015,-500\n11-12-2015,-500\n14-12-2015,-500\n15-12-2015,-500\n16-12-2015,-500\n17-12-2015,-500\n18-12-2015,-500\n21-12-2015,-500\n22-12-2015,-500\n23-12-2015,-500\n24-12-2015,-500\n29-12-2015,-500\n30-12-2015,-500\n31-12-2015,-500\n04-01-2016,-500\n05-01-2016,-500\n06-01-2016,-500\n07-01-2016,-500\n08-01-2016,-500\n11-01-2016,-500\n12-01-2016,-500\n13-01-2016,-500\n14-01-2016,-500\n15-01-2016,-500\n18-01-2016,-500\n19-01-2016,-500\n20-01-2016,-500\n21-01-2016,-500\n22-01-2016,-500\n25-01-2016,-500\n26-01-2016,-500\n27-01-2016,-500\n28-01-2016,-500\n29-01-2016,-500\n01-02-2016,-500\n02-02-2016,-500\n03-02-2016,-500\n04-02-2016,-500\n05-02-2016,-500\n08-02-2016,-500\n09-02-2016,-500\n10-02-2016,-500\n11-02-2016,-500\n12-02-2016,-500\n16-02-2016,-500\n17-02-2016,-500\n18-02-2016,-500\n19-02-2016,-500\n22-02-2016,-500\n23-02-2016,-500\n24-02-2016,-500\n25-02-2016,-500\n26-02-2016,-500\n29-02-2016,-500\n01-03-2016,-500\n02-03-2016,-500\n03-03-2016,-500\n04-03-2016,-500\n07-03-2016,-500\n08-03-2016,-500\n09-03-2016,-500\n10-03-2016,-500\n11-03-2016,-500\n14-03-2016,-500\n15-03-2016,-500\n16-03-2016,-500\n17-03-2016,-500\n18-03-2016,-500\n21-03-2016,-500\n22-03-2016,-500\n23-03-2016,-500\n24-03-2016,-500\n28-03-2016,-500\n29-03-2016,-500\n30-03-2016,-500\n31-03-2016,-500\n01-04-2016,-500\n04-04-2016,-500\n05-04-2016,-500\n06-04-2016,-500\n07-04-2016,-500\n08-04-2016,-500\n11-04-2016,-500\n12-04-2016,-500\n13-04-2016,-500\n14-04-2016,-500\n15-04-2016,-500\n18-04-2016,-500\n19-04-2016,-500\n20-04-2016,-500\n21-04-2016,-500\n22-04-2016,-500\n25-04-2016,-500\n26-04-2016,-500\n27-04-2016,-500\n28-04-2016,-500\n29-04-2016,-500\n02-05-2016,-500\n03-05-2016,-500\n04-05-2016,-500\n05-05-2016,-500\n06-05-2016,-500\n09-05-2016,-500\n10-05-2016,-500\n11-05-2016,-500\n12-05-2016,-500\n13-05-2016,-500\n16-05-2016,-500\n17-05-2016,-500\n18-05-2016,-500\n19-05-2016,-500\n20-05-2016,-500\n24-05-2016,-500\n25-05-2016,-500\n26-05-2016,-500\n27-05-2016,-500\n30-05-2016,-500\n31-05-2016,-500\n01-06-2016,-500\n02-06-2016,-500\n03-06-2016,-500\n06-06-2016,-500\n07-06-2016,-500\n08-06-2016,-500\n09-06-2016,-500\n10-06-2016,-500\n13-06-2016,-500\n14-06-2016,-500\n15-06-2016,-500\n16-06-2016,-500\n17-06-2016,-500\n20-06-2016,-500\n21-06-2016,-500\n22-06-2016,-500\n23-06-2016,-500\n24-06-2016,-500\n27-06-2016,-500\n28-06-2016,-500\n29-06-2016,-500\n30-06-2016,-500\n04-07-2016,-500\n05-07-2016,-500\n06-07-2016,-500\n07-07-2016,-500\n08-07-2016,-500\n11-07-2016,-500\n12-07-2016,-500\n13-07-2016,-500\n14-07-2016,-500\n15-07-2016,-500\n18-07-2016,-500\n19-07-2016,-500\n20-07-2016,-500\n21-07-2016,-500\n22-07-2016,-500\n25-07-2016,-500\n26-07-2016,-500\n27-07-2016,-500\n28-07-2016,-500\n29-07-2016,-500\n02-08-2016,-500\n03-08-2016,-500\n04-08-2016,-500\n05-08-2016,-500\n08-08-2016,-500\n09-08-2016,-500\n10-08-2016,-500\n11-08-2016,-500\n12-08-2016,-500\n15-08-2016,-500\n16-08-2016,-500\n17-08-2016,-500\n18-08-2016,-500\n19-08-2016,-500\n22-08-2016,-500\n23-08-2016,-500\n24-08-2016,-500\n25-08-2016,-500\n26-08-2016,-500\n29-08-2016,-500\n30-08-2016,-500\n31-08-2016,-500\n01-09-2016,-500\n02-09-2016,-500\n06-09-2016,-500\n07-09-2016,-500\n08-09-2016,-500\n09-09-2016,-500\n12-09-2016,-500\n13-09-2016,-500\n14-09-2016,-500\n15-09-2016,-500\n16-09-2016,-500\n19-09-2016,-500\n20-09-2016,-500\n21-09-2016,-500\n22-09-2016,-500\n23-09-2016,-500\n26-09-2016,-500\n27-09-2016,-500\n28-09-2016,-500\n29-09-2016,-500\n30-09-2016,-500\n03-10-2016,-500\n04-10-2016,-500\n05-10-2016,-500\n06-10-2016,-500\n07-10-2016,-500\n11-10-2016,-500\n12-10-2016,-500\n13-10-2016,-500\n14-10-2016,-500\n17-10-2016,-500\n18-10-2016,-500\n19-10-2016,-500\n20-10-2016,-500\n21-10-2016,-500\n24-10-2016,-500\n25-10-2016,-500\n26-10-2016,-500\n27-10-2016,-500\n28-10-2016,-500\n31-10-2016,-500\n01-11-2016,-500\n02-11-2016,-500\n03-11-2016,-500\n04-11-2016,-500\n07-11-2016,-500\n08-11-2016,-500\n09-11-2016,-500\n10-11-2016,-500\n14-11-2016,-500\n15-11-2016,-500\n16-11-2016,-500\n17-11-2016,-500\n18-11-2016,-500\n21-11-2016,-500\n22-11-2016,-500\n23-11-2016,-500\n24-11-2016,-500\n25-11-2016,-500\n28-11-2016,-500\n29-11-2016,-500\n30-11-2016,-500\n01-12-2016,-500\n02-12-2016,-500\n05-12-2016,-500\n06-12-2016,-500\n07-12-2016,-500\n08-12-2016,-500\n09-12-2016,-500\n12-12-2016,-500\n13-12-2016,-500\n14-12-2016,-500\n15-12-2016,-500\n16-12-2016,-500\n19-12-2016,-500\n20-12-2016,-500\n21-12-2016,-500\n22-12-2016,-500\n23-12-2016,-500\n28-12-2016,-500\n29-12-2016,-500\n30-12-2016,-500\n03-01-2017,-500\n04-01-2017,-500\n05-01-2017,-500\n06-01-2017,-500\n09-01-2017,-500\n10-01-2017,-500\n11-01-2017,-500\n12-01-2017,-500\n13-01-2017,-500\n16-01-2017,-500\n17-01-2017,-500\n18-01-2017,-500\n19-01-2017,-500\n20-01-2017,-500\n23-01-2017,-500\n24-01-2017,-500\n25-01-2017,-500\n26-01-2017,-500\n27-01-2017,-500\n30-01-2017,-500\n31-01-2017,-500\n01-02-2017,-500\n02-02-2017,-500\n03-02-2017,-500\n06-02-2017,-500\n07-02-2017,-500\n08-02-2017,-500\n09-02-2017,-500\n10-02-2017,-500\n13-02-2017,-500\n14-02-2017,-500\n15-02-2017,-500\n16-02-2017,-500\n17-02-2017,-500\n21-02-2017,-500\n22-02-2017,-500\n23-02-2017,-500\n24-02-2017,-500\n27-02-2017,-500\n28-02-2017,-500\n01-03-2017,-500\n02-03-2017,-500\n03-03-2017,-500\n06-03-2017,-500\n07-03-2017,-500\n08-03-2017,-500\n09-03-2017,-500\n10-03-2017,-500\n13-03-2017,-500\n14-03-2017,-500\n15-03-2017,-500\n16-03-2017,-500\n17-03-2017,-500\n20-03-2017,-500\n21-03-2017,-500\n22-03-2017,-500\n23-03-2017,-500\n24-03-2017,-500\n27-03-2017,-500\n28-03-2017,-500\n29-03-2017,-500\n30-03-2017,-500\n31-03-2017,-500\n03-04-2017,-500\n04-04-2017,-500\n05-04-2017,-500\n06-04-2017,-500\n07-04-2017,-500\n10-04-2017,-500\n11-04-2017,-500\n12-04-2017,-500\n13-04-2017,-500\n17-04-2017,-500\n18-04-2017,-500\n19-04-2017,-500\n20-04-2017,-500\n21-04-2017,-500\n24-04-2017,-500\n25-04-2017,-500\n26-04-2017,-500\n27-04-2017,-500\n28-04-2017,-500\n01-05-2017,-500\n02-05-2017,-500\n03-05-2017,-500\n04-05-2017,-500\n05-05-2017,-500\n08-05-2017,-500\n09-05-2017,-500\n10-05-2017,-500\n11-05-2017,-500\n12-05-2017,-500\n15-05-2017,-500\n16-05-2017,-500\n17-05-2017,-500\n18-05-2017,-500\n19-05-2017,-500\n23-05-2017,-500\n24-05-2017,-500\n25-05-2017,-500\n26-05-2017,-500\n29-05-2017,-500\n30-05-2017,-500\n31-05-2017,-500\n01-06-2017,-500\n02-06-2017,-500\n05-06-2017,-500\n06-06-2017,-500\n07-06-2017,-500\n08-06-2017,-500\n09-06-2017,-500\n12-06-2017,-500\n13-06-2017,-500\n14-06-2017,-500\n15-06-2017,-500\n16-06-2017,-500\n19-06-2017,-500\n20-06-2017,-500\n21-06-2017,-500\n22-06-2017,-500\n23-06-2017,-500\n26-06-2017,-500\n27-06-2017,-500\n28-06-2017,-500\n29-06-2017,-500\n30-06-2017,-500\n04-07-2017,-500\n05-07-2017,-500\n06-07-2017,-500\n07-07-2017,-500\n10-07-2017,-500\n11-07-2017,-500\n12-07-2017,-500\n13-07-2017,-500\n14-07-2017,-500\n17-07-2017,-500\n18-07-2017,-500\n19-07-2017,-500\n20-07-2017,-500\n21-07-2017,-500\n24-07-2017,-500\n25-07-2017,-500\n26-07-2017,-500\n27-07-2017,-500\n28-07-2017,-500\n31-07-2017,-500\n01-08-2017,-500\n02-08-2017,-500\n03-08-2017,-500\n04-08-2017,-500\n08-08-2017,-500\n09-08-2017,-500\n10-08-2017,-500\n11-08-2017,-500\n14-08-2017,-500\n15-08-2017,-500\n16-08-2017,-500\n17-08-2017,-500\n18-08-2017,-500\n21-08-2017,-500\n22-08-2017,-500\n23-08-2017,-500\n24-08-2017,-500\n25-08-2017,-500\n28-08-2017,-500\n29-08-2017,-500\n30-08-2017,-500\n31-08-2017,-500\n01-09-2017,-500\n05-09-2017,-500\n06-09-2017,-500\n07-09-2017,-500\n08-09-2017,-500\n11-09-2017,-500\n12-09-2017,-500\n13-09-2017,-500\n14-09-2017,-500\n15-09-2017,-500\n18-09-2017,-500\n19-09-2017,-500\n20-09-2017,-500\n21-09-2017,-500\n22-09-2017,-500\n25-09-2017,-500\n26-09-2017,-500\n27-09-2017,-500\n28-09-2017,-500\n29-09-2017,-500\n02-10-2017,-500\n03-10-2017,-500\n04-10-2017,-500\n05-10-2017,-500\n06-10-2017,-500\n10-10-2017,-500\n11-10-2017,-500\n12-10-2017,-500\n13-10-2017,-500\n16-10-2017,-500\n17-10-2017,-500\n18-10-2017,-500\n19-10-2017,-500\n20-10-2017,-500\n23-10-2017,-500\n24-10-2017,-500\n25-10-2017,-500\n26-10-2017,-500\n27-10-2017,-500\n30-10-2017,-500\n31-10-2017,-500\n01-11-2017,-500\n02-11-2017,-500\n03-11-2017,-500\n06-11-2017,-500\n07-11-2017,-500\n08-11-2017,-500\n09-11-2017,-500\n10-11-2017,-500\n14-11-2017,-500\n15-11-2017,-500\n16-11-2017,-500\n17-11-2017,-500\n20-11-2017,-500\n21-11-2017,-500\n22-11-2017,-500\n23-11-2017,-500\n24-11-2017,-500\n27-11-2017,-500\n28-11-2017,-500\n29-11-2017,-500\n30-11-2017,-500\n01-12-2017,-500\n04-12-2017,-500\n05-12-2017,-500\n06-12-2017,-500\n07-12-2017,-500\n08-12-2017,-500\n11-12-2017,-500\n12-12-2017,-500\n13-12-2017,-500\n14-12-2017,-500\n15-12-2017,-500\n18-12-2017,-500\n19-12-2017,-500\n20-12-2017,-500\n21-12-2017,-500\n22-12-2017,-500\n27-12-2017,-500\n28-12-2017,-500\n29-12-2017,-500\n02-01-2018,-500\n03-01-2018,-500\n04-01-2018,-500\n05-01-2018,-500\n08-01-2018,-500\n09-01-2018,-500\n10-01-2018,-500\n11-01-2018,-500\n12-01-2018,-500\n15-01-2018,-500\n16-01-2018,-500\n17-01-2018,-500\n18-01-2018,-500\n19-01-2018,-500\n22-01-2018,-500\n23-01-2018,-500\n24-01-2018,-500\n25-01-2018,-500\n26-01-2018,-500\n29-01-2018,-500\n30-01-2018,-500\n31-01-2018,-500\n01-02-2018,-500\n02-02-2018,-500\n05-02-2018,-500\n06-02-2018,-500\n07-02-2018,-500\n08-02-2018,-500\n09-02-2018,-500\n12-02-2018,-500\n13-02-2018,-500\n14-02-2018,-500\n15-02-2018,-500\n16-02-2018,-500\n20-02-2018,-500\n21-02-2018,-500\n22-02-2018,-500\n23-02-2018,-500\n26-02-2018,-500\n27-02-2018,-500\n28-02-2018,-500\n01-03-2018,-500\n02-03-2018,-500\n05-03-2018,-500\n06-03-2018,-500\n07-03-2018,-500\n08-03-2018,-500\n09-03-2018,-500\n12-03-2018,-500\n13-03-2018,-500\n14-03-2018,-500\n15-03-2018,-500\n16-03-2018,-500\n19-03-2018,-500\n20-03-2018,-500\n21-03-2018,-500\n22-03-2018,-500\n23-03-2018,-500\n26-03-2018,-500\n27-03-2018,-500\n28-03-2018,-500\n29-03-2018,-500\n02-04-2018,-500\n03-04-2018,-500\n04-04-2018,-500\n05-04-2018,-500\n06-04-2018,-500\n09-04-2018,-500\n10-04-2018,-500\n11-04-2018,-500\n12-04-2018,-500\n13-04-2018,-500\n16-04-2018,-500\n17-04-2018,-500\n18-04-2018,-500\n19-04-2018,-500\n20-04-2018,-500\n23-04-2018,-500\n24-04-2018,-500\n25-04-2018,-500\n26-04-2018,-500\n27-04-2018,-500\n30-04-2018,-500\n01-05-2018,-500\n02-05-2018,-500\n03-05-2018,-500\n04-05-2018,-500\n07-05-2018,-500\n08-05-2018,-500\n09-05-2018,-500\n10-05-2018,-500\n11-05-2018,-500\n14-05-2018,-500\n15-05-2018,-500\n16-05-2018,-500\n17-05-2018,-500\n18-05-2018,-500\n22-05-2018,-500\n23-05-2018,-500\n24-05-2018,-500\n25-05-2018,-500\n28-05-2018,-500\n29-05-2018,-500\n30-05-2018,-500\n31-05-2018,-500\n01-06-2018,-500\n04-06-2018,-500\n05-06-2018,-500\n06-06-2018,-500\n07-06-2018,-500\n08-06-2018,-500\n11-06-2018,-500\n12-06-2018,-500\n13-06-2018,-500\n14-06-2018,-500\n15-06-2018,-500\n18-06-2018,-500\n19-06-2018,-500\n20-06-2018,-500\n21-06-2018,-500\n22-06-2018,-500\n25-06-2018,-500\n26-06-2018,-500\n27-06-2018,-500\n28-06-2018,-500\n29-06-2018,-500\n03-07-2018,-500\n04-07-2018,-500\n05-07-2018,-500\n06-07-2018,-500\n09-07-2018,-500\n10-07-2018,-500\n11-07-2018,-500\n12-07-2018,-500\n13-07-2018,-500\n16-07-2018,-500\n17-07-2018,-500\n18-07-2018,-500\n19-07-2018,-500\n20-07-2018,-500\n23-07-2018,-500\n24-07-2018,-500\n25-07-2018,-500\n26-07-2018,-500\n27-07-2018,-500\n30-07-2018,-500\n31-07-2018,-500\n01-08-2018,-500\n02-08-2018,-500\n03-08-2018,-500\n07-08-2018,-500\n08-08-2018,-500\n09-08-2018,-500\n10-08-2018,-500\n13-08-2018,-500\n14-08-2018,-500\n15-08-2018,-500\n16-08-2018,-500\n17-08-2018,-500\n20-08-2018,-500\n21-08-2018,-500\n22-08-2018,-500\n23-08-2018,-500\n24-08-2018,-500\n27-08-2018,-500\n28-08-2018,-500\n29-08-2018,-500\n30-08-2018,-500\n31-08-2018,-500\n04-09-2018,-500\n05-09-2018,-500\n06-09-2018,-500\n07-09-2018,-500\n10-09-2018,-500\n11-09-2018,-500\n12-09-2018,-500\n13-09-2018,-500\n14-09-2018,-500\n17-09-2018,-500\n18-09-2018,-500\n19-09-2018,-500\n20-09-2018,-500\n21-09-2018,-500\n24-09-2018,-500\n25-09-2018,-500\n26-09-2018,-500\n27-09-2018,-500\n28-09-2018,-500\n01-10-2018,-500\n02-10-2018,-500\n03-10-2018,-500\n04-10-2018,-500\n05-10-2018,-500\n09-10-2018,-500\n10-10-2018,-500\n11-10-2018,-500\n12-10-2018,-500\n15-10-2018,-500\n16-10-2018,-500\n17-10-2018,-500\n18-10-2018,-500\n19-10-2018,-500\n22-10-2018,-500\n23-10-2018,-500\n24-10-2018,-500\n25-10-2018,-500\n26-10-2018,-500\n29-10-2018,-500\n30-10-2018,-500\n31-10-2018,-500\n01-11-2018,-500\n02-11-2018,-500\n05-11-2018,-500\n06-11-2018,-500\n07-11-2018,-500\n08-11-2018,-500\n09-11-2018,-500\n13-11-2018,-500\n14-11-2018,-500\n15-11-2018,-500\n16-11-2018,-500\n19-11-2018,-500\n20-11-2018,-500\n21-11-2018,-500\n22-11-2018,-500\n23-11-2018,-500\n26-11-2018,-500\n27-11-2018,-500\n28-11-2018,-500\n29-11-2018,-500\n30-11-2018,-500\n03-12-2018,-500\n04-12-2018,-500\n05-12-2018,-500\n06-12-2018,-500\n07-12-2018,-500\n10-12-2018,-500\n11-12-2018,-500\n12-12-2018,-500\n13-12-2018,-500\n14-12-2018,-500\n17-12-2018,-500\n18-12-2018,-500\n19-12-2018,-500\n20-12-2018,-500\n21-12-2018,-500\n24-12-2018,-500\n27-12-2018,-500\n28-12-2018,-500\n31-12-2018,-500\n02-01-2019,-500\n03-01-2019,-500\n04-01-2019,-500\n07-01-2019,-500\n08-01-2019,-500\n09-01-2019,-500\n10-01-2019,-500\n11-01-2019,-500\n14-01-2019,-500\n15-01-2019,-500\n16-01-2019,-500\n17-01-2019,-500\n18-01-2019,-500\n21-01-2019,-500\n22-01-2019,-500\n23-01-2019,-500\n24-01-2019,-500\n25-01-2019,-500\n28-01-2019,-500\n29-01-2019,-500\n30-01-2019,-500\n31-01-2019,-500\n01-02-2019,-500\n04-02-2019,-500\n05-02-2019,-500\n06-02-2019,-500\n07-02-2019,-500\n08-02-2019,-500\n11-02-2019,-500\n12-02-2019,-500\n13-02-2019,-500\n14-02-2019,-500\n15-02-2019,-500\n19-02-2019,-500\n20-02-2019,-500\n21-02-2019,-500\n22-02-2019,-500\n25-02-2019,-500\n26-02-2019,-500\n27-02-2019,-500\n28-02-2019,-500\n01-03-2019,-500\n04-03-2019,-500\n05-03-2019,-500\n06-03-2019,-500\n07-03-2019,-500\n08-03-2019,-500\n11-03-2019,-500\n12-03-2019,-500\n13-03-2019,-500\n14-03-2019,-500\n15-03-2019,-500\n18-03-2019,-500\n19-03-2019,-500\n20-03-2019,-500\n21-03-2019,-500\n22-03-2019,-500\n25-03-2019,-500\n26-03-2019,-500\n27-03-2019,-500\n28-03-2019,-500\n29-03-2019,-500\n01-04-2019,-500\n02-04-2019,-500\n03-04-2019,-500\n04-04-2019,-500\n05-04-2019,-500\n08-04-2019,-500\n09-04-2019,-500\n10-04-2019,-500\n11-04-2019,-500\n12-04-2019,-500\n15-04-2019,-500\n16-04-2019,-500\n17-04-2019,-500\n18-04-2019,-500\n22-04-2019,-500\n23-04-2019,-500\n24-04-2019,-500\n25-04-2019,-500\n26-04-2019,-500\n29-04-2019,-500\n30-04-2019,-500\n01-05-2019,-500\n02-05-2019,-500\n03-05-2019,-500\n06-05-2019,-500\n07-05-2019,-500\n08-05-2019,-500\n09-05-2019,-500\n10-05-2019,-500\n13-05-2019,-500\n14-05-2019,-500\n15-05-2019,-500\n16-05-2019,-500\n17-05-2019,-500\n21-05-2019,-500\n22-05-2019,-500\n23-05-2019,-500\n24-05-2019,-500\n27-05-2019,-500\n28-05-2019,-500\n29-05-2019,-500\n30-05-2019,-500\n31-05-2019,-500\n03-06-2019,-500\n04-06-2019,-500\n05-06-2019,-500\n06-06-2019,-500\n07-06-2019,-500\n10-06-2019,-500\n11-06-2019,-500\n12-06-2019,-500\n13-06-2019,-500\n14-06-2019,-500\n17-06-2019,-500\n18-06-2019,-500\n19-06-2019,-500\n20-06-2019,-500\n21-06-2019,-500\n24-06-2019,-500\n25-06-2019,-500\n26-06-2019,-500\n27-06-2019,-500\n28-06-2019,-500\n02-07-2019,-500\n03-07-2019,-500\n04-07-2019,-500\n05-07-2019,-500\n08-07-2019,-500\n09-07-2019,-500\n10-07-2019,-500\n11-07-2019,-500\n12-07-2019,-500\n15-07-2019,-500\n16-07-2019,-500\n17-07-2019,-500\n18-07-2019,-500\n19-07-2019,-500\n22-07-2019,-500\n23-07-2019,-500\n24-07-2019,-500\n25-07-2019,-500\n26-07-2019,-500\n29-07-2019,-500\n30-07-2019,-500\n31-07-2019,-500\n01-08-2019,-500\n02-08-2019,-500\n06-08-2019,-500\n07-08-2019,-500\n08-08-2019,-500\n09-08-2019,-500\n12-08-2019,-500\n13-08-2019,-500\n14-08-2019,-500\n15-08-2019,-500\n16-08-2019,-500\n19-08-2019,-500\n20-08-2019,-500\n21-08-2019,-500\n22-08-2019,-500\n23-08-2019,-500\n26-08-2019,-500\n27-08-2019,-500\n28-08-2019,-500\n29-08-2019,-500\n30-08-2019,-500\n03-09-2019,-500\n04-09-2019,-500\n05-09-2019,-500\n06-09-2019,-500\n09-09-2019,-500\n10-09-2019,-500\n11-09-2019,-500\n12-09-2019,-500\n13-09-2019,-500\n16-09-2019,-500\n17-09-2019,-500\n18-09-2019,-500\n19-09-2019,-500\n20-09-2019,-500\n23-09-2019,-500\n24-09-2019,-500\n25-09-2019,-500\n26-09-2019,-500\n27-09-2019,-500\n30-09-2019,-500\n01-10-2019,-500\n02-10-2019,-500\n03-10-2019,-500\n04-10-2019,-500\n07-10-2019,-500\n08-10-2019,-500\n09-10-2019,-500\n10-10-2019,-500\n11-10-2019,-500\n15-10-2019,-500\n16-10-2019,-500\n17-10-2019,-500\n18-10-2019,-500\n21-10-2019,-500\n22-10-2019,-500\n23-10-2019,-500\n24-10-2019,-500\n25-10-2019,-500\n28-10-2019,-500\n29-10-2019,-500\n30-10-2019,-500\n31-10-2019,-500\n01-11-2019,-500\n04-11-2019,-500\n05-11-2019,-500\n06-11-2019,-500\n07-11-2019,-500\n08-11-2019,-500\n12-11-2019,-500\n13-11-2019,-500\n14-11-2019,-500\n15-11-2019,-500\n18-11-2019,-500\n19-11-2019,-500\n20-11-2019,-500\n21-11-2019,-500\n22-11-2019,-500\n25-11-2019,-500\n26-11-2019,-500\n27-11-2019,-500\n28-11-2019,-500\n29-11-2019,-500\n02-12-2019,-500\n03-12-2019,-500\n04-12-2019,-500\n05-12-2019,-500\n06-12-2019,-500\n09-12-2019,-500\n10-12-2019,-500\n11-12-2019,-500\n12-12-2019,-500\n13-12-2019,-500\n16-12-2019,-500\n17-12-2019,-500\n18-12-2019,-500\n19-12-2019,-500\n20-12-2019,-500\n23-12-2019,-500\n24-12-2019,-500\n27-12-2019,-500\n30-12-2019,-500\n31-12-2019,-500\n02-01-2020,-500\n03-01-2020,-500\n06-01-2020,-500\n07-01-2020,-500\n08-01-2020,-500\n09-01-2020,-500\n10-01-2020,-500\n13-01-2020,-500\n14-01-2020,-500\n15-01-2020,-500\n16-01-2020,-500\n17-01-2020,-500\n20-01-2020,-500\n21-01-2020,-500\n22-01-2020,-500\n23-01-2020,-500\n24-01-2020,-500\n27-01-2020,-500\n28-01-2020,-500\n29-01-2020,-500\n30-01-2020,-500\n31-01-2020,-500\n03-02-2020,-500\n04-02-2020,-500\n05-02-2020,-500\n06-02-2020,-500\n07-02-2020,-500\n10-02-2020,-500\n11-02-2020,-500\n12-02-2020,-500\n13-02-2020,-500\n14-02-2020,-500\n18-02-2020,-500\n19-02-2020,-500\n20-02-2020,-500\n21-02-2020,-500\n24-02-2020,-500\n25-02-2020,-500\n26-02-2020,-500\n27-02-2020,-500\n28-02-2020,-500\n02-03-2020,-500\n03-03-2020,-500\n04-03-2020,-500\n05-03-2020,-500\n06-03-2020,-500\n09-03-2020,-500\n10-03-2020,-500\n11-03-2020,-500\n12-03-2020,-500\n13-03-2020,-500\n16-03-2020,-500\n17-03-2020,-500\n18-03-2020,-500\n19-03-2020,-500\n20-03-2020,-500\n23-03-2020,-500\n24-03-2020,-500\n25-03-2020,-500\n26-03-2020,-500\n27-03-2020,-500\n30-03-2020,-500\n31-03-2020,-500\n01-04-2020,-500\n02-04-2020,-500\n03-04-2020,-500\n06-04-2020,-500\n07-04-2020,-500\n08-04-2020,-500\n09-04-2020,-500\n13-04-2020,-500\n14-04-2020,-500\n15-04-2020,-500\n16-04-2020,-500\n17-04-2020,-500\n20-04-2020,-500\n21-04-2020,-500\n22-04-2020,-500\n23-04-2020,-500\n24-04-2020,-500\n27-04-2020,-500\n28-04-2020,-500\n29-04-2020,-500\n30-04-2020,-500\n01-05-2020,-500\n04-05-2020,-500\n05-05-2020,-500\n06-05-2020,-500\n07-05-2020,-500\n08-05-2020,-500\n11-05-2020,-500\n12-05-2020,-500\n13-05-2020,-500\n14-05-2020,-500\n15-05-2020,-500\n19-05-2020,-500\n20-05-2020,-500\n21-05-2020,-500\n22-05-2020,-500\n25-05-2020,-500\n26-05-2020,-500\n27-05-2020,-500\n28-05-2020,-500\n29-05-2020,-500\n01-06-2020,-500\n02-06-2020,-500\n03-06-2020,-500\n04-06-2020,-500\n05-06-2020,-500\n08-06-2020,-500\n09-06-2020,-500\n10-06-2020,-500\n11-06-2020,-500\n12-06-2020,-500\n15-06-2020,-500\n16-06-2020,-500\n17-06-2020,-500\n18-06-2020,-500\n19-06-2020,-500\n22-06-2020,-500\n23-06-2020,-500\n24-06-2020,-500\n25-06-2020,-500\n26-06-2020,-500\n29-06-2020,-500\n30-06-2020,-500\n02-07-2020,-500\n03-07-2020,-500\n06-07-2020,-500\n07-07-2020,-500\n08-07-2020,-500\n09-07-2020,-500\n10-07-2020,-500\n13-07-2020,-500\n14-07-2020,-500\n15-07-2020,-500\n16-07-2020,-500\n17-07-2020,-500\n20-07-2020,-500\n21-07-2020,-500\n22-07-2020,-500\n23-07-2020,-500\n24-07-2020,-500\n27-07-2020,-500\n28-07-2020,-500\n29-07-2020,-500\n30-07-2020,-500\n31-07-2020,-500\n04-08-2020,-500\n05-08-2020,-500\n06-08-2020,-500\n07-08-2020,-500\n10-08-2020,-500\n11-08-2020,-500\n12-08-2020,-500\n13-08-2020,-500\n14-08-2020,-500\n17-08-2020,-500\n18-08-2020,-500\n19-08-2020,-500\n20-08-2020,-500\n21-08-2020,-500\n24-08-2020,-500\n25-08-2020,-500\n26-08-2020,-500\n27-08-2020,-500\n28-08-2020,-500\n31-08-2020,-500\n01-09-2020,-500\n02-09-2020,-500\n03-09-2020,-500\n04-09-2020,-500\n08-09-2020,-500\n09-09-2020,-500\n10-09-2020,-500\n11-09-2020,-500\n14-09-2020,-500\n15-09-2020,-500\n16-09-2020,-500\n17-09-2020,-500\n18-09-2020,-500\n21-09-2020,-500\n22-09-2020,-500\n23-09-2020,-500\n24-09-2020,-500\n25-09-2020,-500\n28-09-2020,-500\n29-09-2020,-500\n30-09-2020,-500\n01-10-2020,-500\n02-10-2020,-500\n05-10-2020,-500\n06-10-2020,-500\n07-10-2020,-500\n08-10-2020,-500\n09-10-2020,-500\n13-10-2020,-500\n14-10-2020,-500\n15-10-2020,-500\n16-10-2020,-500\n19-10-2020,-500\n20-10-2020,-500\n21-10-2020,-500\n22-10-2020,-500\n23-10-2020,-500\n26-10-2020,-500\n27-10-2020,-500\n28-10-2020,-500\n29-10-2020,-500\n30-10-2020,-500\n02-11-2020,-500\n03-11-2020,-500\n04-11-2020,-500\n05-11-2020,-500\n06-11-2020,-500\n09-11-2020,-500\n10-11-2020,-500\n12-11-2020,-500\n13-11-2020,-500\n16-11-2020,-500\n17-11-2020,-500\n18-11-2020,-500\n19-11-2020,-500\n20-11-2020,-500\n23-11-2020,-500\n24-11-2020,-500\n25-11-2020,-500\n26-11-2020,-500\n27-11-2020,-500\n30-11-2020,-500\n01-12-2020,-500\n02-12-2020,-500\n03-12-2020,-500\n04-12-2020,-500\n07-12-2020,-500\n08-12-2020,-500\n09-12-2020,-500\n10-12-2020,-500\n11-12-2020,-500\n14-12-2020,-500\n15-12-2020,-500\n16-12-2020,-500\n17-12-2020,-500\n18-12-2020,-500\n21-12-2020,-500\n22-12-2020,-500\n23-12-2020,-500\n24-12-2020,-500\n29-12-2020,-500\n30-12-2020,-500\n31-12-2020,-500\n04-01-2021,-500\n05-01-2021,-500\n06-01-2021,-500\n07-01-2021,-500\n08-01-2021,-500\n11-01-2021,-500\n12-01-2021,-500\n13-01-2021,-500\n14-01-2021,-500\n15-01-2021,-500\n18-01-2021,-500\n19-01-2021,-500\n20-01-2021,-500\n21-01-2021,-500\n22-01-2021,-500\n25-01-2021,-500\n26-01-2021,-500\n27-01-2021,-500\n28-01-2021,-500\n29-01-2021,-500\n01-02-2021,-500\n02-02-2021,-500\n03-02-2021,-500\n04-02-2021,-500\n05-02-2021,-500\n08-02-2021,-500\n09-02-2021,-500\n10-02-2021,-500\n11-02-2021,-500\n12-02-2021,-500\n16-02-2021,-500\n17-02-2021,-500\n18-02-2021,-500\n19-02-2021,-500\n22-02-2021,-500\n23-02-2021,-500\n24-02-2021,-500\n25-02-2021,-500\n26-02-2021,-500\n01-03-2021,-500\n02-03-2021,-500\n03-03-2021,-500\n04-03-2021,-500\n05-03-2021,-500\n08-03-2021,-500\n09-03-2021,-500\n10-03-2021,-500\n11-03-2021,-500\n12-03-2021,-500\n15-03-2021,-500\n16-03-2021,-500\n17-03-2021,-500\n18-03-2021,-500\n19-03-2021,-500\n22-03-2021,-500\n23-03-2021,-500\n24-03-2021,-500\n25-03-2021,-500\n26-03-2021,-500\n29-03-2021,-500\n30-03-2021,-500\n31-03-2021,-500\n01-04-2021,-500\n05-04-2021,-500\n06-04-2021,-500\n07-04-2021,-500\n08-04-2021,-500\n09-04-2021,-500\n12-04-2021,-500\n13-04-2021,-500\n14-04-2021,-500\n15-04-2021,-500\n16-04-2021,-500\n19-04-2021,-500\n20-04-2021,-500\n21-04-2021,-500\n22-04-2021,-500\n23-04-2021,-500\n26-04-2021,-500\n27-04-2021,-500\n28-04-2021,-500\n29-04-2021,-500\n30-04-2021,-500\n03-05-2021,-500\n04-05-2021,-500\n05-05-2021,-500\n06-05-2021,-500\n07-05-2021,-500\n10-05-2021,-500\n11-05-2021,-500\n12-05-2021,-500\n13-05-2021,-500\n14-05-2021,-500\n17-05-2021,-500\n18-05-2021,-500\n19-05-2021,-500\n20-05-2021,-500\n21-05-2021,-500\n25-05-2021,-500\n26-05-2021,-500\n27-05-2021,-500\n28-05-2021,-500\n31-05-2021,-500\n01-06-2021,-500\n02-06-2021,-500\n03-06-2021,-500\n04-06-2021,-500\n07-06-2021,-500\n08-06-2021,-500\n09-06-2021,-500\n10-06-2021,-500\n11-06-2021,-500\n14-06-2021,-500\n15-06-2021,-500\n16-06-2021,-500\n17-06-2021,-500\n18-06-2021,-500\n21-06-2021,-500\n22-06-2021,-500\n23-06-2021,-500\n24-06-2021,-500\n25-06-2021,-500\n28-06-2021,-500\n29-06-2021,-500\n30-06-2021,-500\n02-07-2021,-500\n05-07-2021,-500\n06-07-2021,-500\n07-07-2021,-500\n08-07-2021,-500\n09-07-2021,-500\n12-07-2021,-500\n13-07-2021,-500\n14-07-2021,-500\n15-07-2021,-500\n16-07-2021,-500\n19-07-2021,-500\n20-07-2021,-500\n21-07-2021,-500\n22-07-2021,-500\n23-07-2021,-500\n26-07-2021,-500\n27-07-2021,-500\n28-07-2021,-500\n29-07-2021,-500\n30-07-2021,-500\n03-08-2021,-500\n04-08-2021,-500\n05-08-2021,-500\n06-08-2021,-500\n09-08-2021,-500\n10-08-2021,-500\n11-08-2021,-500\n12-08-2021,-500\n13-08-2021,-500\n16-08-2021,-500\n17-08-2021,-500\n18-08-2021,-500\n19-08-2021,-500\n20-08-2021,-500\n23-08-2021,-500\n24-08-2021,-500\n25-08-2021,-500\n26-08-2021,-500\n27-08-2021,-500\n30-08-2021,-500\n31-08-2021,-500\n01-09-2021,-500\n02-09-2021,-500\n03-09-2021,-500\n07-09-2021,-500\n08-09-2021,-500\n09-09-2021,-500\n10-09-2021,-500\n13-09-2021,-500\n14-09-2021,-500\n15-09-2021,-500\n16-09-2021,-500\n17-09-2021,-500\n20-09-2021,-500\n21-09-2021,-500\n22-09-2021,-500\n23-09-2021,-500\n24-09-2021,-500\n27-09-2021,-500\n28-09-2021,-500\n29-09-2021,-500\n01-10-2021,-500\n04-10-2021,-500\n05-10-2021,-500\n06-10-2021,-500\n07-10-2021,-500\n08-10-2021,-500\n12-10-2021,-500\n13-10-2021,-500\n14-10-2021,-500\n15-10-2021,-500\n18-10-2021,-500\n19-10-2021,-500\n20-10-2021,-500\n21-10-2021,-500\n22-10-2021,-500\n25-10-2021,-500\n26-10-2021,-500\n27-10-2021,-500\n28-10-2021,-500\n29-10-2021,-500\n01-11-2021,-500\n02-11-2021,-500\n03-11-2021,-500\n04-11-2021,-500\n05-11-2021,-500\n08-11-2021,-500\n09-11-2021,-500\n10-11-2021,-500\n12-11-2021,-500\n15-11-2021,-500\n16-11-2021,-500\n17-11-2021,-500\n18-11-2021,-500\n19-11-2021,-500\n22-11-2021,-500\n23-11-2021,-500\n24-11-2021,-500\n25-11-2021,-500\n26-11-2021,-500\n29-11-2021,-500\n30-11-2021,-500\n01-12-2021,-500\n02-12-2021,-500\n03-12-2021,-500\n06-12-2021,-500\n07-12-2021,-500\n08-12-2021,-500\n09-12-2021,-500\n10-12-2021,-500\n13-12-2021,-500\n14-12-2021,-500\n15-12-2021,-500\n16-12-2021,-500\n17-12-2021,-500\n20-12-2021,-500\n21-12-2021,-500\n22-12-2021,-500\n23-12-2021,-500\n24-12-2021,-500\n29-12-2021,-500\n30-12-2021,-500\n31-12-2021,-500\n04-01-2022,-500\n05-01-2022,-500\n06-01-2022,-500\n07-01-2022,-500\n10-01-2022,-500\n11-01-2022,-500\n12-01-2022,-500\n13-01-2022,-500\n14-01-2022,-500\n17-01-2022,-500\n18-01-2022,-500\n19-01-2022,-500\n20-01-2022,-500\n21-01-2022,-500\n24-01-2022,-500\n25-01-2022,-500\n26-01-2022,-500\n27-01-2022,-500\n28-01-2022,-500\n31-01-2022,-500\n01-02-2022,-500\n02-02-2022,-500\n03-02-2022,-500\n04-02-2022,-500\n07-02-2022,-500\n08-02-2022,-500\n09-02-2022,-500\n10-02-2022,-500\n11-02-2022,-500\n14-02-2022,-500\n15-02-2022,-500\n16-02-2022,-500\n17-02-2022,-500\n18-02-2022,-500\n22-02-2022,-500\n23-02-2022,-500\n24-02-2022,-500\n25-02-2022,-500\n28-02-2022,-500\n01-03-2022,-500\n02-03-2022,-500\n03-03-2022,-500\n04-03-2022,-500\n07-03-2022,-500\n08-03-2022,-500\n09-03-2022,-500\n10-03-2022,-500\n11-03-2022,-500\n14-03-2022,-500\n15-03-2022,-500\n16-03-2022,-500\n17-03-2022,-500\n18-03-2022,-500\n21-03-2022,-500\n22-03-2022,-500\n23-03-2022,-500\n24-03-2022,-500\n25-03-2022,-500\n28-03-2022,-500\n29-03-2022,-500\n30-03-2022,-500\n31-03-2022,-500\n01-04-2022,-500\n04-04-2022,-500\n05-04-2022,-500\n06-04-2022,-500\n07-04-2022,-500\n08-04-2022,-500\n11-04-2022,-500\n12-04-2022,-500\n13-04-2022,-500\n14-04-2022,-500\n18-04-2022,-500\n19-04-2022,-500\n20-04-2022,-500\n21-04-2022,-500\n22-04-2022,-500\n25-04-2022,-500\n26-04-2022,-500\n27-04-2022,-500\n28-04-2022,-500\n29-04-2022,-500\n02-05-2022,-500\n03-05-2022,-500\n04-05-2022,-500\n05-05-2022,-500\n06-05-2022,-500\n09-05-2022,-500\n10-05-2022,-500\n11-05-2022,-500\n12-05-2022,-500\n13-05-2022,-500\n16-05-2022,-500\n17-05-2022,-500\n18-05-2022,-500\n19-05-2022,-500\n20-05-2022,-500\n24-05-2022,-500\n25-05-2022,-500\n26-05-2022,-500\n27-05-2022,-500\n30-05-2022,-500\n31-05-2022,-500\n01-06-2022,-500\n02-06-2022,-500\n03-06-2022,-500\n06-06-2022,-500\n07-06-2022,-500\n08-06-2022,-500\n09-06-2022,-500\n10-06-2022,-500\n13-06-2022,-500\n14-06-2022,-500\n15-06-2022,-500\n16-06-2022,-500\n17-06-2022,-500\n20-06-2022,-500\n21-06-2022,-500\n22-06-2022,-500\n23-06-2022,-500\n24-06-2022,-500\n27-06-2022,-500\n28-06-2022,-500\n29-06-2022,-500\n30-06-2022,-500\n04-07-2022,-500\n05-07-2022,-500\n06-07-2022,-500\n07-07-2022,-500\n08-07-2022,-500\n11-07-2022,-500\n12-07-2022,-500\n13-07-2022,-500\n14-07-2022,-500\n15-07-2022,-500\n18-07-2022,-500\n19-07-2022,-500\n20-07-2022,-500\n21-07-2022,-500\n22-07-2022,-500\n25-07-2022,-500\n26-07-2022,-500\n27-07-2022,-500\n28-07-2022,-500\n29-07-2022,-500\n02-08-2022,-500\n03-08-2022,-500\n04-08-2022,-500\n05-08-2022,-500\n08-08-2022,-500\n09-08-2022,-500\n10-08-2022,-500\n11-08-2022,-500\n12-08-2022,-500\n15-08-2022,-500\n16-08-2022,-500\n17-08-2022,-500\n18-08-2022,-500\n19-08-2022,-500\n22-08-2022,-500\n23-08-2022,-500\n24-08-2022,-500\n25-08-2022,-500\n26-08-2022,-500\n29-08-2022,-500\n30-08-2022,-500\n31-08-2022,-500\n01-09-2022,-500\n02-09-2022,-500\n06-09-2022,-500\n07-09-2022,-500\n08-09-2022,-500\n09-09-2022,-500\n12-09-2022,-500\n13-09-2022,-500\n14-09-2022,-500\n15-09-2022,-500\n16-09-2022,-500\n19-09-2022,-500\n20-09-2022,-500\n21-09-2022,-500\n22-09-2022,-500\n23-09-2022,-500\n26-09-2022,-500\n27-09-2022,-500\n28-09-2022,-500\n29-09-2022,-500\n03-10-2022,-500\n04-10-2022,-500\n05-10-2022,-500\n06-10-2022,-500\n07-10-2022,-500\n11-10-2022,-500\n12-10-2022,-500\n13-10-2022,-500\n14-10-2022,-500\n17-10-2022,-500\n18-10-2022,-500\n19-10-2022,-500\n20-10-2022,-500\n21-10-2022,-500\n24-10-2022,-500\n25-10-2022,-500\n26-10-2022,-500\n27-10-2022,-500\n28-10-2022,-500\n31-10-2022,-500\n01-11-2022,-500\n02-11-2022,-500\n03-11-2022,-500\n04-11-2022,-500\n07-11-2022,-500\n08-11-2022,-500\n09-11-2022,-500\n10-11-2022,-500\n14-11-2022,-500\n15-11-2022,-500\n16-11-2022,-500\n17-11-2022,-500\n18-11-2022,-500\n21-11-2022,-500\n22-11-2022,-500\n23-11-2022,-500\n24-11-2022,-500\n25-11-2022,-500\n28-11-2022,-500\n29-11-2022,-500\n30-11-2022,-500\n01-12-2022,-500\n02-12-2022,-500\n05-12-2022,-500\n06-12-2022,-500\n07-12-2022,-500\n08-12-2022,-500\n09-12-2022,-500\n12-12-2022,-500\n13-12-2022,-500\n14-12-2022,-500\n15-12-2022,-500\n16-12-2022,-500\n19-12-2022,-500\n20-12-2022,-500\n21-12-2022,-500\n22-12-2022,-500\n23-12-2022,-500\n28-12-2022,-500\n29-12-2022,-500\n30-12-2022,-500\n03-01-2023,-500\n04-01-2023,-500\n05-01-2023,-500\n06-01-2023,-500\n09-01-2023,-500\n10-01-2023,-500\n11-01-2023,-500\n12-01-2023,-500\n13-01-2023,-500\n16-01-2023,-500\n17-01-2023,-500\n18-01-2023,-500\n19-01-2023,-500\n20-01-2023,-500\n23-01-2023,-500\n24-01-2023,-500\n25-01-2023,-500\n26-01-2023,-500\n27-01-2023,-500\n30-01-2023,-500\n31-01-2023,-500\n01-02-2023,-500\n02-02-2023,-500\n03-02-2023,-500\n06-02-2023,-500\n07-02-2023,-500\n08-02-2023,-500\n09-02-2023,-500\n10-02-2023,-500\n13-02-2023,-500\n14-02-2023,-500\n15-02-2023,-500\n16-02-2023,-500\n17-02-2023,-500\n21-02-2023,-500\n22-02-2023,-500\n23-02-2023,-500\n24-02-2023,-500\n27-02-2023,-500\n28-02-2023,-500\n01-03-2023,-500\n02-03-2023,-500\n03-03-2023,-500\n06-03-2023,-500\n07-03-2023,-500\n08-03-2023,-500\n09-03-2023,-500\n10-03-2023,-500\n13-03-2023,-500\n14-03-2023,-500\n15-03-2023,-500\n16-03-2023,-500\n17-03-2023,-500\n20-03-2023,-500\n21-03-2023,-500\n22-03-2023,-500\n23-03-2023,-500\n24-03-2023,-500\n27-03-2023,-500\n28-03-2023,-500\n29-03-2023,-500\n30-03-2023,-500\n31-03-2023,-500\n03-04-2023,-500\n04-04-2023,-500\n05-04-2023,-500\n06-04-2023,-500\n10-04-2023,-500\n11-04-2023,-500\n12-04-2023,-500\n13-04-2023,-500\n14-04-2023,-500\n17-04-2023,-500\n18-04-2023,-500\n19-04-2023,-500\n20-04-2023,-500\n21-04-2023,-500\n24-04-2023,-500\n25-04-2023,-500\n26-04-2023,-500\n27-04-2023,-500\n28-04-2023,-500\n01-05-2023,-500\n02-05-2023,-500\n03-05-2023,-500\n04-05-2023,-500\n05-05-2023,-500\n08-05-2023,-500\n09-05-2023,-500\n10-05-2023,-500\n11-05-2023,-500\n12-05-2023,-500\n15-05-2023,-500\n16-05-2023,-500\n17-05-2023,-500\n18-05-2023,-500\n19-05-2023,-500\n23-05-2023,-500\n24-05-2023,-500\n25-05-2023,-500\n26-05-2023,-500\n29-05-2023,-500\n30-05-2023,-500\n31-05-2023,-500\n01-06-2023,-500\n02-06-2023,-500\n05-06-2023,-500\n06-06-2023,-500\n07-06-2023,-500\n08-06-2023,-500\n09-06-2023,-500\n12-06-2023,-500\n13-06-2023,-500\n14-06-2023,-500\n15-06-2023,-500\n16-06-2023,-500\n19-06-2023,-500\n20-06-2023,-500\n21-06-2023,-500\n22-06-2023,-500\n23-06-2023,-500\n26-06-2023,-500\n27-06-2023,-500\n28-06-2023,-500\n29-06-2023,-500\n30-06-2023,-500\n04-07-2023,-500\n05-07-2023,-500\n06-07-2023,-500\n07-07-2023,-500\n10-07-2023,-500\n11-07-2023,-500\n12-07-2023,-500\n13-07-2023,-500\n14-07-2023,-500\n17-07-2023,-500\n18-07-2023,-500\n19-07-2023,-500\n20-07-2023,-500\n21-07-2023,-500\n24-07-2023,-500\n25-07-2023,-500\n26-07-2023,-500\n27-07-2023,-500\n28-07-2023,-500\n31-07-2023,-500\n01-08-2023,-500\n02-08-2023,-500\n03-08-2023,-500\n04-08-2023,-500\n08-08-2023,-500\n09-08-2023,-500\n10-08-2023,-500\n11-08-2023,-500\n14-08-2023,-500\n15-08-2023,-500\n16-08-2023,-500\n17-08-2023,-500\n18-08-2023,-500\n21-08-2023,-500\n22-08-2023,-500\n23-08-2023,-500\n24-08-2023,-500\n25-08-2023,-500\n28-08-2023,-500\n29-08-2023,-500\n30-08-2023,-500\n31-08-2023,-500\n01-09-2023,-500\n05-09-2023,-500\n06-09-2023,-500\n07-09-2023,-500"
  },
  {
    "path": "python/rateslib/data/historical/estr.csv",
    "content": "﻿reference_date,rate\n01-10-2019,-500\n02-10-2019,-500\n03-10-2019,-500\n04-10-2019,-500\n07-10-2019,-500\n08-10-2019,-500\n09-10-2019,-500\n10-10-2019,-500\n11-10-2019,-500\n14-10-2019,-500\n15-10-2019,-500\n16-10-2019,-500\n17-10-2019,-500\n18-10-2019,-500\n21-10-2019,-500\n22-10-2019,-500\n23-10-2019,-500\n24-10-2019,-500\n25-10-2019,-500\n28-10-2019,-500\n29-10-2019,-500\n30-10-2019,-500\n31-10-2019,-500\n01-11-2019,-500\n04-11-2019,-500\n05-11-2019,-500\n06-11-2019,-500\n07-11-2019,-500\n08-11-2019,-500\n11-11-2019,-500\n12-11-2019,-500\n13-11-2019,-500\n14-11-2019,-500\n15-11-2019,-500\n18-11-2019,-500\n19-11-2019,-500\n20-11-2019,-500\n21-11-2019,-500\n22-11-2019,-500\n25-11-2019,-500\n26-11-2019,-500\n27-11-2019,-500\n28-11-2019,-500\n29-11-2019,-500\n02-12-2019,-500\n03-12-2019,-500\n04-12-2019,-500\n05-12-2019,-500\n06-12-2019,-500\n09-12-2019,-500\n10-12-2019,-500\n11-12-2019,-500\n12-12-2019,-500\n13-12-2019,-500\n16-12-2019,-500\n17-12-2019,-500\n18-12-2019,-500\n19-12-2019,-500\n20-12-2019,-500\n23-12-2019,-500\n24-12-2019,-500\n27-12-2019,-500\n30-12-2019,-500\n31-12-2019,-500\n02-01-2020,-500\n03-01-2020,-500\n06-01-2020,-500\n07-01-2020,-500\n08-01-2020,-500\n09-01-2020,-500\n10-01-2020,-500\n13-01-2020,-500\n14-01-2020,-500\n15-01-2020,-500\n16-01-2020,-500\n17-01-2020,-500\n20-01-2020,-500\n21-01-2020,-500\n22-01-2020,-500\n23-01-2020,-500\n24-01-2020,-500\n27-01-2020,-500\n28-01-2020,-500\n29-01-2020,-500\n30-01-2020,-500\n31-01-2020,-500\n03-02-2020,-500\n04-02-2020,-500\n05-02-2020,-500\n06-02-2020,-500\n07-02-2020,-500\n10-02-2020,-500\n11-02-2020,-500\n12-02-2020,-500\n13-02-2020,-500\n14-02-2020,-500\n17-02-2020,-500\n18-02-2020,-500\n19-02-2020,-500\n20-02-2020,-500\n21-02-2020,-500\n24-02-2020,-500\n25-02-2020,-500\n26-02-2020,-500\n27-02-2020,-500\n28-02-2020,-500\n02-03-2020,-500\n03-03-2020,-500\n04-03-2020,-500\n05-03-2020,-500\n06-03-2020,-500\n09-03-2020,-500\n10-03-2020,-500\n11-03-2020,-500\n12-03-2020,-500\n13-03-2020,-500\n16-03-2020,-500\n17-03-2020,-500\n18-03-2020,-500\n19-03-2020,-500\n20-03-2020,-500\n23-03-2020,-500\n24-03-2020,-500\n25-03-2020,-500\n26-03-2020,-500\n27-03-2020,-500\n30-03-2020,-500\n31-03-2020,-500\n01-04-2020,-500\n02-04-2020,-500\n03-04-2020,-500\n06-04-2020,-500\n07-04-2020,-500\n08-04-2020,-500\n09-04-2020,-500\n14-04-2020,-500\n15-04-2020,-500\n16-04-2020,-500\n17-04-2020,-500\n20-04-2020,-500\n21-04-2020,-500\n22-04-2020,-500\n23-04-2020,-500\n24-04-2020,-500\n27-04-2020,-500\n28-04-2020,-500\n29-04-2020,-500\n30-04-2020,-500\n04-05-2020,-500\n05-05-2020,-500\n06-05-2020,-500\n07-05-2020,-500\n08-05-2020,-500\n11-05-2020,-500\n12-05-2020,-500\n13-05-2020,-500\n14-05-2020,-500\n15-05-2020,-500\n18-05-2020,-500\n19-05-2020,-500\n20-05-2020,-500\n21-05-2020,-500\n22-05-2020,-500\n25-05-2020,-500\n26-05-2020,-500\n27-05-2020,-500\n28-05-2020,-500\n29-05-2020,-500\n01-06-2020,-500\n02-06-2020,-500\n03-06-2020,-500\n04-06-2020,-500\n05-06-2020,-500\n08-06-2020,-500\n09-06-2020,-500\n10-06-2020,-500\n11-06-2020,-500\n12-06-2020,-500\n15-06-2020,-500\n16-06-2020,-500\n17-06-2020,-500\n18-06-2020,-500\n19-06-2020,-500\n22-06-2020,-500\n23-06-2020,-500\n24-06-2020,-500\n25-06-2020,-500\n26-06-2020,-500\n29-06-2020,-500\n30-06-2020,-500\n01-07-2020,-500\n02-07-2020,-500\n03-07-2020,-500\n06-07-2020,-500\n07-07-2020,-500\n08-07-2020,-500\n09-07-2020,-500\n10-07-2020,-500\n13-07-2020,-500\n14-07-2020,-500\n15-07-2020,-500\n16-07-2020,-500\n17-07-2020,-500\n20-07-2020,-500\n21-07-2020,-500\n22-07-2020,-500\n23-07-2020,-500\n24-07-2020,-500\n27-07-2020,-500\n28-07-2020,-500\n29-07-2020,-500\n30-07-2020,-500\n31-07-2020,-500\n03-08-2020,-500\n04-08-2020,-500\n05-08-2020,-500\n06-08-2020,-500\n07-08-2020,-500\n10-08-2020,-500\n11-08-2020,-500\n12-08-2020,-500\n13-08-2020,-500\n14-08-2020,-500\n17-08-2020,-500\n18-08-2020,-500\n19-08-2020,-500\n20-08-2020,-500\n21-08-2020,-500\n24-08-2020,-500\n25-08-2020,-500\n26-08-2020,-500\n27-08-2020,-500\n28-08-2020,-500\n31-08-2020,-500\n01-09-2020,-500\n02-09-2020,-500\n03-09-2020,-500\n04-09-2020,-500\n07-09-2020,-500\n08-09-2020,-500\n09-09-2020,-500\n10-09-2020,-500\n11-09-2020,-500\n14-09-2020,-500\n15-09-2020,-500\n16-09-2020,-500\n17-09-2020,-500\n18-09-2020,-500\n21-09-2020,-500\n22-09-2020,-500\n23-09-2020,-500\n24-09-2020,-500\n25-09-2020,-500\n28-09-2020,-500\n29-09-2020,-500\n30-09-2020,-500\n01-10-2020,-500\n02-10-2020,-500\n05-10-2020,-500\n06-10-2020,-500\n07-10-2020,-500\n08-10-2020,-500\n09-10-2020,-500\n12-10-2020,-500\n13-10-2020,-500\n14-10-2020,-500\n15-10-2020,-500\n16-10-2020,-500\n19-10-2020,-500\n20-10-2020,-500\n21-10-2020,-500\n22-10-2020,-500\n23-10-2020,-500\n26-10-2020,-500\n27-10-2020,-500\n28-10-2020,-500\n29-10-2020,-500\n30-10-2020,-500\n02-11-2020,-500\n03-11-2020,-500\n04-11-2020,-500\n05-11-2020,-500\n06-11-2020,-500\n09-11-2020,-500\n10-11-2020,-500\n11-11-2020,-500\n12-11-2020,-500\n13-11-2020,-500\n16-11-2020,-500\n17-11-2020,-500\n18-11-2020,-500\n19-11-2020,-500\n20-11-2020,-500\n23-11-2020,-500\n24-11-2020,-500\n25-11-2020,-500\n26-11-2020,-500\n27-11-2020,-500\n30-11-2020,-500\n01-12-2020,-500\n02-12-2020,-500\n03-12-2020,-500\n04-12-2020,-500\n07-12-2020,-500\n08-12-2020,-500\n09-12-2020,-500\n10-12-2020,-500\n11-12-2020,-500\n14-12-2020,-500\n15-12-2020,-500\n16-12-2020,-500\n17-12-2020,-500\n18-12-2020,-500\n21-12-2020,-500\n22-12-2020,-500\n23-12-2020,-500\n24-12-2020,-500\n28-12-2020,-500\n29-12-2020,-500\n30-12-2020,-500\n31-12-2020,-500\n04-01-2021,-500\n05-01-2021,-500\n06-01-2021,-500\n07-01-2021,-500\n08-01-2021,-500\n11-01-2021,-500\n12-01-2021,-500\n13-01-2021,-500\n14-01-2021,-500\n15-01-2021,-500\n18-01-2021,-500\n19-01-2021,-500\n20-01-2021,-500\n21-01-2021,-500\n22-01-2021,-500\n25-01-2021,-500\n26-01-2021,-500\n27-01-2021,-500\n28-01-2021,-500\n29-01-2021,-500\n01-02-2021,-500\n02-02-2021,-500\n03-02-2021,-500\n04-02-2021,-500\n05-02-2021,-500\n08-02-2021,-500\n09-02-2021,-500\n10-02-2021,-500\n11-02-2021,-500\n12-02-2021,-500\n15-02-2021,-500\n16-02-2021,-500\n17-02-2021,-500\n18-02-2021,-500\n19-02-2021,-500\n22-02-2021,-500\n23-02-2021,-500\n24-02-2021,-500\n25-02-2021,-500\n26-02-2021,-500\n01-03-2021,-500\n02-03-2021,-500\n03-03-2021,-500\n04-03-2021,-500\n05-03-2021,-500\n08-03-2021,-500\n09-03-2021,-500\n10-03-2021,-500\n11-03-2021,-500\n12-03-2021,-500\n15-03-2021,-500\n16-03-2021,-500\n17-03-2021,-500\n18-03-2021,-500\n19-03-2021,-500\n22-03-2021,-500\n23-03-2021,-500\n24-03-2021,-500\n25-03-2021,-500\n26-03-2021,-500\n29-03-2021,-500\n30-03-2021,-500\n31-03-2021,-500\n01-04-2021,-500\n06-04-2021,-500\n07-04-2021,-500\n08-04-2021,-500\n09-04-2021,-500\n12-04-2021,-500\n13-04-2021,-500\n14-04-2021,-500\n15-04-2021,-500\n16-04-2021,-500\n19-04-2021,-500\n20-04-2021,-500\n21-04-2021,-500\n22-04-2021,-500\n23-04-2021,-500\n26-04-2021,-500\n27-04-2021,-500\n28-04-2021,-500\n29-04-2021,-500\n30-04-2021,-500\n03-05-2021,-500\n04-05-2021,-500\n05-05-2021,-500\n06-05-2021,-500\n07-05-2021,-500\n10-05-2021,-500\n11-05-2021,-500\n12-05-2021,-500\n13-05-2021,-500\n14-05-2021,-500\n17-05-2021,-500\n18-05-2021,-500\n19-05-2021,-500\n20-05-2021,-500\n21-05-2021,-500\n24-05-2021,-500\n25-05-2021,-500\n26-05-2021,-500\n27-05-2021,-500\n28-05-2021,-500\n31-05-2021,-500\n01-06-2021,-500\n02-06-2021,-500\n03-06-2021,-500\n04-06-2021,-500\n07-06-2021,-500\n08-06-2021,-500\n09-06-2021,-500\n10-06-2021,-500\n11-06-2021,-500\n14-06-2021,-500\n15-06-2021,-500\n16-06-2021,-500\n17-06-2021,-500\n18-06-2021,-500\n21-06-2021,-500\n22-06-2021,-500\n23-06-2021,-500\n24-06-2021,-500\n25-06-2021,-500\n28-06-2021,-500\n29-06-2021,-500\n30-06-2021,-500\n01-07-2021,-500\n02-07-2021,-500\n05-07-2021,-500\n06-07-2021,-500\n07-07-2021,-500\n08-07-2021,-500\n09-07-2021,-500\n12-07-2021,-500\n13-07-2021,-500\n14-07-2021,-500\n15-07-2021,-500\n16-07-2021,-500\n19-07-2021,-500\n20-07-2021,-500\n21-07-2021,-500\n22-07-2021,-500\n23-07-2021,-500\n26-07-2021,-500\n27-07-2021,-500\n28-07-2021,-500\n29-07-2021,-500\n30-07-2021,-500\n02-08-2021,-500\n03-08-2021,-500\n04-08-2021,-500\n05-08-2021,-500\n06-08-2021,-500\n09-08-2021,-500\n10-08-2021,-500\n11-08-2021,-500\n12-08-2021,-500\n13-08-2021,-500\n16-08-2021,-500\n17-08-2021,-500\n18-08-2021,-500\n19-08-2021,-500\n20-08-2021,-500\n23-08-2021,-500\n24-08-2021,-500\n25-08-2021,-500\n26-08-2021,-500\n27-08-2021,-500\n30-08-2021,-500\n31-08-2021,-500\n01-09-2021,-500\n02-09-2021,-500\n03-09-2021,-500\n06-09-2021,-500\n07-09-2021,-500\n08-09-2021,-500\n09-09-2021,-500\n10-09-2021,-500\n13-09-2021,-500\n14-09-2021,-500\n15-09-2021,-500\n16-09-2021,-500\n17-09-2021,-500\n20-09-2021,-500\n21-09-2021,-500\n22-09-2021,-500\n23-09-2021,-500\n24-09-2021,-500\n27-09-2021,-500\n28-09-2021,-500\n29-09-2021,-500\n30-09-2021,-500\n01-10-2021,-500\n04-10-2021,-500\n05-10-2021,-500\n06-10-2021,-500\n07-10-2021,-500\n08-10-2021,-500\n11-10-2021,-500\n12-10-2021,-500\n13-10-2021,-500\n14-10-2021,-500\n15-10-2021,-500\n18-10-2021,-500\n19-10-2021,-500\n20-10-2021,-500\n21-10-2021,-500\n22-10-2021,-500\n25-10-2021,-500\n26-10-2021,-500\n27-10-2021,-500\n28-10-2021,-500\n29-10-2021,-500\n01-11-2021,-500\n02-11-2021,-500\n03-11-2021,-500\n04-11-2021,-500\n05-11-2021,-500\n08-11-2021,-500\n09-11-2021,-500\n10-11-2021,-500\n11-11-2021,-500\n12-11-2021,-500\n15-11-2021,-500\n16-11-2021,-500\n17-11-2021,-500\n18-11-2021,-500\n19-11-2021,-500\n22-11-2021,-500\n23-11-2021,-500\n24-11-2021,-500\n25-11-2021,-500\n26-11-2021,-500\n29-11-2021,-500\n30-11-2021,-500\n01-12-2021,-500\n02-12-2021,-500\n03-12-2021,-500\n06-12-2021,-500\n07-12-2021,-500\n08-12-2021,-500\n09-12-2021,-500\n10-12-2021,-500\n13-12-2021,-500\n14-12-2021,-500\n15-12-2021,-500\n16-12-2021,-500\n17-12-2021,-500\n20-12-2021,-500\n21-12-2021,-500\n22-12-2021,-500\n23-12-2021,-500\n24-12-2021,-500\n27-12-2021,-500\n28-12-2021,-500\n29-12-2021,-500\n30-12-2021,-500\n31-12-2021,-500\n03-01-2022,-500\n04-01-2022,-500\n05-01-2022,-500\n06-01-2022,-500\n07-01-2022,-500\n10-01-2022,-500\n11-01-2022,-500\n12-01-2022,-500\n13-01-2022,-500\n14-01-2022,-500\n17-01-2022,-500\n18-01-2022,-500\n19-01-2022,-500\n20-01-2022,-500\n21-01-2022,-500\n24-01-2022,-500\n25-01-2022,-500\n26-01-2022,-500\n27-01-2022,-500\n28-01-2022,-500\n31-01-2022,-500\n01-02-2022,-500\n02-02-2022,-500\n03-02-2022,-500\n04-02-2022,-500\n07-02-2022,-500\n08-02-2022,-500\n09-02-2022,-500\n10-02-2022,-500\n11-02-2022,-500\n14-02-2022,-500\n15-02-2022,-500\n16-02-2022,-500\n17-02-2022,-500\n18-02-2022,-500\n21-02-2022,-500\n22-02-2022,-500\n23-02-2022,-500\n24-02-2022,-500\n25-02-2022,-500\n28-02-2022,-500\n01-03-2022,-500\n02-03-2022,-500\n03-03-2022,-500\n04-03-2022,-500\n07-03-2022,-500\n08-03-2022,-500\n09-03-2022,-500\n10-03-2022,-500\n11-03-2022,-500\n14-03-2022,-500\n15-03-2022,-500\n16-03-2022,-500\n17-03-2022,-500\n18-03-2022,-500\n21-03-2022,-500\n22-03-2022,-500\n23-03-2022,-500\n24-03-2022,-500\n25-03-2022,-500\n28-03-2022,-500\n29-03-2022,-500\n30-03-2022,-500\n31-03-2022,-500\n01-04-2022,-500\n04-04-2022,-500\n05-04-2022,-500\n06-04-2022,-500\n07-04-2022,-500\n08-04-2022,-500\n11-04-2022,-500\n12-04-2022,-500\n13-04-2022,-500\n14-04-2022,-500\n19-04-2022,-500\n20-04-2022,-500\n21-04-2022,-500\n22-04-2022,-500\n25-04-2022,-500\n26-04-2022,-500\n27-04-2022,-500\n28-04-2022,-500\n29-04-2022,-500\n02-05-2022,-500\n03-05-2022,-500\n04-05-2022,-500\n05-05-2022,-500\n06-05-2022,-500\n09-05-2022,-500\n10-05-2022,-500\n11-05-2022,-500\n12-05-2022,-500\n13-05-2022,-500\n16-05-2022,-500\n17-05-2022,-500\n18-05-2022,-500\n19-05-2022,-500\n20-05-2022,-500\n23-05-2022,-500\n24-05-2022,-500\n25-05-2022,-500\n26-05-2022,-500\n27-05-2022,-500\n30-05-2022,-500\n31-05-2022,-500\n01-06-2022,-500\n02-06-2022,-500\n03-06-2022,-500\n06-06-2022,-500\n07-06-2022,-500\n08-06-2022,-500\n09-06-2022,-500\n10-06-2022,-500\n13-06-2022,-500\n14-06-2022,-500\n15-06-2022,-500\n16-06-2022,-500\n17-06-2022,-500\n20-06-2022,-500\n21-06-2022,-500\n22-06-2022,-500\n23-06-2022,-500\n24-06-2022,-500\n27-06-2022,-500\n28-06-2022,-500\n29-06-2022,-500\n30-06-2022,-500\n01-07-2022,-500\n04-07-2022,-500\n05-07-2022,-500\n06-07-2022,-500\n07-07-2022,-500\n08-07-2022,-500\n11-07-2022,-500\n12-07-2022,-500\n13-07-2022,-500\n14-07-2022,-500\n15-07-2022,-500\n18-07-2022,-500\n19-07-2022,-500\n20-07-2022,-500\n21-07-2022,-500\n22-07-2022,-500\n25-07-2022,-500\n26-07-2022,-500\n27-07-2022,-500\n28-07-2022,-500\n29-07-2022,-500\n01-08-2022,-500\n02-08-2022,-500\n03-08-2022,-500\n04-08-2022,-500\n05-08-2022,-500\n08-08-2022,-500\n09-08-2022,-500\n10-08-2022,-500\n11-08-2022,-500\n12-08-2022,-500\n15-08-2022,-500\n16-08-2022,-500\n17-08-2022,-500\n18-08-2022,-500\n19-08-2022,-500\n22-08-2022,-500\n23-08-2022,-500\n24-08-2022,-500\n25-08-2022,-500\n26-08-2022,-500\n29-08-2022,-500\n30-08-2022,-500\n31-08-2022,-500\n01-09-2022,-500\n02-09-2022,-500\n05-09-2022,-500\n06-09-2022,-500\n07-09-2022,-500\n08-09-2022,-500\n09-09-2022,-500\n12-09-2022,-500\n13-09-2022,-500\n14-09-2022,-500\n15-09-2022,-500\n16-09-2022,-500\n19-09-2022,-500\n20-09-2022,-500\n21-09-2022,-500\n22-09-2022,-500\n23-09-2022,-500\n26-09-2022,-500\n27-09-2022,-500\n28-09-2022,-500\n29-09-2022,-500\n30-09-2022,-500\n03-10-2022,-500\n04-10-2022,-500\n05-10-2022,-500\n06-10-2022,-500\n07-10-2022,-500\n10-10-2022,-500\n11-10-2022,-500\n12-10-2022,-500\n13-10-2022,-500\n14-10-2022,-500\n17-10-2022,-500\n18-10-2022,-500\n19-10-2022,-500\n20-10-2022,-500\n21-10-2022,-500\n24-10-2022,-500\n25-10-2022,-500\n26-10-2022,-500\n27-10-2022,-500\n28-10-2022,-500\n31-10-2022,-500\n01-11-2022,-500\n02-11-2022,-500\n03-11-2022,-500\n04-11-2022,-500\n07-11-2022,-500\n08-11-2022,-500\n09-11-2022,-500\n10-11-2022,-500\n11-11-2022,-500\n14-11-2022,-500\n15-11-2022,-500\n16-11-2022,-500\n17-11-2022,-500\n18-11-2022,-500\n21-11-2022,-500\n22-11-2022,-500\n23-11-2022,-500\n24-11-2022,-500\n25-11-2022,-500\n28-11-2022,-500\n29-11-2022,-500\n30-11-2022,-500\n01-12-2022,-500\n02-12-2022,-500\n05-12-2022,-500\n06-12-2022,-500\n07-12-2022,-500\n08-12-2022,-500\n09-12-2022,-500\n12-12-2022,-500\n13-12-2022,-500\n14-12-2022,-500\n15-12-2022,-500\n16-12-2022,-500\n19-12-2022,-500\n20-12-2022,-500\n21-12-2022,-500\n22-12-2022,-500\n23-12-2022,-500\n27-12-2022,-500\n28-12-2022,-500\n29-12-2022,-500\n30-12-2022,-500\n02-01-2023,-500\n03-01-2023,-500\n04-01-2023,-500\n05-01-2023,-500\n06-01-2023,-500\n09-01-2023,-500\n10-01-2023,-500\n11-01-2023,-500\n12-01-2023,-500\n13-01-2023,-500\n16-01-2023,-500\n17-01-2023,-500\n18-01-2023,-500\n19-01-2023,-500\n20-01-2023,-500\n23-01-2023,-500\n24-01-2023,-500\n25-01-2023,-500\n26-01-2023,-500\n27-01-2023,-500\n30-01-2023,-500\n31-01-2023,-500\n01-02-2023,-500\n02-02-2023,-500\n03-02-2023,-500\n06-02-2023,-500\n07-02-2023,-500\n08-02-2023,-500\n09-02-2023,-500\n10-02-2023,-500\n13-02-2023,-500\n14-02-2023,-500\n15-02-2023,-500\n16-02-2023,-500\n17-02-2023,-500\n20-02-2023,-500\n21-02-2023,-500\n22-02-2023,-500\n23-02-2023,-500\n24-02-2023,-500\n27-02-2023,-500\n28-02-2023,-500\n01-03-2023,-500\n02-03-2023,-500\n03-03-2023,-500\n06-03-2023,-500\n07-03-2023,-500\n08-03-2023,-500\n09-03-2023,-500\n10-03-2023,-500\n13-03-2023,-500\n14-03-2023,-500\n15-03-2023,-500\n16-03-2023,-500\n17-03-2023,-500\n20-03-2023,-500\n21-03-2023,-500\n22-03-2023,-500\n23-03-2023,-500\n24-03-2023,-500\n27-03-2023,-500\n28-03-2023,-500\n29-03-2023,-500\n30-03-2023,-500\n31-03-2023,-500\n03-04-2023,-500\n04-04-2023,-500\n05-04-2023,-500\n06-04-2023,-500\n11-04-2023,-500\n12-04-2023,-500\n13-04-2023,-500\n14-04-2023,-500\n17-04-2023,-500\n18-04-2023,-500\n19-04-2023,-500\n20-04-2023,-500\n21-04-2023,-500\n24-04-2023,-500\n25-04-2023,-500\n26-04-2023,-500\n27-04-2023,-500\n28-04-2023,-500\n02-05-2023,-500\n03-05-2023,-500\n04-05-2023,-500\n05-05-2023,-500\n08-05-2023,-500\n09-05-2023,-500\n10-05-2023,-500\n11-05-2023,-500\n12-05-2023,-500\n15-05-2023,-500\n16-05-2023,-500\n17-05-2023,-500\n18-05-2023,-500\n19-05-2023,-500\n22-05-2023,-500\n23-05-2023,-500\n24-05-2023,-500\n25-05-2023,-500\n26-05-2023,-500\n29-05-2023,-500\n30-05-2023,-500\n31-05-2023,-500\n01-06-2023,-500\n02-06-2023,-500\n05-06-2023,-500\n06-06-2023,-500\n07-06-2023,-500\n08-06-2023,-500\n09-06-2023,-500\n12-06-2023,-500\n13-06-2023,-500\n14-06-2023,-500\n15-06-2023,-500\n16-06-2023,-500\n19-06-2023,-500\n20-06-2023,-500\n21-06-2023,-500\n22-06-2023,-500\n23-06-2023,-500\n26-06-2023,-500\n27-06-2023,-500\n28-06-2023,-500\n29-06-2023,-500\n30-06-2023,-500\n03-07-2023,-500\n04-07-2023,-500\n05-07-2023,-500\n06-07-2023,-500\n07-07-2023,-500\n10-07-2023,-500\n11-07-2023,-500\n12-07-2023,-500\n13-07-2023,-500\n14-07-2023,-500\n17-07-2023,-500\n18-07-2023,-500\n19-07-2023,-500\n20-07-2023,-500\n21-07-2023,-500\n24-07-2023,-500\n25-07-2023,-500\n26-07-2023,-500\n27-07-2023,-500\n28-07-2023,-500\n31-07-2023,-500\n01-08-2023,-500\n02-08-2023,-500"
  },
  {
    "path": "python/rateslib/data/historical/eur_rfr.csv",
    "content": "﻿reference_date,rate\n01-10-2019,-500\n02-10-2019,-500\n03-10-2019,-500\n04-10-2019,-500\n07-10-2019,-500\n08-10-2019,-500\n09-10-2019,-500\n10-10-2019,-500\n11-10-2019,-500\n14-10-2019,-500\n15-10-2019,-500\n16-10-2019,-500\n17-10-2019,-500\n18-10-2019,-500\n21-10-2019,-500\n22-10-2019,-500\n23-10-2019,-500\n24-10-2019,-500\n25-10-2019,-500\n28-10-2019,-500\n29-10-2019,-500\n30-10-2019,-500\n31-10-2019,-500\n01-11-2019,-500\n04-11-2019,-500\n05-11-2019,-500\n06-11-2019,-500\n07-11-2019,-500\n08-11-2019,-500\n11-11-2019,-500\n12-11-2019,-500\n13-11-2019,-500\n14-11-2019,-500\n15-11-2019,-500\n18-11-2019,-500\n19-11-2019,-500\n20-11-2019,-500\n21-11-2019,-500\n22-11-2019,-500\n25-11-2019,-500\n26-11-2019,-500\n27-11-2019,-500\n28-11-2019,-500\n29-11-2019,-500\n02-12-2019,-500\n03-12-2019,-500\n04-12-2019,-500\n05-12-2019,-500\n06-12-2019,-500\n09-12-2019,-500\n10-12-2019,-500\n11-12-2019,-500\n12-12-2019,-500\n13-12-2019,-500\n16-12-2019,-500\n17-12-2019,-500\n18-12-2019,-500\n19-12-2019,-500\n20-12-2019,-500\n23-12-2019,-500\n24-12-2019,-500\n27-12-2019,-500\n30-12-2019,-500\n31-12-2019,-500\n02-01-2020,-500\n03-01-2020,-500\n06-01-2020,-500\n07-01-2020,-500\n08-01-2020,-500\n09-01-2020,-500\n10-01-2020,-500\n13-01-2020,-500\n14-01-2020,-500\n15-01-2020,-500\n16-01-2020,-500\n17-01-2020,-500\n20-01-2020,-500\n21-01-2020,-500\n22-01-2020,-500\n23-01-2020,-500\n24-01-2020,-500\n27-01-2020,-500\n28-01-2020,-500\n29-01-2020,-500\n30-01-2020,-500\n31-01-2020,-500\n03-02-2020,-500\n04-02-2020,-500\n05-02-2020,-500\n06-02-2020,-500\n07-02-2020,-500\n10-02-2020,-500\n11-02-2020,-500\n12-02-2020,-500\n13-02-2020,-500\n14-02-2020,-500\n17-02-2020,-500\n18-02-2020,-500\n19-02-2020,-500\n20-02-2020,-500\n21-02-2020,-500\n24-02-2020,-500\n25-02-2020,-500\n26-02-2020,-500\n27-02-2020,-500\n28-02-2020,-500\n02-03-2020,-500\n03-03-2020,-500\n04-03-2020,-500\n05-03-2020,-500\n06-03-2020,-500\n09-03-2020,-500\n10-03-2020,-500\n11-03-2020,-500\n12-03-2020,-500\n13-03-2020,-500\n16-03-2020,-500\n17-03-2020,-500\n18-03-2020,-500\n19-03-2020,-500\n20-03-2020,-500\n23-03-2020,-500\n24-03-2020,-500\n25-03-2020,-500\n26-03-2020,-500\n27-03-2020,-500\n30-03-2020,-500\n31-03-2020,-500\n01-04-2020,-500\n02-04-2020,-500\n03-04-2020,-500\n06-04-2020,-500\n07-04-2020,-500\n08-04-2020,-500\n09-04-2020,-500\n14-04-2020,-500\n15-04-2020,-500\n16-04-2020,-500\n17-04-2020,-500\n20-04-2020,-500\n21-04-2020,-500\n22-04-2020,-500\n23-04-2020,-500\n24-04-2020,-500\n27-04-2020,-500\n28-04-2020,-500\n29-04-2020,-500\n30-04-2020,-500\n04-05-2020,-500\n05-05-2020,-500\n06-05-2020,-500\n07-05-2020,-500\n08-05-2020,-500\n11-05-2020,-500\n12-05-2020,-500\n13-05-2020,-500\n14-05-2020,-500\n15-05-2020,-500\n18-05-2020,-500\n19-05-2020,-500\n20-05-2020,-500\n21-05-2020,-500\n22-05-2020,-500\n25-05-2020,-500\n26-05-2020,-500\n27-05-2020,-500\n28-05-2020,-500\n29-05-2020,-500\n01-06-2020,-500\n02-06-2020,-500\n03-06-2020,-500\n04-06-2020,-500\n05-06-2020,-500\n08-06-2020,-500\n09-06-2020,-500\n10-06-2020,-500\n11-06-2020,-500\n12-06-2020,-500\n15-06-2020,-500\n16-06-2020,-500\n17-06-2020,-500\n18-06-2020,-500\n19-06-2020,-500\n22-06-2020,-500\n23-06-2020,-500\n24-06-2020,-500\n25-06-2020,-500\n26-06-2020,-500\n29-06-2020,-500\n30-06-2020,-500\n01-07-2020,-500\n02-07-2020,-500\n03-07-2020,-500\n06-07-2020,-500\n07-07-2020,-500\n08-07-2020,-500\n09-07-2020,-500\n10-07-2020,-500\n13-07-2020,-500\n14-07-2020,-500\n15-07-2020,-500\n16-07-2020,-500\n17-07-2020,-500\n20-07-2020,-500\n21-07-2020,-500\n22-07-2020,-500\n23-07-2020,-500\n24-07-2020,-500\n27-07-2020,-500\n28-07-2020,-500\n29-07-2020,-500\n30-07-2020,-500\n31-07-2020,-500\n03-08-2020,-500\n04-08-2020,-500\n05-08-2020,-500\n06-08-2020,-500\n07-08-2020,-500\n10-08-2020,-500\n11-08-2020,-500\n12-08-2020,-500\n13-08-2020,-500\n14-08-2020,-500\n17-08-2020,-500\n18-08-2020,-500\n19-08-2020,-500\n20-08-2020,-500\n21-08-2020,-500\n24-08-2020,-500\n25-08-2020,-500\n26-08-2020,-500\n27-08-2020,-500\n28-08-2020,-500\n31-08-2020,-500\n01-09-2020,-500\n02-09-2020,-500\n03-09-2020,-500\n04-09-2020,-500\n07-09-2020,-500\n08-09-2020,-500\n09-09-2020,-500\n10-09-2020,-500\n11-09-2020,-500\n14-09-2020,-500\n15-09-2020,-500\n16-09-2020,-500\n17-09-2020,-500\n18-09-2020,-500\n21-09-2020,-500\n22-09-2020,-500\n23-09-2020,-500\n24-09-2020,-500\n25-09-2020,-500\n28-09-2020,-500\n29-09-2020,-500\n30-09-2020,-500\n01-10-2020,-500\n02-10-2020,-500\n05-10-2020,-500\n06-10-2020,-500\n07-10-2020,-500\n08-10-2020,-500\n09-10-2020,-500\n12-10-2020,-500\n13-10-2020,-500\n14-10-2020,-500\n15-10-2020,-500\n16-10-2020,-500\n19-10-2020,-500\n20-10-2020,-500\n21-10-2020,-500\n22-10-2020,-500\n23-10-2020,-500\n26-10-2020,-500\n27-10-2020,-500\n28-10-2020,-500\n29-10-2020,-500\n30-10-2020,-500\n02-11-2020,-500\n03-11-2020,-500\n04-11-2020,-500\n05-11-2020,-500\n06-11-2020,-500\n09-11-2020,-500\n10-11-2020,-500\n11-11-2020,-500\n12-11-2020,-500\n13-11-2020,-500\n16-11-2020,-500\n17-11-2020,-500\n18-11-2020,-500\n19-11-2020,-500\n20-11-2020,-500\n23-11-2020,-500\n24-11-2020,-500\n25-11-2020,-500\n26-11-2020,-500\n27-11-2020,-500\n30-11-2020,-500\n01-12-2020,-500\n02-12-2020,-500\n03-12-2020,-500\n04-12-2020,-500\n07-12-2020,-500\n08-12-2020,-500\n09-12-2020,-500\n10-12-2020,-500\n11-12-2020,-500\n14-12-2020,-500\n15-12-2020,-500\n16-12-2020,-500\n17-12-2020,-500\n18-12-2020,-500\n21-12-2020,-500\n22-12-2020,-500\n23-12-2020,-500\n24-12-2020,-500\n28-12-2020,-500\n29-12-2020,-500\n30-12-2020,-500\n31-12-2020,-500\n04-01-2021,-500\n05-01-2021,-500\n06-01-2021,-500\n07-01-2021,-500\n08-01-2021,-500\n11-01-2021,-500\n12-01-2021,-500\n13-01-2021,-500\n14-01-2021,-500\n15-01-2021,-500\n18-01-2021,-500\n19-01-2021,-500\n20-01-2021,-500\n21-01-2021,-500\n22-01-2021,-500\n25-01-2021,-500\n26-01-2021,-500\n27-01-2021,-500\n28-01-2021,-500\n29-01-2021,-500\n01-02-2021,-500\n02-02-2021,-500\n03-02-2021,-500\n04-02-2021,-500\n05-02-2021,-500\n08-02-2021,-500\n09-02-2021,-500\n10-02-2021,-500\n11-02-2021,-500\n12-02-2021,-500\n15-02-2021,-500\n16-02-2021,-500\n17-02-2021,-500\n18-02-2021,-500\n19-02-2021,-500\n22-02-2021,-500\n23-02-2021,-500\n24-02-2021,-500\n25-02-2021,-500\n26-02-2021,-500\n01-03-2021,-500\n02-03-2021,-500\n03-03-2021,-500\n04-03-2021,-500\n05-03-2021,-500\n08-03-2021,-500\n09-03-2021,-500\n10-03-2021,-500\n11-03-2021,-500\n12-03-2021,-500\n15-03-2021,-500\n16-03-2021,-500\n17-03-2021,-500\n18-03-2021,-500\n19-03-2021,-500\n22-03-2021,-500\n23-03-2021,-500\n24-03-2021,-500\n25-03-2021,-500\n26-03-2021,-500\n29-03-2021,-500\n30-03-2021,-500\n31-03-2021,-500\n01-04-2021,-500\n06-04-2021,-500\n07-04-2021,-500\n08-04-2021,-500\n09-04-2021,-500\n12-04-2021,-500\n13-04-2021,-500\n14-04-2021,-500\n15-04-2021,-500\n16-04-2021,-500\n19-04-2021,-500\n20-04-2021,-500\n21-04-2021,-500\n22-04-2021,-500\n23-04-2021,-500\n26-04-2021,-500\n27-04-2021,-500\n28-04-2021,-500\n29-04-2021,-500\n30-04-2021,-500\n03-05-2021,-500\n04-05-2021,-500\n05-05-2021,-500\n06-05-2021,-500\n07-05-2021,-500\n10-05-2021,-500\n11-05-2021,-500\n12-05-2021,-500\n13-05-2021,-500\n14-05-2021,-500\n17-05-2021,-500\n18-05-2021,-500\n19-05-2021,-500\n20-05-2021,-500\n21-05-2021,-500\n24-05-2021,-500\n25-05-2021,-500\n26-05-2021,-500\n27-05-2021,-500\n28-05-2021,-500\n31-05-2021,-500\n01-06-2021,-500\n02-06-2021,-500\n03-06-2021,-500\n04-06-2021,-500\n07-06-2021,-500\n08-06-2021,-500\n09-06-2021,-500\n10-06-2021,-500\n11-06-2021,-500\n14-06-2021,-500\n15-06-2021,-500\n16-06-2021,-500\n17-06-2021,-500\n18-06-2021,-500\n21-06-2021,-500\n22-06-2021,-500\n23-06-2021,-500\n24-06-2021,-500\n25-06-2021,-500\n28-06-2021,-500\n29-06-2021,-500\n30-06-2021,-500\n01-07-2021,-500\n02-07-2021,-500\n05-07-2021,-500\n06-07-2021,-500\n07-07-2021,-500\n08-07-2021,-500\n09-07-2021,-500\n12-07-2021,-500\n13-07-2021,-500\n14-07-2021,-500\n15-07-2021,-500\n16-07-2021,-500\n19-07-2021,-500\n20-07-2021,-500\n21-07-2021,-500\n22-07-2021,-500\n23-07-2021,-500\n26-07-2021,-500\n27-07-2021,-500\n28-07-2021,-500\n29-07-2021,-500\n30-07-2021,-500\n02-08-2021,-500\n03-08-2021,-500\n04-08-2021,-500\n05-08-2021,-500\n06-08-2021,-500\n09-08-2021,-500\n10-08-2021,-500\n11-08-2021,-500\n12-08-2021,-500\n13-08-2021,-500\n16-08-2021,-500\n17-08-2021,-500\n18-08-2021,-500\n19-08-2021,-500\n20-08-2021,-500\n23-08-2021,-500\n24-08-2021,-500\n25-08-2021,-500\n26-08-2021,-500\n27-08-2021,-500\n30-08-2021,-500\n31-08-2021,-500\n01-09-2021,-500\n02-09-2021,-500\n03-09-2021,-500\n06-09-2021,-500\n07-09-2021,-500\n08-09-2021,-500\n09-09-2021,-500\n10-09-2021,-500\n13-09-2021,-500\n14-09-2021,-500\n15-09-2021,-500\n16-09-2021,-500\n17-09-2021,-500\n20-09-2021,-500\n21-09-2021,-500\n22-09-2021,-500\n23-09-2021,-500\n24-09-2021,-500\n27-09-2021,-500\n28-09-2021,-500\n29-09-2021,-500\n30-09-2021,-500\n01-10-2021,-500\n04-10-2021,-500\n05-10-2021,-500\n06-10-2021,-500\n07-10-2021,-500\n08-10-2021,-500\n11-10-2021,-500\n12-10-2021,-500\n13-10-2021,-500\n14-10-2021,-500\n15-10-2021,-500\n18-10-2021,-500\n19-10-2021,-500\n20-10-2021,-500\n21-10-2021,-500\n22-10-2021,-500\n25-10-2021,-500\n26-10-2021,-500\n27-10-2021,-500\n28-10-2021,-500\n29-10-2021,-500\n01-11-2021,-500\n02-11-2021,-500\n03-11-2021,-500\n04-11-2021,-500\n05-11-2021,-500\n08-11-2021,-500\n09-11-2021,-500\n10-11-2021,-500\n11-11-2021,-500\n12-11-2021,-500\n15-11-2021,-500\n16-11-2021,-500\n17-11-2021,-500\n18-11-2021,-500\n19-11-2021,-500\n22-11-2021,-500\n23-11-2021,-500\n24-11-2021,-500\n25-11-2021,-500\n26-11-2021,-500\n29-11-2021,-500\n30-11-2021,-500\n01-12-2021,-500\n02-12-2021,-500\n03-12-2021,-500\n06-12-2021,-500\n07-12-2021,-500\n08-12-2021,-500\n09-12-2021,-500\n10-12-2021,-500\n13-12-2021,-500\n14-12-2021,-500\n15-12-2021,-500\n16-12-2021,-500\n17-12-2021,-500\n20-12-2021,-500\n21-12-2021,-500\n22-12-2021,-500\n23-12-2021,-500\n24-12-2021,-500\n27-12-2021,-500\n28-12-2021,-500\n29-12-2021,-500\n30-12-2021,-500\n31-12-2021,-500\n03-01-2022,-500\n04-01-2022,-500\n05-01-2022,-500\n06-01-2022,-500\n07-01-2022,-500\n10-01-2022,-500\n11-01-2022,-500\n12-01-2022,-500\n13-01-2022,-500\n14-01-2022,-500\n17-01-2022,-500\n18-01-2022,-500\n19-01-2022,-500\n20-01-2022,-500\n21-01-2022,-500\n24-01-2022,-500\n25-01-2022,-500\n26-01-2022,-500\n27-01-2022,-500\n28-01-2022,-500\n31-01-2022,-500\n01-02-2022,-500\n02-02-2022,-500\n03-02-2022,-500\n04-02-2022,-500\n07-02-2022,-500\n08-02-2022,-500\n09-02-2022,-500\n10-02-2022,-500\n11-02-2022,-500\n14-02-2022,-500\n15-02-2022,-500\n16-02-2022,-500\n17-02-2022,-500\n18-02-2022,-500\n21-02-2022,-500\n22-02-2022,-500\n23-02-2022,-500\n24-02-2022,-500\n25-02-2022,-500\n28-02-2022,-500\n01-03-2022,-500\n02-03-2022,-500\n03-03-2022,-500\n04-03-2022,-500\n07-03-2022,-500\n08-03-2022,-500\n09-03-2022,-500\n10-03-2022,-500\n11-03-2022,-500\n14-03-2022,-500\n15-03-2022,-500\n16-03-2022,-500\n17-03-2022,-500\n18-03-2022,-500\n21-03-2022,-500\n22-03-2022,-500\n23-03-2022,-500\n24-03-2022,-500\n25-03-2022,-500\n28-03-2022,-500\n29-03-2022,-500\n30-03-2022,-500\n31-03-2022,-500\n01-04-2022,-500\n04-04-2022,-500\n05-04-2022,-500\n06-04-2022,-500\n07-04-2022,-500\n08-04-2022,-500\n11-04-2022,-500\n12-04-2022,-500\n13-04-2022,-500\n14-04-2022,-500\n19-04-2022,-500\n20-04-2022,-500\n21-04-2022,-500\n22-04-2022,-500\n25-04-2022,-500\n26-04-2022,-500\n27-04-2022,-500\n28-04-2022,-500\n29-04-2022,-500\n02-05-2022,-500\n03-05-2022,-500\n04-05-2022,-500\n05-05-2022,-500\n06-05-2022,-500\n09-05-2022,-500\n10-05-2022,-500\n11-05-2022,-500\n12-05-2022,-500\n13-05-2022,-500\n16-05-2022,-500\n17-05-2022,-500\n18-05-2022,-500\n19-05-2022,-500\n20-05-2022,-500\n23-05-2022,-500\n24-05-2022,-500\n25-05-2022,-500\n26-05-2022,-500\n27-05-2022,-500\n30-05-2022,-500\n31-05-2022,-500\n01-06-2022,-500\n02-06-2022,-500\n03-06-2022,-500\n06-06-2022,-500\n07-06-2022,-500\n08-06-2022,-500\n09-06-2022,-500\n10-06-2022,-500\n13-06-2022,-500\n14-06-2022,-500\n15-06-2022,-500\n16-06-2022,-500\n17-06-2022,-500\n20-06-2022,-500\n21-06-2022,-500\n22-06-2022,-500\n23-06-2022,-500\n24-06-2022,-500\n27-06-2022,-500\n28-06-2022,-500\n29-06-2022,-500\n30-06-2022,-500\n01-07-2022,-500\n04-07-2022,-500\n05-07-2022,-500\n06-07-2022,-500\n07-07-2022,-500\n08-07-2022,-500\n11-07-2022,-500\n12-07-2022,-500\n13-07-2022,-500\n14-07-2022,-500\n15-07-2022,-500\n18-07-2022,-500\n19-07-2022,-500\n20-07-2022,-500\n21-07-2022,-500\n22-07-2022,-500\n25-07-2022,-500\n26-07-2022,-500\n27-07-2022,-500\n28-07-2022,-500\n29-07-2022,-500\n01-08-2022,-500\n02-08-2022,-500\n03-08-2022,-500\n04-08-2022,-500\n05-08-2022,-500\n08-08-2022,-500\n09-08-2022,-500\n10-08-2022,-500\n11-08-2022,-500\n12-08-2022,-500\n15-08-2022,-500\n16-08-2022,-500\n17-08-2022,-500\n18-08-2022,-500\n19-08-2022,-500\n22-08-2022,-500\n23-08-2022,-500\n24-08-2022,-500\n25-08-2022,-500\n26-08-2022,-500\n29-08-2022,-500\n30-08-2022,-500\n31-08-2022,-500\n01-09-2022,-500\n02-09-2022,-500\n05-09-2022,-500\n06-09-2022,-500\n07-09-2022,-500\n08-09-2022,-500\n09-09-2022,-500\n12-09-2022,-500\n13-09-2022,-500\n14-09-2022,-500\n15-09-2022,-500\n16-09-2022,-500\n19-09-2022,-500\n20-09-2022,-500\n21-09-2022,-500\n22-09-2022,-500\n23-09-2022,-500\n26-09-2022,-500\n27-09-2022,-500\n28-09-2022,-500\n29-09-2022,-500\n30-09-2022,-500\n03-10-2022,-500\n04-10-2022,-500\n05-10-2022,-500\n06-10-2022,-500\n07-10-2022,-500\n10-10-2022,-500\n11-10-2022,-500\n12-10-2022,-500\n13-10-2022,-500\n14-10-2022,-500\n17-10-2022,-500\n18-10-2022,-500\n19-10-2022,-500\n20-10-2022,-500\n21-10-2022,-500\n24-10-2022,-500\n25-10-2022,-500\n26-10-2022,-500\n27-10-2022,-500\n28-10-2022,-500\n31-10-2022,-500\n01-11-2022,-500\n02-11-2022,-500\n03-11-2022,-500\n04-11-2022,-500\n07-11-2022,-500\n08-11-2022,-500\n09-11-2022,-500\n10-11-2022,-500\n11-11-2022,-500\n14-11-2022,-500\n15-11-2022,-500\n16-11-2022,-500\n17-11-2022,-500\n18-11-2022,-500\n21-11-2022,-500\n22-11-2022,-500\n23-11-2022,-500\n24-11-2022,-500\n25-11-2022,-500\n28-11-2022,-500\n29-11-2022,-500\n30-11-2022,-500\n01-12-2022,-500\n02-12-2022,-500\n05-12-2022,-500\n06-12-2022,-500\n07-12-2022,-500\n08-12-2022,-500\n09-12-2022,-500\n12-12-2022,-500\n13-12-2022,-500\n14-12-2022,-500\n15-12-2022,-500\n16-12-2022,-500\n19-12-2022,-500\n20-12-2022,-500\n21-12-2022,-500\n22-12-2022,-500\n23-12-2022,-500\n27-12-2022,-500\n28-12-2022,-500\n29-12-2022,-500\n30-12-2022,-500\n02-01-2023,-500\n03-01-2023,-500\n04-01-2023,-500\n05-01-2023,-500\n06-01-2023,-500\n09-01-2023,-500\n10-01-2023,-500\n11-01-2023,-500\n12-01-2023,-500\n13-01-2023,-500\n16-01-2023,-500\n17-01-2023,-500\n18-01-2023,-500\n19-01-2023,-500\n20-01-2023,-500\n23-01-2023,-500\n24-01-2023,-500\n25-01-2023,-500\n26-01-2023,-500\n27-01-2023,-500\n30-01-2023,-500\n31-01-2023,-500\n01-02-2023,-500\n02-02-2023,-500\n03-02-2023,-500\n06-02-2023,-500\n07-02-2023,-500\n08-02-2023,-500\n09-02-2023,-500\n10-02-2023,-500\n13-02-2023,-500\n14-02-2023,-500\n15-02-2023,-500\n16-02-2023,-500\n17-02-2023,-500\n20-02-2023,-500\n21-02-2023,-500\n22-02-2023,-500\n23-02-2023,-500\n24-02-2023,-500\n27-02-2023,-500\n28-02-2023,-500\n01-03-2023,-500\n02-03-2023,-500\n03-03-2023,-500\n06-03-2023,-500\n07-03-2023,-500\n08-03-2023,-500\n09-03-2023,-500\n10-03-2023,-500\n13-03-2023,-500\n14-03-2023,-500\n15-03-2023,-500\n16-03-2023,-500\n17-03-2023,-500\n20-03-2023,-500\n21-03-2023,-500\n22-03-2023,-500\n23-03-2023,-500\n24-03-2023,-500\n27-03-2023,-500\n28-03-2023,-500\n29-03-2023,-500\n30-03-2023,-500\n31-03-2023,-500\n03-04-2023,-500\n04-04-2023,-500\n05-04-2023,-500\n06-04-2023,-500\n11-04-2023,-500\n12-04-2023,-500\n13-04-2023,-500\n14-04-2023,-500\n17-04-2023,-500\n18-04-2023,-500\n19-04-2023,-500\n20-04-2023,-500\n21-04-2023,-500\n24-04-2023,-500\n25-04-2023,-500\n26-04-2023,-500\n27-04-2023,-500\n28-04-2023,-500\n02-05-2023,-500\n03-05-2023,-500\n04-05-2023,-500\n05-05-2023,-500\n08-05-2023,-500\n09-05-2023,-500\n10-05-2023,-500\n11-05-2023,-500\n12-05-2023,-500\n15-05-2023,-500\n16-05-2023,-500\n17-05-2023,-500\n18-05-2023,-500\n19-05-2023,-500\n22-05-2023,-500\n23-05-2023,-500\n24-05-2023,-500\n25-05-2023,-500\n26-05-2023,-500\n29-05-2023,-500\n30-05-2023,-500\n31-05-2023,-500\n01-06-2023,-500\n02-06-2023,-500\n05-06-2023,-500\n06-06-2023,-500\n07-06-2023,-500\n08-06-2023,-500\n09-06-2023,-500\n12-06-2023,-500\n13-06-2023,-500\n14-06-2023,-500\n15-06-2023,-500\n16-06-2023,-500\n19-06-2023,-500\n20-06-2023,-500\n21-06-2023,-500\n22-06-2023,-500\n23-06-2023,-500\n26-06-2023,-500\n27-06-2023,-500\n28-06-2023,-500\n29-06-2023,-500\n30-06-2023,-500\n03-07-2023,-500\n04-07-2023,-500\n05-07-2023,-500\n06-07-2023,-500\n07-07-2023,-500\n10-07-2023,-500\n11-07-2023,-500\n12-07-2023,-500\n13-07-2023,-500\n14-07-2023,-500\n17-07-2023,-500\n18-07-2023,-500\n19-07-2023,-500\n20-07-2023,-500\n21-07-2023,-500\n24-07-2023,-500\n25-07-2023,-500\n26-07-2023,-500\n27-07-2023,-500\n28-07-2023,-500\n31-07-2023,-500\n01-08-2023,-500\n02-08-2023,-500"
  },
  {
    "path": "python/rateslib/data/historical/gbp_rfr.csv",
    "content": "﻿reference_date,rate\n01-08-2023,-500\n31-07-2023,-500\n28-07-2023,-500\n27-07-2023,-500\n26-07-2023,-500\n25-07-2023,-500\n24-07-2023,-500\n21-07-2023,-500\n20-07-2023,-500\n19-07-2023,-500\n18-07-2023,-500\n17-07-2023,-500\n14-07-2023,-500\n13-07-2023,-500\n12-07-2023,-500\n11-07-2023,-500\n10-07-2023,-500\n07-07-2023,-500\n06-07-2023,-500\n05-07-2023,-500\n04-07-2023,-500\n03-07-2023,-500\n30-06-2023,-500\n29-06-2023,-500\n28-06-2023,-500\n27-06-2023,-500\n26-06-2023,-500\n23-06-2023,-500\n22-06-2023,-500\n21-06-2023,-500\n20-06-2023,-500\n19-06-2023,-500\n16-06-2023,-500\n15-06-2023,-500\n14-06-2023,-500\n13-06-2023,-500\n12-06-2023,-500\n09-06-2023,-500\n08-06-2023,-500\n07-06-2023,-500\n06-06-2023,-500\n05-06-2023,-500\n02-06-2023,-500\n01-06-2023,-500\n31-05-2023,-500\n30-05-2023,-500\n26-05-2023,-500\n25-05-2023,-500\n24-05-2023,-500\n23-05-2023,-500\n22-05-2023,-500\n19-05-2023,-500\n18-05-2023,-500\n17-05-2023,-500\n16-05-2023,-500\n15-05-2023,-500\n12-05-2023,-500\n11-05-2023,-500\n10-05-2023,-500\n09-05-2023,-500\n05-05-2023,-500\n04-05-2023,-500\n03-05-2023,-500\n02-05-2023,-500\n28-04-2023,-500\n27-04-2023,-500\n26-04-2023,-500\n25-04-2023,-500\n24-04-2023,-500\n21-04-2023,-500\n20-04-2023,-500\n19-04-2023,-500\n18-04-2023,-500\n17-04-2023,-500\n14-04-2023,-500\n13-04-2023,-500\n12-04-2023,-500\n11-04-2023,-500\n06-04-2023,-500\n05-04-2023,-500\n04-04-2023,-500\n03-04-2023,-500\n31-03-2023,-500\n30-03-2023,-500\n29-03-2023,-500\n28-03-2023,-500\n27-03-2023,-500\n24-03-2023,-500\n23-03-2023,-500\n22-03-2023,-500\n21-03-2023,-500\n20-03-2023,-500\n17-03-2023,-500\n16-03-2023,-500\n15-03-2023,-500\n14-03-2023,-500\n13-03-2023,-500\n10-03-2023,-500\n09-03-2023,-500\n08-03-2023,-500\n07-03-2023,-500\n06-03-2023,-500\n03-03-2023,-500\n02-03-2023,-500\n01-03-2023,-500\n28-02-2023,-500\n27-02-2023,-500\n24-02-2023,-500\n23-02-2023,-500\n22-02-2023,-500\n21-02-2023,-500\n20-02-2023,-500\n17-02-2023,-500\n16-02-2023,-500\n15-02-2023,-500\n14-02-2023,-500\n13-02-2023,-500\n10-02-2023,-500\n09-02-2023,-500\n08-02-2023,-500\n07-02-2023,-500\n06-02-2023,-500\n03-02-2023,-500\n02-02-2023,-500\n01-02-2023,-500\n31-01-2023,-500\n30-01-2023,-500\n27-01-2023,-500\n26-01-2023,-500\n25-01-2023,-500\n24-01-2023,-500\n23-01-2023,-500\n20-01-2023,-500\n19-01-2023,-500\n18-01-2023,-500\n17-01-2023,-500\n16-01-2023,-500\n13-01-2023,-500\n12-01-2023,-500\n11-01-2023,-500\n10-01-2023,-500\n09-01-2023,-500\n06-01-2023,-500\n05-01-2023,-500\n04-01-2023,-500\n03-01-2023,-500\n30-12-2022,-500\n29-12-2022,-500\n28-12-2022,-500\n23-12-2022,-500\n22-12-2022,-500\n21-12-2022,-500\n20-12-2022,-500\n19-12-2022,-500\n16-12-2022,-500\n15-12-2022,-500\n14-12-2022,-500\n13-12-2022,-500\n12-12-2022,-500\n09-12-2022,-500\n08-12-2022,-500\n07-12-2022,-500\n06-12-2022,-500\n05-12-2022,-500\n02-12-2022,-500\n01-12-2022,-500\n30-11-2022,-500\n29-11-2022,-500\n28-11-2022,-500\n25-11-2022,-500\n24-11-2022,-500\n23-11-2022,-500\n22-11-2022,-500\n21-11-2022,-500\n18-11-2022,-500\n17-11-2022,-500\n16-11-2022,-500\n15-11-2022,-500\n14-11-2022,-500\n11-11-2022,-500\n10-11-2022,-500\n09-11-2022,-500\n08-11-2022,-500\n07-11-2022,-500\n04-11-2022,-500\n03-11-2022,-500\n02-11-2022,-500\n01-11-2022,-500\n31-10-2022,-500\n28-10-2022,-500\n27-10-2022,-500\n26-10-2022,-500\n25-10-2022,-500\n24-10-2022,-500\n21-10-2022,-500\n20-10-2022,-500\n19-10-2022,-500\n18-10-2022,-500\n17-10-2022,-500\n14-10-2022,-500\n13-10-2022,-500\n12-10-2022,-500\n11-10-2022,-500\n10-10-2022,-500\n07-10-2022,-500\n06-10-2022,-500\n05-10-2022,-500\n04-10-2022,-500\n03-10-2022,-500\n30-09-2022,-500\n29-09-2022,-500\n28-09-2022,-500\n27-09-2022,-500\n26-09-2022,-500\n23-09-2022,-500\n22-09-2022,-500\n21-09-2022,-500\n20-09-2022,-500\n16-09-2022,-500\n15-09-2022,-500\n14-09-2022,-500\n13-09-2022,-500\n12-09-2022,-500\n09-09-2022,-500\n08-09-2022,-500\n07-09-2022,-500\n06-09-2022,-500\n05-09-2022,-500\n02-09-2022,-500\n01-09-2022,-500\n31-08-2022,-500\n30-08-2022,-500\n26-08-2022,-500\n25-08-2022,-500\n24-08-2022,-500\n23-08-2022,-500\n22-08-2022,-500\n19-08-2022,-500\n18-08-2022,-500\n17-08-2022,-500\n16-08-2022,-500\n15-08-2022,-500\n12-08-2022,-500\n11-08-2022,-500\n10-08-2022,-500\n09-08-2022,-500\n08-08-2022,-500\n05-08-2022,-500\n04-08-2022,-500\n03-08-2022,-500\n02-08-2022,-500\n01-08-2022,-500\n29-07-2022,-500\n28-07-2022,-500\n27-07-2022,-500\n26-07-2022,-500\n25-07-2022,-500\n22-07-2022,-500\n21-07-2022,-500\n20-07-2022,-500\n19-07-2022,-500\n18-07-2022,-500\n15-07-2022,-500\n14-07-2022,-500\n13-07-2022,-500\n12-07-2022,-500\n11-07-2022,-500\n08-07-2022,-500\n07-07-2022,-500\n06-07-2022,-500\n05-07-2022,-500\n04-07-2022,-500\n01-07-2022,-500\n30-06-2022,-500\n29-06-2022,-500\n28-06-2022,-500\n27-06-2022,-500\n24-06-2022,-500\n23-06-2022,-500\n22-06-2022,-500\n21-06-2022,-500\n20-06-2022,-500\n17-06-2022,-500\n16-06-2022,-500\n15-06-2022,-500\n14-06-2022,-500\n13-06-2022,-500\n10-06-2022,-500\n09-06-2022,-500\n08-06-2022,-500\n07-06-2022,-500\n06-06-2022,-500\n01-06-2022,-500\n31-05-2022,-500\n30-05-2022,-500\n27-05-2022,-500\n26-05-2022,-500\n25-05-2022,-500\n24-05-2022,-500\n23-05-2022,-500\n20-05-2022,-500\n19-05-2022,-500\n18-05-2022,-500\n17-05-2022,-500\n16-05-2022,-500\n13-05-2022,-500\n12-05-2022,-500\n11-05-2022,-500\n10-05-2022,-500\n09-05-2022,-500\n06-05-2022,-500\n05-05-2022,-500\n04-05-2022,-500\n03-05-2022,-500\n29-04-2022,-500\n28-04-2022,-500\n27-04-2022,-500\n26-04-2022,-500\n25-04-2022,-500\n22-04-2022,-500\n21-04-2022,-500\n20-04-2022,-500\n19-04-2022,-500\n14-04-2022,-500\n13-04-2022,-500\n12-04-2022,-500\n11-04-2022,-500\n08-04-2022,-500\n07-04-2022,-500\n06-04-2022,-500\n05-04-2022,-500\n04-04-2022,-500\n01-04-2022,-500\n31-03-2022,-500\n30-03-2022,-500\n29-03-2022,-500\n28-03-2022,-500\n25-03-2022,-500\n24-03-2022,-500\n23-03-2022,-500\n22-03-2022,-500\n21-03-2022,-500\n18-03-2022,-500\n17-03-2022,-500\n16-03-2022,-500\n15-03-2022,-500\n14-03-2022,-500\n11-03-2022,-500\n10-03-2022,-500\n09-03-2022,-500\n08-03-2022,-500\n07-03-2022,-500\n04-03-2022,-500\n03-03-2022,-500\n02-03-2022,-500\n01-03-2022,-500\n28-02-2022,-500\n25-02-2022,-500\n24-02-2022,-500\n23-02-2022,-500\n22-02-2022,-500\n21-02-2022,-500\n18-02-2022,-500\n17-02-2022,-500\n16-02-2022,-500\n15-02-2022,-500\n14-02-2022,-500\n11-02-2022,-500\n10-02-2022,-500\n09-02-2022,-500\n08-02-2022,-500\n07-02-2022,-500\n04-02-2022,-500\n03-02-2022,-500\n02-02-2022,-500\n01-02-2022,-500\n31-01-2022,-500\n28-01-2022,-500\n27-01-2022,-500\n26-01-2022,-500\n25-01-2022,-500\n24-01-2022,-500\n21-01-2022,-500\n20-01-2022,-500\n19-01-2022,-500\n18-01-2022,-500\n17-01-2022,-500\n14-01-2022,-500\n13-01-2022,-500\n12-01-2022,-500\n11-01-2022,-500\n10-01-2022,-500\n07-01-2022,-500\n06-01-2022,-500\n05-01-2022,-500\n04-01-2022,-500\n31-12-2021,-500\n30-12-2021,-500\n29-12-2021,-500\n24-12-2021,-500\n23-12-2021,-500\n22-12-2021,-500\n21-12-2021,-500\n20-12-2021,-500\n17-12-2021,-500\n16-12-2021,-500\n15-12-2021,-500\n14-12-2021,-500\n13-12-2021,-500\n10-12-2021,-500\n09-12-2021,-500\n08-12-2021,-500\n07-12-2021,-500\n06-12-2021,-500\n03-12-2021,-500\n02-12-2021,-500\n01-12-2021,-500\n30-11-2021,-500\n29-11-2021,-500\n26-11-2021,-500\n25-11-2021,-500\n24-11-2021,-500\n23-11-2021,-500\n22-11-2021,-500\n19-11-2021,-500\n18-11-2021,-500\n17-11-2021,-500\n16-11-2021,-500\n15-11-2021,-500\n12-11-2021,-500\n11-11-2021,-500\n10-11-2021,-500\n09-11-2021,-500\n08-11-2021,-500\n05-11-2021,-500\n04-11-2021,-500\n03-11-2021,-500\n02-11-2021,-500\n01-11-2021,-500\n29-10-2021,-500\n28-10-2021,-500\n27-10-2021,-500\n26-10-2021,-500\n25-10-2021,-500\n22-10-2021,-500\n21-10-2021,-500\n20-10-2021,-500\n19-10-2021,-500\n18-10-2021,-500\n15-10-2021,-500\n14-10-2021,-500\n13-10-2021,-500\n12-10-2021,-500\n11-10-2021,-500\n08-10-2021,-500\n07-10-2021,-500\n06-10-2021,-500\n05-10-2021,-500\n04-10-2021,-500\n01-10-2021,-500\n30-09-2021,-500\n29-09-2021,-500\n28-09-2021,-500\n27-09-2021,-500\n24-09-2021,-500\n23-09-2021,-500\n22-09-2021,-500\n21-09-2021,-500\n20-09-2021,-500\n17-09-2021,-500\n16-09-2021,-500\n15-09-2021,-500\n14-09-2021,-500\n13-09-2021,-500\n10-09-2021,-500\n09-09-2021,-500\n08-09-2021,-500\n07-09-2021,-500\n06-09-2021,-500\n03-09-2021,-500\n02-09-2021,-500\n01-09-2021,-500\n31-08-2021,-500\n27-08-2021,-500\n26-08-2021,-500\n25-08-2021,-500\n24-08-2021,-500\n23-08-2021,-500\n20-08-2021,-500\n19-08-2021,-500\n18-08-2021,-500\n17-08-2021,-500\n16-08-2021,-500\n13-08-2021,-500\n12-08-2021,-500\n11-08-2021,-500\n10-08-2021,-500\n09-08-2021,-500\n06-08-2021,-500\n05-08-2021,-500\n04-08-2021,-500\n03-08-2021,-500\n02-08-2021,-500\n30-07-2021,-500\n29-07-2021,-500\n28-07-2021,-500\n27-07-2021,-500\n26-07-2021,-500\n23-07-2021,-500\n22-07-2021,-500\n21-07-2021,-500\n20-07-2021,-500\n19-07-2021,-500\n16-07-2021,-500\n15-07-2021,-500\n14-07-2021,-500\n13-07-2021,-500\n12-07-2021,-500\n09-07-2021,-500\n08-07-2021,-500\n07-07-2021,-500\n06-07-2021,-500\n05-07-2021,-500\n02-07-2021,-500\n01-07-2021,-500\n30-06-2021,-500\n29-06-2021,-500\n28-06-2021,-500\n25-06-2021,-500\n24-06-2021,-500\n23-06-2021,-500\n22-06-2021,-500\n21-06-2021,-500\n18-06-2021,-500\n17-06-2021,-500\n16-06-2021,-500\n15-06-2021,-500\n14-06-2021,-500\n11-06-2021,-500\n10-06-2021,-500\n09-06-2021,-500\n08-06-2021,-500\n07-06-2021,-500\n04-06-2021,-500\n03-06-2021,-500\n02-06-2021,-500\n01-06-2021,-500\n28-05-2021,-500\n27-05-2021,-500\n26-05-2021,-500\n25-05-2021,-500\n24-05-2021,-500\n21-05-2021,-500\n20-05-2021,-500\n19-05-2021,-500\n18-05-2021,-500\n17-05-2021,-500\n14-05-2021,-500\n13-05-2021,-500\n12-05-2021,-500\n11-05-2021,-500\n10-05-2021,-500\n07-05-2021,-500\n06-05-2021,-500\n05-05-2021,-500\n04-05-2021,-500\n30-04-2021,-500\n29-04-2021,-500\n28-04-2021,-500\n27-04-2021,-500\n26-04-2021,-500\n23-04-2021,-500\n22-04-2021,-500\n21-04-2021,-500\n20-04-2021,-500\n19-04-2021,-500\n16-04-2021,-500\n15-04-2021,-500\n14-04-2021,-500\n13-04-2021,-500\n12-04-2021,-500\n09-04-2021,-500\n08-04-2021,-500\n07-04-2021,-500\n06-04-2021,-500\n01-04-2021,-500\n31-03-2021,-500\n30-03-2021,-500\n29-03-2021,-500\n26-03-2021,-500\n25-03-2021,-500\n24-03-2021,-500\n23-03-2021,-500\n22-03-2021,-500\n19-03-2021,-500\n18-03-2021,-500\n17-03-2021,-500\n16-03-2021,-500\n15-03-2021,-500\n12-03-2021,-500\n11-03-2021,-500\n10-03-2021,-500\n09-03-2021,-500\n08-03-2021,-500\n05-03-2021,-500\n04-03-2021,-500\n03-03-2021,-500\n02-03-2021,-500\n01-03-2021,-500\n26-02-2021,-500\n25-02-2021,-500\n24-02-2021,-500\n23-02-2021,-500\n22-02-2021,-500\n19-02-2021,-500\n18-02-2021,-500\n17-02-2021,-500\n16-02-2021,-500\n15-02-2021,-500\n12-02-2021,-500\n11-02-2021,-500\n10-02-2021,-500\n09-02-2021,-500\n08-02-2021,-500\n05-02-2021,-500\n04-02-2021,-500\n03-02-2021,-500\n02-02-2021,-500\n01-02-2021,-500\n29-01-2021,-500\n28-01-2021,-500\n27-01-2021,-500\n26-01-2021,-500\n25-01-2021,-500\n22-01-2021,-500\n21-01-2021,-500\n20-01-2021,-500\n19-01-2021,-500\n18-01-2021,-500\n15-01-2021,-500\n14-01-2021,-500\n13-01-2021,-500\n12-01-2021,-500\n11-01-2021,-500\n08-01-2021,-500\n07-01-2021,-500\n06-01-2021,-500\n05-01-2021,-500\n04-01-2021,-500\n31-12-2020,-500\n30-12-2020,-500\n29-12-2020,-500\n24-12-2020,-500\n23-12-2020,-500\n22-12-2020,-500\n21-12-2020,-500\n18-12-2020,-500\n17-12-2020,-500\n16-12-2020,-500\n15-12-2020,-500\n14-12-2020,-500\n11-12-2020,-500\n10-12-2020,-500\n09-12-2020,-500\n08-12-2020,-500\n07-12-2020,-500\n04-12-2020,-500\n03-12-2020,-500\n02-12-2020,-500\n01-12-2020,-500\n30-11-2020,-500\n27-11-2020,-500\n26-11-2020,-500\n25-11-2020,-500\n24-11-2020,-500\n23-11-2020,-500\n20-11-2020,-500\n19-11-2020,-500\n18-11-2020,-500\n17-11-2020,-500\n16-11-2020,-500\n13-11-2020,-500\n12-11-2020,-500\n11-11-2020,-500\n10-11-2020,-500\n09-11-2020,-500\n06-11-2020,-500\n05-11-2020,-500\n04-11-2020,-500\n03-11-2020,-500\n02-11-2020,-500\n30-10-2020,-500\n29-10-2020,-500\n28-10-2020,-500\n27-10-2020,-500\n26-10-2020,-500\n23-10-2020,-500\n22-10-2020,-500\n21-10-2020,-500\n20-10-2020,-500\n19-10-2020,-500\n16-10-2020,-500\n15-10-2020,-500\n14-10-2020,-500\n13-10-2020,-500\n12-10-2020,-500\n09-10-2020,-500\n08-10-2020,-500\n07-10-2020,-500\n06-10-2020,-500\n05-10-2020,-500\n02-10-2020,-500\n01-10-2020,-500\n30-09-2020,-500\n29-09-2020,-500\n28-09-2020,-500\n25-09-2020,-500\n24-09-2020,-500\n23-09-2020,-500\n22-09-2020,-500\n21-09-2020,-500\n18-09-2020,-500\n17-09-2020,-500\n16-09-2020,-500\n15-09-2020,-500\n14-09-2020,-500\n11-09-2020,-500\n10-09-2020,-500\n09-09-2020,-500\n08-09-2020,-500\n07-09-2020,-500\n04-09-2020,-500\n03-09-2020,-500\n02-09-2020,-500\n01-09-2020,-500\n28-08-2020,-500\n27-08-2020,-500\n26-08-2020,-500\n25-08-2020,-500\n24-08-2020,-500\n21-08-2020,-500\n20-08-2020,-500\n19-08-2020,-500\n18-08-2020,-500\n17-08-2020,-500\n14-08-2020,-500\n13-08-2020,-500\n12-08-2020,-500\n11-08-2020,-500\n10-08-2020,-500\n07-08-2020,-500\n06-08-2020,-500\n05-08-2020,-500\n04-08-2020,-500\n03-08-2020,-500\n31-07-2020,-500\n30-07-2020,-500\n29-07-2020,-500\n28-07-2020,-500\n27-07-2020,-500\n24-07-2020,-500\n23-07-2020,-500\n22-07-2020,-500\n21-07-2020,-500\n20-07-2020,-500\n17-07-2020,-500\n16-07-2020,-500\n15-07-2020,-500\n14-07-2020,-500\n13-07-2020,-500\n10-07-2020,-500\n09-07-2020,-500\n08-07-2020,-500\n07-07-2020,-500\n06-07-2020,-500\n03-07-2020,-500\n02-07-2020,-500\n01-07-2020,-500\n30-06-2020,-500\n29-06-2020,-500\n26-06-2020,-500\n25-06-2020,-500\n24-06-2020,-500\n23-06-2020,-500\n22-06-2020,-500\n19-06-2020,-500\n18-06-2020,-500\n17-06-2020,-500\n16-06-2020,-500\n15-06-2020,-500\n12-06-2020,-500\n11-06-2020,-500\n10-06-2020,-500\n09-06-2020,-500\n08-06-2020,-500\n05-06-2020,-500\n04-06-2020,-500\n03-06-2020,-500\n02-06-2020,-500\n01-06-2020,-500\n29-05-2020,-500\n28-05-2020,-500\n27-05-2020,-500\n26-05-2020,-500\n22-05-2020,-500\n21-05-2020,-500\n20-05-2020,-500\n19-05-2020,-500\n18-05-2020,-500\n15-05-2020,-500\n14-05-2020,-500\n13-05-2020,-500\n12-05-2020,-500\n11-05-2020,-500\n07-05-2020,-500\n06-05-2020,-500\n05-05-2020,-500\n04-05-2020,-500\n01-05-2020,-500\n30-04-2020,-500\n29-04-2020,-500\n28-04-2020,-500\n27-04-2020,-500\n24-04-2020,-500\n23-04-2020,-500\n22-04-2020,-500\n21-04-2020,-500\n20-04-2020,-500\n17-04-2020,-500\n16-04-2020,-500\n15-04-2020,-500\n14-04-2020,-500\n09-04-2020,-500\n08-04-2020,-500\n07-04-2020,-500\n06-04-2020,-500\n03-04-2020,-500\n02-04-2020,-500\n01-04-2020,-500\n31-03-2020,-500\n30-03-2020,-500\n27-03-2020,-500\n26-03-2020,-500\n25-03-2020,-500\n24-03-2020,-500\n23-03-2020,-500\n20-03-2020,-500\n19-03-2020,-500\n18-03-2020,-500\n17-03-2020,-500\n16-03-2020,-500\n13-03-2020,-500\n12-03-2020,-500\n11-03-2020,-500\n10-03-2020,-500\n09-03-2020,-500\n06-03-2020,-500\n05-03-2020,-500\n04-03-2020,-500\n03-03-2020,-500\n02-03-2020,-500\n28-02-2020,-500\n27-02-2020,-500\n26-02-2020,-500\n25-02-2020,-500\n24-02-2020,-500\n21-02-2020,-500\n20-02-2020,-500\n19-02-2020,-500\n18-02-2020,-500\n17-02-2020,-500\n14-02-2020,-500\n13-02-2020,-500\n12-02-2020,-500\n11-02-2020,-500\n10-02-2020,-500\n07-02-2020,-500\n06-02-2020,-500\n05-02-2020,-500\n04-02-2020,-500\n03-02-2020,-500\n31-01-2020,-500\n30-01-2020,-500\n29-01-2020,-500\n28-01-2020,-500\n27-01-2020,-500\n24-01-2020,-500\n23-01-2020,-500\n22-01-2020,-500\n21-01-2020,-500\n20-01-2020,-500\n17-01-2020,-500\n16-01-2020,-500\n15-01-2020,-500\n14-01-2020,-500\n13-01-2020,-500\n10-01-2020,-500\n09-01-2020,-500\n08-01-2020,-500\n07-01-2020,-500\n06-01-2020,-500\n03-01-2020,-500\n02-01-2020,-500\n31-12-2019,-500\n30-12-2019,-500\n27-12-2019,-500\n24-12-2019,-500\n23-12-2019,-500\n20-12-2019,-500\n19-12-2019,-500\n18-12-2019,-500\n17-12-2019,-500\n16-12-2019,-500\n13-12-2019,-500\n12-12-2019,-500\n11-12-2019,-500\n10-12-2019,-500\n09-12-2019,-500\n06-12-2019,-500\n05-12-2019,-500\n04-12-2019,-500\n03-12-2019,-500\n02-12-2019,-500\n29-11-2019,-500\n28-11-2019,-500\n27-11-2019,-500\n26-11-2019,-500\n25-11-2019,-500\n22-11-2019,-500\n21-11-2019,-500\n20-11-2019,-500\n19-11-2019,-500\n18-11-2019,-500\n15-11-2019,-500\n14-11-2019,-500\n13-11-2019,-500\n12-11-2019,-500\n11-11-2019,-500\n08-11-2019,-500\n07-11-2019,-500\n06-11-2019,-500\n05-11-2019,-500\n04-11-2019,-500\n01-11-2019,-500\n31-10-2019,-500\n30-10-2019,-500\n29-10-2019,-500\n28-10-2019,-500\n25-10-2019,-500\n24-10-2019,-500\n23-10-2019,-500\n22-10-2019,-500\n21-10-2019,-500\n18-10-2019,-500\n17-10-2019,-500\n16-10-2019,-500\n15-10-2019,-500\n14-10-2019,-500\n11-10-2019,-500\n10-10-2019,-500\n09-10-2019,-500\n08-10-2019,-500\n07-10-2019,-500\n04-10-2019,-500\n03-10-2019,-500\n02-10-2019,-500\n01-10-2019,-500\n30-09-2019,-500\n27-09-2019,-500\n26-09-2019,-500\n25-09-2019,-500\n24-09-2019,-500\n23-09-2019,-500\n20-09-2019,-500\n19-09-2019,-500\n18-09-2019,-500\n17-09-2019,-500\n16-09-2019,-500\n13-09-2019,-500\n12-09-2019,-500\n11-09-2019,-500\n10-09-2019,-500\n09-09-2019,-500\n06-09-2019,-500\n05-09-2019,-500\n04-09-2019,-500\n03-09-2019,-500\n02-09-2019,-500\n30-08-2019,-500\n29-08-2019,-500\n28-08-2019,-500\n27-08-2019,-500\n23-08-2019,-500\n22-08-2019,-500\n21-08-2019,-500\n20-08-2019,-500\n19-08-2019,-500\n16-08-2019,-500\n15-08-2019,-500\n14-08-2019,-500\n13-08-2019,-500\n12-08-2019,-500\n09-08-2019,-500\n08-08-2019,-500\n07-08-2019,-500\n06-08-2019,-500\n05-08-2019,-500\n02-08-2019,-500\n01-08-2019,-500\n31-07-2019,-500\n30-07-2019,-500\n29-07-2019,-500\n26-07-2019,-500\n25-07-2019,-500\n24-07-2019,-500\n23-07-2019,-500\n22-07-2019,-500\n19-07-2019,-500\n18-07-2019,-500\n17-07-2019,-500\n16-07-2019,-500\n15-07-2019,-500\n12-07-2019,-500\n11-07-2019,-500\n10-07-2019,-500\n09-07-2019,-500\n08-07-2019,-500\n05-07-2019,-500\n04-07-2019,-500\n03-07-2019,-500\n02-07-2019,-500\n01-07-2019,-500\n28-06-2019,-500\n27-06-2019,-500\n26-06-2019,-500\n25-06-2019,-500\n24-06-2019,-500\n21-06-2019,-500\n20-06-2019,-500\n19-06-2019,-500\n18-06-2019,-500\n17-06-2019,-500\n14-06-2019,-500\n13-06-2019,-500\n12-06-2019,-500\n11-06-2019,-500\n10-06-2019,-500\n07-06-2019,-500\n06-06-2019,-500\n05-06-2019,-500\n04-06-2019,-500\n03-06-2019,-500\n31-05-2019,-500\n30-05-2019,-500\n29-05-2019,-500\n28-05-2019,-500\n24-05-2019,-500\n23-05-2019,-500\n22-05-2019,-500\n21-05-2019,-500\n20-05-2019,-500\n17-05-2019,-500\n16-05-2019,-500\n15-05-2019,-500\n14-05-2019,-500\n13-05-2019,-500\n10-05-2019,-500\n09-05-2019,-500\n08-05-2019,-500\n07-05-2019,-500\n03-05-2019,-500\n02-05-2019,-500\n01-05-2019,-500\n30-04-2019,-500\n29-04-2019,-500\n26-04-2019,-500\n25-04-2019,-500\n24-04-2019,-500\n23-04-2019,-500\n18-04-2019,-500\n17-04-2019,-500\n16-04-2019,-500\n15-04-2019,-500\n12-04-2019,-500\n11-04-2019,-500\n10-04-2019,-500\n09-04-2019,-500\n08-04-2019,-500\n05-04-2019,-500\n04-04-2019,-500\n03-04-2019,-500\n02-04-2019,-500\n01-04-2019,-500\n29-03-2019,-500\n28-03-2019,-500\n27-03-2019,-500\n26-03-2019,-500\n25-03-2019,-500\n22-03-2019,-500\n21-03-2019,-500\n20-03-2019,-500\n19-03-2019,-500\n18-03-2019,-500\n15-03-2019,-500\n14-03-2019,-500\n13-03-2019,-500\n12-03-2019,-500\n11-03-2019,-500\n08-03-2019,-500\n07-03-2019,-500\n06-03-2019,-500\n05-03-2019,-500\n04-03-2019,-500\n01-03-2019,-500\n28-02-2019,-500\n27-02-2019,-500\n26-02-2019,-500\n25-02-2019,-500\n22-02-2019,-500\n21-02-2019,-500\n20-02-2019,-500\n19-02-2019,-500\n18-02-2019,-500\n15-02-2019,-500\n14-02-2019,-500\n13-02-2019,-500\n12-02-2019,-500\n11-02-2019,-500\n08-02-2019,-500\n07-02-2019,-500\n06-02-2019,-500\n05-02-2019,-500\n04-02-2019,-500\n01-02-2019,-500\n31-01-2019,-500\n30-01-2019,-500\n29-01-2019,-500\n28-01-2019,-500\n25-01-2019,-500\n24-01-2019,-500\n23-01-2019,-500\n22-01-2019,-500\n21-01-2019,-500\n18-01-2019,-500\n17-01-2019,-500\n16-01-2019,-500\n15-01-2019,-500\n14-01-2019,-500\n11-01-2019,-500\n10-01-2019,-500\n09-01-2019,-500\n08-01-2019,-500\n07-01-2019,-500\n04-01-2019,-500\n03-01-2019,-500\n02-01-2019,-500\n31-12-2018,-500\n28-12-2018,-500\n27-12-2018,-500\n24-12-2018,-500\n21-12-2018,-500\n20-12-2018,-500\n19-12-2018,-500\n18-12-2018,-500\n17-12-2018,-500\n14-12-2018,-500\n13-12-2018,-500\n12-12-2018,-500\n11-12-2018,-500\n10-12-2018,-500\n07-12-2018,-500\n06-12-2018,-500\n05-12-2018,-500\n04-12-2018,-500\n03-12-2018,-500\n30-11-2018,-500\n29-11-2018,-500\n28-11-2018,-500\n27-11-2018,-500\n26-11-2018,-500\n23-11-2018,-500\n22-11-2018,-500\n21-11-2018,-500\n20-11-2018,-500\n19-11-2018,-500\n16-11-2018,-500\n15-11-2018,-500\n14-11-2018,-500\n13-11-2018,-500\n12-11-2018,-500\n09-11-2018,-500\n08-11-2018,-500\n07-11-2018,-500\n06-11-2018,-500\n05-11-2018,-500\n02-11-2018,-500\n01-11-2018,-500\n31-10-2018,-500\n30-10-2018,-500\n29-10-2018,-500\n26-10-2018,-500\n25-10-2018,-500\n24-10-2018,-500\n23-10-2018,-500\n22-10-2018,-500\n19-10-2018,-500\n18-10-2018,-500\n17-10-2018,-500\n16-10-2018,-500\n15-10-2018,-500\n12-10-2018,-500\n11-10-2018,-500\n10-10-2018,-500\n09-10-2018,-500\n08-10-2018,-500\n05-10-2018,-500\n04-10-2018,-500\n03-10-2018,-500\n02-10-2018,-500\n01-10-2018,-500\n28-09-2018,-500\n27-09-2018,-500\n26-09-2018,-500\n25-09-2018,-500\n24-09-2018,-500\n21-09-2018,-500\n20-09-2018,-500\n19-09-2018,-500\n18-09-2018,-500\n17-09-2018,-500\n14-09-2018,-500\n13-09-2018,-500\n12-09-2018,-500\n11-09-2018,-500\n10-09-2018,-500\n07-09-2018,-500\n06-09-2018,-500\n05-09-2018,-500\n04-09-2018,-500\n03-09-2018,-500\n31-08-2018,-500\n30-08-2018,-500\n29-08-2018,-500\n28-08-2018,-500\n24-08-2018,-500\n23-08-2018,-500\n22-08-2018,-500\n21-08-2018,-500\n20-08-2018,-500\n17-08-2018,-500\n16-08-2018,-500\n15-08-2018,-500\n14-08-2018,-500\n13-08-2018,-500\n10-08-2018,-500\n09-08-2018,-500\n08-08-2018,-500\n07-08-2018,-500\n06-08-2018,-500\n03-08-2018,-500\n02-08-2018,-500\n01-08-2018,-500\n31-07-2018,-500\n30-07-2018,-500\n27-07-2018,-500\n26-07-2018,-500\n25-07-2018,-500\n24-07-2018,-500\n23-07-2018,-500\n20-07-2018,-500\n19-07-2018,-500\n18-07-2018,-500\n17-07-2018,-500\n16-07-2018,-500\n13-07-2018,-500\n12-07-2018,-500\n11-07-2018,-500\n10-07-2018,-500\n09-07-2018,-500\n06-07-2018,-500\n05-07-2018,-500\n04-07-2018,-500\n03-07-2018,-500\n02-07-2018,-500\n29-06-2018,-500\n28-06-2018,-500\n27-06-2018,-500\n26-06-2018,-500\n25-06-2018,-500\n22-06-2018,-500\n21-06-2018,-500\n20-06-2018,-500\n19-06-2018,-500\n18-06-2018,-500\n15-06-2018,-500\n14-06-2018,-500\n13-06-2018,-500\n12-06-2018,-500\n11-06-2018,-500\n08-06-2018,-500\n07-06-2018,-500\n06-06-2018,-500\n05-06-2018,-500\n04-06-2018,-500\n01-06-2018,-500\n31-05-2018,-500\n30-05-2018,-500\n29-05-2018,-500\n25-05-2018,-500\n24-05-2018,-500\n23-05-2018,-500\n22-05-2018,-500\n21-05-2018,-500\n18-05-2018,-500\n17-05-2018,-500\n16-05-2018,-500\n15-05-2018,-500\n14-05-2018,-500\n11-05-2018,-500\n10-05-2018,-500\n09-05-2018,-500\n08-05-2018,-500\n04-05-2018,-500\n03-05-2018,-500\n02-05-2018,-500\n01-05-2018,-500\n30-04-2018,-500\n27-04-2018,-500\n26-04-2018,-500\n25-04-2018,-500\n24-04-2018,-500\n23-04-2018,-500\n20-04-2018,-500\n19-04-2018,-500\n18-04-2018,-500\n17-04-2018,-500\n16-04-2018,-500\n13-04-2018,-500\n12-04-2018,-500\n11-04-2018,-500\n10-04-2018,-500\n09-04-2018,-500\n06-04-2018,-500\n05-04-2018,-500\n04-04-2018,-500\n03-04-2018,-500\n29-03-2018,-500\n28-03-2018,-500\n27-03-2018,-500\n26-03-2018,-500\n23-03-2018,-500\n22-03-2018,-500\n21-03-2018,-500\n20-03-2018,-500\n19-03-2018,-500\n16-03-2018,-500\n15-03-2018,-500\n14-03-2018,-500\n13-03-2018,-500\n12-03-2018,-500\n09-03-2018,-500\n08-03-2018,-500\n07-03-2018,-500\n06-03-2018,-500\n05-03-2018,-500\n02-03-2018,-500\n01-03-2018,-500\n28-02-2018,-500\n27-02-2018,-500\n26-02-2018,-500\n23-02-2018,-500\n22-02-2018,-500\n21-02-2018,-500\n20-02-2018,-500\n19-02-2018,-500\n16-02-2018,-500\n15-02-2018,-500\n14-02-2018,-500\n13-02-2018,-500\n12-02-2018,-500\n09-02-2018,-500\n08-02-2018,-500\n07-02-2018,-500\n06-02-2018,-500\n05-02-2018,-500\n02-02-2018,-500\n01-02-2018,-500\n31-01-2018,-500\n30-01-2018,-500\n29-01-2018,-500\n26-01-2018,-500\n25-01-2018,-500\n24-01-2018,-500\n23-01-2018,-500\n22-01-2018,-500\n19-01-2018,-500\n18-01-2018,-500\n17-01-2018,-500\n16-01-2018,-500\n15-01-2018,-500\n12-01-2018,-500\n11-01-2018,-500\n10-01-2018,-500\n09-01-2018,-500\n08-01-2018,-500\n05-01-2018,-500\n04-01-2018,-500\n03-01-2018,-500\n02-01-2018,-500\n29-12-2017,-500\n28-12-2017,-500\n27-12-2017,-500\n22-12-2017,-500\n21-12-2017,-500\n20-12-2017,-500\n19-12-2017,-500\n18-12-2017,-500\n15-12-2017,-500\n14-12-2017,-500\n13-12-2017,-500\n12-12-2017,-500\n11-12-2017,-500\n08-12-2017,-500\n07-12-2017,-500\n06-12-2017,-500\n05-12-2017,-500\n04-12-2017,-500\n01-12-2017,-500\n30-11-2017,-500\n29-11-2017,-500\n28-11-2017,-500\n27-11-2017,-500\n24-11-2017,-500\n23-11-2017,-500\n22-11-2017,-500\n21-11-2017,-500\n20-11-2017,-500\n17-11-2017,-500\n16-11-2017,-500\n15-11-2017,-500\n14-11-2017,-500\n13-11-2017,-500\n10-11-2017,-500\n09-11-2017,-500\n08-11-2017,-500\n07-11-2017,-500\n06-11-2017,-500\n03-11-2017,-500\n02-11-2017,-500\n01-11-2017,-500\n31-10-2017,-500\n30-10-2017,-500\n27-10-2017,-500\n26-10-2017,-500\n25-10-2017,-500\n24-10-2017,-500\n23-10-2017,-500\n20-10-2017,-500\n19-10-2017,-500\n18-10-2017,-500\n17-10-2017,-500\n16-10-2017,-500\n13-10-2017,-500\n12-10-2017,-500\n11-10-2017,-500\n10-10-2017,-500\n09-10-2017,-500\n06-10-2017,-500\n05-10-2017,-500\n04-10-2017,-500\n03-10-2017,-500\n02-10-2017,-500\n29-09-2017,-500\n28-09-2017,-500\n27-09-2017,-500\n26-09-2017,-500\n25-09-2017,-500\n22-09-2017,-500\n21-09-2017,-500\n20-09-2017,-500\n19-09-2017,-500\n18-09-2017,-500\n15-09-2017,-500\n14-09-2017,-500\n13-09-2017,-500\n12-09-2017,-500\n11-09-2017,-500\n08-09-2017,-500\n07-09-2017,-500\n06-09-2017,-500\n05-09-2017,-500\n04-09-2017,-500\n01-09-2017,-500\n31-08-2017,-500\n30-08-2017,-500\n29-08-2017,-500\n25-08-2017,-500\n24-08-2017,-500\n23-08-2017,-500\n22-08-2017,-500\n21-08-2017,-500\n18-08-2017,-500\n17-08-2017,-500\n16-08-2017,-500\n15-08-2017,-500\n14-08-2017,-500\n11-08-2017,-500\n10-08-2017,-500\n09-08-2017,-500\n08-08-2017,-500\n07-08-2017,-500\n04-08-2017,-500\n03-08-2017,-500\n02-08-2017,-500\n01-08-2017,-500\n31-07-2017,-500\n28-07-2017,-500\n27-07-2017,-500\n26-07-2017,-500\n25-07-2017,-500\n24-07-2017,-500\n21-07-2017,-500\n20-07-2017,-500\n19-07-2017,-500\n18-07-2017,-500\n17-07-2017,-500\n14-07-2017,-500\n13-07-2017,-500\n12-07-2017,-500\n11-07-2017,-500\n10-07-2017,-500\n07-07-2017,-500\n06-07-2017,-500\n05-07-2017,-500\n04-07-2017,-500\n03-07-2017,-500\n30-06-2017,-500\n29-06-2017,-500\n28-06-2017,-500\n27-06-2017,-500\n26-06-2017,-500\n23-06-2017,-500\n22-06-2017,-500\n21-06-2017,-500\n20-06-2017,-500\n19-06-2017,-500\n16-06-2017,-500\n15-06-2017,-500\n14-06-2017,-500\n13-06-2017,-500\n12-06-2017,-500\n09-06-2017,-500\n08-06-2017,-500\n07-06-2017,-500\n06-06-2017,-500\n05-06-2017,-500\n02-06-2017,-500\n01-06-2017,-500\n31-05-2017,-500\n30-05-2017,-500\n26-05-2017,-500\n25-05-2017,-500\n24-05-2017,-500\n23-05-2017,-500\n22-05-2017,-500\n19-05-2017,-500\n18-05-2017,-500\n17-05-2017,-500\n16-05-2017,-500\n15-05-2017,-500\n12-05-2017,-500\n11-05-2017,-500\n10-05-2017,-500\n09-05-2017,-500\n08-05-2017,-500\n05-05-2017,-500\n04-05-2017,-500\n03-05-2017,-500\n02-05-2017,-500\n28-04-2017,-500\n27-04-2017,-500\n26-04-2017,-500\n25-04-2017,-500\n24-04-2017,-500\n21-04-2017,-500\n20-04-2017,-500\n19-04-2017,-500\n18-04-2017,-500\n13-04-2017,-500\n12-04-2017,-500\n11-04-2017,-500\n10-04-2017,-500\n07-04-2017,-500\n06-04-2017,-500\n05-04-2017,-500\n04-04-2017,-500\n03-04-2017,-500\n31-03-2017,-500\n30-03-2017,-500\n29-03-2017,-500\n28-03-2017,-500\n27-03-2017,-500\n24-03-2017,-500\n23-03-2017,-500\n22-03-2017,-500\n21-03-2017,-500\n20-03-2017,-500\n17-03-2017,-500\n16-03-2017,-500\n15-03-2017,-500\n14-03-2017,-500\n13-03-2017,-500\n10-03-2017,-500\n09-03-2017,-500\n08-03-2017,-500\n07-03-2017,-500\n06-03-2017,-500\n03-03-2017,-500\n02-03-2017,-500\n01-03-2017,-500\n28-02-2017,-500\n27-02-2017,-500\n24-02-2017,-500\n23-02-2017,-500\n22-02-2017,-500\n21-02-2017,-500\n20-02-2017,-500\n17-02-2017,-500\n16-02-2017,-500\n15-02-2017,-500\n14-02-2017,-500\n13-02-2017,-500\n10-02-2017,-500\n09-02-2017,-500\n08-02-2017,-500\n07-02-2017,-500\n06-02-2017,-500\n03-02-2017,-500\n02-02-2017,-500\n01-02-2017,-500\n31-01-2017,-500\n30-01-2017,-500\n27-01-2017,-500\n26-01-2017,-500\n25-01-2017,-500\n24-01-2017,-500\n23-01-2017,-500\n20-01-2017,-500\n19-01-2017,-500\n18-01-2017,-500\n17-01-2017,-500\n16-01-2017,-500\n13-01-2017,-500\n12-01-2017,-500\n11-01-2017,-500\n10-01-2017,-500\n09-01-2017,-500\n06-01-2017,-500\n05-01-2017,-500\n04-01-2017,-500\n03-01-2017,-500\n30-12-2016,-500\n29-12-2016,-500\n28-12-2016,-500\n23-12-2016,-500\n22-12-2016,-500\n21-12-2016,-500\n20-12-2016,-500\n19-12-2016,-500\n16-12-2016,-500\n15-12-2016,-500\n14-12-2016,-500\n13-12-2016,-500\n12-12-2016,-500\n09-12-2016,-500\n08-12-2016,-500\n07-12-2016,-500\n06-12-2016,-500\n05-12-2016,-500\n02-12-2016,-500\n01-12-2016,-500\n30-11-2016,-500\n29-11-2016,-500\n28-11-2016,-500\n25-11-2016,-500\n24-11-2016,-500\n23-11-2016,-500\n22-11-2016,-500\n21-11-2016,-500\n18-11-2016,-500\n17-11-2016,-500\n16-11-2016,-500\n15-11-2016,-500\n14-11-2016,-500\n11-11-2016,-500\n10-11-2016,-500\n09-11-2016,-500\n08-11-2016,-500\n07-11-2016,-500\n04-11-2016,-500\n03-11-2016,-500\n02-11-2016,-500\n01-11-2016,-500\n31-10-2016,-500\n28-10-2016,-500\n27-10-2016,-500\n26-10-2016,-500\n25-10-2016,-500\n24-10-2016,-500\n21-10-2016,-500\n20-10-2016,-500\n19-10-2016,-500\n18-10-2016,-500\n17-10-2016,-500\n14-10-2016,-500\n13-10-2016,-500\n12-10-2016,-500\n11-10-2016,-500\n10-10-2016,-500\n07-10-2016,-500\n06-10-2016,-500\n05-10-2016,-500\n04-10-2016,-500\n03-10-2016,-500\n30-09-2016,-500\n29-09-2016,-500\n28-09-2016,-500\n27-09-2016,-500\n26-09-2016,-500\n23-09-2016,-500\n22-09-2016,-500\n21-09-2016,-500\n20-09-2016,-500\n19-09-2016,-500\n16-09-2016,-500\n15-09-2016,-500\n14-09-2016,-500\n13-09-2016,-500\n12-09-2016,-500\n09-09-2016,-500\n08-09-2016,-500\n07-09-2016,-500\n06-09-2016,-500\n05-09-2016,-500\n02-09-2016,-500\n01-09-2016,-500\n31-08-2016,-500\n30-08-2016,-500\n26-08-2016,-500\n25-08-2016,-500\n24-08-2016,-500\n23-08-2016,-500\n22-08-2016,-500\n19-08-2016,-500\n18-08-2016,-500\n17-08-2016,-500\n16-08-2016,-500\n15-08-2016,-500\n12-08-2016,-500\n11-08-2016,-500\n10-08-2016,-500\n09-08-2016,-500\n08-08-2016,-500\n05-08-2016,-500\n04-08-2016,-500\n03-08-2016,-500\n02-08-2016,-500\n01-08-2016,-500\n29-07-2016,-500\n28-07-2016,-500\n27-07-2016,-500\n26-07-2016,-500\n25-07-2016,-500\n22-07-2016,-500\n21-07-2016,-500\n20-07-2016,-500\n19-07-2016,-500\n18-07-2016,-500\n15-07-2016,-500\n14-07-2016,-500\n13-07-2016,-500\n12-07-2016,-500\n11-07-2016,-500\n08-07-2016,-500\n07-07-2016,-500\n06-07-2016,-500\n05-07-2016,-500\n04-07-2016,-500\n01-07-2016,-500\n30-06-2016,-500\n29-06-2016,-500\n28-06-2016,-500\n27-06-2016,-500\n24-06-2016,-500\n23-06-2016,-500\n22-06-2016,-500\n21-06-2016,-500\n20-06-2016,-500\n17-06-2016,-500\n16-06-2016,-500\n15-06-2016,-500\n14-06-2016,-500\n13-06-2016,-500\n10-06-2016,-500\n09-06-2016,-500\n08-06-2016,-500\n07-06-2016,-500\n06-06-2016,-500\n03-06-2016,-500\n02-06-2016,-500\n01-06-2016,-500\n31-05-2016,-500\n27-05-2016,-500\n26-05-2016,-500\n25-05-2016,-500\n24-05-2016,-500\n23-05-2016,-500\n20-05-2016,-500\n19-05-2016,-500\n18-05-2016,-500\n17-05-2016,-500\n16-05-2016,-500\n13-05-2016,-500\n12-05-2016,-500\n11-05-2016,-500\n10-05-2016,-500\n09-05-2016,-500\n06-05-2016,-500\n05-05-2016,-500\n04-05-2016,-500\n03-05-2016,-500\n29-04-2016,-500\n28-04-2016,-500\n27-04-2016,-500\n26-04-2016,-500\n25-04-2016,-500\n22-04-2016,-500\n21-04-2016,-500\n20-04-2016,-500\n19-04-2016,-500\n18-04-2016,-500\n15-04-2016,-500\n14-04-2016,-500\n13-04-2016,-500\n12-04-2016,-500\n11-04-2016,-500\n08-04-2016,-500\n07-04-2016,-500\n06-04-2016,-500\n05-04-2016,-500\n04-04-2016,-500\n01-04-2016,-500\n31-03-2016,-500\n30-03-2016,-500\n29-03-2016,-500\n24-03-2016,-500\n23-03-2016,-500\n22-03-2016,-500\n21-03-2016,-500\n18-03-2016,-500\n17-03-2016,-500\n16-03-2016,-500\n15-03-2016,-500\n14-03-2016,-500\n11-03-2016,-500\n10-03-2016,-500\n09-03-2016,-500\n08-03-2016,-500\n07-03-2016,-500\n04-03-2016,-500\n03-03-2016,-500\n02-03-2016,-500\n01-03-2016,-500\n29-02-2016,-500\n26-02-2016,-500\n25-02-2016,-500\n24-02-2016,-500\n23-02-2016,-500\n22-02-2016,-500\n19-02-2016,-500\n18-02-2016,-500\n17-02-2016,-500\n16-02-2016,-500\n15-02-2016,-500\n12-02-2016,-500\n11-02-2016,-500\n10-02-2016,-500\n09-02-2016,-500\n08-02-2016,-500\n05-02-2016,-500\n04-02-2016,-500\n03-02-2016,-500\n02-02-2016,-500\n01-02-2016,-500\n29-01-2016,-500\n28-01-2016,-500\n27-01-2016,-500\n26-01-2016,-500\n25-01-2016,-500\n22-01-2016,-500\n21-01-2016,-500\n20-01-2016,-500\n19-01-2016,-500\n18-01-2016,-500\n15-01-2016,-500\n14-01-2016,-500\n13-01-2016,-500\n12-01-2016,-500\n11-01-2016,-500\n08-01-2016,-500\n07-01-2016,-500\n06-01-2016,-500\n05-01-2016,-500\n04-01-2016,-500\n31-12-2015,-500\n30-12-2015,-500\n29-12-2015,-500\n24-12-2015,-500\n23-12-2015,-500\n22-12-2015,-500\n21-12-2015,-500\n18-12-2015,-500\n17-12-2015,-500\n16-12-2015,-500\n15-12-2015,-500\n14-12-2015,-500\n11-12-2015,-500\n10-12-2015,-500\n09-12-2015,-500\n08-12-2015,-500\n07-12-2015,-500\n04-12-2015,-500\n03-12-2015,-500\n02-12-2015,-500\n01-12-2015,-500\n30-11-2015,-500\n27-11-2015,-500\n26-11-2015,-500\n25-11-2015,-500\n24-11-2015,-500\n23-11-2015,-500\n20-11-2015,-500\n19-11-2015,-500\n18-11-2015,-500\n17-11-2015,-500\n16-11-2015,-500\n13-11-2015,-500\n12-11-2015,-500\n11-11-2015,-500\n10-11-2015,-500\n09-11-2015,-500\n06-11-2015,-500\n05-11-2015,-500\n04-11-2015,-500\n03-11-2015,-500\n02-11-2015,-500\n30-10-2015,-500\n29-10-2015,-500\n28-10-2015,-500\n27-10-2015,-500\n26-10-2015,-500\n23-10-2015,-500\n22-10-2015,-500\n21-10-2015,-500\n20-10-2015,-500\n19-10-2015,-500\n16-10-2015,-500\n15-10-2015,-500\n14-10-2015,-500\n13-10-2015,-500\n12-10-2015,-500\n09-10-2015,-500\n08-10-2015,-500\n07-10-2015,-500\n06-10-2015,-500\n05-10-2015,-500\n02-10-2015,-500\n01-10-2015,-500\n30-09-2015,-500\n29-09-2015,-500\n28-09-2015,-500\n25-09-2015,-500\n24-09-2015,-500\n23-09-2015,-500\n22-09-2015,-500\n21-09-2015,-500\n18-09-2015,-500\n17-09-2015,-500\n16-09-2015,-500\n15-09-2015,-500\n14-09-2015,-500\n11-09-2015,-500\n10-09-2015,-500\n09-09-2015,-500\n08-09-2015,-500\n07-09-2015,-500\n04-09-2015,-500\n03-09-2015,-500\n02-09-2015,-500\n01-09-2015,-500\n28-08-2015,-500\n27-08-2015,-500\n26-08-2015,-500\n25-08-2015,-500\n24-08-2015,-500\n21-08-2015,-500\n20-08-2015,-500\n19-08-2015,-500\n18-08-2015,-500\n17-08-2015,-500\n14-08-2015,-500\n13-08-2015,-500\n12-08-2015,-500\n11-08-2015,-500\n10-08-2015,-500\n07-08-2015,-500\n06-08-2015,-500\n05-08-2015,-500\n04-08-2015,-500\n03-08-2015,-500\n31-07-2015,-500\n30-07-2015,-500\n29-07-2015,-500\n28-07-2015,-500\n27-07-2015,-500\n24-07-2015,-500\n23-07-2015,-500\n22-07-2015,-500\n21-07-2015,-500\n20-07-2015,-500\n17-07-2015,-500\n16-07-2015,-500\n15-07-2015,-500\n14-07-2015,-500\n13-07-2015,-500\n10-07-2015,-500\n09-07-2015,-500\n08-07-2015,-500\n07-07-2015,-500\n06-07-2015,-500\n03-07-2015,-500\n02-07-2015,-500\n01-07-2015,-500\n30-06-2015,-500\n29-06-2015,-500\n26-06-2015,-500\n25-06-2015,-500\n24-06-2015,-500\n23-06-2015,-500\n22-06-2015,-500\n19-06-2015,-500\n18-06-2015,-500\n17-06-2015,-500\n16-06-2015,-500\n15-06-2015,-500\n12-06-2015,-500\n11-06-2015,-500\n10-06-2015,-500\n09-06-2015,-500\n08-06-2015,-500\n05-06-2015,-500\n04-06-2015,-500\n03-06-2015,-500\n02-06-2015,-500\n01-06-2015,-500\n29-05-2015,-500\n28-05-2015,-500\n27-05-2015,-500\n26-05-2015,-500\n22-05-2015,-500\n21-05-2015,-500\n20-05-2015,-500\n19-05-2015,-500\n18-05-2015,-500\n15-05-2015,-500\n14-05-2015,-500\n13-05-2015,-500\n12-05-2015,-500\n11-05-2015,-500\n08-05-2015,-500\n07-05-2015,-500\n06-05-2015,-500\n05-05-2015,-500\n01-05-2015,-500\n30-04-2015,-500\n29-04-2015,-500\n28-04-2015,-500\n27-04-2015,-500\n24-04-2015,-500\n23-04-2015,-500\n22-04-2015,-500\n21-04-2015,-500\n20-04-2015,-500\n17-04-2015,-500\n16-04-2015,-500\n15-04-2015,-500\n14-04-2015,-500\n13-04-2015,-500\n10-04-2015,-500\n09-04-2015,-500\n08-04-2015,-500\n07-04-2015,-500\n02-04-2015,-500\n01-04-2015,-500\n31-03-2015,-500\n30-03-2015,-500\n27-03-2015,-500\n26-03-2015,-500\n25-03-2015,-500\n24-03-2015,-500\n23-03-2015,-500\n20-03-2015,-500\n19-03-2015,-500\n18-03-2015,-500\n17-03-2015,-500\n16-03-2015,-500\n13-03-2015,-500\n12-03-2015,-500\n11-03-2015,-500\n10-03-2015,-500\n09-03-2015,-500\n06-03-2015,-500\n05-03-2015,-500\n04-03-2015,-500\n03-03-2015,-500\n02-03-2015,-500\n27-02-2015,-500\n26-02-2015,-500\n25-02-2015,-500\n24-02-2015,-500\n23-02-2015,-500\n20-02-2015,-500\n19-02-2015,-500\n18-02-2015,-500\n17-02-2015,-500\n16-02-2015,-500\n13-02-2015,-500\n12-02-2015,-500\n11-02-2015,-500\n10-02-2015,-500\n09-02-2015,-500\n06-02-2015,-500\n05-02-2015,-500\n04-02-2015,-500\n03-02-2015,-500\n02-02-2015,-500\n30-01-2015,-500\n29-01-2015,-500\n28-01-2015,-500\n27-01-2015,-500\n26-01-2015,-500\n23-01-2015,-500\n22-01-2015,-500\n21-01-2015,-500\n20-01-2015,-500\n19-01-2015,-500\n16-01-2015,-500\n15-01-2015,-500\n14-01-2015,-500\n13-01-2015,-500\n12-01-2015,-500\n09-01-2015,-500\n08-01-2015,-500\n07-01-2015,-500\n06-01-2015,-500\n05-01-2015,-500\n02-01-2015,-500\n31-12-2014,-500\n30-12-2014,-500\n29-12-2014,-500\n24-12-2014,-500\n23-12-2014,-500\n22-12-2014,-500\n19-12-2014,-500\n18-12-2014,-500\n17-12-2014,-500\n16-12-2014,-500\n15-12-2014,-500\n12-12-2014,-500\n11-12-2014,-500\n10-12-2014,-500\n09-12-2014,-500\n08-12-2014,-500\n05-12-2014,-500\n04-12-2014,-500\n03-12-2014,-500\n02-12-2014,-500\n01-12-2014,-500\n28-11-2014,-500\n27-11-2014,-500\n26-11-2014,-500\n25-11-2014,-500\n24-11-2014,-500\n21-11-2014,-500\n20-11-2014,-500\n19-11-2014,-500\n18-11-2014,-500\n17-11-2014,-500\n14-11-2014,-500\n13-11-2014,-500\n12-11-2014,-500\n11-11-2014,-500\n10-11-2014,-500\n07-11-2014,-500\n06-11-2014,-500\n05-11-2014,-500\n04-11-2014,-500\n03-11-2014,-500\n31-10-2014,-500\n30-10-2014,-500\n29-10-2014,-500\n28-10-2014,-500\n27-10-2014,-500\n24-10-2014,-500\n23-10-2014,-500\n22-10-2014,-500\n21-10-2014,-500\n20-10-2014,-500\n17-10-2014,-500\n16-10-2014,-500\n15-10-2014,-500\n14-10-2014,-500\n13-10-2014,-500\n10-10-2014,-500\n09-10-2014,-500\n08-10-2014,-500\n07-10-2014,-500\n06-10-2014,-500\n03-10-2014,-500\n02-10-2014,-500\n01-10-2014,-500\n30-09-2014,-500\n29-09-2014,-500\n26-09-2014,-500\n25-09-2014,-500\n24-09-2014,-500\n23-09-2014,-500\n22-09-2014,-500\n19-09-2014,-500\n18-09-2014,-500\n17-09-2014,-500\n16-09-2014,-500\n15-09-2014,-500\n12-09-2014,-500\n11-09-2014,-500\n10-09-2014,-500\n09-09-2014,-500\n08-09-2014,-500\n05-09-2014,-500\n04-09-2014,-500\n03-09-2014,-500\n02-09-2014,-500\n01-09-2014,-500\n29-08-2014,-500\n28-08-2014,-500\n27-08-2014,-500\n26-08-2014,-500\n22-08-2014,-500\n21-08-2014,-500\n20-08-2014,-500\n19-08-2014,-500\n18-08-2014,-500\n15-08-2014,-500\n14-08-2014,-500\n13-08-2014,-500\n12-08-2014,-500\n11-08-2014,-500\n08-08-2014,-500\n07-08-2014,-500\n06-08-2014,-500\n05-08-2014,-500\n04-08-2014,-500\n01-08-2014,-500\n31-07-2014,-500\n30-07-2014,-500\n29-07-2014,-500\n28-07-2014,-500\n25-07-2014,-500\n24-07-2014,-500\n23-07-2014,-500\n22-07-2014,-500\n21-07-2014,-500\n18-07-2014,-500\n17-07-2014,-500\n16-07-2014,-500\n15-07-2014,-500\n14-07-2014,-500\n11-07-2014,-500\n10-07-2014,-500\n09-07-2014,-500\n08-07-2014,-500\n07-07-2014,-500\n04-07-2014,-500\n03-07-2014,-500\n02-07-2014,-500\n01-07-2014,-500\n30-06-2014,-500\n27-06-2014,-500\n26-06-2014,-500\n25-06-2014,-500\n24-06-2014,-500\n23-06-2014,-500\n20-06-2014,-500\n19-06-2014,-500\n18-06-2014,-500\n17-06-2014,-500\n16-06-2014,-500\n13-06-2014,-500\n12-06-2014,-500\n11-06-2014,-500\n10-06-2014,-500\n09-06-2014,-500\n06-06-2014,-500\n05-06-2014,-500\n04-06-2014,-500\n03-06-2014,-500\n02-06-2014,-500\n30-05-2014,-500\n29-05-2014,-500\n28-05-2014,-500\n27-05-2014,-500\n23-05-2014,-500\n22-05-2014,-500\n21-05-2014,-500\n20-05-2014,-500\n19-05-2014,-500\n16-05-2014,-500\n15-05-2014,-500\n14-05-2014,-500\n13-05-2014,-500\n12-05-2014,-500\n09-05-2014,-500\n08-05-2014,-500\n07-05-2014,-500\n06-05-2014,-500\n02-05-2014,-500\n01-05-2014,-500\n30-04-2014,-500\n29-04-2014,-500\n28-04-2014,-500\n25-04-2014,-500\n24-04-2014,-500\n23-04-2014,-500\n22-04-2014,-500\n17-04-2014,-500\n16-04-2014,-500\n15-04-2014,-500\n14-04-2014,-500\n11-04-2014,-500\n10-04-2014,-500\n09-04-2014,-500\n08-04-2014,-500\n07-04-2014,-500\n04-04-2014,-500\n03-04-2014,-500\n02-04-2014,-500\n01-04-2014,-500\n31-03-2014,-500\n28-03-2014,-500\n27-03-2014,-500\n26-03-2014,-500\n25-03-2014,-500\n24-03-2014,-500\n21-03-2014,-500\n20-03-2014,-500\n19-03-2014,-500\n18-03-2014,-500\n17-03-2014,-500\n14-03-2014,-500\n13-03-2014,-500\n12-03-2014,-500\n11-03-2014,-500\n10-03-2014,-500\n07-03-2014,-500\n06-03-2014,-500\n05-03-2014,-500\n04-03-2014,-500\n03-03-2014,-500\n28-02-2014,-500\n27-02-2014,-500\n26-02-2014,-500\n25-02-2014,-500\n24-02-2014,-500\n21-02-2014,-500\n20-02-2014,-500\n19-02-2014,-500\n18-02-2014,-500\n17-02-2014,-500\n14-02-2014,-500\n13-02-2014,-500\n12-02-2014,-500\n11-02-2014,-500\n10-02-2014,-500\n07-02-2014,-500\n06-02-2014,-500\n05-02-2014,-500\n04-02-2014,-500\n03-02-2014,-500\n31-01-2014,-500\n30-01-2014,-500\n29-01-2014,-500\n28-01-2014,-500\n27-01-2014,-500\n24-01-2014,-500\n23-01-2014,-500\n22-01-2014,-500\n21-01-2014,-500\n20-01-2014,-500\n17-01-2014,-500\n16-01-2014,-500\n15-01-2014,-500\n14-01-2014,-500\n13-01-2014,-500\n10-01-2014,-500\n09-01-2014,-500\n08-01-2014,-500\n07-01-2014,-500\n06-01-2014,-500\n03-01-2014,-500\n02-01-2014,-500\n31-12-2013,-500\n30-12-2013,-500\n27-12-2013,-500\n24-12-2013,-500\n23-12-2013,-500\n20-12-2013,-500\n19-12-2013,-500\n18-12-2013,-500\n17-12-2013,-500\n16-12-2013,-500\n13-12-2013,-500\n12-12-2013,-500\n11-12-2013,-500\n10-12-2013,-500\n09-12-2013,-500\n06-12-2013,-500\n05-12-2013,-500\n04-12-2013,-500\n03-12-2013,-500\n02-12-2013,-500\n29-11-2013,-500\n28-11-2013,-500\n27-11-2013,-500\n26-11-2013,-500\n25-11-2013,-500\n22-11-2013,-500\n21-11-2013,-500\n20-11-2013,-500\n19-11-2013,-500\n18-11-2013,-500\n15-11-2013,-500\n14-11-2013,-500\n13-11-2013,-500\n12-11-2013,-500\n11-11-2013,-500\n08-11-2013,-500\n07-11-2013,-500\n06-11-2013,-500\n05-11-2013,-500\n04-11-2013,-500\n01-11-2013,-500\n31-10-2013,-500\n30-10-2013,-500\n29-10-2013,-500\n28-10-2013,-500\n25-10-2013,-500\n24-10-2013,-500\n23-10-2013,-500\n22-10-2013,-500\n21-10-2013,-500\n18-10-2013,-500\n17-10-2013,-500\n16-10-2013,-500\n15-10-2013,-500\n14-10-2013,-500\n11-10-2013,-500\n10-10-2013,-500\n09-10-2013,-500\n08-10-2013,-500\n07-10-2013,-500\n04-10-2013,-500\n03-10-2013,-500\n02-10-2013,-500\n01-10-2013,-500\n30-09-2013,-500\n27-09-2013,-500\n26-09-2013,-500\n25-09-2013,-500\n24-09-2013,-500\n23-09-2013,-500\n20-09-2013,-500\n19-09-2013,-500\n18-09-2013,-500\n17-09-2013,-500\n16-09-2013,-500\n13-09-2013,-500\n12-09-2013,-500\n11-09-2013,-500\n10-09-2013,-500\n09-09-2013,-500\n06-09-2013,-500\n05-09-2013,-500\n04-09-2013,-500\n03-09-2013,-500\n02-09-2013,-500\n30-08-2013,-500\n29-08-2013,-500\n28-08-2013,-500\n27-08-2013,-500\n23-08-2013,-500\n22-08-2013,-500\n21-08-2013,-500\n20-08-2013,-500\n19-08-2013,-500\n16-08-2013,-500\n15-08-2013,-500\n14-08-2013,-500\n13-08-2013,-500\n12-08-2013,-500\n09-08-2013,-500\n08-08-2013,-500\n07-08-2013,-500\n06-08-2013,-500\n05-08-2013,-500\n02-08-2013,-500\n01-08-2013,-500\n31-07-2013,-500\n30-07-2013,-500\n29-07-2013,-500\n26-07-2013,-500\n25-07-2013,-500\n24-07-2013,-500\n23-07-2013,-500\n22-07-2013,-500\n19-07-2013,-500\n18-07-2013,-500\n17-07-2013,-500\n16-07-2013,-500\n15-07-2013,-500\n12-07-2013,-500\n11-07-2013,-500\n10-07-2013,-500\n09-07-2013,-500\n08-07-2013,-500\n05-07-2013,-500\n04-07-2013,-500\n03-07-2013,-500\n02-07-2013,-500\n01-07-2013,-500\n28-06-2013,-500\n27-06-2013,-500\n26-06-2013,-500\n25-06-2013,-500\n24-06-2013,-500\n21-06-2013,-500\n20-06-2013,-500\n19-06-2013,-500\n18-06-2013,-500\n17-06-2013,-500\n14-06-2013,-500\n13-06-2013,-500\n12-06-2013,-500\n11-06-2013,-500\n10-06-2013,-500\n07-06-2013,-500\n06-06-2013,-500\n05-06-2013,-500\n04-06-2013,-500\n03-06-2013,-500\n31-05-2013,-500\n30-05-2013,-500\n29-05-2013,-500\n28-05-2013,-500\n24-05-2013,-500\n23-05-2013,-500\n22-05-2013,-500\n21-05-2013,-500\n20-05-2013,-500\n17-05-2013,-500\n16-05-2013,-500\n15-05-2013,-500\n14-05-2013,-500\n13-05-2013,-500\n10-05-2013,-500\n09-05-2013,-500\n08-05-2013,-500\n07-05-2013,-500\n03-05-2013,-500\n02-05-2013,-500\n01-05-2013,-500\n30-04-2013,-500\n29-04-2013,-500\n26-04-2013,-500\n25-04-2013,-500\n24-04-2013,-500\n23-04-2013,-500\n22-04-2013,-500\n19-04-2013,-500\n18-04-2013,-500\n17-04-2013,-500\n16-04-2013,-500\n15-04-2013,-500\n12-04-2013,-500\n11-04-2013,-500\n10-04-2013,-500\n09-04-2013,-500\n08-04-2013,-500\n05-04-2013,-500\n04-04-2013,-500\n03-04-2013,-500\n02-04-2013,-500\n28-03-2013,-500\n27-03-2013,-500\n26-03-2013,-500\n25-03-2013,-500\n22-03-2013,-500\n21-03-2013,-500\n20-03-2013,-500\n19-03-2013,-500\n18-03-2013,-500\n15-03-2013,-500\n14-03-2013,-500\n13-03-2013,-500\n12-03-2013,-500\n11-03-2013,-500\n08-03-2013,-500\n07-03-2013,-500\n06-03-2013,-500\n05-03-2013,-500\n04-03-2013,-500\n01-03-2013,-500\n28-02-2013,-500\n27-02-2013,-500\n26-02-2013,-500\n25-02-2013,-500\n22-02-2013,-500\n21-02-2013,-500\n20-02-2013,-500\n19-02-2013,-500\n18-02-2013,-500\n15-02-2013,-500\n14-02-2013,-500\n13-02-2013,-500\n12-02-2013,-500\n11-02-2013,-500\n08-02-2013,-500\n07-02-2013,-500\n06-02-2013,-500\n05-02-2013,-500\n04-02-2013,-500\n01-02-2013,-500\n31-01-2013,-500\n30-01-2013,-500\n29-01-2013,-500\n28-01-2013,-500\n25-01-2013,-500\n24-01-2013,-500\n23-01-2013,-500\n22-01-2013,-500\n21-01-2013,-500\n18-01-2013,-500\n17-01-2013,-500\n16-01-2013,-500\n15-01-2013,-500\n14-01-2013,-500\n11-01-2013,-500\n10-01-2013,-500\n09-01-2013,-500\n08-01-2013,-500\n07-01-2013,-500\n04-01-2013,-500\n03-01-2013,-500\n02-01-2013,-500"
  },
  {
    "path": "python/rateslib/data/historical/inr_rfr.csv",
    "content": "reference_date,rate\n01-01-2025,-500\n31-12-2024,-500\n30-12-2024,-500\n27-12-2024,-500\n26-12-2024,-500\n24-12-2024,-500\n23-12-2024,-500\n20-12-2024,-500\n19-12-2024,-500\n18-12-2024,-500\n17-12-2024,-500\n16-12-2024,-500\n13-12-2024,-500\n12-12-2024,-500\n11-12-2024,-500\n10-12-2024,-500\n09-12-2024,-500\n06-12-2024,-500\n05-12-2024,-500\n04-12-2024,-500\n03-12-2024,-500\n02-12-2024,-500\n29-11-2024,-500\n28-11-2024,-500\n27-11-2024,-500\n26-11-2024,-500\n25-11-2024,-500\n22-11-2024,-500\n21-11-2024,-500\n19-11-2024,-500\n18-11-2024,-500\n14-11-2024,-500\n13-11-2024,-500\n12-11-2024,-500\n11-11-2024,-500\n08-11-2024,-500\n07-11-2024,-500\n06-11-2024,-500\n05-11-2024,-500\n04-11-2024,-500\n31-10-2024,-500\n30-10-2024,-500\n29-10-2024,-500\n28-10-2024,-500\n25-10-2024,-500\n24-10-2024,-500\n23-10-2024,-500\n22-10-2024,-500\n21-10-2024,-500\n18-10-2024,-500\n17-10-2024,-500\n16-10-2024,-500\n15-10-2024,-500\n14-10-2024,-500\n11-10-2024,-500\n10-10-2024,-500\n09-10-2024,-500\n08-10-2024,-500\n07-10-2024,-500\n04-10-2024,-500\n03-10-2024,-500\n01-10-2024,-500\n30-09-2024,-500\n27-09-2024,-500\n26-09-2024,-500\n25-09-2024,-500\n24-09-2024,-500\n23-09-2024,-500\n20-09-2024,-500\n19-09-2024,-500\n17-09-2024,-500\n16-09-2024,-500\n13-09-2024,-500\n12-09-2024,-500\n11-09-2024,-500\n10-09-2024,-500\n09-09-2024,-500\n06-09-2024,-500\n05-09-2024,-500\n04-09-2024,-500\n03-09-2024,-500\n02-09-2024,-500\n30-08-2024,-500\n29-08-2024,-500\n28-08-2024,-500\n27-08-2024,-500\n26-08-2024,-500\n23-08-2024,-500\n22-08-2024,-500\n21-08-2024,-500\n20-08-2024,-500\n19-08-2024,-500\n16-08-2024,-500\n14-08-2024,-500\n13-08-2024,-500\n12-08-2024,-500\n09-08-2024,-500\n08-08-2024,-500\n07-08-2024,-500\n06-08-2024,-500\n05-08-2024,-500\n02-08-2024,-500\n01-08-2024,-500\n31-07-2024,-500\n30-07-2024,-500\n29-07-2024,-500\n26-07-2024,-500\n25-07-2024,-500\n24-07-2024,-500\n23-07-2024,-500\n22-07-2024,-500\n19-07-2024,-500\n18-07-2024,-500\n16-07-2024,-500\n15-07-2024,-500\n12-07-2024,-500\n11-07-2024,-500\n10-07-2024,-500\n09-07-2024,-500\n08-07-2024,-500\n05-07-2024,-500\n04-07-2024,-500\n03-07-2024,-500\n02-07-2024,-500\n01-07-2024,-500\n28-06-2024,-500\n27-06-2024,-500\n26-06-2024,-500\n25-06-2024,-500\n24-06-2024,-500\n21-06-2024,-500\n20-06-2024,-500\n19-06-2024,-500\n18-06-2024,-500\n14-06-2024,-500\n13-06-2024,-500\n12-06-2024,-500\n11-06-2024,-500\n10-06-2024,-500\n07-06-2024,-500\n06-06-2024,-500\n05-06-2024,-500\n04-06-2024,-500\n03-06-2024,-500\n31-05-2024,-500\n30-05-2024,-500\n29-05-2024,-500\n28-05-2024,-500\n27-05-2024,-500\n24-05-2024,-500\n22-05-2024,-500\n21-05-2024,-500\n17-05-2024,-500\n16-05-2024,-500\n15-05-2024,-500\n14-05-2024,-500\n13-05-2024,-500\n10-05-2024,-500\n09-05-2024,-500\n08-05-2024,-500\n07-05-2024,-500\n06-05-2024,-500\n03-05-2024,-500\n02-05-2024,-500\n30-04-2024,-500\n29-04-2024,-500\n26-04-2024,-500\n25-04-2024,-500\n24-04-2024,-500\n23-04-2024,-500\n22-04-2024,-500\n19-04-2024,-500\n18-04-2024,-500\n16-04-2024,-500\n15-04-2024,-500\n12-04-2024,-500\n10-04-2024,-500\n08-04-2024,-500\n05-04-2024,-500\n04-04-2024,-500\n03-04-2024,-500\n02-04-2024,-500\n28-03-2024,-500\n27-03-2024,-500\n26-03-2024,-500\n22-03-2024,-500\n21-03-2024,-500\n20-03-2024,-500\n19-03-2024,-500\n18-03-2024,-500\n15-03-2024,-500\n14-03-2024,-500\n13-03-2024,-500\n12-03-2024,-500\n11-03-2024,-500\n07-03-2024,-500\n06-03-2024,-500\n05-03-2024,-500\n04-03-2024,-500\n01-03-2024,-500\n29-02-2024,-500\n28-02-2024,-500\n27-02-2024,-500\n26-02-2024,-500\n23-02-2024,-500\n22-02-2024,-500\n21-02-2024,-500\n20-02-2024,-500\n16-02-2024,-500\n15-02-2024,-500\n14-02-2024,-500\n13-02-2024,-500\n12-02-2024,-500\n09-02-2024,-500\n08-02-2024,-500\n07-02-2024,-500\n06-02-2024,-500\n05-02-2024,-500\n02-02-2024,-500\n01-02-2024,-500\n31-01-2024,-500\n30-01-2024,-500\n29-01-2024,-500\n25-01-2024,-500\n24-01-2024,-500\n23-01-2024,-500\n19-01-2024,-500\n18-01-2024,-500\n17-01-2024,-500\n16-01-2024,-500\n15-01-2024,-500\n12-01-2024,-500\n11-01-2024,-500\n10-01-2024,-500\n09-01-2024,-500\n08-01-2024,-500\n05-01-2024,-500\n04-01-2024,-500\n03-01-2024,-500\n02-01-2024,-500\n01-01-2024,-500\n29-12-2023,-500\n28-12-2023,-500\n27-12-2023,-500\n26-12-2023,-500\n22-12-2023,-500\n21-12-2023,-500\n20-12-2023,-500\n19-12-2023,-500\n18-12-2023,-500\n15-12-2023,-500\n14-12-2023,-500\n13-12-2023,-500\n12-12-2023,-500\n11-12-2023,-500\n08-12-2023,-500\n07-12-2023,-500\n06-12-2023,-500\n05-12-2023,-500\n04-12-2023,-500\n01-12-2023,-500\n30-11-2023,-500\n29-11-2023,-500\n28-11-2023,-500\n24-11-2023,-500\n23-11-2023,-500\n22-11-2023,-500\n21-11-2023,-500\n20-11-2023,-500\n17-11-2023,-500\n16-11-2023,-500\n15-11-2023,-500\n13-11-2023,-500\n10-11-2023,-500\n09-11-2023,-500\n08-11-2023,-500\n07-11-2023,-500\n06-11-2023,-500\n03-11-2023,-500\n02-11-2023,-500\n01-11-2023,-500\n31-10-2023,-500\n30-10-2023,-500\n27-10-2023,-500\n26-10-2023,-500\n25-10-2023,-500\n23-10-2023,-500\n20-10-2023,-500\n19-10-2023,-500\n18-10-2023,-500\n17-10-2023,-500\n16-10-2023,-500\n13-10-2023,-500\n12-10-2023,-500\n11-10-2023,-500\n10-10-2023,-500\n09-10-2023,-500\n06-10-2023,-500\n05-10-2023,-500\n04-10-2023,-500\n03-10-2023,-500\n29-09-2023,-500\n28-09-2023,-500\n27-09-2023,-500\n26-09-2023,-500\n25-09-2023,-500\n22-09-2023,-500\n21-09-2023,-500\n20-09-2023,-500\n18-09-2023,-500\n15-09-2023,-500\n14-09-2023,-500\n13-09-2023,-500\n12-09-2023,-500\n11-09-2023,-500\n08-09-2023,-500\n07-09-2023,-500\n06-09-2023,-500\n05-09-2023,-500\n04-09-2023,-500\n01-09-2023,-500\n31-08-2023,-500\n30-08-2023,-500\n29-08-2023,-500\n28-08-2023,-500\n25-08-2023,-500\n24-08-2023,-500\n23-08-2023,-500\n22-08-2023,-500\n21-08-2023,-500\n18-08-2023,-500\n17-08-2023,-500\n14-08-2023,-500\n11-08-2023,-500\n10-08-2023,-500\n09-08-2023,-500\n08-08-2023,-500\n07-08-2023,-500\n04-08-2023,-500\n03-08-2023,-500\n02-08-2023,-500\n01-08-2023,-500\n31-07-2023,-500\n28-07-2023,-500\n27-07-2023,-500\n26-07-2023,-500\n25-07-2023,-500\n24-07-2023,-500\n21-07-2023,-500\n20-07-2023,-500\n19-07-2023,-500\n18-07-2023,-500\n17-07-2023,-500\n14-07-2023,-500\n13-07-2023,-500\n12-07-2023,-500\n11-07-2023,-500\n10-07-2023,-500\n07-07-2023,-500\n06-07-2023,-500\n05-07-2023,-500\n04-07-2023,-500\n03-07-2023,-500\n30-06-2023,-500\n28-06-2023,-500\n27-06-2023,-500\n26-06-2023,-500\n23-06-2023,-500\n22-06-2023,-500\n21-06-2023,-500\n20-06-2023,-500\n19-06-2023,-500\n16-06-2023,-500\n15-06-2023,-500\n14-06-2023,-500\n13-06-2023,-500\n12-06-2023,-500\n09-06-2023,-500\n08-06-2023,-500\n07-06-2023,-500\n06-06-2023,-500\n05-06-2023,-500\n02-06-2023,-500\n01-06-2023,-500\n31-05-2023,-500\n30-05-2023,-500\n29-05-2023,-500\n26-05-2023,-500\n25-05-2023,-500\n24-05-2023,-500\n23-05-2023,-500\n22-05-2023,-500\n19-05-2023,-500\n18-05-2023,-500\n17-05-2023,-500\n16-05-2023,-500\n15-05-2023,-500\n12-05-2023,-500\n11-05-2023,-500\n10-05-2023,-500\n09-05-2023,-500\n08-05-2023,-500\n04-05-2023,-500\n03-05-2023,-500\n02-05-2023,-500\n28-04-2023,-500\n27-04-2023,-500\n26-04-2023,-500\n25-04-2023,-500\n24-04-2023,-500\n21-04-2023,-500\n20-04-2023,-500\n19-04-2023,-500\n18-04-2023,-500\n17-04-2023,-500\n13-04-2023,-500\n12-04-2023,-500\n11-04-2023,-500\n10-04-2023,-500\n06-04-2023,-500\n05-04-2023,-500\n03-04-2023,-500\n31-03-2023,-500\n29-03-2023,-500\n28-03-2023,-500\n27-03-2023,-500\n24-03-2023,-500\n23-03-2023,-500\n21-03-2023,-500\n20-03-2023,-500\n17-03-2023,-500\n16-03-2023,-500\n15-03-2023,-500\n14-03-2023,-500\n13-03-2023,-500\n10-03-2023,-500\n09-03-2023,-500\n08-03-2023,-500\n06-03-2023,-500\n03-03-2023,-500\n02-03-2023,-500\n01-03-2023,-500\n28-02-2023,-500\n27-02-2023,-500\n24-02-2023,-500\n23-02-2023,-500\n22-02-2023,-500\n21-02-2023,-500\n20-02-2023,-500\n17-02-2023,-500\n16-02-2023,-500\n15-02-2023,-500\n14-02-2023,-500\n13-02-2023,-500\n10-02-2023,-500\n09-02-2023,-500\n08-02-2023,-500\n07-02-2023,-500\n06-02-2023,-500\n03-02-2023,-500\n02-02-2023,-500\n01-02-2023,-500\n31-01-2023,-500\n30-01-2023,-500\n27-01-2023,-500\n25-01-2023,-500\n24-01-2023,-500\n23-01-2023,-500\n20-01-2023,-500\n19-01-2023,-500\n18-01-2023,-500\n17-01-2023,-500\n16-01-2023,-500\n13-01-2023,-500\n12-01-2023,-500\n11-01-2023,-500\n10-01-2023,-500\n09-01-2023,-500\n06-01-2023,-500\n05-01-2023,-500\n04-01-2023,-500\n03-01-2023,-500\n02-01-2023,-500\n30-12-2022,-500\n29-12-2022,-500\n28-12-2022,-500\n27-12-2022,-500\n26-12-2022,-500\n23-12-2022,-500\n22-12-2022,-500\n21-12-2022,-500\n20-12-2022,-500\n19-12-2022,-500\n16-12-2022,-500\n15-12-2022,-500\n14-12-2022,-500\n13-12-2022,-500\n12-12-2022,-500\n09-12-2022,-500\n08-12-2022,-500\n07-12-2022,-500\n06-12-2022,-500\n05-12-2022,-500\n02-12-2022,-500\n01-12-2022,-500\n30-11-2022,-500\n29-11-2022,-500\n28-11-2022,-500\n25-11-2022,-500\n24-11-2022,-500\n23-11-2022,-500\n22-11-2022,-500\n21-11-2022,-500\n18-11-2022,-500\n17-11-2022,-500\n16-11-2022,-500\n15-11-2022,-500\n14-11-2022,-500\n11-11-2022,-500\n10-11-2022,-500\n09-11-2022,-500\n07-11-2022,-500\n04-11-2022,-500\n03-11-2022,-500\n02-11-2022,-500\n01-11-2022,-500\n31-10-2022,-500\n28-10-2022,-500\n27-10-2022,-500\n25-10-2022,-500\n21-10-2022,-500\n20-10-2022,-500\n19-10-2022,-500\n18-10-2022,-500\n17-10-2022,-500\n14-10-2022,-500\n13-10-2022,-500\n12-10-2022,-500\n11-10-2022,-500\n10-10-2022,-500\n07-10-2022,-500\n06-10-2022,-500\n04-10-2022,-500\n03-10-2022,-500\n30-09-2022,-500\n29-09-2022,-500\n28-09-2022,-500\n27-09-2022,-500\n26-09-2022,-500\n23-09-2022,-500\n22-09-2022,-500\n21-09-2022,-500\n20-09-2022,-500\n19-09-2022,-500\n16-09-2022,-500\n15-09-2022,-500\n14-09-2022,-500\n13-09-2022,-500\n12-09-2022,-500\n09-09-2022,-500\n08-09-2022,-500\n07-09-2022,-500\n06-09-2022,-500\n05-09-2022,-500\n02-09-2022,-500\n01-09-2022,-500\n30-08-2022,-500\n29-08-2022,-500\n26-08-2022,-500\n25-08-2022,-500\n24-08-2022,-500\n23-08-2022,-500\n22-08-2022,-500\n19-08-2022,-500\n18-08-2022,-500\n17-08-2022,-500\n12-08-2022,-500\n11-08-2022,-500\n10-08-2022,-500\n08-08-2022,-500\n05-08-2022,-500\n04-08-2022,-500\n03-08-2022,-500\n02-08-2022,-500\n01-08-2022,-500\n29-07-2022,-500\n28-07-2022,-500\n27-07-2022,-500\n26-07-2022,-500\n25-07-2022,-500\n22-07-2022,-500\n21-07-2022,-500\n20-07-2022,-500\n19-07-2022,-500\n18-07-2022,-500\n15-07-2022,-500\n14-07-2022,-500\n13-07-2022,-500\n12-07-2022,-500\n11-07-2022,-500\n08-07-2022,-500\n07-07-2022,-500\n06-07-2022,-500\n05-07-2022,-500\n04-07-2022,-500\n01-07-2022,-500\n30-06-2022,-500\n29-06-2022,-500\n28-06-2022,-500\n27-06-2022,-500\n24-06-2022,-500\n23-06-2022,-500\n22-06-2022,-500\n21-06-2022,-500\n20-06-2022,-500\n17-06-2022,-500\n16-06-2022,-500\n15-06-2022,-500\n14-06-2022,-500\n13-06-2022,-500\n10-06-2022,-500\n09-06-2022,-500\n08-06-2022,-500\n07-06-2022,-500\n06-06-2022,-500\n03-06-2022,-500\n02-06-2022,-500\n01-06-2022,-500\n31-05-2022,-500\n30-05-2022,-500\n27-05-2022,-500\n26-05-2022,-500\n25-05-2022,-500\n24-05-2022,-500\n23-05-2022,-500\n20-05-2022,-500\n19-05-2022,-500\n18-05-2022,-500\n17-05-2022,-500\n13-05-2022,-500\n12-05-2022,-500\n11-05-2022,-500\n10-05-2022,-500\n09-05-2022,-500\n06-05-2022,-500\n05-05-2022,-500\n04-05-2022,-500\n02-05-2022,-500\n29-04-2022,-500\n28-04-2022,-500\n27-04-2022,-500\n26-04-2022,-500\n25-04-2022,-500\n22-04-2022,-500\n21-04-2022,-500\n20-04-2022,-500\n19-04-2022,-500\n18-04-2022,-500\n13-04-2022,-500\n12-04-2022,-500\n11-04-2022,-500\n08-04-2022,-500\n07-04-2022,-500\n06-04-2022,-500\n05-04-2022,-500\n04-04-2022,-500\n31-03-2022,-500\n30-03-2022,-500\n29-03-2022,-500\n28-03-2022,-500\n25-03-2022,-500\n24-03-2022,-500\n23-03-2022,-500\n22-03-2022,-500\n21-03-2022,-500\n17-03-2022,-500\n16-03-2022,-500\n15-03-2022,-500\n14-03-2022,-500\n11-03-2022,-500\n10-03-2022,-500\n09-03-2022,-500\n08-03-2022,-500\n07-03-2022,-500\n04-03-2022,-500\n03-03-2022,-500\n02-03-2022,-500\n28-02-2022,-500\n25-02-2022,-500\n24-02-2022,-500\n23-02-2022,-500\n22-02-2022,-500\n21-02-2022,-500\n18-02-2022,-500\n17-02-2022,-500\n16-02-2022,-500\n15-02-2022,-500\n14-02-2022,-500\n11-02-2022,-500\n10-02-2022,-500\n09-02-2022,-500\n08-02-2022,-500\n04-02-2022,-500\n03-02-2022,-500\n02-02-2022,-500\n01-02-2022,-500\n31-01-2022,-500\n28-01-2022,-500\n27-01-2022,-500\n25-01-2022,-500\n24-01-2022,-500\n21-01-2022,-500\n20-01-2022,-500\n19-01-2022,-500\n18-01-2022,-500\n17-01-2022,-500\n14-01-2022,-500\n13-01-2022,-500\n12-01-2022,-500\n11-01-2022,-500\n10-01-2022,-500\n07-01-2022,-500\n06-01-2022,-500\n05-01-2022,-500\n04-01-2022,-500\n03-01-2022,-500\n31-12-2021,-500\n30-12-2021,-500\n29-12-2021,-500\n28-12-2021,-500\n27-12-2021,-500\n24-12-2021,-500\n23-12-2021,-500\n22-12-2021,-500\n21-12-2021,-500\n20-12-2021,-500\n17-12-2021,-500\n16-12-2021,-500\n15-12-2021,-500\n14-12-2021,-500\n13-12-2021,-500\n10-12-2021,-500\n09-12-2021,-500\n08-12-2021,-500\n07-12-2021,-500\n06-12-2021,-500\n03-12-2021,-500\n02-12-2021,-500\n01-12-2021,-500\n30-11-2021,-500\n29-11-2021,-500\n26-11-2021,-500\n25-11-2021,-500\n24-11-2021,-500\n23-11-2021,-500\n22-11-2021,-500\n18-11-2021,-500\n17-11-2021,-500\n16-11-2021,-500\n15-11-2021,-500\n12-11-2021,-500\n11-11-2021,-500\n10-11-2021,-500\n09-11-2021,-500\n08-11-2021,-500\n03-11-2021,-500\n02-11-2021,-500\n01-11-2021,-500\n29-10-2021,-500\n28-10-2021,-500\n27-10-2021,-500\n26-10-2021,-500\n25-10-2021,-500\n22-10-2021,-500\n21-10-2021,-500\n20-10-2021,-500\n18-10-2021,-500\n14-10-2021,-500\n13-10-2021,-500\n12-10-2021,-500\n11-10-2021,-500\n08-10-2021,-500\n07-10-2021,-500\n06-10-2021,-500\n05-10-2021,-500\n04-10-2021,-500\n01-10-2021,-500\n30-09-2021,-500\n29-09-2021,-500\n28-09-2021,-500\n27-09-2021,-500\n24-09-2021,-500\n23-09-2021,-500\n22-09-2021,-500\n21-09-2021,-500\n20-09-2021,-500\n17-09-2021,-500\n16-09-2021,-500\n15-09-2021,-500\n14-09-2021,-500\n13-09-2021,-500\n09-09-2021,-500\n08-09-2021,-500\n07-09-2021,-500\n06-09-2021,-500\n03-09-2021,-500\n02-09-2021,-500\n01-09-2021,-500\n31-08-2021,-500\n30-08-2021,-500\n27-08-2021,-500\n26-08-2021,-500\n25-08-2021,-500\n24-08-2021,-500\n23-08-2021,-500\n20-08-2021,-500\n18-08-2021,-500\n17-08-2021,-500\n13-08-2021,-500\n12-08-2021,-500\n11-08-2021,-500\n10-08-2021,-500\n09-08-2021,-500\n06-08-2021,-500\n05-08-2021,-500\n04-08-2021,-500\n03-08-2021,-500\n02-08-2021,-500\n30-07-2021,-500\n29-07-2021,-500\n28-07-2021,-500\n27-07-2021,-500\n26-07-2021,-500\n23-07-2021,-500\n22-07-2021,-500\n20-07-2021,-500\n19-07-2021,-500\n16-07-2021,-500\n15-07-2021,-500\n14-07-2021,-500\n13-07-2021,-500\n12-07-2021,-500\n09-07-2021,-500\n08-07-2021,-500\n07-07-2021,-500\n06-07-2021,-500\n05-07-2021,-500\n02-07-2021,-500\n01-07-2021,-500\n30-06-2021,-500\n29-06-2021,-500\n28-06-2021,-500\n25-06-2021,-500\n24-06-2021,-500\n23-06-2021,-500\n22-06-2021,-500\n21-06-2021,-500\n18-06-2021,-500\n17-06-2021,-500\n16-06-2021,-500\n15-06-2021,-500\n14-06-2021,-500\n11-06-2021,-500\n10-06-2021,-500\n09-06-2021,-500\n08-06-2021,-500\n07-06-2021,-500\n04-06-2021,-500\n03-06-2021,-500\n02-06-2021,-500\n01-06-2021,-500\n31-05-2021,-500\n28-05-2021,-500\n27-05-2021,-500\n25-05-2021,-500\n24-05-2021,-500\n21-05-2021,-500\n20-05-2021,-500\n19-05-2021,-500\n18-05-2021,-500\n17-05-2021,-500\n14-05-2021,-500\n12-05-2021,-500\n11-05-2021,-500\n10-05-2021,-500\n07-05-2021,-500\n06-05-2021,-500\n05-05-2021,-500\n04-05-2021,-500\n03-05-2021,-500\n30-04-2021,-500\n29-04-2021,-500\n28-04-2021,-500\n27-04-2021,-500\n26-04-2021,-500\n23-04-2021,-500\n22-04-2021,-500\n20-04-2021,-500\n19-04-2021,-500\n16-04-2021,-500\n15-04-2021,-500\n12-04-2021,-500\n09-04-2021,-500\n08-04-2021,-500\n07-04-2021,-500\n06-04-2021,-500\n05-04-2021,-500\n31-03-2021,-500\n30-03-2021,-500\n26-03-2021,-500\n25-03-2021,-500\n24-03-2021,-500\n23-03-2021,-500\n22-03-2021,-500\n19-03-2021,-500\n18-03-2021,-500\n17-03-2021,-500\n16-03-2021,-500\n15-03-2021,-500\n12-03-2021,-500\n10-03-2021,-500\n09-03-2021,-500\n08-03-2021,-500\n05-03-2021,-500\n04-03-2021,-500\n03-03-2021,-500\n02-03-2021,-500\n01-03-2021,-500\n26-02-2021,-500\n25-02-2021,-500\n24-02-2021,-500\n23-02-2021,-500\n22-02-2021,-500\n18-02-2021,-500\n17-02-2021,-500\n16-02-2021,-500\n15-02-2021,-500\n12-02-2021,-500\n11-02-2021,-500\n10-02-2021,-500\n09-02-2021,-500\n08-02-2021,-500\n05-02-2021,-500\n04-02-2021,-500\n03-02-2021,-500\n02-02-2021,-500\n01-02-2021,-500\n29-01-2021,-500\n28-01-2021,-500\n27-01-2021,-500\n25-01-2021,-500\n22-01-2021,-500\n21-01-2021,-500\n20-01-2021,-500\n19-01-2021,-500\n18-01-2021,-500\n15-01-2021,-500\n14-01-2021,-500\n13-01-2021,-500\n12-01-2021,-500\n11-01-2021,-500\n08-01-2021,-500\n07-01-2021,-500\n06-01-2021,-500\n05-01-2021,-500\n04-01-2021,-500\n01-01-2021,-500\n31-12-2020,-500\n30-12-2020,-500\n29-12-2020,-500\n28-12-2020,-500\n24-12-2020,-500\n23-12-2020,-500\n22-12-2020,-500\n21-12-2020,-500\n18-12-2020,-500\n17-12-2020,-500\n16-12-2020,-500\n15-12-2020,-500\n14-12-2020,-500\n11-12-2020,-500\n10-12-2020,-500\n09-12-2020,-500\n08-12-2020,-500\n07-12-2020,-500\n04-12-2020,-500\n03-12-2020,-500\n02-12-2020,-500\n01-12-2020,-500\n27-11-2020,-500\n26-11-2020,-500\n25-11-2020,-500\n24-11-2020,-500\n23-11-2020,-500\n20-11-2020,-500\n19-11-2020,-500\n18-11-2020,-500\n17-11-2020,-500\n13-11-2020,-500\n12-11-2020,-500\n11-11-2020,-500\n10-11-2020,-500\n09-11-2020,-500\n06-11-2020,-500\n05-11-2020,-500\n04-11-2020,-500\n03-11-2020,-500\n02-11-2020,-500\n29-10-2020,-500\n28-10-2020,-500\n27-10-2020,-500\n26-10-2020,-500\n23-10-2020,-500\n22-10-2020,-500\n21-10-2020,-500\n20-10-2020,-500\n19-10-2020,-500\n16-10-2020,-500\n15-10-2020,-500\n14-10-2020,-500\n13-10-2020,-500\n12-10-2020,-500\n09-10-2020,-500\n08-10-2020,-500\n07-10-2020,-500\n06-10-2020,-500\n05-10-2020,-500\n01-10-2020,-500\n30-09-2020,-500\n29-09-2020,-500\n28-09-2020,-500\n25-09-2020,-500\n24-09-2020,-500\n23-09-2020,-500\n22-09-2020,-500\n21-09-2020,-500\n18-09-2020,-500\n17-09-2020,-500\n16-09-2020,-500\n15-09-2020,-500\n14-09-2020,-500\n11-09-2020,-500\n10-09-2020,-500\n09-09-2020,-500\n08-09-2020,-500\n07-09-2020,-500\n04-09-2020,-500\n03-09-2020,-500\n02-09-2020,-500\n01-09-2020,-500\n31-08-2020,-500\n28-08-2020,-500\n27-08-2020,-500\n26-08-2020,-500\n25-08-2020,-500\n24-08-2020,-500\n21-08-2020,-500\n20-08-2020,-500\n19-08-2020,-500\n18-08-2020,-500\n17-08-2020,-500\n14-08-2020,-500\n13-08-2020,-500\n12-08-2020,-500\n11-08-2020,-500\n10-08-2020,-500\n07-08-2020,-500\n06-08-2020,-500\n05-08-2020,-500\n04-08-2020,-500\n03-08-2020,-500\n31-07-2020,-500\n30-07-2020,-500\n29-07-2020,-500\n28-07-2020,-500\n27-07-2020,-500\n24-07-2020,-500\n23-07-2020,-500\n22-07-2020,-500\n21-07-2020,-500\n20-07-2020,-500\n17-07-2020,-500\n16-07-2020,-500\n15-07-2020,-500\n14-07-2020,-500\n13-07-2020,-500\n10-07-2020,-500\n09-07-2020,-500\n08-07-2020,-500\n07-07-2020,-500\n06-07-2020,-500\n03-07-2020,-500\n02-07-2020,-500\n01-07-2020,-500\n30-06-2020,-500\n29-06-2020,-500\n26-06-2020,-500\n25-06-2020,-500\n24-06-2020,-500\n23-06-2020,-500\n22-06-2020,-500\n19-06-2020,-500\n18-06-2020,-500\n17-06-2020,-500\n16-06-2020,-500\n15-06-2020,-500\n12-06-2020,-500\n11-06-2020,-500\n10-06-2020,-500\n09-06-2020,-500\n08-06-2020,-500\n05-06-2020,-500\n04-06-2020,-500\n03-06-2020,-500\n02-06-2020,-500\n01-06-2020,-500\n29-05-2020,-500\n28-05-2020,-500\n27-05-2020,-500\n26-05-2020,-500\n22-05-2020,-500\n21-05-2020,-500\n20-05-2020,-500\n19-05-2020,-500\n18-05-2020,-500\n15-05-2020,-500\n14-05-2020,-500\n13-05-2020,-500\n12-05-2020,-500\n11-05-2020,-500\n08-05-2020,-500\n06-05-2020,-500\n05-05-2020,-500\n04-05-2020,-500\n30-04-2020,-500\n29-04-2020,-500\n28-04-2020,-500\n27-04-2020,-500\n24-04-2020,-500\n23-04-2020,-500\n22-04-2020,-500\n21-04-2020,-500\n20-04-2020,-500\n17-04-2020,-500\n16-04-2020,-500\n15-04-2020,-500\n13-04-2020,-500\n09-04-2020,-500\n08-04-2020,-500\n07-04-2020,-500\n03-04-2020,-500\n31-03-2020,-500\n30-03-2020,-500\n27-03-2020,-500\n26-03-2020,-500\n24-03-2020,-500\n23-03-2020,-500\n20-03-2020,-500\n19-03-2020,-500\n18-03-2020,-500\n17-03-2020,-500\n16-03-2020,-500\n13-03-2020,-500\n12-03-2020,-500\n11-03-2020,-500\n09-03-2020,-500\n06-03-2020,-500\n05-03-2020,-500\n04-03-2020,-500\n03-03-2020,-500\n02-03-2020,-500\n28-02-2020,-500\n27-02-2020,-500\n26-02-2020,-500\n25-02-2020,-500\n24-02-2020,-500\n20-02-2020,-500\n18-02-2020,-500\n17-02-2020,-500\n14-02-2020,-500\n13-02-2020,-500\n12-02-2020,-500\n11-02-2020,-500\n10-02-2020,-500\n07-02-2020,-500\n06-02-2020,-500\n05-02-2020,-500\n04-02-2020,-500\n03-02-2020,-500\n31-01-2020,-500\n30-01-2020,-500\n29-01-2020,-500\n28-01-2020,-500\n27-01-2020,-500\n24-01-2020,-500\n23-01-2020,-500\n22-01-2020,-500\n21-01-2020,-500\n20-01-2020,-500\n17-01-2020,-500\n16-01-2020,-500\n15-01-2020,-500\n14-01-2020,-500\n13-01-2020,-500\n10-01-2020,-500\n09-01-2020,-500\n08-01-2020,-500\n07-01-2020,-500\n06-01-2020,-500\n03-01-2020,-500\n02-01-2020,-500\n01-01-2020,-500\n31-12-2019,-500\n30-12-2019,-500\n27-12-2019,-500\n26-12-2019,-500\n24-12-2019,-500\n23-12-2019,-500\n20-12-2019,-500\n19-12-2019,-500\n18-12-2019,-500\n17-12-2019,-500\n16-12-2019,-500\n13-12-2019,-500\n12-12-2019,-500\n11-12-2019,-500\n10-12-2019,-500\n09-12-2019,-500\n06-12-2019,-500\n05-12-2019,-500\n04-12-2019,-500\n03-12-2019,-500\n02-12-2019,-500\n29-11-2019,-500\n28-11-2019,-500\n27-11-2019,-500\n26-11-2019,-500\n25-11-2019,-500\n22-11-2019,-500\n21-11-2019,-500\n20-11-2019,-500\n19-11-2019,-500\n18-11-2019,-500\n15-11-2019,-500\n14-11-2019,-500\n13-11-2019,-500\n11-11-2019,-500\n08-11-2019,-500\n07-11-2019,-500\n06-11-2019,-500\n05-11-2019,-500\n04-11-2019,-500\n01-11-2019,-500\n31-10-2019,-500\n30-10-2019,-500\n29-10-2019,-500\n25-10-2019,-500\n24-10-2019,-500\n23-10-2019,-500\n22-10-2019,-500\n18-10-2019,-500\n17-10-2019,-500\n16-10-2019,-500\n15-10-2019,-500\n14-10-2019,-500\n11-10-2019,-500\n10-10-2019,-500\n09-10-2019,-500\n07-10-2019,-500\n04-10-2019,-500\n03-10-2019,-500\n01-10-2019,-500\n30-09-2019,-500\n27-09-2019,-500\n26-09-2019,-500\n25-09-2019,-500\n24-09-2019,-500\n23-09-2019,-500\n20-09-2019,-500\n19-09-2019,-500\n18-09-2019,-500\n17-09-2019,-500\n16-09-2019,-500\n13-09-2019,-500\n12-09-2019,-500\n11-09-2019,-500\n09-09-2019,-500\n06-09-2019,-500\n05-09-2019,-500\n04-09-2019,-500\n03-09-2019,-500\n30-08-2019,-500\n29-08-2019,-500\n28-08-2019,-500\n27-08-2019,-500\n26-08-2019,-500\n23-08-2019,-500\n22-08-2019,-500\n21-08-2019,-500\n20-08-2019,-500\n19-08-2019,-500\n16-08-2019,-500\n14-08-2019,-500\n13-08-2019,-500\n09-08-2019,-500\n08-08-2019,-500\n07-08-2019,-500\n06-08-2019,-500\n05-08-2019,-500\n02-08-2019,-500\n01-08-2019,-500\n31-07-2019,-500\n30-07-2019,-500\n29-07-2019,-500\n26-07-2019,-500\n25-07-2019,-500\n24-07-2019,-500\n23-07-2019,-500\n22-07-2019,-500\n19-07-2019,-500\n18-07-2019,-500\n17-07-2019,-500\n16-07-2019,-500\n15-07-2019,-500\n12-07-2019,-500\n11-07-2019,-500\n10-07-2019,-500\n09-07-2019,-500\n08-07-2019,-500\n05-07-2019,-500\n04-07-2019,-500\n03-07-2019,-500\n02-07-2019,-500\n01-07-2019,-500\n28-06-2019,-500\n27-06-2019,-500\n26-06-2019,-500\n25-06-2019,-500\n24-06-2019,-500\n21-06-2019,-500\n20-06-2019,-500\n19-06-2019,-500\n18-06-2019,-500\n17-06-2019,-500\n14-06-2019,-500\n13-06-2019,-500\n12-06-2019,-500\n11-06-2019,-500\n10-06-2019,-500\n07-06-2019,-500\n06-06-2019,-500\n04-06-2019,-500\n03-06-2019,-500\n31-05-2019,-500\n30-05-2019,-500\n29-05-2019,-500\n28-05-2019,-500\n27-05-2019,-500\n24-05-2019,-500\n23-05-2019,-500\n22-05-2019,-500\n21-05-2019,-500\n20-05-2019,-500\n17-05-2019,-500\n16-05-2019,-500\n15-05-2019,-500\n14-05-2019,-500\n13-05-2019,-500\n10-05-2019,-500\n09-05-2019,-500\n08-05-2019,-500\n07-05-2019,-500\n06-05-2019,-500\n03-05-2019,-500\n02-05-2019,-500\n30-04-2019,-500\n26-04-2019,-500\n25-04-2019,-500\n24-04-2019,-500\n23-04-2019,-500\n22-04-2019,-500\n18-04-2019,-500\n16-04-2019,-500\n15-04-2019,-500\n12-04-2019,-500\n11-04-2019,-500\n10-04-2019,-500\n09-04-2019,-500\n08-04-2019,-500\n05-04-2019,-500\n04-04-2019,-500\n03-04-2019,-500\n02-04-2019,-500\n29-03-2019,-500\n28-03-2019,-500\n27-03-2019,-500\n26-03-2019,-500\n25-03-2019,-500\n22-03-2019,-500\n20-03-2019,-500\n19-03-2019,-500\n18-03-2019,-500\n15-03-2019,-500\n14-03-2019,-500\n13-03-2019,-500\n12-03-2019,-500\n11-03-2019,-500\n08-03-2019,-500\n07-03-2019,-500\n06-03-2019,-500\n05-03-2019,-500\n01-03-2019,-500\n28-02-2019,-500\n27-02-2019,-500\n26-02-2019,-500\n25-02-2019,-500\n22-02-2019,-500\n21-02-2019,-500\n20-02-2019,-500\n18-02-2019,-500\n15-02-2019,-500\n14-02-2019,-500\n13-02-2019,-500\n12-02-2019,-500\n11-02-2019,-500\n08-02-2019,-500\n07-02-2019,-500\n06-02-2019,-500\n05-02-2019,-500\n04-02-2019,-500\n01-02-2019,-500\n31-01-2019,-500\n30-01-2019,-500\n29-01-2019,-500\n28-01-2019,-500\n25-01-2019,-500\n24-01-2019,-500\n23-01-2019,-500\n22-01-2019,-500\n21-01-2019,-500\n18-01-2019,-500\n17-01-2019,-500\n16-01-2019,-500\n15-01-2019,-500\n14-01-2019,-500\n11-01-2019,-500\n10-01-2019,-500\n09-01-2019,-500\n08-01-2019,-500\n07-01-2019,-500\n04-01-2019,-500\n03-01-2019,-500\n02-01-2019,-500\n01-01-2019,-500"
  },
  {
    "path": "python/rateslib/data/historical/jpy_rfr.csv",
    "content": "﻿reference_date,rate\n09-06-2015,-500\n10-06-2015,-500\n11-06-2015,-500\n12-06-2015,-500\n15-06-2015,-500\n16-06-2015,-500\n17-06-2015,-500\n18-06-2015,-500\n19-06-2015,-500\n22-06-2015,-500\n23-06-2015,-500\n24-06-2015,-500\n25-06-2015,-500\n26-06-2015,-500\n29-06-2015,-500\n30-06-2015,-500\n01-07-2015,-500\n02-07-2015,-500\n03-07-2015,-500\n06-07-2015,-500\n07-07-2015,-500\n08-07-2015,-500\n09-07-2015,-500\n10-07-2015,-500\n13-07-2015,-500\n14-07-2015,-500\n15-07-2015,-500\n16-07-2015,-500\n17-07-2015,-500\n21-07-2015,-500\n22-07-2015,-500\n23-07-2015,-500\n24-07-2015,-500\n27-07-2015,-500\n28-07-2015,-500\n29-07-2015,-500\n30-07-2015,-500\n31-07-2015,-500\n03-08-2015,-500\n04-08-2015,-500\n05-08-2015,-500\n06-08-2015,-500\n07-08-2015,-500\n10-08-2015,-500\n11-08-2015,-500\n12-08-2015,-500\n13-08-2015,-500\n14-08-2015,-500\n17-08-2015,-500\n18-08-2015,-500\n19-08-2015,-500\n20-08-2015,-500\n21-08-2015,-500\n24-08-2015,-500\n25-08-2015,-500\n26-08-2015,-500\n27-08-2015,-500\n28-08-2015,-500\n31-08-2015,-500\n01-09-2015,-500\n02-09-2015,-500\n03-09-2015,-500\n04-09-2015,-500\n07-09-2015,-500\n08-09-2015,-500\n09-09-2015,-500\n10-09-2015,-500\n11-09-2015,-500\n14-09-2015,-500\n15-09-2015,-500\n16-09-2015,-500\n17-09-2015,-500\n18-09-2015,-500\n24-09-2015,-500\n25-09-2015,-500\n28-09-2015,-500\n29-09-2015,-500\n30-09-2015,-500\n01-10-2015,-500\n02-10-2015,-500\n05-10-2015,-500\n06-10-2015,-500\n07-10-2015,-500\n08-10-2015,-500\n09-10-2015,-500\n13-10-2015,-500\n14-10-2015,-500\n15-10-2015,-500\n16-10-2015,-500\n19-10-2015,-500\n20-10-2015,-500\n21-10-2015,-500\n22-10-2015,-500\n23-10-2015,-500\n26-10-2015,-500\n27-10-2015,-500\n28-10-2015,-500\n29-10-2015,-500\n30-10-2015,-500\n02-11-2015,-500\n04-11-2015,-500\n05-11-2015,-500\n06-11-2015,-500\n09-11-2015,-500\n10-11-2015,-500\n11-11-2015,-500\n12-11-2015,-500\n13-11-2015,-500\n16-11-2015,-500\n17-11-2015,-500\n18-11-2015,-500\n19-11-2015,-500\n20-11-2015,-500\n24-11-2015,-500\n25-11-2015,-500\n26-11-2015,-500\n27-11-2015,-500\n30-11-2015,-500\n01-12-2015,-500\n02-12-2015,-500\n03-12-2015,-500\n04-12-2015,-500\n07-12-2015,-500\n08-12-2015,-500\n09-12-2015,-500\n10-12-2015,-500\n11-12-2015,-500\n14-12-2015,-500\n15-12-2015,-500\n16-12-2015,-500\n17-12-2015,-500\n18-12-2015,-500\n21-12-2015,-500\n22-12-2015,-500\n24-12-2015,-500\n25-12-2015,-500\n28-12-2015,-500\n29-12-2015,-500\n30-12-2015,-500\n04-01-2016,-500\n05-01-2016,-500\n06-01-2016,-500\n07-01-2016,-500\n08-01-2016,-500\n12-01-2016,-500\n13-01-2016,-500\n14-01-2016,-500\n15-01-2016,-500\n18-01-2016,-500\n19-01-2016,-500\n20-01-2016,-500\n21-01-2016,-500\n22-01-2016,-500\n25-01-2016,-500\n26-01-2016,-500\n27-01-2016,-500\n28-01-2016,-500\n29-01-2016,-500\n01-02-2016,-500\n02-02-2016,-500\n03-02-2016,-500\n04-02-2016,-500\n05-02-2016,-500\n08-02-2016,-500\n09-02-2016,-500\n10-02-2016,-500\n12-02-2016,-500\n15-02-2016,-500\n16-02-2016,-500\n17-02-2016,-500\n18-02-2016,-500\n19-02-2016,-500\n22-02-2016,-500\n23-02-2016,-500\n24-02-2016,-500\n25-02-2016,-500\n26-02-2016,-500\n29-02-2016,-500\n01-03-2016,-500\n02-03-2016,-500\n03-03-2016,-500\n04-03-2016,-500\n07-03-2016,-500\n08-03-2016,-500\n09-03-2016,-500\n10-03-2016,-500\n11-03-2016,-500\n14-03-2016,-500\n15-03-2016,-500\n16-03-2016,-500\n17-03-2016,-500\n18-03-2016,-500\n22-03-2016,-500\n23-03-2016,-500\n24-03-2016,-500\n25-03-2016,-500\n28-03-2016,-500\n29-03-2016,-500\n30-03-2016,-500\n31-03-2016,-500\n01-04-2016,-500\n04-04-2016,-500\n05-04-2016,-500\n06-04-2016,-500\n07-04-2016,-500\n08-04-2016,-500\n11-04-2016,-500\n12-04-2016,-500\n13-04-2016,-500\n14-04-2016,-500\n15-04-2016,-500\n18-04-2016,-500\n19-04-2016,-500\n20-04-2016,-500\n21-04-2016,-500\n22-04-2016,-500\n25-04-2016,-500\n26-04-2016,-500\n27-04-2016,-500\n28-04-2016,-500\n02-05-2016,-500\n06-05-2016,-500\n09-05-2016,-500\n10-05-2016,-500\n11-05-2016,-500\n12-05-2016,-500\n13-05-2016,-500\n16-05-2016,-500\n17-05-2016,-500\n18-05-2016,-500\n19-05-2016,-500\n20-05-2016,-500\n23-05-2016,-500\n24-05-2016,-500\n25-05-2016,-500\n26-05-2016,-500\n27-05-2016,-500\n30-05-2016,-500\n31-05-2016,-500\n01-06-2016,-500\n02-06-2016,-500\n03-06-2016,-500\n06-06-2016,-500\n07-06-2016,-500\n08-06-2016,-500\n09-06-2016,-500\n10-06-2016,-500\n13-06-2016,-500\n14-06-2016,-500\n15-06-2016,-500\n16-06-2016,-500\n17-06-2016,-500\n20-06-2016,-500\n21-06-2016,-500\n22-06-2016,-500\n23-06-2016,-500\n24-06-2016,-500\n27-06-2016,-500\n28-06-2016,-500\n29-06-2016,-500\n30-06-2016,-500\n01-07-2016,-500\n04-07-2016,-500\n05-07-2016,-500\n06-07-2016,-500\n07-07-2016,-500\n08-07-2016,-500\n11-07-2016,-500\n12-07-2016,-500\n13-07-2016,-500\n14-07-2016,-500\n15-07-2016,-500\n19-07-2016,-500\n20-07-2016,-500\n21-07-2016,-500\n22-07-2016,-500\n25-07-2016,-500\n26-07-2016,-500\n27-07-2016,-500\n28-07-2016,-500\n29-07-2016,-500\n01-08-2016,-500\n02-08-2016,-500\n03-08-2016,-500\n04-08-2016,-500\n05-08-2016,-500\n08-08-2016,-500\n09-08-2016,-500\n10-08-2016,-500\n12-08-2016,-500\n15-08-2016,-500\n16-08-2016,-500\n17-08-2016,-500\n18-08-2016,-500\n19-08-2016,-500\n22-08-2016,-500\n23-08-2016,-500\n24-08-2016,-500\n25-08-2016,-500\n26-08-2016,-500\n29-08-2016,-500\n30-08-2016,-500\n31-08-2016,-500\n01-09-2016,-500\n02-09-2016,-500\n05-09-2016,-500\n06-09-2016,-500\n07-09-2016,-500\n08-09-2016,-500\n09-09-2016,-500\n12-09-2016,-500\n13-09-2016,-500\n14-09-2016,-500\n15-09-2016,-500\n16-09-2016,-500\n20-09-2016,-500\n21-09-2016,-500\n23-09-2016,-500\n26-09-2016,-500\n27-09-2016,-500\n28-09-2016,-500\n29-09-2016,-500\n30-09-2016,-500\n03-10-2016,-500\n04-10-2016,-500\n05-10-2016,-500\n06-10-2016,-500\n07-10-2016,-500\n11-10-2016,-500\n12-10-2016,-500\n13-10-2016,-500\n14-10-2016,-500\n17-10-2016,-500\n18-10-2016,-500\n19-10-2016,-500\n20-10-2016,-500\n21-10-2016,-500\n24-10-2016,-500\n25-10-2016,-500\n26-10-2016,-500\n27-10-2016,-500\n28-10-2016,-500\n31-10-2016,-500\n01-11-2016,-500\n02-11-2016,-500\n04-11-2016,-500\n07-11-2016,-500\n08-11-2016,-500\n09-11-2016,-500\n10-11-2016,-500\n11-11-2016,-500\n14-11-2016,-500\n15-11-2016,-500\n16-11-2016,-500\n17-11-2016,-500\n18-11-2016,-500\n21-11-2016,-500\n22-11-2016,-500\n24-11-2016,-500\n25-11-2016,-500\n28-11-2016,-500\n29-11-2016,-500\n30-11-2016,-500\n01-12-2016,-500\n02-12-2016,-500\n05-12-2016,-500\n06-12-2016,-500\n07-12-2016,-500\n08-12-2016,-500\n09-12-2016,-500\n12-12-2016,-500\n13-12-2016,-500\n14-12-2016,-500\n15-12-2016,-500\n16-12-2016,-500\n19-12-2016,-500\n20-12-2016,-500\n21-12-2016,-500\n22-12-2016,-500\n26-12-2016,-500\n27-12-2016,-500\n28-12-2016,-500\n29-12-2016,-500\n30-12-2016,-500\n04-01-2017,-500\n05-01-2017,-500\n06-01-2017,-500\n10-01-2017,-500\n11-01-2017,-500\n12-01-2017,-500\n13-01-2017,-500\n16-01-2017,-500\n17-01-2017,-500\n18-01-2017,-500\n19-01-2017,-500\n20-01-2017,-500\n23-01-2017,-500\n24-01-2017,-500\n25-01-2017,-500\n26-01-2017,-500\n27-01-2017,-500\n30-01-2017,-500\n31-01-2017,-500\n01-02-2017,-500\n02-02-2017,-500\n03-02-2017,-500\n06-02-2017,-500\n07-02-2017,-500\n08-02-2017,-500\n09-02-2017,-500\n10-02-2017,-500\n13-02-2017,-500\n14-02-2017,-500\n15-02-2017,-500\n16-02-2017,-500\n17-02-2017,-500\n20-02-2017,-500\n21-02-2017,-500\n22-02-2017,-500\n23-02-2017,-500\n24-02-2017,-500\n27-02-2017,-500\n28-02-2017,-500\n01-03-2017,-500\n02-03-2017,-500\n03-03-2017,-500\n06-03-2017,-500\n07-03-2017,-500\n08-03-2017,-500\n09-03-2017,-500\n10-03-2017,-500\n13-03-2017,-500\n14-03-2017,-500\n15-03-2017,-500\n16-03-2017,-500\n17-03-2017,-500\n21-03-2017,-500\n22-03-2017,-500\n23-03-2017,-500\n24-03-2017,-500\n27-03-2017,-500\n28-03-2017,-500\n29-03-2017,-500\n30-03-2017,-500\n31-03-2017,-500\n03-04-2017,-500\n04-04-2017,-500\n05-04-2017,-500\n06-04-2017,-500\n07-04-2017,-500\n10-04-2017,-500\n11-04-2017,-500\n12-04-2017,-500\n13-04-2017,-500\n14-04-2017,-500\n17-04-2017,-500\n18-04-2017,-500\n19-04-2017,-500\n20-04-2017,-500\n21-04-2017,-500\n24-04-2017,-500\n25-04-2017,-500\n26-04-2017,-500\n27-04-2017,-500\n28-04-2017,-500\n01-05-2017,-500\n02-05-2017,-500\n08-05-2017,-500\n09-05-2017,-500\n10-05-2017,-500\n11-05-2017,-500\n12-05-2017,-500\n15-05-2017,-500\n16-05-2017,-500\n17-05-2017,-500\n18-05-2017,-500\n19-05-2017,-500\n22-05-2017,-500\n23-05-2017,-500\n24-05-2017,-500\n25-05-2017,-500\n26-05-2017,-500\n29-05-2017,-500\n30-05-2017,-500\n31-05-2017,-500\n01-06-2017,-500\n02-06-2017,-500\n05-06-2017,-500\n06-06-2017,-500\n07-06-2017,-500\n08-06-2017,-500\n09-06-2017,-500\n12-06-2017,-500\n13-06-2017,-500\n14-06-2017,-500\n15-06-2017,-500\n16-06-2017,-500\n19-06-2017,-500\n20-06-2017,-500\n21-06-2017,-500\n22-06-2017,-500\n23-06-2017,-500\n26-06-2017,-500\n27-06-2017,-500\n28-06-2017,-500\n29-06-2017,-500\n30-06-2017,-500\n03-07-2017,-500\n04-07-2017,-500\n05-07-2017,-500\n06-07-2017,-500\n07-07-2017,-500\n10-07-2017,-500\n11-07-2017,-500\n12-07-2017,-500\n13-07-2017,-500\n14-07-2017,-500\n18-07-2017,-500\n19-07-2017,-500\n20-07-2017,-500\n21-07-2017,-500\n24-07-2017,-500\n25-07-2017,-500\n26-07-2017,-500\n27-07-2017,-500\n28-07-2017,-500\n31-07-2017,-500\n01-08-2017,-500\n02-08-2017,-500\n03-08-2017,-500\n04-08-2017,-500\n07-08-2017,-500\n08-08-2017,-500\n09-08-2017,-500\n10-08-2017,-500\n14-08-2017,-500\n15-08-2017,-500\n16-08-2017,-500\n17-08-2017,-500\n18-08-2017,-500\n21-08-2017,-500\n22-08-2017,-500\n23-08-2017,-500\n24-08-2017,-500\n25-08-2017,-500\n28-08-2017,-500\n29-08-2017,-500\n30-08-2017,-500\n31-08-2017,-500\n01-09-2017,-500\n04-09-2017,-500\n05-09-2017,-500\n06-09-2017,-500\n07-09-2017,-500\n08-09-2017,-500\n11-09-2017,-500\n12-09-2017,-500\n13-09-2017,-500\n14-09-2017,-500\n15-09-2017,-500\n19-09-2017,-500\n20-09-2017,-500\n21-09-2017,-500\n22-09-2017,-500\n25-09-2017,-500\n26-09-2017,-500\n27-09-2017,-500\n28-09-2017,-500\n29-09-2017,-500\n02-10-2017,-500\n03-10-2017,-500\n04-10-2017,-500\n05-10-2017,-500\n06-10-2017,-500\n10-10-2017,-500\n11-10-2017,-500\n12-10-2017,-500\n13-10-2017,-500\n16-10-2017,-500\n17-10-2017,-500\n18-10-2017,-500\n19-10-2017,-500\n20-10-2017,-500\n23-10-2017,-500\n24-10-2017,-500\n25-10-2017,-500\n26-10-2017,-500\n27-10-2017,-500\n30-10-2017,-500\n31-10-2017,-500\n01-11-2017,-500\n02-11-2017,-500\n06-11-2017,-500\n07-11-2017,-500\n08-11-2017,-500\n09-11-2017,-500\n10-11-2017,-500\n13-11-2017,-500\n14-11-2017,-500\n15-11-2017,-500\n16-11-2017,-500\n17-11-2017,-500\n20-11-2017,-500\n21-11-2017,-500\n22-11-2017,-500\n24-11-2017,-500\n27-11-2017,-500\n28-11-2017,-500\n29-11-2017,-500\n30-11-2017,-500\n01-12-2017,-500\n04-12-2017,-500\n05-12-2017,-500\n06-12-2017,-500\n07-12-2017,-500\n08-12-2017,-500\n11-12-2017,-500\n12-12-2017,-500\n13-12-2017,-500\n14-12-2017,-500\n15-12-2017,-500\n18-12-2017,-500\n19-12-2017,-500\n20-12-2017,-500\n21-12-2017,-500\n22-12-2017,-500\n25-12-2017,-500\n26-12-2017,-500\n27-12-2017,-500\n28-12-2017,-500\n29-12-2017,-500\n04-01-2018,-500\n05-01-2018,-500\n09-01-2018,-500\n10-01-2018,-500\n11-01-2018,-500\n12-01-2018,-500\n15-01-2018,-500\n16-01-2018,-500\n17-01-2018,-500\n18-01-2018,-500\n19-01-2018,-500\n22-01-2018,-500\n23-01-2018,-500\n24-01-2018,-500\n25-01-2018,-500\n26-01-2018,-500\n29-01-2018,-500\n30-01-2018,-500\n31-01-2018,-500\n01-02-2018,-500\n02-02-2018,-500\n05-02-2018,-500\n06-02-2018,-500\n07-02-2018,-500\n08-02-2018,-500\n09-02-2018,-500\n13-02-2018,-500\n14-02-2018,-500\n15-02-2018,-500\n16-02-2018,-500\n19-02-2018,-500\n20-02-2018,-500\n21-02-2018,-500\n22-02-2018,-500\n23-02-2018,-500\n26-02-2018,-500\n27-02-2018,-500\n28-02-2018,-500\n01-03-2018,-500\n02-03-2018,-500\n05-03-2018,-500\n06-03-2018,-500\n07-03-2018,-500\n08-03-2018,-500\n09-03-2018,-500\n12-03-2018,-500\n13-03-2018,-500\n14-03-2018,-500\n15-03-2018,-500\n16-03-2018,-500\n19-03-2018,-500\n20-03-2018,-500\n22-03-2018,-500\n23-03-2018,-500\n26-03-2018,-500\n27-03-2018,-500\n28-03-2018,-500\n29-03-2018,-500\n30-03-2018,-500\n02-04-2018,-500\n03-04-2018,-500\n04-04-2018,-500\n05-04-2018,-500\n06-04-2018,-500\n09-04-2018,-500\n10-04-2018,-500\n11-04-2018,-500\n12-04-2018,-500\n13-04-2018,-500\n16-04-2018,-500\n17-04-2018,-500\n18-04-2018,-500\n19-04-2018,-500\n20-04-2018,-500\n23-04-2018,-500\n24-04-2018,-500\n25-04-2018,-500\n26-04-2018,-500\n27-04-2018,-500\n01-05-2018,-500\n02-05-2018,-500\n07-05-2018,-500\n08-05-2018,-500\n09-05-2018,-500\n10-05-2018,-500\n11-05-2018,-500\n14-05-2018,-500\n15-05-2018,-500\n16-05-2018,-500\n17-05-2018,-500\n18-05-2018,-500\n21-05-2018,-500\n22-05-2018,-500\n23-05-2018,-500\n24-05-2018,-500\n25-05-2018,-500\n28-05-2018,-500\n29-05-2018,-500\n30-05-2018,-500\n31-05-2018,-500\n01-06-2018,-500\n04-06-2018,-500\n05-06-2018,-500\n06-06-2018,-500\n07-06-2018,-500\n08-06-2018,-500\n11-06-2018,-500\n12-06-2018,-500\n13-06-2018,-500\n14-06-2018,-500\n15-06-2018,-500\n18-06-2018,-500\n19-06-2018,-500\n20-06-2018,-500\n21-06-2018,-500\n22-06-2018,-500\n25-06-2018,-500\n26-06-2018,-500\n27-06-2018,-500\n28-06-2018,-500\n29-06-2018,-500\n02-07-2018,-500\n03-07-2018,-500\n04-07-2018,-500\n05-07-2018,-500\n06-07-2018,-500\n09-07-2018,-500\n10-07-2018,-500\n11-07-2018,-500\n12-07-2018,-500\n13-07-2018,-500\n17-07-2018,-500\n18-07-2018,-500\n19-07-2018,-500\n20-07-2018,-500\n23-07-2018,-500\n24-07-2018,-500\n25-07-2018,-500\n26-07-2018,-500\n27-07-2018,-500\n30-07-2018,-500\n31-07-2018,-500\n01-08-2018,-500\n02-08-2018,-500\n03-08-2018,-500\n06-08-2018,-500\n07-08-2018,-500\n08-08-2018,-500\n09-08-2018,-500\n10-08-2018,-500\n13-08-2018,-500\n14-08-2018,-500\n15-08-2018,-500\n16-08-2018,-500\n17-08-2018,-500\n20-08-2018,-500\n21-08-2018,-500\n22-08-2018,-500\n23-08-2018,-500\n24-08-2018,-500\n27-08-2018,-500\n28-08-2018,-500\n29-08-2018,-500\n30-08-2018,-500\n31-08-2018,-500\n03-09-2018,-500\n04-09-2018,-500\n05-09-2018,-500\n06-09-2018,-500\n07-09-2018,-500\n10-09-2018,-500\n11-09-2018,-500\n12-09-2018,-500\n13-09-2018,-500\n14-09-2018,-500\n18-09-2018,-500\n19-09-2018,-500\n20-09-2018,-500\n21-09-2018,-500\n25-09-2018,-500\n26-09-2018,-500\n27-09-2018,-500\n28-09-2018,-500\n01-10-2018,-500\n02-10-2018,-500\n03-10-2018,-500\n04-10-2018,-500\n05-10-2018,-500\n09-10-2018,-500\n10-10-2018,-500\n11-10-2018,-500\n12-10-2018,-500\n15-10-2018,-500\n16-10-2018,-500\n17-10-2018,-500\n18-10-2018,-500\n19-10-2018,-500\n22-10-2018,-500\n23-10-2018,-500\n24-10-2018,-500\n25-10-2018,-500\n26-10-2018,-500\n29-10-2018,-500\n30-10-2018,-500\n31-10-2018,-500\n01-11-2018,-500\n02-11-2018,-500\n05-11-2018,-500\n06-11-2018,-500\n07-11-2018,-500\n08-11-2018,-500\n09-11-2018,-500\n12-11-2018,-500\n13-11-2018,-500\n14-11-2018,-500\n15-11-2018,-500\n16-11-2018,-500\n19-11-2018,-500\n20-11-2018,-500\n21-11-2018,-500\n22-11-2018,-500\n26-11-2018,-500\n27-11-2018,-500\n28-11-2018,-500\n29-11-2018,-500\n30-11-2018,-500\n03-12-2018,-500\n04-12-2018,-500\n05-12-2018,-500\n06-12-2018,-500\n07-12-2018,-500\n10-12-2018,-500\n11-12-2018,-500\n12-12-2018,-500\n13-12-2018,-500\n14-12-2018,-500\n17-12-2018,-500\n18-12-2018,-500\n19-12-2018,-500\n20-12-2018,-500\n21-12-2018,-500\n25-12-2018,-500\n26-12-2018,-500\n27-12-2018,-500\n28-12-2018,-500\n04-01-2019,-500\n07-01-2019,-500\n08-01-2019,-500\n09-01-2019,-500\n10-01-2019,-500\n11-01-2019,-500\n15-01-2019,-500\n16-01-2019,-500\n17-01-2019,-500\n18-01-2019,-500\n21-01-2019,-500\n22-01-2019,-500\n23-01-2019,-500\n24-01-2019,-500\n25-01-2019,-500\n28-01-2019,-500\n29-01-2019,-500\n30-01-2019,-500\n31-01-2019,-500\n01-02-2019,-500\n04-02-2019,-500\n05-02-2019,-500\n06-02-2019,-500\n07-02-2019,-500\n08-02-2019,-500\n12-02-2019,-500\n13-02-2019,-500\n14-02-2019,-500\n15-02-2019,-500\n18-02-2019,-500\n19-02-2019,-500\n20-02-2019,-500\n21-02-2019,-500\n22-02-2019,-500\n25-02-2019,-500\n26-02-2019,-500\n27-02-2019,-500\n28-02-2019,-500\n01-03-2019,-500\n04-03-2019,-500\n05-03-2019,-500\n06-03-2019,-500\n07-03-2019,-500\n08-03-2019,-500\n11-03-2019,-500\n12-03-2019,-500\n13-03-2019,-500\n14-03-2019,-500\n15-03-2019,-500\n18-03-2019,-500\n19-03-2019,-500\n20-03-2019,-500\n22-03-2019,-500\n25-03-2019,-500\n26-03-2019,-500\n27-03-2019,-500\n28-03-2019,-500\n29-03-2019,-500\n01-04-2019,-500\n02-04-2019,-500\n03-04-2019,-500\n04-04-2019,-500\n05-04-2019,-500\n08-04-2019,-500\n09-04-2019,-500\n10-04-2019,-500\n11-04-2019,-500\n12-04-2019,-500\n15-04-2019,-500\n16-04-2019,-500\n17-04-2019,-500\n18-04-2019,-500\n19-04-2019,-500\n22-04-2019,-500\n23-04-2019,-500\n24-04-2019,-500\n25-04-2019,-500\n26-04-2019,-500\n07-05-2019,-500\n08-05-2019,-500\n09-05-2019,-500\n10-05-2019,-500\n13-05-2019,-500\n14-05-2019,-500\n15-05-2019,-500\n16-05-2019,-500\n17-05-2019,-500\n20-05-2019,-500\n21-05-2019,-500\n22-05-2019,-500\n23-05-2019,-500\n24-05-2019,-500\n27-05-2019,-500\n28-05-2019,-500\n29-05-2019,-500\n30-05-2019,-500\n31-05-2019,-500\n03-06-2019,-500\n04-06-2019,-500\n05-06-2019,-500\n06-06-2019,-500\n07-06-2019,-500\n10-06-2019,-500\n11-06-2019,-500\n12-06-2019,-500\n13-06-2019,-500\n14-06-2019,-500\n17-06-2019,-500\n18-06-2019,-500\n19-06-2019,-500\n20-06-2019,-500\n21-06-2019,-500\n24-06-2019,-500\n25-06-2019,-500\n26-06-2019,-500\n27-06-2019,-500\n28-06-2019,-500\n01-07-2019,-500\n02-07-2019,-500\n03-07-2019,-500\n04-07-2019,-500\n05-07-2019,-500\n08-07-2019,-500\n09-07-2019,-500\n10-07-2019,-500\n11-07-2019,-500\n12-07-2019,-500\n16-07-2019,-500\n17-07-2019,-500\n18-07-2019,-500\n19-07-2019,-500\n22-07-2019,-500\n23-07-2019,-500\n24-07-2019,-500\n25-07-2019,-500\n26-07-2019,-500\n29-07-2019,-500\n30-07-2019,-500\n31-07-2019,-500\n01-08-2019,-500\n02-08-2019,-500\n05-08-2019,-500\n06-08-2019,-500\n07-08-2019,-500\n08-08-2019,-500\n09-08-2019,-500\n13-08-2019,-500\n14-08-2019,-500\n15-08-2019,-500\n16-08-2019,-500\n19-08-2019,-500\n20-08-2019,-500\n21-08-2019,-500\n22-08-2019,-500\n23-08-2019,-500\n26-08-2019,-500\n27-08-2019,-500\n28-08-2019,-500\n29-08-2019,-500\n30-08-2019,-500\n02-09-2019,-500\n03-09-2019,-500\n04-09-2019,-500\n05-09-2019,-500\n06-09-2019,-500\n09-09-2019,-500\n10-09-2019,-500\n11-09-2019,-500\n12-09-2019,-500\n13-09-2019,-500\n17-09-2019,-500\n18-09-2019,-500\n19-09-2019,-500\n20-09-2019,-500\n24-09-2019,-500\n25-09-2019,-500\n26-09-2019,-500\n27-09-2019,-500\n30-09-2019,-500\n01-10-2019,-500\n02-10-2019,-500\n03-10-2019,-500\n04-10-2019,-500\n07-10-2019,-500\n08-10-2019,-500\n09-10-2019,-500\n10-10-2019,-500\n11-10-2019,-500\n15-10-2019,-500\n16-10-2019,-500\n17-10-2019,-500\n18-10-2019,-500\n21-10-2019,-500\n23-10-2019,-500\n24-10-2019,-500\n25-10-2019,-500\n28-10-2019,-500\n29-10-2019,-500\n30-10-2019,-500\n31-10-2019,-500\n01-11-2019,-500\n05-11-2019,-500\n06-11-2019,-500\n07-11-2019,-500\n08-11-2019,-500\n11-11-2019,-500\n12-11-2019,-500\n13-11-2019,-500\n14-11-2019,-500\n15-11-2019,-500\n18-11-2019,-500\n19-11-2019,-500\n20-11-2019,-500\n21-11-2019,-500\n22-11-2019,-500\n25-11-2019,-500\n26-11-2019,-500\n27-11-2019,-500\n28-11-2019,-500\n29-11-2019,-500\n02-12-2019,-500\n03-12-2019,-500\n04-12-2019,-500\n05-12-2019,-500\n06-12-2019,-500\n09-12-2019,-500\n10-12-2019,-500\n11-12-2019,-500\n12-12-2019,-500\n13-12-2019,-500\n16-12-2019,-500\n17-12-2019,-500\n18-12-2019,-500\n19-12-2019,-500\n20-12-2019,-500\n23-12-2019,-500\n24-12-2019,-500\n25-12-2019,-500\n26-12-2019,-500\n27-12-2019,-500\n30-12-2019,-500\n06-01-2020,-500\n07-01-2020,-500\n08-01-2020,-500\n09-01-2020,-500\n10-01-2020,-500\n14-01-2020,-500\n15-01-2020,-500\n16-01-2020,-500\n17-01-2020,-500\n20-01-2020,-500\n21-01-2020,-500\n22-01-2020,-500\n23-01-2020,-500\n24-01-2020,-500\n27-01-2020,-500\n28-01-2020,-500\n29-01-2020,-500\n30-01-2020,-500\n31-01-2020,-500\n03-02-2020,-500\n04-02-2020,-500\n05-02-2020,-500\n06-02-2020,-500\n07-02-2020,-500\n10-02-2020,-500\n12-02-2020,-500\n13-02-2020,-500\n14-02-2020,-500\n17-02-2020,-500\n18-02-2020,-500\n19-02-2020,-500\n20-02-2020,-500\n21-02-2020,-500\n25-02-2020,-500\n26-02-2020,-500\n27-02-2020,-500\n28-02-2020,-500\n02-03-2020,-500\n03-03-2020,-500\n04-03-2020,-500\n05-03-2020,-500\n06-03-2020,-500\n09-03-2020,-500\n10-03-2020,-500\n11-03-2020,-500\n12-03-2020,-500\n13-03-2020,-500\n16-03-2020,-500\n17-03-2020,-500\n18-03-2020,-500\n19-03-2020,-500\n23-03-2020,-500\n24-03-2020,-500\n25-03-2020,-500\n26-03-2020,-500\n27-03-2020,-500\n30-03-2020,-500\n31-03-2020,-500\n01-04-2020,-500\n02-04-2020,-500\n03-04-2020,-500\n06-04-2020,-500\n07-04-2020,-500\n08-04-2020,-500\n09-04-2020,-500\n10-04-2020,-500\n13-04-2020,-500\n14-04-2020,-500\n15-04-2020,-500\n16-04-2020,-500\n17-04-2020,-500\n20-04-2020,-500\n21-04-2020,-500\n22-04-2020,-500\n23-04-2020,-500\n24-04-2020,-500\n27-04-2020,-500\n28-04-2020,-500\n30-04-2020,-500\n01-05-2020,-500\n07-05-2020,-500\n08-05-2020,-500\n11-05-2020,-500\n12-05-2020,-500\n13-05-2020,-500\n14-05-2020,-500\n15-05-2020,-500\n18-05-2020,-500\n19-05-2020,-500\n20-05-2020,-500\n21-05-2020,-500\n22-05-2020,-500\n25-05-2020,-500\n26-05-2020,-500\n27-05-2020,-500\n28-05-2020,-500\n29-05-2020,-500\n01-06-2020,-500\n02-06-2020,-500\n03-06-2020,-500\n04-06-2020,-500\n05-06-2020,-500\n08-06-2020,-500\n09-06-2020,-500\n10-06-2020,-500\n11-06-2020,-500\n12-06-2020,-500\n15-06-2020,-500\n16-06-2020,-500\n17-06-2020,-500\n18-06-2020,-500\n19-06-2020,-500\n22-06-2020,-500\n23-06-2020,-500\n24-06-2020,-500\n25-06-2020,-500\n26-06-2020,-500\n29-06-2020,-500\n30-06-2020,-500\n01-07-2020,-500\n02-07-2020,-500\n03-07-2020,-500\n06-07-2020,-500\n07-07-2020,-500\n08-07-2020,-500\n09-07-2020,-500\n10-07-2020,-500\n13-07-2020,-500\n14-07-2020,-500\n15-07-2020,-500\n16-07-2020,-500\n17-07-2020,-500\n20-07-2020,-500\n21-07-2020,-500\n22-07-2020,-500\n27-07-2020,-500\n28-07-2020,-500\n29-07-2020,-500\n30-07-2020,-500\n31-07-2020,-500\n03-08-2020,-500\n04-08-2020,-500\n05-08-2020,-500\n06-08-2020,-500\n07-08-2020,-500\n11-08-2020,-500\n12-08-2020,-500\n13-08-2020,-500\n14-08-2020,-500\n17-08-2020,-500\n18-08-2020,-500\n19-08-2020,-500\n20-08-2020,-500\n21-08-2020,-500\n24-08-2020,-500\n25-08-2020,-500\n26-08-2020,-500\n27-08-2020,-500\n28-08-2020,-500\n31-08-2020,-500\n01-09-2020,-500\n02-09-2020,-500\n03-09-2020,-500\n04-09-2020,-500\n07-09-2020,-500\n08-09-2020,-500\n09-09-2020,-500\n10-09-2020,-500\n11-09-2020,-500\n14-09-2020,-500\n15-09-2020,-500\n16-09-2020,-500\n17-09-2020,-500\n18-09-2020,-500\n23-09-2020,-500\n24-09-2020,-500\n25-09-2020,-500\n28-09-2020,-500\n29-09-2020,-500\n30-09-2020,-500\n01-10-2020,-500\n02-10-2020,-500\n05-10-2020,-500\n06-10-2020,-500\n07-10-2020,-500\n08-10-2020,-500\n09-10-2020,-500\n12-10-2020,-500\n13-10-2020,-500\n14-10-2020,-500\n15-10-2020,-500\n16-10-2020,-500\n19-10-2020,-500\n20-10-2020,-500\n21-10-2020,-500\n22-10-2020,-500\n23-10-2020,-500\n26-10-2020,-500\n27-10-2020,-500\n28-10-2020,-500\n29-10-2020,-500\n30-10-2020,-500\n02-11-2020,-500\n04-11-2020,-500\n05-11-2020,-500\n06-11-2020,-500\n09-11-2020,-500\n10-11-2020,-500\n11-11-2020,-500\n12-11-2020,-500\n13-11-2020,-500\n16-11-2020,-500\n17-11-2020,-500\n18-11-2020,-500\n19-11-2020,-500\n20-11-2020,-500\n24-11-2020,-500\n25-11-2020,-500\n26-11-2020,-500\n27-11-2020,-500\n30-11-2020,-500\n01-12-2020,-500\n02-12-2020,-500\n03-12-2020,-500\n04-12-2020,-500\n07-12-2020,-500\n08-12-2020,-500\n09-12-2020,-500\n10-12-2020,-500\n11-12-2020,-500\n14-12-2020,-500\n15-12-2020,-500\n16-12-2020,-500\n17-12-2020,-500\n18-12-2020,-500\n21-12-2020,-500\n22-12-2020,-500\n23-12-2020,-500\n24-12-2020,-500\n25-12-2020,-500\n28-12-2020,-500\n29-12-2020,-500\n30-12-2020,-500\n04-01-2021,-500\n05-01-2021,-500\n06-01-2021,-500\n07-01-2021,-500\n08-01-2021,-500\n12-01-2021,-500\n13-01-2021,-500\n14-01-2021,-500\n15-01-2021,-500\n18-01-2021,-500\n19-01-2021,-500\n20-01-2021,-500\n21-01-2021,-500\n22-01-2021,-500\n25-01-2021,-500\n26-01-2021,-500\n27-01-2021,-500\n28-01-2021,-500\n29-01-2021,-500\n01-02-2021,-500\n02-02-2021,-500\n03-02-2021,-500\n04-02-2021,-500\n05-02-2021,-500\n08-02-2021,-500\n09-02-2021,-500\n10-02-2021,-500\n12-02-2021,-500\n15-02-2021,-500\n16-02-2021,-500\n17-02-2021,-500\n18-02-2021,-500\n19-02-2021,-500\n22-02-2021,-500\n24-02-2021,-500\n25-02-2021,-500\n26-02-2021,-500\n01-03-2021,-500\n02-03-2021,-500\n03-03-2021,-500\n04-03-2021,-500\n05-03-2021,-500\n08-03-2021,-500\n09-03-2021,-500\n10-03-2021,-500\n11-03-2021,-500\n12-03-2021,-500\n15-03-2021,-500\n16-03-2021,-500\n17-03-2021,-500\n18-03-2021,-500\n19-03-2021,-500\n22-03-2021,-500\n23-03-2021,-500\n24-03-2021,-500\n25-03-2021,-500\n26-03-2021,-500\n29-03-2021,-500\n30-03-2021,-500\n31-03-2021,-500\n01-04-2021,-500\n02-04-2021,-500\n05-04-2021,-500\n06-04-2021,-500\n07-04-2021,-500\n08-04-2021,-500\n09-04-2021,-500\n12-04-2021,-500\n13-04-2021,-500\n14-04-2021,-500\n15-04-2021,-500\n16-04-2021,-500\n19-04-2021,-500\n20-04-2021,-500\n21-04-2021,-500\n22-04-2021,-500\n23-04-2021,-500\n26-04-2021,-500\n27-04-2021,-500\n28-04-2021,-500\n30-04-2021,-500\n06-05-2021,-500\n07-05-2021,-500\n10-05-2021,-500\n11-05-2021,-500\n12-05-2021,-500\n13-05-2021,-500\n14-05-2021,-500\n17-05-2021,-500\n18-05-2021,-500\n19-05-2021,-500\n20-05-2021,-500\n21-05-2021,-500\n24-05-2021,-500\n25-05-2021,-500\n26-05-2021,-500\n27-05-2021,-500\n28-05-2021,-500\n31-05-2021,-500\n01-06-2021,-500\n02-06-2021,-500\n03-06-2021,-500\n04-06-2021,-500\n07-06-2021,-500\n08-06-2021,-500\n09-06-2021,-500\n10-06-2021,-500\n11-06-2021,-500\n14-06-2021,-500\n15-06-2021,-500\n16-06-2021,-500\n17-06-2021,-500\n18-06-2021,-500\n21-06-2021,-500\n22-06-2021,-500\n23-06-2021,-500\n24-06-2021,-500\n25-06-2021,-500\n28-06-2021,-500\n29-06-2021,-500\n30-06-2021,-500\n01-07-2021,-500\n02-07-2021,-500\n05-07-2021,-500\n06-07-2021,-500\n07-07-2021,-500\n08-07-2021,-500\n09-07-2021,-500\n12-07-2021,-500\n13-07-2021,-500\n14-07-2021,-500\n15-07-2021,-500\n16-07-2021,-500\n19-07-2021,-500\n20-07-2021,-500\n21-07-2021,-500\n26-07-2021,-500\n27-07-2021,-500\n28-07-2021,-500\n29-07-2021,-500\n30-07-2021,-500\n02-08-2021,-500\n03-08-2021,-500\n04-08-2021,-500\n05-08-2021,-500\n06-08-2021,-500\n10-08-2021,-500\n11-08-2021,-500\n12-08-2021,-500\n13-08-2021,-500\n16-08-2021,-500\n17-08-2021,-500\n18-08-2021,-500\n19-08-2021,-500\n20-08-2021,-500\n23-08-2021,-500\n24-08-2021,-500\n25-08-2021,-500\n26-08-2021,-500\n27-08-2021,-500\n30-08-2021,-500\n31-08-2021,-500\n01-09-2021,-500\n02-09-2021,-500\n03-09-2021,-500\n06-09-2021,-500\n07-09-2021,-500\n08-09-2021,-500\n09-09-2021,-500\n10-09-2021,-500\n13-09-2021,-500\n14-09-2021,-500\n15-09-2021,-500\n16-09-2021,-500\n17-09-2021,-500\n21-09-2021,-500\n22-09-2021,-500\n24-09-2021,-500\n27-09-2021,-500\n28-09-2021,-500\n29-09-2021,-500\n30-09-2021,-500\n01-10-2021,-500\n04-10-2021,-500\n05-10-2021,-500\n06-10-2021,-500\n07-10-2021,-500\n08-10-2021,-500\n11-10-2021,-500\n12-10-2021,-500\n13-10-2021,-500\n14-10-2021,-500\n15-10-2021,-500\n18-10-2021,-500\n19-10-2021,-500\n20-10-2021,-500\n21-10-2021,-500\n22-10-2021,-500\n25-10-2021,-500\n26-10-2021,-500\n27-10-2021,-500\n28-10-2021,-500\n29-10-2021,-500\n01-11-2021,-500\n02-11-2021,-500\n04-11-2021,-500\n05-11-2021,-500\n08-11-2021,-500\n09-11-2021,-500\n10-11-2021,-500\n11-11-2021,-500\n12-11-2021,-500\n15-11-2021,-500\n16-11-2021,-500\n17-11-2021,-500\n18-11-2021,-500\n19-11-2021,-500\n22-11-2021,-500\n24-11-2021,-500\n25-11-2021,-500\n26-11-2021,-500\n29-11-2021,-500\n30-11-2021,-500\n01-12-2021,-500\n02-12-2021,-500\n03-12-2021,-500\n06-12-2021,-500\n07-12-2021,-500\n08-12-2021,-500\n09-12-2021,-500\n10-12-2021,-500\n13-12-2021,-500\n14-12-2021,-500\n15-12-2021,-500\n16-12-2021,-500\n17-12-2021,-500\n20-12-2021,-500\n21-12-2021,-500\n22-12-2021,-500\n23-12-2021,-500\n24-12-2021,-500\n27-12-2021,-500\n28-12-2021,-500\n29-12-2021,-500\n30-12-2021,-500\n04-01-2022,-500\n05-01-2022,-500\n06-01-2022,-500\n07-01-2022,-500\n11-01-2022,-500\n12-01-2022,-500\n13-01-2022,-500\n14-01-2022,-500\n17-01-2022,-500\n18-01-2022,-500\n19-01-2022,-500\n20-01-2022,-500\n21-01-2022,-500\n24-01-2022,-500\n25-01-2022,-500\n26-01-2022,-500\n27-01-2022,-500\n28-01-2022,-500\n31-01-2022,-500\n01-02-2022,-500\n02-02-2022,-500\n03-02-2022,-500\n04-02-2022,-500\n07-02-2022,-500\n08-02-2022,-500\n09-02-2022,-500\n10-02-2022,-500\n14-02-2022,-500\n15-02-2022,-500\n16-02-2022,-500\n17-02-2022,-500\n18-02-2022,-500\n21-02-2022,-500\n22-02-2022,-500\n24-02-2022,-500\n25-02-2022,-500\n28-02-2022,-500\n01-03-2022,-500\n02-03-2022,-500\n03-03-2022,-500\n04-03-2022,-500\n07-03-2022,-500\n08-03-2022,-500\n09-03-2022,-500\n10-03-2022,-500\n11-03-2022,-500\n14-03-2022,-500\n15-03-2022,-500\n16-03-2022,-500\n17-03-2022,-500\n18-03-2022,-500\n22-03-2022,-500\n23-03-2022,-500\n24-03-2022,-500\n25-03-2022,-500\n28-03-2022,-500\n29-03-2022,-500\n30-03-2022,-500\n31-03-2022,-500\n01-04-2022,-500\n04-04-2022,-500\n05-04-2022,-500\n06-04-2022,-500\n07-04-2022,-500\n08-04-2022,-500\n11-04-2022,-500\n12-04-2022,-500\n13-04-2022,-500\n14-04-2022,-500\n15-04-2022,-500\n18-04-2022,-500\n19-04-2022,-500\n20-04-2022,-500\n21-04-2022,-500\n22-04-2022,-500\n25-04-2022,-500\n26-04-2022,-500\n27-04-2022,-500\n28-04-2022,-500\n02-05-2022,-500\n06-05-2022,-500\n09-05-2022,-500\n10-05-2022,-500\n11-05-2022,-500\n12-05-2022,-500\n13-05-2022,-500\n16-05-2022,-500\n17-05-2022,-500\n18-05-2022,-500\n19-05-2022,-500\n20-05-2022,-500\n23-05-2022,-500\n24-05-2022,-500\n25-05-2022,-500\n26-05-2022,-500\n27-05-2022,-500\n30-05-2022,-500\n31-05-2022,-500\n01-06-2022,-500\n02-06-2022,-500\n03-06-2022,-500\n06-06-2022,-500\n07-06-2022,-500\n08-06-2022,-500\n09-06-2022,-500\n10-06-2022,-500\n13-06-2022,-500\n14-06-2022,-500\n15-06-2022,-500\n16-06-2022,-500\n17-06-2022,-500\n20-06-2022,-500\n21-06-2022,-500\n22-06-2022,-500\n23-06-2022,-500\n24-06-2022,-500\n27-06-2022,-500\n28-06-2022,-500\n29-06-2022,-500\n30-06-2022,-500\n01-07-2022,-500\n04-07-2022,-500\n05-07-2022,-500\n06-07-2022,-500\n07-07-2022,-500\n08-07-2022,-500\n11-07-2022,-500\n12-07-2022,-500\n13-07-2022,-500\n14-07-2022,-500\n15-07-2022,-500\n19-07-2022,-500\n20-07-2022,-500\n21-07-2022,-500\n22-07-2022,-500\n25-07-2022,-500\n26-07-2022,-500\n27-07-2022,-500\n28-07-2022,-500\n29-07-2022,-500\n01-08-2022,-500\n02-08-2022,-500\n03-08-2022,-500\n04-08-2022,-500\n05-08-2022,-500\n08-08-2022,-500\n09-08-2022,-500\n10-08-2022,-500\n12-08-2022,-500\n15-08-2022,-500\n16-08-2022,-500\n17-08-2022,-500\n18-08-2022,-500\n19-08-2022,-500\n22-08-2022,-500\n23-08-2022,-500\n24-08-2022,-500\n25-08-2022,-500\n26-08-2022,-500\n29-08-2022,-500\n30-08-2022,-500\n31-08-2022,-500\n01-09-2022,-500\n02-09-2022,-500\n05-09-2022,-500\n06-09-2022,-500\n07-09-2022,-500\n08-09-2022,-500\n09-09-2022,-500\n12-09-2022,-500\n13-09-2022,-500\n14-09-2022,-500\n15-09-2022,-500\n16-09-2022,-500\n20-09-2022,-500\n21-09-2022,-500\n22-09-2022,-500\n26-09-2022,-500\n27-09-2022,-500\n28-09-2022,-500\n29-09-2022,-500\n30-09-2022,-500\n03-10-2022,-500\n04-10-2022,-500\n05-10-2022,-500\n06-10-2022,-500\n07-10-2022,-500\n11-10-2022,-500\n12-10-2022,-500\n13-10-2022,-500\n14-10-2022,-500\n17-10-2022,-500\n18-10-2022,-500\n19-10-2022,-500\n20-10-2022,-500\n21-10-2022,-500\n24-10-2022,-500\n25-10-2022,-500\n26-10-2022,-500\n27-10-2022,-500\n28-10-2022,-500\n31-10-2022,-500\n01-11-2022,-500\n02-11-2022,-500\n04-11-2022,-500\n07-11-2022,-500\n08-11-2022,-500\n09-11-2022,-500\n10-11-2022,-500\n11-11-2022,-500\n14-11-2022,-500\n15-11-2022,-500\n16-11-2022,-500\n17-11-2022,-500\n18-11-2022,-500\n21-11-2022,-500\n22-11-2022,-500\n24-11-2022,-500\n25-11-2022,-500\n28-11-2022,-500\n29-11-2022,-500\n30-11-2022,-500\n01-12-2022,-500\n02-12-2022,-500\n05-12-2022,-500\n06-12-2022,-500\n07-12-2022,-500\n08-12-2022,-500\n09-12-2022,-500\n12-12-2022,-500\n13-12-2022,-500\n14-12-2022,-500\n15-12-2022,-500\n16-12-2022,-500\n19-12-2022,-500\n20-12-2022,-500\n21-12-2022,-500\n22-12-2022,-500\n23-12-2022,-500\n26-12-2022,-500\n27-12-2022,-500\n28-12-2022,-500\n29-12-2022,-500\n30-12-2022,-500\n04-01-2023,-500\n05-01-2023,-500\n06-01-2023,-500\n10-01-2023,-500\n11-01-2023,-500\n12-01-2023,-500\n13-01-2023,-500\n16-01-2023,-500\n17-01-2023,-500\n18-01-2023,-500\n19-01-2023,-500\n20-01-2023,-500\n23-01-2023,-500\n24-01-2023,-500\n25-01-2023,-500\n26-01-2023,-500\n27-01-2023,-500\n30-01-2023,-500\n31-01-2023,-500\n01-02-2023,-500\n02-02-2023,-500\n03-02-2023,-500\n06-02-2023,-500\n07-02-2023,-500\n08-02-2023,-500\n09-02-2023,-500\n10-02-2023,-500\n13-02-2023,-500\n14-02-2023,-500\n15-02-2023,-500\n16-02-2023,-500\n17-02-2023,-500\n20-02-2023,-500\n21-02-2023,-500\n22-02-2023,-500\n24-02-2023,-500\n27-02-2023,-500\n28-02-2023,-500\n01-03-2023,-500\n02-03-2023,-500\n03-03-2023,-500\n06-03-2023,-500\n07-03-2023,-500\n08-03-2023,-500\n09-03-2023,-500\n10-03-2023,-500\n13-03-2023,-500\n14-03-2023,-500\n15-03-2023,-500\n16-03-2023,-500\n17-03-2023,-500\n20-03-2023,-500\n22-03-2023,-500\n23-03-2023,-500\n24-03-2023,-500\n27-03-2023,-500\n28-03-2023,-500\n29-03-2023,-500\n30-03-2023,-500\n31-03-2023,-500\n03-04-2023,-500\n04-04-2023,-500\n05-04-2023,-500\n06-04-2023,-500\n07-04-2023,-500\n10-04-2023,-500\n11-04-2023,-500\n12-04-2023,-500\n13-04-2023,-500\n14-04-2023,-500\n17-04-2023,-500\n18-04-2023,-500\n19-04-2023,-500\n20-04-2023,-500\n21-04-2023,-500\n24-04-2023,-500\n25-04-2023,-500\n26-04-2023,-500\n27-04-2023,-500\n28-04-2023,-500\n01-05-2023,-500\n02-05-2023,-500\n08-05-2023,-500\n09-05-2023,-500\n10-05-2023,-500\n11-05-2023,-500\n12-05-2023,-500\n15-05-2023,-500\n16-05-2023,-500\n17-05-2023,-500\n18-05-2023,-500\n19-05-2023,-500\n22-05-2023,-500\n23-05-2023,-500\n24-05-2023,-500\n25-05-2023,-500\n26-05-2023,-500\n29-05-2023,-500\n30-05-2023,-500\n31-05-2023,-500\n01-06-2023,-500\n02-06-2023,-500\n05-06-2023,-500\n06-06-2023,-500\n07-06-2023,-500\n08-06-2023,-500\n09-06-2023,-500\n12-06-2023,-500\n13-06-2023,-500\n14-06-2023,-500\n15-06-2023,-500\n16-06-2023,-500\n19-06-2023,-500\n20-06-2023,-500\n21-06-2023,-500\n22-06-2023,-500\n23-06-2023,-500\n26-06-2023,-500\n27-06-2023,-500\n28-06-2023,-500\n29-06-2023,-500\n30-06-2023,-500\n03-07-2023,-500\n04-07-2023,-500\n05-07-2023,-500\n06-07-2023,-500\n07-07-2023,-500\n10-07-2023,-500\n11-07-2023,-500\n12-07-2023,-500\n13-07-2023,-500\n14-07-2023,-500\n18-07-2023,-500\n19-07-2023,-500\n20-07-2023,-500\n21-07-2023,-500\n24-07-2023,-500\n25-07-2023,-500\n26-07-2023,-500\n27-07-2023,-500\n28-07-2023,-500\n31-07-2023,-500\n01-08-2023,-500\n02-08-2023,-500\n03-08-2023,-500\n04-08-2023,-500\n07-08-2023,-500\n08-08-2023,-500\n09-08-2023,-500\n10-08-2023,-500\n14-08-2023,-500\n15-08-2023,-500\n16-08-2023,-500\n17-08-2023,-500\n18-08-2023,-500\n21-08-2023,-500\n22-08-2023,-500\n23-08-2023,-500\n24-08-2023,-500\n25-08-2023,-500\n28-08-2023,-500\n29-08-2023,-500\n30-08-2023,-500\n31-08-2023,-500\n01-09-2023,-500\n04-09-2023,-500\n05-09-2023,-500\n06-09-2023,-500\n07-09-2023,-500\n08-09-2023,-500\n11-09-2023,-500\n12-09-2023,-500\n13-09-2023,-500\n14-09-2023,-500\n15-09-2023,-500\n19-09-2023,-500\n20-09-2023,-500\n21-09-2023,-500\n22-09-2023,-500\n25-09-2023,-500\n26-09-2023,-500\n27-09-2023,-500\n28-09-2023,-500\n29-09-2023,-500\n02-10-2023,-500\n03-10-2023,-500\n04-10-2023,-500\n05-10-2023,-500\n06-10-2023,-500\n10-10-2023,-500\n11-10-2023,-500\n12-10-2023,-500\n13-10-2023,-500\n16-10-2023,-500\n17-10-2023,-500\n18-10-2023,-500\n19-10-2023,-500\n20-10-2023,-500\n23-10-2023,-500\n24-10-2023,-500\n25-10-2023,-500\n26-10-2023,-500\n27-10-2023,-500\n30-10-2023,-500\n31-10-2023,-500\n01-11-2023,-500\n02-11-2023,-500\n06-11-2023,-500\n07-11-2023,-500\n08-11-2023,-500\n09-11-2023,-500\n10-11-2023,-500\n13-11-2023,-500\n14-11-2023,-500\n15-11-2023,-500\n16-11-2023,-500\n17-11-2023,-500\n20-11-2023,-500\n21-11-2023,-500\n22-11-2023,-500\n24-11-2023,-500\n27-11-2023,-500\n28-11-2023,-500\n29-11-2023,-500\n30-11-2023,-500\n01-12-2023,-500\n04-12-2023,-500\n05-12-2023,-500\n06-12-2023,-500\n07-12-2023,-500\n08-12-2023,-500\n11-12-2023,-500\n12-12-2023,-500\n13-12-2023,-500\n14-12-2023,-500\n15-12-2023,-500\n18-12-2023,-500\n19-12-2023,-500\n20-12-2023,-500\n21-12-2023,-500\n22-12-2023,-500\n25-12-2023,-500\n26-12-2023,-500\n27-12-2023,-500\n28-12-2023,-500\n29-12-2023,-500\n04-01-2024,-500\n05-01-2024,-500\n09-01-2024,-500\n10-01-2024,-500\n11-01-2024,-500\n12-01-2024,-500\n15-01-2024,-500\n16-01-2024,-500\n17-01-2024,-500\n18-01-2024,-500\n19-01-2024,-500\n22-01-2024,-500\n23-01-2024,-500\n24-01-2024,-500\n25-01-2024,-500\n26-01-2024,-500\n29-01-2024,-500\n30-01-2024,-500\n31-01-2024,-500\n01-02-2024,-500\n02-02-2024,-500\n05-02-2024,-500\n06-02-2024,-500\n07-02-2024,-500\n08-02-2024,-500\n09-02-2024,-500\n13-02-2024,-500\n14-02-2024,-500\n15-02-2024,-500\n16-02-2024,-500\n19-02-2024,-500\n20-02-2024,-500\n21-02-2024,-500\n22-02-2024,-500\n26-02-2024,-500\n27-02-2024,-500\n28-02-2024,-500\n29-02-2024,-500\n01-03-2024,-500\n04-03-2024,-500\n05-03-2024,-500\n06-03-2024,-500\n07-03-2024,-500\n08-03-2024,-500\n11-03-2024,-500\n12-03-2024,-500\n13-03-2024,-500\n14-03-2024,-500\n15-03-2024,-500\n18-03-2024,-500\n19-03-2024,-500\n21-03-2024,-500\n22-03-2024,-500\n25-03-2024,-500\n26-03-2024,-500\n27-03-2024,-500\n28-03-2024,-500\n29-03-2024,-500\n01-04-2024,-500\n02-04-2024,-500\n03-04-2024,-500\n04-04-2024,-500\n05-04-2024,-500\n08-04-2024,-500\n09-04-2024,-500\n10-04-2024,-500\n11-04-2024,-500\n12-04-2024,-500\n15-04-2024,-500\n16-04-2024,-500\n17-04-2024,-500\n18-04-2024,-500\n19-04-2024,-500\n22-04-2024,-500\n23-04-2024,-500\n24-04-2024,-500\n25-04-2024,-500\n26-04-2024,-500\n30-04-2024,-500\n01-05-2024,-500\n02-05-2024,-500\n07-05-2024,-500\n08-05-2024,-500\n09-05-2024,-500\n10-05-2024,-500\n13-05-2024,-500\n14-05-2024,-500\n15-05-2024,-500\n16-05-2024,-500\n17-05-2024,-500\n20-05-2024,-500\n21-05-2024,-500\n22-05-2024,-500\n23-05-2024,-500\n24-05-2024,-500\n27-05-2024,-500\n28-05-2024,-500\n29-05-2024,-500\n30-05-2024,-500\n31-05-2024,-500\n03-06-2024,-500\n04-06-2024,-500\n05-06-2024,-500\n06-06-2024,-500"
  },
  {
    "path": "python/rateslib/data/historical/nok_rfr.csv",
    "content": "﻿reference_date,rate\n02-01-2020,1.49\n03-01-2020,1.49\n06-01-2020,1.49\n07-01-2020,1.49\n08-01-2020,1.49\n09-01-2020,1.49\n10-01-2020,1.49\n13-01-2020,1.49\n14-01-2020,1.49\n15-01-2020,1.49\n16-01-2020,1.49\n17-01-2020,1.49\n20-01-2020,1.49\n21-01-2020,1.49\n22-01-2020,1.49\n23-01-2020,1.49\n24-01-2020,1.49\n27-01-2020,1.49\n28-01-2020,1.49\n29-01-2020,1.49\n30-01-2020,1.49\n31-01-2020,1.49\n03-02-2020,1.49\n04-02-2020,1.49\n05-02-2020,1.49\n06-02-2020,1.49\n07-02-2020,1.49\n10-02-2020,1.49\n11-02-2020,1.49\n12-02-2020,1.49\n13-02-2020,1.49\n14-02-2020,1.49\n17-02-2020,1.49\n18-02-2020,1.49\n19-02-2020,1.49\n20-02-2020,1.49\n21-02-2020,1.49\n24-02-2020,1.49\n25-02-2020,1.49\n26-02-2020,1.49\n27-02-2020,1.49\n28-02-2020,1.51\n02-03-2020,1.49\n03-03-2020,1.49\n04-03-2020,1.49\n05-03-2020,1.49\n06-03-2020,1.49\n09-03-2020,1.49\n10-03-2020,1.49\n11-03-2020,1.49\n12-03-2020,1.49\n13-03-2020,1.49\n16-03-2020,0.99\n17-03-2020,0.99\n18-03-2020,0.99\n19-03-2020,0.99\n20-03-2020,0.99\n23-03-2020,0.24\n24-03-2020,0.24\n25-03-2020,0.24\n26-03-2020,0.24\n27-03-2020,0.24\n30-03-2020,0.24\n31-03-2020,0.24\n01-04-2020,0.25\n02-04-2020,0.25\n03-04-2020,0.25\n06-04-2020,0.24\n07-04-2020,0.25\n08-04-2020,0.25\n14-04-2020,0.24\n15-04-2020,0.24\n16-04-2020,0.24\n17-04-2020,0.24\n20-04-2020,0.23\n21-04-2020,0.24\n22-04-2020,0.24\n23-04-2020,0.25\n24-04-2020,0.25\n27-04-2020,0.24\n28-04-2020,0.24\n29-04-2020,0.24\n30-04-2020,0.24\n04-05-2020,0.24\n05-05-2020,0.24\n06-05-2020,0.24\n07-05-2020,0.24\n08-05-2020,0\n11-05-2020,0\n12-05-2020,0\n13-05-2020,0\n14-05-2020,0\n15-05-2020,0\n18-05-2020,0\n19-05-2020,0\n20-05-2020,0\n22-05-2020,0\n25-05-2020,0\n26-05-2020,0\n27-05-2020,0\n28-05-2020,0\n29-05-2020,0\n02-06-2020,0\n03-06-2020,0\n04-06-2020,0\n05-06-2020,0\n08-06-2020,0\n09-06-2020,0\n10-06-2020,0\n11-06-2020,0\n12-06-2020,0\n15-06-2020,0\n16-06-2020,0\n17-06-2020,0\n18-06-2020,0\n19-06-2020,0\n22-06-2020,0\n23-06-2020,0.01\n24-06-2020,-0.01\n25-06-2020,-0.01\n26-06-2020,-0.01\n29-06-2020,0\n30-06-2020,-0.01\n01-07-2020,0\n02-07-2020,0\n03-07-2020,0\n06-07-2020,0\n07-07-2020,0\n08-07-2020,-0.01\n09-07-2020,0\n10-07-2020,-0.01\n13-07-2020,0\n14-07-2020,0\n15-07-2020,0\n16-07-2020,0\n17-07-2020,0\n20-07-2020,0\n21-07-2020,0\n22-07-2020,0\n23-07-2020,0\n24-07-2020,0\n27-07-2020,0\n28-07-2020,0\n29-07-2020,-0.01\n30-07-2020,0\n31-07-2020,-0.01\n03-08-2020,-0.01\n04-08-2020,-0.01\n05-08-2020,-0.01\n06-08-2020,-0.01\n07-08-2020,-0.01\n10-08-2020,-0.01\n11-08-2020,-0.01\n12-08-2020,0\n13-08-2020,0\n14-08-2020,0\n17-08-2020,-0.01\n18-08-2020,-0.01\n19-08-2020,-0.01\n20-08-2020,-0.01\n21-08-2020,-0.01\n24-08-2020,-0.01\n25-08-2020,-0.01\n26-08-2020,-0.01\n27-08-2020,-0.01\n28-08-2020,-0.01\n31-08-2020,0.04\n01-09-2020,-0.01\n02-09-2020,0\n03-09-2020,-0.01\n04-09-2020,-0.01\n07-09-2020,-0.01\n08-09-2020,0\n09-09-2020,0\n10-09-2020,0\n11-09-2020,-0.01\n14-09-2020,0\n15-09-2020,0\n16-09-2020,0\n17-09-2020,0\n18-09-2020,0\n21-09-2020,0\n22-09-2020,0\n23-09-2020,0\n24-09-2020,0\n25-09-2020,0\n28-09-2020,0\n29-09-2020,0\n30-09-2020,0\n01-10-2020,0\n02-10-2020,0\n05-10-2020,0\n06-10-2020,0\n07-10-2020,0\n08-10-2020,0\n09-10-2020,0\n12-10-2020,0\n13-10-2020,0\n14-10-2020,0\n15-10-2020,0\n16-10-2020,0\n19-10-2020,0\n20-10-2020,0\n21-10-2020,0\n22-10-2020,0\n23-10-2020,0\n26-10-2020,0\n27-10-2020,0\n28-10-2020,0\n29-10-2020,0\n30-10-2020,0\n02-11-2020,0\n03-11-2020,0\n04-11-2020,0\n05-11-2020,0\n06-11-2020,0\n09-11-2020,0\n10-11-2020,0\n11-11-2020,0\n12-11-2020,0\n13-11-2020,0\n16-11-2020,0\n17-11-2020,0\n18-11-2020,0\n19-11-2020,0\n20-11-2020,0\n23-11-2020,0\n24-11-2020,0\n25-11-2020,0\n26-11-2020,0\n27-11-2020,0\n30-11-2020,0\n01-12-2020,0\n02-12-2020,0\n03-12-2020,0\n04-12-2020,0\n07-12-2020,0\n08-12-2020,0\n09-12-2020,0\n10-12-2020,0\n11-12-2020,0\n14-12-2020,0\n15-12-2020,0\n16-12-2020,0\n17-12-2020,0\n18-12-2020,0\n21-12-2020,0\n22-12-2020,0\n23-12-2020,0\n28-12-2020,0\n29-12-2020,0\n30-12-2020,0\n31-12-2020,0\n04-01-2021,0\n05-01-2021,-0.01\n06-01-2021,-0.01\n07-01-2021,-0.01\n08-01-2021,0\n11-01-2021,0\n12-01-2021,0\n13-01-2021,0\n14-01-2021,0\n15-01-2021,0\n18-01-2021,0\n19-01-2021,0\n20-01-2021,0\n21-01-2021,0\n22-01-2021,0\n25-01-2021,0\n26-01-2021,0\n27-01-2021,0\n28-01-2021,0\n29-01-2021,0\n01-02-2021,0\n02-02-2021,0\n03-02-2021,0\n04-02-2021,0\n05-02-2021,0\n08-02-2021,0\n09-02-2021,0\n10-02-2021,0\n11-02-2021,0\n12-02-2021,0\n15-02-2021,0\n16-02-2021,0\n17-02-2021,0\n18-02-2021,0\n19-02-2021,0\n22-02-2021,0\n23-02-2021,0\n24-02-2021,0\n25-02-2021,0\n26-02-2021,0\n01-03-2021,0\n02-03-2021,0\n03-03-2021,0\n04-03-2021,0\n05-03-2021,0\n08-03-2021,0\n09-03-2021,0\n10-03-2021,0\n11-03-2021,0\n12-03-2021,0\n15-03-2021,0\n16-03-2021,0\n17-03-2021,0\n18-03-2021,0\n19-03-2021,0\n22-03-2021,0\n23-03-2021,0\n24-03-2021,0\n25-03-2021,0\n26-03-2021,0\n29-03-2021,0\n30-03-2021,0\n31-03-2021,0\n06-04-2021,0\n07-04-2021,0\n08-04-2021,0\n09-04-2021,0\n12-04-2021,0\n13-04-2021,0\n14-04-2021,0\n15-04-2021,0\n16-04-2021,0\n19-04-2021,0\n20-04-2021,0\n21-04-2021,0\n22-04-2021,0\n23-04-2021,0\n26-04-2021,0\n27-04-2021,0\n28-04-2021,0\n29-04-2021,0\n30-04-2021,0\n03-05-2021,0\n04-05-2021,0\n05-05-2021,0\n06-05-2021,0\n07-05-2021,0\n10-05-2021,0\n11-05-2021,0\n12-05-2021,0\n14-05-2021,0\n18-05-2021,0\n19-05-2021,0\n20-05-2021,0\n21-05-2021,0\n25-05-2021,0\n26-05-2021,0\n27-05-2021,0\n28-05-2021,0\n31-05-2021,0\n01-06-2021,0\n02-06-2021,0\n03-06-2021,0\n04-06-2021,-0.01\n07-06-2021,0\n08-06-2021,0\n09-06-2021,0\n10-06-2021,0\n11-06-2021,0\n14-06-2021,0\n15-06-2021,0\n16-06-2021,0\n17-06-2021,0\n18-06-2021,0\n21-06-2021,0\n22-06-2021,0\n23-06-2021,0\n24-06-2021,0\n25-06-2021,0\n28-06-2021,0\n29-06-2021,0\n30-06-2021,-0.01\n01-07-2021,0\n02-07-2021,0\n05-07-2021,0\n06-07-2021,0\n07-07-2021,0\n08-07-2021,0\n09-07-2021,0\n12-07-2021,0\n13-07-2021,0\n14-07-2021,0\n15-07-2021,0\n16-07-2021,0\n19-07-2021,0\n20-07-2021,0\n21-07-2021,0\n22-07-2021,0\n23-07-2021,0\n26-07-2021,0\n27-07-2021,0\n28-07-2021,0\n29-07-2021,0\n30-07-2021,0\n02-08-2021,0\n03-08-2021,0\n04-08-2021,0\n05-08-2021,0\n06-08-2021,0\n09-08-2021,0\n10-08-2021,0\n11-08-2021,0\n12-08-2021,0\n13-08-2021,0\n16-08-2021,0\n17-08-2021,0\n18-08-2021,0\n19-08-2021,0\n20-08-2021,0\n23-08-2021,0\n24-08-2021,0\n25-08-2021,0\n26-08-2021,0\n27-08-2021,0\n30-08-2021,0\n31-08-2021,0\n01-09-2021,0\n02-09-2021,0\n03-09-2021,0\n06-09-2021,0\n07-09-2021,0\n08-09-2021,0\n09-09-2021,0\n10-09-2021,0\n13-09-2021,0\n14-09-2021,0\n15-09-2021,0\n16-09-2021,0\n17-09-2021,0\n20-09-2021,0\n21-09-2021,0\n22-09-2021,0\n23-09-2021,0\n24-09-2021,0.25\n27-09-2021,0.25\n28-09-2021,0.25\n29-09-2021,0.25\n30-09-2021,0.25\n01-10-2021,0.25\n04-10-2021,0.25\n05-10-2021,0.25\n06-10-2021,0.25\n07-10-2021,0.25\n08-10-2021,0.25\n11-10-2021,0.25\n12-10-2021,0.25\n13-10-2021,0.25\n14-10-2021,0.25\n15-10-2021,0.25\n18-10-2021,0.25\n19-10-2021,0.25\n20-10-2021,0.25\n21-10-2021,0.25\n22-10-2021,0.25\n25-10-2021,0.25\n26-10-2021,0.25\n27-10-2021,0.25\n28-10-2021,0.25\n29-10-2021,0.25\n01-11-2021,0.25\n02-11-2021,0.25\n03-11-2021,0.25\n04-11-2021,0.25\n05-11-2021,0.25\n08-11-2021,0.25\n09-11-2021,0.25\n10-11-2021,0.25\n11-11-2021,0.25\n12-11-2021,0.25\n15-11-2021,0.25\n16-11-2021,0.25\n17-11-2021,0.25\n18-11-2021,0.25\n19-11-2021,0.25\n22-11-2021,0.25\n23-11-2021,0.25\n24-11-2021,0.25\n25-11-2021,0.25\n26-11-2021,0.25\n29-11-2021,0.25\n30-11-2021,0.25\n01-12-2021,0.25\n02-12-2021,0.25\n03-12-2021,0.25\n06-12-2021,0.25\n07-12-2021,0.25\n08-12-2021,0.25\n09-12-2021,0.25\n10-12-2021,0.25\n13-12-2021,0.25\n14-12-2021,0.25\n15-12-2021,0.25\n16-12-2021,0.25\n17-12-2021,0.5\n20-12-2021,0.5\n21-12-2021,0.5\n22-12-2021,0.5\n23-12-2021,0.5\n27-12-2021,0.5\n28-12-2021,0.5\n29-12-2021,0.5\n30-12-2021,0.5\n31-12-2021,0.5\n03-01-2022,0.5\n04-01-2022,0.5\n05-01-2022,0.5\n06-01-2022,0.5\n07-01-2022,0.5\n10-01-2022,0.5\n11-01-2022,0.5\n12-01-2022,0.5\n13-01-2022,0.5\n14-01-2022,0.5\n17-01-2022,0.5\n18-01-2022,0.5\n19-01-2022,0.5\n20-01-2022,0.5\n21-01-2022,0.5\n24-01-2022,0.5\n25-01-2022,0.5\n26-01-2022,0.5\n27-01-2022,0.5\n28-01-2022,0.5\n31-01-2022,0.5\n01-02-2022,0.5\n02-02-2022,0.5\n03-02-2022,0.5\n04-02-2022,0.5\n07-02-2022,0.5\n08-02-2022,0.5\n09-02-2022,0.5\n10-02-2022,0.5\n11-02-2022,0.5\n14-02-2022,0.5\n15-02-2022,0.5\n16-02-2022,0.5\n17-02-2022,0.5\n18-02-2022,0.5\n21-02-2022,0.5\n22-02-2022,0.5\n23-02-2022,0.5\n24-02-2022,0.5\n25-02-2022,0.5\n28-02-2022,0.5\n01-03-2022,0.5\n02-03-2022,0.5\n03-03-2022,0.5\n04-03-2022,0.5\n07-03-2022,0.5\n08-03-2022,0.5\n09-03-2022,0.5\n10-03-2022,0.5\n11-03-2022,0.5\n14-03-2022,0.5\n15-03-2022,0.5\n16-03-2022,0.5\n17-03-2022,0.5\n18-03-2022,0.5\n21-03-2022,0.5\n22-03-2022,0.5\n23-03-2022,0.5\n24-03-2022,0.5\n25-03-2022,0.75\n28-03-2022,0.75\n29-03-2022,0.75\n30-03-2022,0.75\n31-03-2022,0.75\n01-04-2022,0.75\n04-04-2022,0.75\n05-04-2022,0.75\n06-04-2022,0.75\n07-04-2022,0.75\n08-04-2022,0.75\n11-04-2022,0.75\n12-04-2022,0.75\n13-04-2022,0.75\n19-04-2022,0.75\n20-04-2022,0.75\n21-04-2022,0.75\n22-04-2022,0.75\n25-04-2022,0.75\n26-04-2022,0.75\n27-04-2022,0.75\n28-04-2022,0.75\n29-04-2022,0.75\n02-05-2022,0.75\n03-05-2022,0.75\n04-05-2022,0.75\n05-05-2022,0.75\n06-05-2022,0.75\n09-05-2022,0.75\n10-05-2022,0.75\n11-05-2022,0.75\n12-05-2022,0.75\n13-05-2022,0.75\n16-05-2022,0.75\n18-05-2022,0.75\n19-05-2022,0.75\n20-05-2022,0.75\n23-05-2022,0.75\n24-05-2022,0.75\n25-05-2022,0.75\n27-05-2022,0.75\n30-05-2022,0.75\n31-05-2022,0.75\n01-06-2022,0.75\n02-06-2022,0.75\n03-06-2022,0.75\n07-06-2022,0.75\n08-06-2022,0.75\n09-06-2022,0.75\n10-06-2022,0.75\n13-06-2022,0.75\n14-06-2022,0.75\n15-06-2022,0.75\n16-06-2022,0.75\n17-06-2022,0.75\n20-06-2022,0.75\n21-06-2022,0.75\n22-06-2022,0.75\n23-06-2022,0.75\n24-06-2022,1.25\n27-06-2022,1.25\n28-06-2022,1.25\n29-06-2022,1.25\n30-06-2022,1.25\n01-07-2022,1.25\n04-07-2022,1.25\n05-07-2022,1.25\n06-07-2022,1.25\n07-07-2022,1.25\n08-07-2022,1.25\n11-07-2022,1.25\n12-07-2022,1.25\n13-07-2022,1.25\n14-07-2022,1.25\n15-07-2022,1.25\n18-07-2022,1.25\n19-07-2022,1.25\n20-07-2022,1.25\n21-07-2022,1.25\n22-07-2022,1.25\n25-07-2022,1.25\n26-07-2022,1.25\n27-07-2022,1.25\n28-07-2022,1.25\n29-07-2022,1.25\n01-08-2022,1.25\n02-08-2022,1.25\n03-08-2022,1.25\n04-08-2022,1.25\n05-08-2022,1.25\n08-08-2022,1.25\n09-08-2022,1.25\n10-08-2022,1.25\n11-08-2022,1.25\n12-08-2022,1.25\n15-08-2022,1.25\n16-08-2022,1.25\n17-08-2022,1.25\n18-08-2022,1.25\n19-08-2022,1.75\n22-08-2022,1.75\n23-08-2022,1.75\n24-08-2022,1.75\n25-08-2022,1.75\n26-08-2022,1.75\n29-08-2022,1.75\n30-08-2022,1.75\n31-08-2022,1.75\n01-09-2022,1.75\n02-09-2022,1.75\n05-09-2022,1.75\n06-09-2022,1.75\n07-09-2022,1.75\n08-09-2022,1.75\n09-09-2022,1.75\n12-09-2022,1.75\n13-09-2022,1.75\n14-09-2022,1.75\n15-09-2022,1.75\n16-09-2022,1.75\n19-09-2022,1.75\n20-09-2022,1.75\n21-09-2022,1.75\n22-09-2022,1.75\n23-09-2022,2.25\n26-09-2022,2.25\n27-09-2022,2.25\n28-09-2022,2.25\n29-09-2022,2.25\n30-09-2022,2.27\n03-10-2022,2.3\n04-10-2022,2.25\n05-10-2022,2.25\n06-10-2022,2.25\n07-10-2022,2.25\n10-10-2022,2.25\n11-10-2022,2.25\n12-10-2022,2.25\n13-10-2022,2.25\n14-10-2022,2.25\n17-10-2022,2.25\n18-10-2022,2.25\n19-10-2022,2.25\n20-10-2022,2.25\n21-10-2022,2.25\n24-10-2022,2.25\n25-10-2022,2.25\n26-10-2022,2.25\n27-10-2022,2.25\n28-10-2022,2.25\n31-10-2022,2.25\n01-11-2022,2.25\n02-11-2022,2.25\n03-11-2022,2.25\n04-11-2022,2.5\n07-11-2022,2.5\n08-11-2022,2.5\n09-11-2022,2.5\n10-11-2022,2.5\n11-11-2022,2.5\n14-11-2022,2.5\n15-11-2022,2.5\n16-11-2022,2.5\n17-11-2022,2.5\n18-11-2022,2.5\n21-11-2022,2.5\n22-11-2022,2.5\n23-11-2022,2.5\n24-11-2022,2.5\n25-11-2022,2.5\n28-11-2022,2.5\n29-11-2022,2.5\n30-11-2022,2.5\n01-12-2022,2.5\n02-12-2022,2.5\n05-12-2022,2.5\n06-12-2022,2.5\n07-12-2022,2.5\n08-12-2022,2.5\n09-12-2022,2.5\n12-12-2022,2.5\n13-12-2022,2.5\n14-12-2022,2.5\n15-12-2022,2.5\n16-12-2022,2.75\n19-12-2022,2.75\n20-12-2022,2.75\n21-12-2022,2.75\n22-12-2022,2.75\n23-12-2022,2.75\n27-12-2022,2.75\n28-12-2022,2.75\n29-12-2022,2.75\n30-12-2022,2.75\n02-01-2023,2.75\n03-01-2023,2.75\n04-01-2023,2.75\n05-01-2023,2.75\n06-01-2023,2.75\n09-01-2023,2.75\n10-01-2023,2.75\n11-01-2023,2.75\n12-01-2023,2.75\n13-01-2023,2.75\n16-01-2023,2.75\n17-01-2023,2.75\n18-01-2023,2.75\n19-01-2023,2.75\n20-01-2023,2.75\n23-01-2023,2.75\n24-01-2023,2.75\n25-01-2023,2.75\n26-01-2023,2.75\n27-01-2023,2.75\n30-01-2023,2.75\n31-01-2023,2.75\n01-02-2023,2.75\n02-02-2023,2.75\n03-02-2023,2.75\n06-02-2023,2.75\n07-02-2023,2.75\n08-02-2023,2.75\n09-02-2023,2.75\n10-02-2023,2.75\n13-02-2023,2.75\n14-02-2023,2.75\n15-02-2023,2.75\n16-02-2023,2.75\n17-02-2023,2.75\n20-02-2023,2.75\n21-02-2023,2.75\n22-02-2023,2.75\n23-02-2023,2.75\n24-02-2023,2.75\n27-02-2023,2.75\n28-02-2023,2.75\n01-03-2023,2.75\n02-03-2023,2.75\n03-03-2023,2.75\n06-03-2023,2.75\n07-03-2023,2.75\n08-03-2023,2.75\n09-03-2023,2.75\n10-03-2023,2.75\n13-03-2023,2.75\n14-03-2023,2.75\n15-03-2023,2.75\n16-03-2023,2.75\n17-03-2023,2.75\n20-03-2023,2.75\n21-03-2023,2.75\n22-03-2023,2.75\n23-03-2023,2.75\n24-03-2023,3\n27-03-2023,3\n28-03-2023,3\n29-03-2023,3\n30-03-2023,3\n31-03-2023,3\n03-04-2023,3\n04-04-2023,3\n05-04-2023,3\n11-04-2023,3\n12-04-2023,3\n13-04-2023,3\n14-04-2023,3\n17-04-2023,3\n18-04-2023,3\n19-04-2023,3\n20-04-2023,3\n21-04-2023,3\n24-04-2023,3\n25-04-2023,3\n26-04-2023,3\n27-04-2023,3\n28-04-2023,3\n02-05-2023,3\n03-05-2023,3\n04-05-2023,3\n05-05-2023,3.25\n08-05-2023,3.25\n09-05-2023,3.25\n10-05-2023,3.25\n11-05-2023,3.25\n12-05-2023,3.25\n15-05-2023,3.25\n16-05-2023,3.25\n19-05-2023,3.25\n22-05-2023,3.25\n23-05-2023,3.25\n24-05-2023,3.25\n25-05-2023,3.25\n26-05-2023,3.25\n30-05-2023,3.25\n31-05-2023,3.25\n01-06-2023,3.25\n02-06-2023,3.25\n05-06-2023,3.25\n06-06-2023,3.25\n07-06-2023,3.25\n08-06-2023,3.25\n09-06-2023,3.25\n12-06-2023,3.25\n13-06-2023,3.25\n14-06-2023,3.25\n15-06-2023,3.25\n16-06-2023,3.25\n19-06-2023,3.25\n20-06-2023,3.25\n21-06-2023,3.25\n22-06-2023,3.25\n23-06-2023,3.75\n26-06-2023,3.75\n27-06-2023,3.75\n28-06-2023,3.75\n29-06-2023,3.75\n30-06-2023,3.96\n03-07-2023,3.75\n04-07-2023,3.75\n05-07-2023,3.75\n06-07-2023,3.75\n07-07-2023,3.75\n10-07-2023,3.75\n11-07-2023,3.75\n12-07-2023,3.75\n13-07-2023,3.75\n14-07-2023,3.75\n17-07-2023,3.75\n18-07-2023,3.75\n19-07-2023,3.75\n20-07-2023,3.75\n21-07-2023,3.75\n24-07-2023,3.75\n25-07-2023,3.75\n26-07-2023,3.75\n27-07-2023,3.75\n28-07-2023,3.75\n31-07-2023,3.75\n01-08-2023,3.75\n02-08-2023,3.75"
  },
  {
    "path": "python/rateslib/data/historical/nowa.csv",
    "content": "﻿reference_date,rate\n02-01-2020,1.49\n03-01-2020,1.49\n06-01-2020,1.49\n07-01-2020,1.49\n08-01-2020,1.49\n09-01-2020,1.49\n10-01-2020,1.49\n13-01-2020,1.49\n14-01-2020,1.49\n15-01-2020,1.49\n16-01-2020,1.49\n17-01-2020,1.49\n20-01-2020,1.49\n21-01-2020,1.49\n22-01-2020,1.49\n23-01-2020,1.49\n24-01-2020,1.49\n27-01-2020,1.49\n28-01-2020,1.49\n29-01-2020,1.49\n30-01-2020,1.49\n31-01-2020,1.49\n03-02-2020,1.49\n04-02-2020,1.49\n05-02-2020,1.49\n06-02-2020,1.49\n07-02-2020,1.49\n10-02-2020,1.49\n11-02-2020,1.49\n12-02-2020,1.49\n13-02-2020,1.49\n14-02-2020,1.49\n17-02-2020,1.49\n18-02-2020,1.49\n19-02-2020,1.49\n20-02-2020,1.49\n21-02-2020,1.49\n24-02-2020,1.49\n25-02-2020,1.49\n26-02-2020,1.49\n27-02-2020,1.49\n28-02-2020,1.51\n02-03-2020,1.49\n03-03-2020,1.49\n04-03-2020,1.49\n05-03-2020,1.49\n06-03-2020,1.49\n09-03-2020,1.49\n10-03-2020,1.49\n11-03-2020,1.49\n12-03-2020,1.49\n13-03-2020,1.49\n16-03-2020,0.99\n17-03-2020,0.99\n18-03-2020,0.99\n19-03-2020,0.99\n20-03-2020,0.99\n23-03-2020,0.24\n24-03-2020,0.24\n25-03-2020,0.24\n26-03-2020,0.24\n27-03-2020,0.24\n30-03-2020,0.24\n31-03-2020,0.24\n01-04-2020,0.25\n02-04-2020,0.25\n03-04-2020,0.25\n06-04-2020,0.24\n07-04-2020,0.25\n08-04-2020,0.25\n14-04-2020,0.24\n15-04-2020,0.24\n16-04-2020,0.24\n17-04-2020,0.24\n20-04-2020,0.23\n21-04-2020,0.24\n22-04-2020,0.24\n23-04-2020,0.25\n24-04-2020,0.25\n27-04-2020,0.24\n28-04-2020,0.24\n29-04-2020,0.24\n30-04-2020,0.24\n04-05-2020,0.24\n05-05-2020,0.24\n06-05-2020,0.24\n07-05-2020,0.24\n08-05-2020,0\n11-05-2020,0\n12-05-2020,0\n13-05-2020,0\n14-05-2020,0\n15-05-2020,0\n18-05-2020,0\n19-05-2020,0\n20-05-2020,0\n22-05-2020,0\n25-05-2020,0\n26-05-2020,0\n27-05-2020,0\n28-05-2020,0\n29-05-2020,0\n02-06-2020,0\n03-06-2020,0\n04-06-2020,0\n05-06-2020,0\n08-06-2020,0\n09-06-2020,0\n10-06-2020,0\n11-06-2020,0\n12-06-2020,0\n15-06-2020,0\n16-06-2020,0\n17-06-2020,0\n18-06-2020,0\n19-06-2020,0\n22-06-2020,0\n23-06-2020,0.01\n24-06-2020,-0.01\n25-06-2020,-0.01\n26-06-2020,-0.01\n29-06-2020,0\n30-06-2020,-0.01\n01-07-2020,0\n02-07-2020,0\n03-07-2020,0\n06-07-2020,0\n07-07-2020,0\n08-07-2020,-0.01\n09-07-2020,0\n10-07-2020,-0.01\n13-07-2020,0\n14-07-2020,0\n15-07-2020,0\n16-07-2020,0\n17-07-2020,0\n20-07-2020,0\n21-07-2020,0\n22-07-2020,0\n23-07-2020,0\n24-07-2020,0\n27-07-2020,0\n28-07-2020,0\n29-07-2020,-0.01\n30-07-2020,0\n31-07-2020,-0.01\n03-08-2020,-0.01\n04-08-2020,-0.01\n05-08-2020,-0.01\n06-08-2020,-0.01\n07-08-2020,-0.01\n10-08-2020,-0.01\n11-08-2020,-0.01\n12-08-2020,0\n13-08-2020,0\n14-08-2020,0\n17-08-2020,-0.01\n18-08-2020,-0.01\n19-08-2020,-0.01\n20-08-2020,-0.01\n21-08-2020,-0.01\n24-08-2020,-0.01\n25-08-2020,-0.01\n26-08-2020,-0.01\n27-08-2020,-0.01\n28-08-2020,-0.01\n31-08-2020,0.04\n01-09-2020,-0.01\n02-09-2020,0\n03-09-2020,-0.01\n04-09-2020,-0.01\n07-09-2020,-0.01\n08-09-2020,0\n09-09-2020,0\n10-09-2020,0\n11-09-2020,-0.01\n14-09-2020,0\n15-09-2020,0\n16-09-2020,0\n17-09-2020,0\n18-09-2020,0\n21-09-2020,0\n22-09-2020,0\n23-09-2020,0\n24-09-2020,0\n25-09-2020,0\n28-09-2020,0\n29-09-2020,0\n30-09-2020,0\n01-10-2020,0\n02-10-2020,0\n05-10-2020,0\n06-10-2020,0\n07-10-2020,0\n08-10-2020,0\n09-10-2020,0\n12-10-2020,0\n13-10-2020,0\n14-10-2020,0\n15-10-2020,0\n16-10-2020,0\n19-10-2020,0\n20-10-2020,0\n21-10-2020,0\n22-10-2020,0\n23-10-2020,0\n26-10-2020,0\n27-10-2020,0\n28-10-2020,0\n29-10-2020,0\n30-10-2020,0\n02-11-2020,0\n03-11-2020,0\n04-11-2020,0\n05-11-2020,0\n06-11-2020,0\n09-11-2020,0\n10-11-2020,0\n11-11-2020,0\n12-11-2020,0\n13-11-2020,0\n16-11-2020,0\n17-11-2020,0\n18-11-2020,0\n19-11-2020,0\n20-11-2020,0\n23-11-2020,0\n24-11-2020,0\n25-11-2020,0\n26-11-2020,0\n27-11-2020,0\n30-11-2020,0\n01-12-2020,0\n02-12-2020,0\n03-12-2020,0\n04-12-2020,0\n07-12-2020,0\n08-12-2020,0\n09-12-2020,0\n10-12-2020,0\n11-12-2020,0\n14-12-2020,0\n15-12-2020,0\n16-12-2020,0\n17-12-2020,0\n18-12-2020,0\n21-12-2020,0\n22-12-2020,0\n23-12-2020,0\n28-12-2020,0\n29-12-2020,0\n30-12-2020,0\n31-12-2020,0\n04-01-2021,0\n05-01-2021,-0.01\n06-01-2021,-0.01\n07-01-2021,-0.01\n08-01-2021,0\n11-01-2021,0\n12-01-2021,0\n13-01-2021,0\n14-01-2021,0\n15-01-2021,0\n18-01-2021,0\n19-01-2021,0\n20-01-2021,0\n21-01-2021,0\n22-01-2021,0\n25-01-2021,0\n26-01-2021,0\n27-01-2021,0\n28-01-2021,0\n29-01-2021,0\n01-02-2021,0\n02-02-2021,0\n03-02-2021,0\n04-02-2021,0\n05-02-2021,0\n08-02-2021,0\n09-02-2021,0\n10-02-2021,0\n11-02-2021,0\n12-02-2021,0\n15-02-2021,0\n16-02-2021,0\n17-02-2021,0\n18-02-2021,0\n19-02-2021,0\n22-02-2021,0\n23-02-2021,0\n24-02-2021,0\n25-02-2021,0\n26-02-2021,0\n01-03-2021,0\n02-03-2021,0\n03-03-2021,0\n04-03-2021,0\n05-03-2021,0\n08-03-2021,0\n09-03-2021,0\n10-03-2021,0\n11-03-2021,0\n12-03-2021,0\n15-03-2021,0\n16-03-2021,0\n17-03-2021,0\n18-03-2021,0\n19-03-2021,0\n22-03-2021,0\n23-03-2021,0\n24-03-2021,0\n25-03-2021,0\n26-03-2021,0\n29-03-2021,0\n30-03-2021,0\n31-03-2021,0\n06-04-2021,0\n07-04-2021,0\n08-04-2021,0\n09-04-2021,0\n12-04-2021,0\n13-04-2021,0\n14-04-2021,0\n15-04-2021,0\n16-04-2021,0\n19-04-2021,0\n20-04-2021,0\n21-04-2021,0\n22-04-2021,0\n23-04-2021,0\n26-04-2021,0\n27-04-2021,0\n28-04-2021,0\n29-04-2021,0\n30-04-2021,0\n03-05-2021,0\n04-05-2021,0\n05-05-2021,0\n06-05-2021,0\n07-05-2021,0\n10-05-2021,0\n11-05-2021,0\n12-05-2021,0\n14-05-2021,0\n18-05-2021,0\n19-05-2021,0\n20-05-2021,0\n21-05-2021,0\n25-05-2021,0\n26-05-2021,0\n27-05-2021,0\n28-05-2021,0\n31-05-2021,0\n01-06-2021,0\n02-06-2021,0\n03-06-2021,0\n04-06-2021,-0.01\n07-06-2021,0\n08-06-2021,0\n09-06-2021,0\n10-06-2021,0\n11-06-2021,0\n14-06-2021,0\n15-06-2021,0\n16-06-2021,0\n17-06-2021,0\n18-06-2021,0\n21-06-2021,0\n22-06-2021,0\n23-06-2021,0\n24-06-2021,0\n25-06-2021,0\n28-06-2021,0\n29-06-2021,0\n30-06-2021,-0.01\n01-07-2021,0\n02-07-2021,0\n05-07-2021,0\n06-07-2021,0\n07-07-2021,0\n08-07-2021,0\n09-07-2021,0\n12-07-2021,0\n13-07-2021,0\n14-07-2021,0\n15-07-2021,0\n16-07-2021,0\n19-07-2021,0\n20-07-2021,0\n21-07-2021,0\n22-07-2021,0\n23-07-2021,0\n26-07-2021,0\n27-07-2021,0\n28-07-2021,0\n29-07-2021,0\n30-07-2021,0\n02-08-2021,0\n03-08-2021,0\n04-08-2021,0\n05-08-2021,0\n06-08-2021,0\n09-08-2021,0\n10-08-2021,0\n11-08-2021,0\n12-08-2021,0\n13-08-2021,0\n16-08-2021,0\n17-08-2021,0\n18-08-2021,0\n19-08-2021,0\n20-08-2021,0\n23-08-2021,0\n24-08-2021,0\n25-08-2021,0\n26-08-2021,0\n27-08-2021,0\n30-08-2021,0\n31-08-2021,0\n01-09-2021,0\n02-09-2021,0\n03-09-2021,0\n06-09-2021,0\n07-09-2021,0\n08-09-2021,0\n09-09-2021,0\n10-09-2021,0\n13-09-2021,0\n14-09-2021,0\n15-09-2021,0\n16-09-2021,0\n17-09-2021,0\n20-09-2021,0\n21-09-2021,0\n22-09-2021,0\n23-09-2021,0\n24-09-2021,0.25\n27-09-2021,0.25\n28-09-2021,0.25\n29-09-2021,0.25\n30-09-2021,0.25\n01-10-2021,0.25\n04-10-2021,0.25\n05-10-2021,0.25\n06-10-2021,0.25\n07-10-2021,0.25\n08-10-2021,0.25\n11-10-2021,0.25\n12-10-2021,0.25\n13-10-2021,0.25\n14-10-2021,0.25\n15-10-2021,0.25\n18-10-2021,0.25\n19-10-2021,0.25\n20-10-2021,0.25\n21-10-2021,0.25\n22-10-2021,0.25\n25-10-2021,0.25\n26-10-2021,0.25\n27-10-2021,0.25\n28-10-2021,0.25\n29-10-2021,0.25\n01-11-2021,0.25\n02-11-2021,0.25\n03-11-2021,0.25\n04-11-2021,0.25\n05-11-2021,0.25\n08-11-2021,0.25\n09-11-2021,0.25\n10-11-2021,0.25\n11-11-2021,0.25\n12-11-2021,0.25\n15-11-2021,0.25\n16-11-2021,0.25\n17-11-2021,0.25\n18-11-2021,0.25\n19-11-2021,0.25\n22-11-2021,0.25\n23-11-2021,0.25\n24-11-2021,0.25\n25-11-2021,0.25\n26-11-2021,0.25\n29-11-2021,0.25\n30-11-2021,0.25\n01-12-2021,0.25\n02-12-2021,0.25\n03-12-2021,0.25\n06-12-2021,0.25\n07-12-2021,0.25\n08-12-2021,0.25\n09-12-2021,0.25\n10-12-2021,0.25\n13-12-2021,0.25\n14-12-2021,0.25\n15-12-2021,0.25\n16-12-2021,0.25\n17-12-2021,0.5\n20-12-2021,0.5\n21-12-2021,0.5\n22-12-2021,0.5\n23-12-2021,0.5\n27-12-2021,0.5\n28-12-2021,0.5\n29-12-2021,0.5\n30-12-2021,0.5\n31-12-2021,0.5\n03-01-2022,0.5\n04-01-2022,0.5\n05-01-2022,0.5\n06-01-2022,0.5\n07-01-2022,0.5\n10-01-2022,0.5\n11-01-2022,0.5\n12-01-2022,0.5\n13-01-2022,0.5\n14-01-2022,0.5\n17-01-2022,0.5\n18-01-2022,0.5\n19-01-2022,0.5\n20-01-2022,0.5\n21-01-2022,0.5\n24-01-2022,0.5\n25-01-2022,0.5\n26-01-2022,0.5\n27-01-2022,0.5\n28-01-2022,0.5\n31-01-2022,0.5\n01-02-2022,0.5\n02-02-2022,0.5\n03-02-2022,0.5\n04-02-2022,0.5\n07-02-2022,0.5\n08-02-2022,0.5\n09-02-2022,0.5\n10-02-2022,0.5\n11-02-2022,0.5\n14-02-2022,0.5\n15-02-2022,0.5\n16-02-2022,0.5\n17-02-2022,0.5\n18-02-2022,0.5\n21-02-2022,0.5\n22-02-2022,0.5\n23-02-2022,0.5\n24-02-2022,0.5\n25-02-2022,0.5\n28-02-2022,0.5\n01-03-2022,0.5\n02-03-2022,0.5\n03-03-2022,0.5\n04-03-2022,0.5\n07-03-2022,0.5\n08-03-2022,0.5\n09-03-2022,0.5\n10-03-2022,0.5\n11-03-2022,0.5\n14-03-2022,0.5\n15-03-2022,0.5\n16-03-2022,0.5\n17-03-2022,0.5\n18-03-2022,0.5\n21-03-2022,0.5\n22-03-2022,0.5\n23-03-2022,0.5\n24-03-2022,0.5\n25-03-2022,0.75\n28-03-2022,0.75\n29-03-2022,0.75\n30-03-2022,0.75\n31-03-2022,0.75\n01-04-2022,0.75\n04-04-2022,0.75\n05-04-2022,0.75\n06-04-2022,0.75\n07-04-2022,0.75\n08-04-2022,0.75\n11-04-2022,0.75\n12-04-2022,0.75\n13-04-2022,0.75\n19-04-2022,0.75\n20-04-2022,0.75\n21-04-2022,0.75\n22-04-2022,0.75\n25-04-2022,0.75\n26-04-2022,0.75\n27-04-2022,0.75\n28-04-2022,0.75\n29-04-2022,0.75\n02-05-2022,0.75\n03-05-2022,0.75\n04-05-2022,0.75\n05-05-2022,0.75\n06-05-2022,0.75\n09-05-2022,0.75\n10-05-2022,0.75\n11-05-2022,0.75\n12-05-2022,0.75\n13-05-2022,0.75\n16-05-2022,0.75\n18-05-2022,0.75\n19-05-2022,0.75\n20-05-2022,0.75\n23-05-2022,0.75\n24-05-2022,0.75\n25-05-2022,0.75\n27-05-2022,0.75\n30-05-2022,0.75\n31-05-2022,0.75\n01-06-2022,0.75\n02-06-2022,0.75\n03-06-2022,0.75\n07-06-2022,0.75\n08-06-2022,0.75\n09-06-2022,0.75\n10-06-2022,0.75\n13-06-2022,0.75\n14-06-2022,0.75\n15-06-2022,0.75\n16-06-2022,0.75\n17-06-2022,0.75\n20-06-2022,0.75\n21-06-2022,0.75\n22-06-2022,0.75\n23-06-2022,0.75\n24-06-2022,1.25\n27-06-2022,1.25\n28-06-2022,1.25\n29-06-2022,1.25\n30-06-2022,1.25\n01-07-2022,1.25\n04-07-2022,1.25\n05-07-2022,1.25\n06-07-2022,1.25\n07-07-2022,1.25\n08-07-2022,1.25\n11-07-2022,1.25\n12-07-2022,1.25\n13-07-2022,1.25\n14-07-2022,1.25\n15-07-2022,1.25\n18-07-2022,1.25\n19-07-2022,1.25\n20-07-2022,1.25\n21-07-2022,1.25\n22-07-2022,1.25\n25-07-2022,1.25\n26-07-2022,1.25\n27-07-2022,1.25\n28-07-2022,1.25\n29-07-2022,1.25\n01-08-2022,1.25\n02-08-2022,1.25\n03-08-2022,1.25\n04-08-2022,1.25\n05-08-2022,1.25\n08-08-2022,1.25\n09-08-2022,1.25\n10-08-2022,1.25\n11-08-2022,1.25\n12-08-2022,1.25\n15-08-2022,1.25\n16-08-2022,1.25\n17-08-2022,1.25\n18-08-2022,1.25\n19-08-2022,1.75\n22-08-2022,1.75\n23-08-2022,1.75\n24-08-2022,1.75\n25-08-2022,1.75\n26-08-2022,1.75\n29-08-2022,1.75\n30-08-2022,1.75\n31-08-2022,1.75\n01-09-2022,1.75\n02-09-2022,1.75\n05-09-2022,1.75\n06-09-2022,1.75\n07-09-2022,1.75\n08-09-2022,1.75\n09-09-2022,1.75\n12-09-2022,1.75\n13-09-2022,1.75\n14-09-2022,1.75\n15-09-2022,1.75\n16-09-2022,1.75\n19-09-2022,1.75\n20-09-2022,1.75\n21-09-2022,1.75\n22-09-2022,1.75\n23-09-2022,2.25\n26-09-2022,2.25\n27-09-2022,2.25\n28-09-2022,2.25\n29-09-2022,2.25\n30-09-2022,2.27\n03-10-2022,2.3\n04-10-2022,2.25\n05-10-2022,2.25\n06-10-2022,2.25\n07-10-2022,2.25\n10-10-2022,2.25\n11-10-2022,2.25\n12-10-2022,2.25\n13-10-2022,2.25\n14-10-2022,2.25\n17-10-2022,2.25\n18-10-2022,2.25\n19-10-2022,2.25\n20-10-2022,2.25\n21-10-2022,2.25\n24-10-2022,2.25\n25-10-2022,2.25\n26-10-2022,2.25\n27-10-2022,2.25\n28-10-2022,2.25\n31-10-2022,2.25\n01-11-2022,2.25\n02-11-2022,2.25\n03-11-2022,2.25\n04-11-2022,2.5\n07-11-2022,2.5\n08-11-2022,2.5\n09-11-2022,2.5\n10-11-2022,2.5\n11-11-2022,2.5\n14-11-2022,2.5\n15-11-2022,2.5\n16-11-2022,2.5\n17-11-2022,2.5\n18-11-2022,2.5\n21-11-2022,2.5\n22-11-2022,2.5\n23-11-2022,2.5\n24-11-2022,2.5\n25-11-2022,2.5\n28-11-2022,2.5\n29-11-2022,2.5\n30-11-2022,2.5\n01-12-2022,2.5\n02-12-2022,2.5\n05-12-2022,2.5\n06-12-2022,2.5\n07-12-2022,2.5\n08-12-2022,2.5\n09-12-2022,2.5\n12-12-2022,2.5\n13-12-2022,2.5\n14-12-2022,2.5\n15-12-2022,2.5\n16-12-2022,2.75\n19-12-2022,2.75\n20-12-2022,2.75\n21-12-2022,2.75\n22-12-2022,2.75\n23-12-2022,2.75\n27-12-2022,2.75\n28-12-2022,2.75\n29-12-2022,2.75\n30-12-2022,2.75\n02-01-2023,2.75\n03-01-2023,2.75\n04-01-2023,2.75\n05-01-2023,2.75\n06-01-2023,2.75\n09-01-2023,2.75\n10-01-2023,2.75\n11-01-2023,2.75\n12-01-2023,2.75\n13-01-2023,2.75\n16-01-2023,2.75\n17-01-2023,2.75\n18-01-2023,2.75\n19-01-2023,2.75\n20-01-2023,2.75\n23-01-2023,2.75\n24-01-2023,2.75\n25-01-2023,2.75\n26-01-2023,2.75\n27-01-2023,2.75\n30-01-2023,2.75\n31-01-2023,2.75\n01-02-2023,2.75\n02-02-2023,2.75\n03-02-2023,2.75\n06-02-2023,2.75\n07-02-2023,2.75\n08-02-2023,2.75\n09-02-2023,2.75\n10-02-2023,2.75\n13-02-2023,2.75\n14-02-2023,2.75\n15-02-2023,2.75\n16-02-2023,2.75\n17-02-2023,2.75\n20-02-2023,2.75\n21-02-2023,2.75\n22-02-2023,2.75\n23-02-2023,2.75\n24-02-2023,2.75\n27-02-2023,2.75\n28-02-2023,2.75\n01-03-2023,2.75\n02-03-2023,2.75\n03-03-2023,2.75\n06-03-2023,2.75\n07-03-2023,2.75\n08-03-2023,2.75\n09-03-2023,2.75\n10-03-2023,2.75\n13-03-2023,2.75\n14-03-2023,2.75\n15-03-2023,2.75\n16-03-2023,2.75\n17-03-2023,2.75\n20-03-2023,2.75\n21-03-2023,2.75\n22-03-2023,2.75\n23-03-2023,2.75\n24-03-2023,3\n27-03-2023,3\n28-03-2023,3\n29-03-2023,3\n30-03-2023,3\n31-03-2023,3\n03-04-2023,3\n04-04-2023,3\n05-04-2023,3\n11-04-2023,3\n12-04-2023,3\n13-04-2023,3\n14-04-2023,3\n17-04-2023,3\n18-04-2023,3\n19-04-2023,3\n20-04-2023,3\n21-04-2023,3\n24-04-2023,3\n25-04-2023,3\n26-04-2023,3\n27-04-2023,3\n28-04-2023,3\n02-05-2023,3\n03-05-2023,3\n04-05-2023,3\n05-05-2023,3.25\n08-05-2023,3.25\n09-05-2023,3.25\n10-05-2023,3.25\n11-05-2023,3.25\n12-05-2023,3.25\n15-05-2023,3.25\n16-05-2023,3.25\n19-05-2023,3.25\n22-05-2023,3.25\n23-05-2023,3.25\n24-05-2023,3.25\n25-05-2023,3.25\n26-05-2023,3.25\n30-05-2023,3.25\n31-05-2023,3.25\n01-06-2023,3.25\n02-06-2023,3.25\n05-06-2023,3.25\n06-06-2023,3.25\n07-06-2023,3.25\n08-06-2023,3.25\n09-06-2023,3.25\n12-06-2023,3.25\n13-06-2023,3.25\n14-06-2023,3.25\n15-06-2023,3.25\n16-06-2023,3.25\n19-06-2023,3.25\n20-06-2023,3.25\n21-06-2023,3.25\n22-06-2023,3.25\n23-06-2023,3.75\n26-06-2023,3.75\n27-06-2023,3.75\n28-06-2023,3.75\n29-06-2023,3.75\n30-06-2023,3.96\n03-07-2023,3.75\n04-07-2023,3.75\n05-07-2023,3.75\n06-07-2023,3.75\n07-07-2023,3.75\n10-07-2023,3.75\n11-07-2023,3.75\n12-07-2023,3.75\n13-07-2023,3.75\n14-07-2023,3.75\n17-07-2023,3.75\n18-07-2023,3.75\n19-07-2023,3.75\n20-07-2023,3.75\n21-07-2023,3.75\n24-07-2023,3.75\n25-07-2023,3.75\n26-07-2023,3.75\n27-07-2023,3.75\n28-07-2023,3.75\n31-07-2023,3.75\n01-08-2023,3.75\n02-08-2023,3.75"
  },
  {
    "path": "python/rateslib/data/historical/sek_rfr.csv",
    "content": "﻿reference_date,rate\n01-09-2021,-500\n02-09-2021,-500\n03-09-2021,-500\n06-09-2021,-500\n07-09-2021,-500\n08-09-2021,-500\n09-09-2021,-500\n10-09-2021,-500\n13-09-2021,-500\n14-09-2021,-500\n15-09-2021,-500\n16-09-2021,-500\n17-09-2021,-500\n20-09-2021,-500\n21-09-2021,-500\n22-09-2021,-500\n23-09-2021,-500\n24-09-2021,-500\n27-09-2021,-500\n28-09-2021,-500\n29-09-2021,-500\n30-09-2021,-500\n01-10-2021,-500\n04-10-2021,-500\n05-10-2021,-500\n06-10-2021,-500\n07-10-2021,-500\n08-10-2021,-500\n11-10-2021,-500\n12-10-2021,-500\n13-10-2021,-500\n14-10-2021,-500\n15-10-2021,-500\n18-10-2021,-500\n19-10-2021,-500\n20-10-2021,-500\n21-10-2021,-500\n22-10-2021,-500\n25-10-2021,-500\n26-10-2021,-500\n27-10-2021,-500\n28-10-2021,-500\n29-10-2021,-500\n01-11-2021,-500\n02-11-2021,-500\n03-11-2021,-500\n04-11-2021,-500\n05-11-2021,-500\n08-11-2021,-500\n09-11-2021,-500\n10-11-2021,-500\n11-11-2021,-500\n12-11-2021,-500\n15-11-2021,-500\n16-11-2021,-500\n17-11-2021,-500\n18-11-2021,-500\n19-11-2021,-500\n22-11-2021,-500\n23-11-2021,-500\n24-11-2021,-500\n25-11-2021,-500\n26-11-2021,-500\n29-11-2021,-500\n30-11-2021,-500\n01-12-2021,-500\n02-12-2021,-500\n03-12-2021,-500\n06-12-2021,-500\n07-12-2021,-500\n08-12-2021,-500\n09-12-2021,-500\n10-12-2021,-500\n13-12-2021,-500\n14-12-2021,-500\n15-12-2021,-500\n16-12-2021,-500\n17-12-2021,-500\n20-12-2021,-500\n21-12-2021,-500\n22-12-2021,-500\n23-12-2021,-500\n27-12-2021,-500\n28-12-2021,-500\n29-12-2021,-500\n30-12-2021,-500\n03-01-2022,-500\n04-01-2022,-500\n05-01-2022,-500\n07-01-2022,-500\n10-01-2022,-500\n11-01-2022,-500\n12-01-2022,-500\n13-01-2022,-500\n14-01-2022,-500\n17-01-2022,-500\n18-01-2022,-500\n19-01-2022,-500\n20-01-2022,-500\n21-01-2022,-500\n24-01-2022,-500\n25-01-2022,-500\n26-01-2022,-500\n27-01-2022,-500\n28-01-2022,-500\n31-01-2022,-500\n01-02-2022,-500\n02-02-2022,-500\n03-02-2022,-500\n04-02-2022,-500\n07-02-2022,-500\n08-02-2022,-500\n09-02-2022,-500\n10-02-2022,-500\n11-02-2022,-500\n14-02-2022,-500\n15-02-2022,-500\n16-02-2022,-500\n17-02-2022,-500\n18-02-2022,-500\n21-02-2022,-500\n22-02-2022,-500\n23-02-2022,-500\n24-02-2022,-500\n25-02-2022,-500\n28-02-2022,-500\n01-03-2022,-500\n02-03-2022,-500\n03-03-2022,-500\n04-03-2022,-500\n07-03-2022,-500\n08-03-2022,-500\n09-03-2022,-500\n10-03-2022,-500\n11-03-2022,-500\n14-03-2022,-500\n15-03-2022,-500\n16-03-2022,-500\n17-03-2022,-500\n18-03-2022,-500\n21-03-2022,-500\n22-03-2022,-500\n23-03-2022,-500\n24-03-2022,-500\n25-03-2022,-500\n28-03-2022,-500\n29-03-2022,-500\n30-03-2022,-500\n31-03-2022,-500\n01-04-2022,-500\n04-04-2022,-500\n05-04-2022,-500\n06-04-2022,-500\n07-04-2022,-500\n08-04-2022,-500\n11-04-2022,-500\n12-04-2022,-500\n13-04-2022,-500\n14-04-2022,-500\n19-04-2022,-500\n20-04-2022,-500\n21-04-2022,-500\n22-04-2022,-500\n25-04-2022,-500\n26-04-2022,-500\n27-04-2022,-500\n28-04-2022,-500\n29-04-2022,-500\n02-05-2022,-500\n03-05-2022,-500\n04-05-2022,-500\n05-05-2022,-500\n06-05-2022,-500\n09-05-2022,-500\n10-05-2022,-500\n11-05-2022,-500\n12-05-2022,-500\n13-05-2022,-500\n16-05-2022,-500\n17-05-2022,-500\n18-05-2022,-500\n19-05-2022,-500\n20-05-2022,-500\n23-05-2022,-500\n24-05-2022,-500\n25-05-2022,-500\n27-05-2022,-500\n30-05-2022,-500\n31-05-2022,-500\n01-06-2022,-500\n02-06-2022,-500\n03-06-2022,-500\n07-06-2022,-500\n08-06-2022,-500\n09-06-2022,-500\n10-06-2022,-500\n13-06-2022,-500\n14-06-2022,-500\n15-06-2022,-500\n16-06-2022,-500\n17-06-2022,-500\n20-06-2022,-500\n21-06-2022,-500\n22-06-2022,-500\n23-06-2022,-500\n27-06-2022,-500\n28-06-2022,-500\n29-06-2022,-500\n30-06-2022,-500\n01-07-2022,-500\n04-07-2022,-500\n05-07-2022,-500\n06-07-2022,-500\n07-07-2022,-500\n08-07-2022,-500\n11-07-2022,-500\n12-07-2022,-500\n13-07-2022,-500\n14-07-2022,-500\n15-07-2022,-500\n18-07-2022,-500\n19-07-2022,-500\n20-07-2022,-500\n21-07-2022,-500\n22-07-2022,-500\n25-07-2022,-500\n26-07-2022,-500\n27-07-2022,-500\n28-07-2022,-500\n29-07-2022,-500\n01-08-2022,-500\n02-08-2022,-500\n03-08-2022,-500\n04-08-2022,-500\n05-08-2022,-500\n08-08-2022,-500\n09-08-2022,-500\n10-08-2022,-500\n11-08-2022,-500\n12-08-2022,-500\n15-08-2022,-500\n16-08-2022,-500\n17-08-2022,-500\n18-08-2022,-500\n19-08-2022,-500\n22-08-2022,-500\n23-08-2022,-500\n24-08-2022,-500\n25-08-2022,-500\n26-08-2022,-500\n29-08-2022,-500\n30-08-2022,-500\n31-08-2022,-500\n01-09-2022,-500\n02-09-2022,-500\n05-09-2022,-500\n06-09-2022,-500\n07-09-2022,-500\n08-09-2022,-500\n09-09-2022,-500\n12-09-2022,-500\n13-09-2022,-500\n14-09-2022,-500\n15-09-2022,-500\n16-09-2022,-500\n19-09-2022,-500\n20-09-2022,-500\n21-09-2022,-500\n22-09-2022,-500\n23-09-2022,-500\n26-09-2022,-500\n27-09-2022,-500\n28-09-2022,-500\n29-09-2022,-500\n30-09-2022,-500\n03-10-2022,-500\n04-10-2022,-500\n05-10-2022,-500\n06-10-2022,-500\n07-10-2022,-500\n10-10-2022,-500\n11-10-2022,-500\n12-10-2022,-500\n13-10-2022,-500\n14-10-2022,-500\n17-10-2022,-500\n18-10-2022,-500\n19-10-2022,-500\n20-10-2022,-500\n21-10-2022,-500\n24-10-2022,-500\n25-10-2022,-500\n26-10-2022,-500\n27-10-2022,-500\n28-10-2022,-500\n31-10-2022,-500\n01-11-2022,-500\n02-11-2022,-500\n03-11-2022,-500\n04-11-2022,-500\n07-11-2022,-500\n08-11-2022,-500\n09-11-2022,-500\n10-11-2022,-500\n11-11-2022,-500\n14-11-2022,-500\n15-11-2022,-500\n16-11-2022,-500\n17-11-2022,-500\n18-11-2022,-500\n21-11-2022,-500\n22-11-2022,-500\n23-11-2022,-500\n24-11-2022,-500\n25-11-2022,-500\n28-11-2022,-500\n29-11-2022,-500\n30-11-2022,-500\n01-12-2022,-500\n02-12-2022,-500\n05-12-2022,-500\n06-12-2022,-500\n07-12-2022,-500\n08-12-2022,-500\n09-12-2022,-500\n12-12-2022,-500\n13-12-2022,-500\n14-12-2022,-500\n15-12-2022,-500\n16-12-2022,-500\n19-12-2022,-500\n20-12-2022,-500\n21-12-2022,-500\n22-12-2022,-500\n23-12-2022,-500\n27-12-2022,-500\n28-12-2022,-500\n29-12-2022,-500\n30-12-2022,-500\n02-01-2023,-500\n03-01-2023,-500\n04-01-2023,-500\n05-01-2023,-500\n09-01-2023,-500\n10-01-2023,-500\n11-01-2023,-500\n12-01-2023,-500\n13-01-2023,-500\n16-01-2023,-500\n17-01-2023,-500\n18-01-2023,-500\n19-01-2023,-500\n20-01-2023,-500\n23-01-2023,-500\n24-01-2023,-500\n25-01-2023,-500\n26-01-2023,-500\n27-01-2023,-500\n30-01-2023,-500\n31-01-2023,-500\n01-02-2023,-500\n02-02-2023,-500\n03-02-2023,-500\n06-02-2023,-500\n07-02-2023,-500\n08-02-2023,-500\n09-02-2023,-500\n10-02-2023,-500\n13-02-2023,-500\n14-02-2023,-500\n15-02-2023,-500\n16-02-2023,-500\n17-02-2023,-500\n20-02-2023,-500\n21-02-2023,-500\n22-02-2023,-500\n23-02-2023,-500\n24-02-2023,-500\n27-02-2023,-500\n28-02-2023,-500\n01-03-2023,-500\n02-03-2023,-500\n03-03-2023,-500\n06-03-2023,-500\n07-03-2023,-500\n08-03-2023,-500\n09-03-2023,-500\n10-03-2023,-500\n13-03-2023,-500\n14-03-2023,-500\n15-03-2023,-500\n16-03-2023,-500\n17-03-2023,-500\n20-03-2023,-500\n21-03-2023,-500\n22-03-2023,-500\n23-03-2023,-500\n24-03-2023,-500\n27-03-2023,-500\n28-03-2023,-500\n29-03-2023,-500\n30-03-2023,-500\n31-03-2023,-500\n03-04-2023,-500\n04-04-2023,-500\n05-04-2023,-500\n06-04-2023,-500\n11-04-2023,-500\n12-04-2023,-500\n13-04-2023,-500\n14-04-2023,-500\n17-04-2023,-500\n18-04-2023,-500\n19-04-2023,-500\n20-04-2023,-500\n21-04-2023,-500\n24-04-2023,-500\n25-04-2023,-500\n26-04-2023,-500\n27-04-2023,-500\n28-04-2023,-500\n02-05-2023,-500\n03-05-2023,-500\n04-05-2023,-500\n05-05-2023,-500\n08-05-2023,-500\n09-05-2023,-500\n10-05-2023,-500\n11-05-2023,-500\n12-05-2023,-500\n15-05-2023,-500\n16-05-2023,-500\n17-05-2023,-500\n19-05-2023,-500\n22-05-2023,-500\n23-05-2023,-500\n24-05-2023,-500\n25-05-2023,-500\n26-05-2023,-500\n29-05-2023,-500\n30-05-2023,-500\n31-05-2023,-500\n01-06-2023,-500\n02-06-2023,-500\n05-06-2023,-500\n07-06-2023,-500\n08-06-2023,-500\n09-06-2023,-500\n12-06-2023,-500\n13-06-2023,-500\n14-06-2023,-500\n15-06-2023,-500\n16-06-2023,-500\n19-06-2023,-500\n20-06-2023,-500\n21-06-2023,-500\n22-06-2023,-500\n26-06-2023,-500\n27-06-2023,-500\n28-06-2023,-500\n29-06-2023,-500\n30-06-2023,-500\n03-07-2023,-500\n04-07-2023,-500\n05-07-2023,-500\n06-07-2023,-500\n07-07-2023,-500\n10-07-2023,-500\n11-07-2023,-500\n12-07-2023,-500\n13-07-2023,-500\n14-07-2023,-500\n17-07-2023,-500\n18-07-2023,-500\n19-07-2023,-500\n20-07-2023,-500\n21-07-2023,-500\n24-07-2023,-500\n25-07-2023,-500\n26-07-2023,-500\n27-07-2023,-500\n28-07-2023,-500\n31-07-2023,-500\n01-08-2023,-500\n02-08-2023,-500"
  },
  {
    "path": "python/rateslib/data/historical/sofr.csv",
    "content": "﻿reference_date,rate\n01-08-2023,-500\n31-07-2023,-500\n28-07-2023,-500\n27-07-2023,-500\n26-07-2023,-500\n25-07-2023,-500\n24-07-2023,-500\n21-07-2023,-500\n20-07-2023,-500\n19-07-2023,-500\n18-07-2023,-500\n17-07-2023,-500\n14-07-2023,-500\n13-07-2023,-500\n12-07-2023,-500\n11-07-2023,-500\n10-07-2023,-500\n07-07-2023,-500\n06-07-2023,-500\n05-07-2023,-500\n03-07-2023,-500\n30-06-2023,-500\n29-06-2023,-500\n28-06-2023,-500\n27-06-2023,-500\n26-06-2023,-500\n23-06-2023,-500\n22-06-2023,-500\n21-06-2023,-500\n20-06-2023,-500\n16-06-2023,-500\n15-06-2023,-500\n14-06-2023,-500\n13-06-2023,-500\n12-06-2023,-500\n09-06-2023,-500\n08-06-2023,-500\n07-06-2023,-500\n06-06-2023,-500\n05-06-2023,-500\n02-06-2023,-500\n01-06-2023,-500\n31-05-2023,-500\n30-05-2023,-500\n26-05-2023,-500\n25-05-2023,-500\n24-05-2023,-500\n23-05-2023,-500\n22-05-2023,-500\n19-05-2023,-500\n18-05-2023,-500\n17-05-2023,-500\n16-05-2023,-500\n15-05-2023,-500\n12-05-2023,-500\n11-05-2023,-500\n10-05-2023,-500\n09-05-2023,-500\n08-05-2023,-500\n05-05-2023,-500\n04-05-2023,-500\n03-05-2023,-500\n02-05-2023,-500\n01-05-2023,-500\n28-04-2023,-500\n27-04-2023,-500\n26-04-2023,-500\n25-04-2023,-500\n24-04-2023,-500\n21-04-2023,-500\n20-04-2023,-500\n19-04-2023,-500\n18-04-2023,-500\n17-04-2023,-500\n14-04-2023,-500\n13-04-2023,-500\n12-04-2023,-500\n11-04-2023,-500\n10-04-2023,-500\n06-04-2023,-500\n05-04-2023,-500\n04-04-2023,-500\n03-04-2023,-500\n31-03-2023,-500\n30-03-2023,-500\n29-03-2023,-500\n28-03-2023,-500\n27-03-2023,-500\n24-03-2023,-500\n23-03-2023,-500\n22-03-2023,-500\n21-03-2023,-500\n20-03-2023,-500\n17-03-2023,-500\n16-03-2023,-500\n15-03-2023,-500\n14-03-2023,-500\n13-03-2023,-500\n10-03-2023,-500\n09-03-2023,-500\n08-03-2023,-500\n07-03-2023,-500\n06-03-2023,-500\n03-03-2023,-500\n02-03-2023,-500\n01-03-2023,-500\n28-02-2023,-500\n27-02-2023,-500\n24-02-2023,-500\n23-02-2023,-500\n22-02-2023,-500\n21-02-2023,-500\n17-02-2023,-500\n16-02-2023,-500\n15-02-2023,-500\n14-02-2023,-500\n13-02-2023,-500\n10-02-2023,-500\n09-02-2023,-500\n08-02-2023,-500\n07-02-2023,-500\n06-02-2023,-500\n03-02-2023,-500\n02-02-2023,-500\n01-02-2023,-500\n31-01-2023,-500\n30-01-2023,-500\n27-01-2023,-500\n26-01-2023,-500\n25-01-2023,-500\n24-01-2023,-500\n23-01-2023,-500\n20-01-2023,-500\n19-01-2023,-500\n18-01-2023,-500\n17-01-2023,-500\n13-01-2023,-500\n12-01-2023,-500\n11-01-2023,-500\n10-01-2023,-500\n09-01-2023,-500\n06-01-2023,-500\n05-01-2023,-500\n04-01-2023,-500\n03-01-2023,-500\n30-12-2022,-500\n29-12-2022,-500\n28-12-2022,-500\n27-12-2022,-500\n23-12-2022,-500\n22-12-2022,-500\n21-12-2022,-500\n20-12-2022,-500\n19-12-2022,-500\n16-12-2022,-500\n15-12-2022,-500\n14-12-2022,-500\n13-12-2022,-500\n12-12-2022,-500\n09-12-2022,-500\n08-12-2022,-500\n07-12-2022,-500\n06-12-2022,-500\n05-12-2022,-500\n02-12-2022,-500\n01-12-2022,-500\n30-11-2022,-500\n29-11-2022,-500\n28-11-2022,-500\n25-11-2022,-500\n23-11-2022,-500\n22-11-2022,-500\n21-11-2022,-500\n18-11-2022,-500\n17-11-2022,-500\n16-11-2022,-500\n15-11-2022,-500\n14-11-2022,-500\n10-11-2022,-500\n09-11-2022,-500\n08-11-2022,-500\n07-11-2022,-500\n04-11-2022,-500\n03-11-2022,-500\n02-11-2022,-500\n01-11-2022,-500\n31-10-2022,-500\n28-10-2022,-500\n27-10-2022,-500\n26-10-2022,-500\n25-10-2022,-500\n24-10-2022,-500\n21-10-2022,-500\n20-10-2022,-500\n19-10-2022,-500\n18-10-2022,-500\n17-10-2022,-500\n14-10-2022,-500\n13-10-2022,-500\n12-10-2022,-500\n11-10-2022,-500\n07-10-2022,-500\n06-10-2022,-500\n05-10-2022,-500\n04-10-2022,-500\n03-10-2022,-500\n30-09-2022,-500\n29-09-2022,-500\n28-09-2022,-500\n27-09-2022,-500\n26-09-2022,-500\n23-09-2022,-500\n22-09-2022,-500\n21-09-2022,-500\n20-09-2022,-500\n19-09-2022,-500\n16-09-2022,-500\n15-09-2022,-500\n14-09-2022,-500\n13-09-2022,-500\n12-09-2022,-500\n09-09-2022,-500\n08-09-2022,-500\n07-09-2022,-500\n06-09-2022,-500\n02-09-2022,-500\n01-09-2022,-500\n31-08-2022,-500\n30-08-2022,-500\n29-08-2022,-500\n26-08-2022,-500\n25-08-2022,-500\n24-08-2022,-500\n23-08-2022,-500\n22-08-2022,-500\n19-08-2022,-500\n18-08-2022,-500\n17-08-2022,-500\n16-08-2022,-500\n15-08-2022,-500\n12-08-2022,-500\n11-08-2022,-500\n10-08-2022,-500\n09-08-2022,-500\n08-08-2022,-500\n05-08-2022,-500\n04-08-2022,-500\n03-08-2022,-500\n02-08-2022,-500\n01-08-2022,-500\n29-07-2022,-500\n28-07-2022,-500\n27-07-2022,-500\n26-07-2022,-500\n25-07-2022,-500\n22-07-2022,-500\n21-07-2022,-500\n20-07-2022,-500\n19-07-2022,-500\n18-07-2022,-500\n15-07-2022,-500\n14-07-2022,-500\n13-07-2022,-500\n12-07-2022,-500\n11-07-2022,-500\n08-07-2022,-500\n07-07-2022,-500\n06-07-2022,-500\n05-07-2022,-500\n01-07-2022,-500\n30-06-2022,-500\n29-06-2022,-500\n28-06-2022,-500\n27-06-2022,-500\n24-06-2022,-500\n23-06-2022,-500\n22-06-2022,-500\n21-06-2022,-500\n17-06-2022,-500\n16-06-2022,-500\n15-06-2022,-500\n14-06-2022,-500\n13-06-2022,-500\n10-06-2022,-500\n09-06-2022,-500\n08-06-2022,-500\n07-06-2022,-500\n06-06-2022,-500\n03-06-2022,-500\n02-06-2022,-500\n01-06-2022,-500\n31-05-2022,-500\n27-05-2022,-500\n26-05-2022,-500\n25-05-2022,-500\n24-05-2022,-500\n23-05-2022,-500\n20-05-2022,-500\n19-05-2022,-500\n18-05-2022,-500\n17-05-2022,-500\n16-05-2022,-500\n13-05-2022,-500\n12-05-2022,-500\n11-05-2022,-500\n10-05-2022,-500\n09-05-2022,-500\n06-05-2022,-500\n05-05-2022,-500\n04-05-2022,-500\n03-05-2022,-500\n02-05-2022,-500\n29-04-2022,-500\n28-04-2022,-500\n27-04-2022,-500\n26-04-2022,-500\n25-04-2022,-500\n22-04-2022,-500\n21-04-2022,-500\n20-04-2022,-500\n19-04-2022,-500\n18-04-2022,-500\n14-04-2022,-500\n13-04-2022,-500\n12-04-2022,-500\n11-04-2022,-500\n08-04-2022,-500\n07-04-2022,-500\n06-04-2022,-500\n05-04-2022,-500\n04-04-2022,-500\n01-04-2022,-500\n31-03-2022,-500\n30-03-2022,-500\n29-03-2022,-500\n28-03-2022,-500\n25-03-2022,-500\n24-03-2022,-500\n23-03-2022,-500\n22-03-2022,-500\n21-03-2022,-500\n18-03-2022,-500\n17-03-2022,-500\n16-03-2022,-500\n15-03-2022,-500\n14-03-2022,-500\n11-03-2022,-500\n10-03-2022,-500\n09-03-2022,-500\n08-03-2022,-500\n07-03-2022,-500\n04-03-2022,-500\n03-03-2022,-500\n02-03-2022,-500\n01-03-2022,-500\n28-02-2022,-500\n25-02-2022,-500\n24-02-2022,-500\n23-02-2022,-500\n22-02-2022,-500\n18-02-2022,-500\n17-02-2022,-500\n16-02-2022,-500\n15-02-2022,-500\n14-02-2022,-500\n11-02-2022,-500\n10-02-2022,-500\n09-02-2022,-500\n08-02-2022,-500\n07-02-2022,-500\n04-02-2022,-500\n03-02-2022,-500\n02-02-2022,-500\n01-02-2022,-500\n31-01-2022,-500\n28-01-2022,-500\n27-01-2022,-500\n26-01-2022,-500\n25-01-2022,-500\n24-01-2022,-500\n21-01-2022,-500\n20-01-2022,-500\n19-01-2022,-500\n18-01-2022,-500\n14-01-2022,-500\n13-01-2022,-500\n12-01-2022,-500\n11-01-2022,-500\n10-01-2022,-500\n07-01-2022,-500\n06-01-2022,-500\n05-01-2022,-500\n04-01-2022,-500\n03-01-2022,-500\n31-12-2021,-500\n30-12-2021,-500\n29-12-2021,-500\n28-12-2021,-500\n27-12-2021,-500\n23-12-2021,-500\n22-12-2021,-500\n21-12-2021,-500\n20-12-2021,-500\n17-12-2021,-500\n16-12-2021,-500\n15-12-2021,-500\n14-12-2021,-500\n13-12-2021,-500\n10-12-2021,-500\n09-12-2021,-500\n08-12-2021,-500\n07-12-2021,-500\n06-12-2021,-500\n03-12-2021,-500\n02-12-2021,-500\n01-12-2021,-500\n30-11-2021,-500\n29-11-2021,-500\n26-11-2021,-500\n24-11-2021,-500\n23-11-2021,-500\n22-11-2021,-500\n19-11-2021,-500\n18-11-2021,-500\n17-11-2021,-500\n16-11-2021,-500\n15-11-2021,-500\n12-11-2021,-500\n10-11-2021,-500\n09-11-2021,-500\n08-11-2021,-500\n05-11-2021,-500\n04-11-2021,-500\n03-11-2021,-500\n02-11-2021,-500\n01-11-2021,-500\n29-10-2021,-500\n28-10-2021,-500\n27-10-2021,-500\n26-10-2021,-500\n25-10-2021,-500\n22-10-2021,-500\n21-10-2021,-500\n20-10-2021,-500\n19-10-2021,-500\n18-10-2021,-500\n15-10-2021,-500\n14-10-2021,-500\n13-10-2021,-500\n12-10-2021,-500\n08-10-2021,-500\n07-10-2021,-500\n06-10-2021,-500\n05-10-2021,-500\n04-10-2021,-500\n01-10-2021,-500\n30-09-2021,-500\n29-09-2021,-500\n28-09-2021,-500\n27-09-2021,-500\n24-09-2021,-500\n23-09-2021,-500\n22-09-2021,-500\n21-09-2021,-500\n20-09-2021,-500\n17-09-2021,-500\n16-09-2021,-500\n15-09-2021,-500\n14-09-2021,-500\n13-09-2021,-500\n10-09-2021,-500\n09-09-2021,-500\n08-09-2021,-500\n07-09-2021,-500\n03-09-2021,-500\n02-09-2021,-500\n01-09-2021,-500\n31-08-2021,-500\n30-08-2021,-500\n27-08-2021,-500\n26-08-2021,-500\n25-08-2021,-500\n24-08-2021,-500\n23-08-2021,-500\n20-08-2021,-500\n19-08-2021,-500\n18-08-2021,-500\n17-08-2021,-500\n16-08-2021,-500\n13-08-2021,-500\n12-08-2021,-500\n11-08-2021,-500\n10-08-2021,-500\n09-08-2021,-500\n06-08-2021,-500\n05-08-2021,-500\n04-08-2021,-500\n03-08-2021,-500\n02-08-2021,-500\n30-07-2021,-500\n29-07-2021,-500\n28-07-2021,-500\n27-07-2021,-500\n26-07-2021,-500\n23-07-2021,-500\n22-07-2021,-500\n21-07-2021,-500\n20-07-2021,-500\n19-07-2021,-500\n16-07-2021,-500\n15-07-2021,-500\n14-07-2021,-500\n13-07-2021,-500\n12-07-2021,-500\n09-07-2021,-500\n08-07-2021,-500\n07-07-2021,-500\n06-07-2021,-500\n02-07-2021,-500\n01-07-2021,-500\n30-06-2021,-500\n29-06-2021,-500\n28-06-2021,-500\n25-06-2021,-500\n24-06-2021,-500\n23-06-2021,-500\n22-06-2021,-500\n21-06-2021,-500\n18-06-2021,-500\n17-06-2021,-500\n16-06-2021,-500\n15-06-2021,-500\n14-06-2021,-500\n11-06-2021,-500\n10-06-2021,-500\n09-06-2021,-500\n08-06-2021,-500\n07-06-2021,-500\n04-06-2021,-500\n03-06-2021,-500\n02-06-2021,-500\n01-06-2021,-500\n28-05-2021,-500\n27-05-2021,-500\n26-05-2021,-500\n25-05-2021,-500\n24-05-2021,-500\n21-05-2021,-500\n20-05-2021,-500\n19-05-2021,-500\n18-05-2021,-500\n17-05-2021,-500\n14-05-2021,-500\n13-05-2021,-500\n12-05-2021,-500\n11-05-2021,-500\n10-05-2021,-500\n07-05-2021,-500\n06-05-2021,-500\n05-05-2021,-500\n04-05-2021,-500\n03-05-2021,-500\n30-04-2021,-500\n29-04-2021,-500\n28-04-2021,-500\n27-04-2021,-500\n26-04-2021,-500\n23-04-2021,-500\n22-04-2021,-500\n21-04-2021,-500\n20-04-2021,-500\n19-04-2021,-500\n16-04-2021,-500\n15-04-2021,-500\n14-04-2021,-500\n13-04-2021,-500\n12-04-2021,-500\n09-04-2021,-500\n08-04-2021,-500\n07-04-2021,-500\n06-04-2021,-500\n05-04-2021,-500\n01-04-2021,-500\n31-03-2021,-500\n30-03-2021,-500\n29-03-2021,-500\n26-03-2021,-500\n25-03-2021,-500\n24-03-2021,-500\n23-03-2021,-500\n22-03-2021,-500\n19-03-2021,-500\n18-03-2021,-500\n17-03-2021,-500\n16-03-2021,-500\n15-03-2021,-500\n12-03-2021,-500\n11-03-2021,-500\n10-03-2021,-500\n09-03-2021,-500\n08-03-2021,-500\n05-03-2021,-500\n04-03-2021,-500\n03-03-2021,-500\n02-03-2021,-500\n01-03-2021,-500\n26-02-2021,-500\n25-02-2021,-500\n24-02-2021,-500\n23-02-2021,-500\n22-02-2021,-500\n19-02-2021,-500\n18-02-2021,-500\n17-02-2021,-500\n16-02-2021,-500\n12-02-2021,-500\n11-02-2021,-500\n10-02-2021,-500\n09-02-2021,-500\n08-02-2021,-500\n05-02-2021,-500\n04-02-2021,-500\n03-02-2021,-500\n02-02-2021,-500\n01-02-2021,-500\n29-01-2021,-500\n28-01-2021,-500\n27-01-2021,-500\n26-01-2021,-500\n25-01-2021,-500\n22-01-2021,-500\n21-01-2021,-500\n20-01-2021,-500\n19-01-2021,-500\n15-01-2021,-500\n14-01-2021,-500\n13-01-2021,-500\n12-01-2021,-500\n11-01-2021,-500\n08-01-2021,-500\n07-01-2021,-500\n06-01-2021,-500\n05-01-2021,-500\n04-01-2021,-500\n31-12-2020,-500\n30-12-2020,-500\n29-12-2020,-500\n28-12-2020,-500\n24-12-2020,-500\n23-12-2020,-500\n22-12-2020,-500\n21-12-2020,-500\n18-12-2020,-500\n17-12-2020,-500\n16-12-2020,-500\n15-12-2020,-500\n14-12-2020,-500\n11-12-2020,-500\n10-12-2020,-500\n09-12-2020,-500\n08-12-2020,-500\n07-12-2020,-500\n04-12-2020,-500\n03-12-2020,-500\n02-12-2020,-500\n01-12-2020,-500\n30-11-2020,-500\n27-11-2020,-500\n25-11-2020,-500\n24-11-2020,-500\n23-11-2020,-500\n20-11-2020,-500\n19-11-2020,-500\n18-11-2020,-500\n17-11-2020,-500\n16-11-2020,-500\n13-11-2020,-500\n12-11-2020,-500\n10-11-2020,-500\n09-11-2020,-500\n06-11-2020,-500\n05-11-2020,-500\n04-11-2020,-500\n03-11-2020,-500\n02-11-2020,-500\n30-10-2020,-500\n29-10-2020,-500\n28-10-2020,-500\n27-10-2020,-500\n26-10-2020,-500\n23-10-2020,-500\n22-10-2020,-500\n21-10-2020,-500\n20-10-2020,-500\n19-10-2020,-500\n16-10-2020,-500\n15-10-2020,-500\n14-10-2020,-500\n13-10-2020,-500\n09-10-2020,-500\n08-10-2020,-500\n07-10-2020,-500\n06-10-2020,-500\n05-10-2020,-500\n02-10-2020,-500\n01-10-2020,-500\n30-09-2020,-500\n29-09-2020,-500\n28-09-2020,-500\n25-09-2020,-500\n24-09-2020,-500\n23-09-2020,-500\n22-09-2020,-500\n21-09-2020,-500\n18-09-2020,-500\n17-09-2020,-500\n16-09-2020,-500\n15-09-2020,-500\n14-09-2020,-500\n11-09-2020,-500\n10-09-2020,-500\n09-09-2020,-500\n08-09-2020,-500\n04-09-2020,-500\n03-09-2020,-500\n02-09-2020,-500\n01-09-2020,-500\n31-08-2020,-500\n28-08-2020,-500\n27-08-2020,-500\n26-08-2020,-500\n25-08-2020,-500\n24-08-2020,-500\n21-08-2020,-500\n20-08-2020,-500\n19-08-2020,-500\n18-08-2020,-500\n17-08-2020,-500\n14-08-2020,-500\n13-08-2020,-500\n12-08-2020,-500\n11-08-2020,-500\n10-08-2020,-500\n07-08-2020,-500\n06-08-2020,-500\n05-08-2020,-500\n04-08-2020,-500\n03-08-2020,-500\n31-07-2020,-500\n30-07-2020,-500\n29-07-2020,-500\n28-07-2020,-500\n27-07-2020,-500\n24-07-2020,-500\n23-07-2020,-500\n22-07-2020,-500\n21-07-2020,-500\n20-07-2020,-500\n17-07-2020,-500\n16-07-2020,-500\n15-07-2020,-500\n14-07-2020,-500\n13-07-2020,-500\n10-07-2020,-500\n09-07-2020,-500\n08-07-2020,-500\n07-07-2020,-500\n06-07-2020,-500\n02-07-2020,-500\n01-07-2020,-500\n30-06-2020,-500\n29-06-2020,-500\n26-06-2020,-500\n25-06-2020,-500\n24-06-2020,-500\n23-06-2020,-500\n22-06-2020,-500\n19-06-2020,-500\n18-06-2020,-500\n17-06-2020,-500\n16-06-2020,-500\n15-06-2020,-500\n12-06-2020,-500\n11-06-2020,-500\n10-06-2020,-500\n09-06-2020,-500\n08-06-2020,-500\n05-06-2020,-500\n04-06-2020,-500\n03-06-2020,-500\n02-06-2020,-500\n01-06-2020,-500\n29-05-2020,-500\n28-05-2020,-500\n27-05-2020,-500\n26-05-2020,-500\n22-05-2020,-500\n21-05-2020,-500\n20-05-2020,-500\n19-05-2020,-500\n18-05-2020,-500\n15-05-2020,-500\n14-05-2020,-500\n13-05-2020,-500\n12-05-2020,-500\n11-05-2020,-500\n08-05-2020,-500\n07-05-2020,-500\n06-05-2020,-500\n05-05-2020,-500\n04-05-2020,-500\n01-05-2020,-500\n30-04-2020,-500\n29-04-2020,-500\n28-04-2020,-500\n27-04-2020,-500\n24-04-2020,-500\n23-04-2020,-500\n22-04-2020,-500\n21-04-2020,-500\n20-04-2020,-500\n17-04-2020,-500\n16-04-2020,-500\n15-04-2020,-500\n14-04-2020,-500\n13-04-2020,-500\n09-04-2020,-500\n08-04-2020,-500\n07-04-2020,-500\n06-04-2020,-500\n03-04-2020,-500\n02-04-2020,-500\n01-04-2020,-500\n31-03-2020,-500\n30-03-2020,-500\n27-03-2020,-500\n26-03-2020,-500\n25-03-2020,-500\n24-03-2020,-500\n23-03-2020,-500\n20-03-2020,-500\n19-03-2020,-500\n18-03-2020,-500\n17-03-2020,-500\n16-03-2020,-500\n13-03-2020,-500\n12-03-2020,-500\n11-03-2020,-500\n10-03-2020,-500\n09-03-2020,-500\n06-03-2020,-500\n05-03-2020,-500\n04-03-2020,-500\n03-03-2020,-500\n02-03-2020,-500\n28-02-2020,-500\n27-02-2020,-500\n26-02-2020,-500\n25-02-2020,-500\n24-02-2020,-500\n21-02-2020,-500\n20-02-2020,-500\n19-02-2020,-500\n18-02-2020,-500\n14-02-2020,-500\n13-02-2020,-500\n12-02-2020,-500\n11-02-2020,-500\n10-02-2020,-500\n07-02-2020,-500\n06-02-2020,-500\n05-02-2020,-500\n04-02-2020,-500\n03-02-2020,-500\n31-01-2020,-500\n30-01-2020,-500\n29-01-2020,-500\n28-01-2020,-500\n27-01-2020,-500\n24-01-2020,-500\n23-01-2020,-500\n22-01-2020,-500\n21-01-2020,-500\n17-01-2020,-500\n16-01-2020,-500\n15-01-2020,-500\n14-01-2020,-500\n13-01-2020,-500\n10-01-2020,-500\n09-01-2020,-500\n08-01-2020,-500\n07-01-2020,-500\n06-01-2020,-500\n03-01-2020,-500\n02-01-2020,-500\n31-12-2019,-500\n30-12-2019,-500\n27-12-2019,-500\n26-12-2019,-500\n24-12-2019,-500\n23-12-2019,-500\n20-12-2019,-500\n19-12-2019,-500\n18-12-2019,-500\n17-12-2019,-500\n16-12-2019,-500\n13-12-2019,-500\n12-12-2019,-500\n11-12-2019,-500\n10-12-2019,-500\n09-12-2019,-500\n06-12-2019,-500\n05-12-2019,-500\n04-12-2019,-500\n03-12-2019,-500\n02-12-2019,-500\n29-11-2019,-500\n27-11-2019,-500\n26-11-2019,-500\n25-11-2019,-500\n22-11-2019,-500\n21-11-2019,-500\n20-11-2019,-500\n19-11-2019,-500\n18-11-2019,-500\n15-11-2019,-500\n14-11-2019,-500\n13-11-2019,-500\n12-11-2019,-500\n08-11-2019,-500\n07-11-2019,-500\n06-11-2019,-500\n05-11-2019,-500\n04-11-2019,-500\n01-11-2019,-500\n31-10-2019,-500\n30-10-2019,-500\n29-10-2019,-500\n28-10-2019,-500\n25-10-2019,-500\n24-10-2019,-500\n23-10-2019,-500\n22-10-2019,-500\n21-10-2019,-500\n18-10-2019,-500\n17-10-2019,-500\n16-10-2019,-500\n15-10-2019,-500\n11-10-2019,-500\n10-10-2019,-500\n09-10-2019,-500\n08-10-2019,-500\n07-10-2019,-500\n04-10-2019,-500\n03-10-2019,-500\n02-10-2019,-500\n01-10-2019,-500\n30-09-2019,-500\n27-09-2019,-500\n26-09-2019,-500\n25-09-2019,-500\n24-09-2019,-500\n23-09-2019,-500\n20-09-2019,-500\n19-09-2019,-500\n18-09-2019,-500\n17-09-2019,-500\n16-09-2019,-500\n13-09-2019,-500\n12-09-2019,-500\n11-09-2019,-500\n10-09-2019,-500\n09-09-2019,-500\n06-09-2019,-500\n05-09-2019,-500\n04-09-2019,-500\n03-09-2019,-500\n30-08-2019,-500\n29-08-2019,-500\n28-08-2019,-500\n27-08-2019,-500\n26-08-2019,-500\n23-08-2019,-500\n22-08-2019,-500\n21-08-2019,-500\n20-08-2019,-500\n19-08-2019,-500\n16-08-2019,-500\n15-08-2019,-500\n14-08-2019,-500\n13-08-2019,-500\n12-08-2019,-500\n09-08-2019,-500\n08-08-2019,-500\n07-08-2019,-500\n06-08-2019,-500\n05-08-2019,-500\n02-08-2019,-500\n01-08-2019,-500\n31-07-2019,-500\n30-07-2019,-500\n29-07-2019,-500\n26-07-2019,-500\n25-07-2019,-500\n24-07-2019,-500\n23-07-2019,-500\n22-07-2019,-500\n19-07-2019,-500\n18-07-2019,-500\n17-07-2019,-500\n16-07-2019,-500\n15-07-2019,-500\n12-07-2019,-500\n11-07-2019,-500\n10-07-2019,-500\n09-07-2019,-500\n08-07-2019,-500\n05-07-2019,-500\n03-07-2019,-500\n02-07-2019,-500\n01-07-2019,-500\n28-06-2019,-500\n27-06-2019,-500\n26-06-2019,-500\n25-06-2019,-500\n24-06-2019,-500\n21-06-2019,-500\n20-06-2019,-500\n19-06-2019,-500\n18-06-2019,-500\n17-06-2019,-500\n14-06-2019,-500\n13-06-2019,-500\n12-06-2019,-500\n11-06-2019,-500\n10-06-2019,-500\n07-06-2019,-500\n06-06-2019,-500\n05-06-2019,-500\n04-06-2019,-500\n03-06-2019,-500\n31-05-2019,-500\n30-05-2019,-500\n29-05-2019,-500\n28-05-2019,-500\n24-05-2019,-500\n23-05-2019,-500\n22-05-2019,-500\n21-05-2019,-500\n20-05-2019,-500\n17-05-2019,-500\n16-05-2019,-500\n15-05-2019,-500\n14-05-2019,-500\n13-05-2019,-500\n10-05-2019,-500\n09-05-2019,-500\n08-05-2019,-500\n07-05-2019,-500\n06-05-2019,-500\n03-05-2019,-500\n02-05-2019,-500\n01-05-2019,-500\n30-04-2019,-500\n29-04-2019,-500\n26-04-2019,-500\n25-04-2019,-500\n24-04-2019,-500\n23-04-2019,-500\n22-04-2019,-500\n18-04-2019,-500\n17-04-2019,-500\n16-04-2019,-500\n15-04-2019,-500\n12-04-2019,-500\n11-04-2019,-500\n10-04-2019,-500\n09-04-2019,-500\n08-04-2019,-500\n05-04-2019,-500\n04-04-2019,-500\n03-04-2019,-500\n02-04-2019,-500\n01-04-2019,-500\n29-03-2019,-500\n28-03-2019,-500\n27-03-2019,-500\n26-03-2019,-500\n25-03-2019,-500\n22-03-2019,-500\n21-03-2019,-500\n20-03-2019,-500\n19-03-2019,-500\n18-03-2019,-500\n15-03-2019,-500\n14-03-2019,-500\n13-03-2019,-500\n12-03-2019,-500\n11-03-2019,-500\n08-03-2019,-500\n07-03-2019,-500\n06-03-2019,-500\n05-03-2019,-500\n04-03-2019,-500\n01-03-2019,-500\n28-02-2019,-500\n27-02-2019,-500\n26-02-2019,-500\n25-02-2019,-500\n22-02-2019,-500\n21-02-2019,-500\n20-02-2019,-500\n19-02-2019,-500\n15-02-2019,-500\n14-02-2019,-500\n13-02-2019,-500\n12-02-2019,-500\n11-02-2019,-500\n08-02-2019,-500\n07-02-2019,-500\n06-02-2019,-500\n05-02-2019,-500\n04-02-2019,-500\n01-02-2019,-500\n31-01-2019,-500\n30-01-2019,-500\n29-01-2019,-500\n28-01-2019,-500\n25-01-2019,-500\n24-01-2019,-500\n23-01-2019,-500\n22-01-2019,-500\n18-01-2019,-500\n17-01-2019,-500\n16-01-2019,-500\n15-01-2019,-500\n14-01-2019,-500\n11-01-2019,-500\n10-01-2019,-500\n09-01-2019,-500\n08-01-2019,-500\n07-01-2019,-500\n04-01-2019,-500\n03-01-2019,-500\n02-01-2019,-500\n31-12-2018,-500\n28-12-2018,-500\n27-12-2018,-500\n26-12-2018,-500\n24-12-2018,-500\n21-12-2018,-500\n20-12-2018,-500\n19-12-2018,-500\n18-12-2018,-500\n17-12-2018,-500\n14-12-2018,-500\n13-12-2018,-500\n12-12-2018,-500\n11-12-2018,-500\n10-12-2018,-500\n07-12-2018,-500\n06-12-2018,-500\n04-12-2018,-500\n03-12-2018,-500\n30-11-2018,-500\n29-11-2018,-500\n28-11-2018,-500\n27-11-2018,-500\n26-11-2018,-500\n23-11-2018,-500\n21-11-2018,-500\n20-11-2018,-500\n19-11-2018,-500\n16-11-2018,-500\n15-11-2018,-500\n14-11-2018,-500\n13-11-2018,-500\n09-11-2018,-500\n08-11-2018,-500\n07-11-2018,-500\n06-11-2018,-500\n05-11-2018,-500\n02-11-2018,-500\n01-11-2018,-500\n31-10-2018,-500\n30-10-2018,-500\n29-10-2018,-500\n26-10-2018,-500\n25-10-2018,-500\n24-10-2018,-500\n23-10-2018,-500\n22-10-2018,-500\n19-10-2018,-500\n18-10-2018,-500\n17-10-2018,-500\n16-10-2018,-500\n15-10-2018,-500\n12-10-2018,-500\n11-10-2018,-500\n10-10-2018,-500\n09-10-2018,-500\n05-10-2018,-500\n04-10-2018,-500\n03-10-2018,-500\n02-10-2018,-500\n01-10-2018,-500\n28-09-2018,-500\n27-09-2018,-500\n26-09-2018,-500\n25-09-2018,-500\n24-09-2018,-500\n21-09-2018,-500\n20-09-2018,-500\n19-09-2018,-500\n18-09-2018,-500\n17-09-2018,-500\n14-09-2018,-500\n13-09-2018,-500\n12-09-2018,-500\n11-09-2018,-500\n10-09-2018,-500\n07-09-2018,-500\n06-09-2018,-500\n05-09-2018,-500\n04-09-2018,-500\n31-08-2018,-500\n30-08-2018,-500\n29-08-2018,-500\n28-08-2018,-500\n27-08-2018,-500\n24-08-2018,-500\n23-08-2018,-500\n22-08-2018,-500\n21-08-2018,-500\n20-08-2018,-500\n17-08-2018,-500\n16-08-2018,-500\n15-08-2018,-500\n14-08-2018,-500\n13-08-2018,-500\n10-08-2018,-500\n09-08-2018,-500\n08-08-2018,-500\n07-08-2018,-500\n06-08-2018,-500\n03-08-2018,-500\n02-08-2018,-500\n01-08-2018,-500\n31-07-2018,-500\n30-07-2018,-500\n27-07-2018,-500\n26-07-2018,-500\n25-07-2018,-500\n24-07-2018,-500\n23-07-2018,-500\n20-07-2018,-500\n19-07-2018,-500\n18-07-2018,-500\n17-07-2018,-500\n16-07-2018,-500\n13-07-2018,-500\n12-07-2018,-500\n11-07-2018,-500\n10-07-2018,-500\n09-07-2018,-500\n06-07-2018,-500\n05-07-2018,-500\n03-07-2018,-500\n02-07-2018,-500\n29-06-2018,-500\n28-06-2018,-500\n27-06-2018,-500\n26-06-2018,-500\n25-06-2018,-500\n22-06-2018,-500\n21-06-2018,-500\n20-06-2018,-500\n19-06-2018,-500\n18-06-2018,-500\n15-06-2018,-500\n14-06-2018,-500\n13-06-2018,-500\n12-06-2018,-500\n11-06-2018,-500\n08-06-2018,-500\n07-06-2018,-500\n06-06-2018,-500\n05-06-2018,-500\n04-06-2018,-500\n01-06-2018,-500\n31-05-2018,-500\n30-05-2018,-500\n29-05-2018,-500\n25-05-2018,-500\n24-05-2018,-500\n23-05-2018,-500\n22-05-2018,-500\n21-05-2018,-500\n18-05-2018,-500\n17-05-2018,-500\n16-05-2018,-500\n15-05-2018,-500\n14-05-2018,-500\n11-05-2018,-500\n10-05-2018,-500\n09-05-2018,-500\n08-05-2018,-500\n07-05-2018,-500\n04-05-2018,-500\n03-05-2018,-500\n02-05-2018,-500\n01-05-2018,-500\n30-04-2018,-500\n27-04-2018,-500\n26-04-2018,-500\n25-04-2018,-500\n24-04-2018,-500\n23-04-2018,-500\n20-04-2018,-500\n19-04-2018,-500\n18-04-2018,-500\n17-04-2018,-500\n16-04-2018,-500\n13-04-2018,-500\n12-04-2018,-500\n11-04-2018,-500\n10-04-2018,-500\n09-04-2018,-500\n06-04-2018,-500\n05-04-2018,-500\n04-04-2018,-500\n03-04-2018,-500\n02-04-2018,-500"
  },
  {
    "path": "python/rateslib/data/historical/sonia.csv",
    "content": "﻿reference_date,rate\n01-08-2023,-500\n31-07-2023,-500\n28-07-2023,-500\n27-07-2023,-500\n26-07-2023,-500\n25-07-2023,-500\n24-07-2023,-500\n21-07-2023,-500\n20-07-2023,-500\n19-07-2023,-500\n18-07-2023,-500\n17-07-2023,-500\n14-07-2023,-500\n13-07-2023,-500\n12-07-2023,-500\n11-07-2023,-500\n10-07-2023,-500\n07-07-2023,-500\n06-07-2023,-500\n05-07-2023,-500\n04-07-2023,-500\n03-07-2023,-500\n30-06-2023,-500\n29-06-2023,-500\n28-06-2023,-500\n27-06-2023,-500\n26-06-2023,-500\n23-06-2023,-500\n22-06-2023,-500\n21-06-2023,-500\n20-06-2023,-500\n19-06-2023,-500\n16-06-2023,-500\n15-06-2023,-500\n14-06-2023,-500\n13-06-2023,-500\n12-06-2023,-500\n09-06-2023,-500\n08-06-2023,-500\n07-06-2023,-500\n06-06-2023,-500\n05-06-2023,-500\n02-06-2023,-500\n01-06-2023,-500\n31-05-2023,-500\n30-05-2023,-500\n26-05-2023,-500\n25-05-2023,-500\n24-05-2023,-500\n23-05-2023,-500\n22-05-2023,-500\n19-05-2023,-500\n18-05-2023,-500\n17-05-2023,-500\n16-05-2023,-500\n15-05-2023,-500\n12-05-2023,-500\n11-05-2023,-500\n10-05-2023,-500\n09-05-2023,-500\n05-05-2023,-500\n04-05-2023,-500\n03-05-2023,-500\n02-05-2023,-500\n28-04-2023,-500\n27-04-2023,-500\n26-04-2023,-500\n25-04-2023,-500\n24-04-2023,-500\n21-04-2023,-500\n20-04-2023,-500\n19-04-2023,-500\n18-04-2023,-500\n17-04-2023,-500\n14-04-2023,-500\n13-04-2023,-500\n12-04-2023,-500\n11-04-2023,-500\n06-04-2023,-500\n05-04-2023,-500\n04-04-2023,-500\n03-04-2023,-500\n31-03-2023,-500\n30-03-2023,-500\n29-03-2023,-500\n28-03-2023,-500\n27-03-2023,-500\n24-03-2023,-500\n23-03-2023,-500\n22-03-2023,-500\n21-03-2023,-500\n20-03-2023,-500\n17-03-2023,-500\n16-03-2023,-500\n15-03-2023,-500\n14-03-2023,-500\n13-03-2023,-500\n10-03-2023,-500\n09-03-2023,-500\n08-03-2023,-500\n07-03-2023,-500\n06-03-2023,-500\n03-03-2023,-500\n02-03-2023,-500\n01-03-2023,-500\n28-02-2023,-500\n27-02-2023,-500\n24-02-2023,-500\n23-02-2023,-500\n22-02-2023,-500\n21-02-2023,-500\n20-02-2023,-500\n17-02-2023,-500\n16-02-2023,-500\n15-02-2023,-500\n14-02-2023,-500\n13-02-2023,-500\n10-02-2023,-500\n09-02-2023,-500\n08-02-2023,-500\n07-02-2023,-500\n06-02-2023,-500\n03-02-2023,-500\n02-02-2023,-500\n01-02-2023,-500\n31-01-2023,-500\n30-01-2023,-500\n27-01-2023,-500\n26-01-2023,-500\n25-01-2023,-500\n24-01-2023,-500\n23-01-2023,-500\n20-01-2023,-500\n19-01-2023,-500\n18-01-2023,-500\n17-01-2023,-500\n16-01-2023,-500\n13-01-2023,-500\n12-01-2023,-500\n11-01-2023,-500\n10-01-2023,-500\n09-01-2023,-500\n06-01-2023,-500\n05-01-2023,-500\n04-01-2023,-500\n03-01-2023,-500\n30-12-2022,-500\n29-12-2022,-500\n28-12-2022,-500\n23-12-2022,-500\n22-12-2022,-500\n21-12-2022,-500\n20-12-2022,-500\n19-12-2022,-500\n16-12-2022,-500\n15-12-2022,-500\n14-12-2022,-500\n13-12-2022,-500\n12-12-2022,-500\n09-12-2022,-500\n08-12-2022,-500\n07-12-2022,-500\n06-12-2022,-500\n05-12-2022,-500\n02-12-2022,-500\n01-12-2022,-500\n30-11-2022,-500\n29-11-2022,-500\n28-11-2022,-500\n25-11-2022,-500\n24-11-2022,-500\n23-11-2022,-500\n22-11-2022,-500\n21-11-2022,-500\n18-11-2022,-500\n17-11-2022,-500\n16-11-2022,-500\n15-11-2022,-500\n14-11-2022,-500\n11-11-2022,-500\n10-11-2022,-500\n09-11-2022,-500\n08-11-2022,-500\n07-11-2022,-500\n04-11-2022,-500\n03-11-2022,-500\n02-11-2022,-500\n01-11-2022,-500\n31-10-2022,-500\n28-10-2022,-500\n27-10-2022,-500\n26-10-2022,-500\n25-10-2022,-500\n24-10-2022,-500\n21-10-2022,-500\n20-10-2022,-500\n19-10-2022,-500\n18-10-2022,-500\n17-10-2022,-500\n14-10-2022,-500\n13-10-2022,-500\n12-10-2022,-500\n11-10-2022,-500\n10-10-2022,-500\n07-10-2022,-500\n06-10-2022,-500\n05-10-2022,-500\n04-10-2022,-500\n03-10-2022,-500\n30-09-2022,-500\n29-09-2022,-500\n28-09-2022,-500\n27-09-2022,-500\n26-09-2022,-500\n23-09-2022,-500\n22-09-2022,-500\n21-09-2022,-500\n20-09-2022,-500\n16-09-2022,-500\n15-09-2022,-500\n14-09-2022,-500\n13-09-2022,-500\n12-09-2022,-500\n09-09-2022,-500\n08-09-2022,-500\n07-09-2022,-500\n06-09-2022,-500\n05-09-2022,-500\n02-09-2022,-500\n01-09-2022,-500\n31-08-2022,-500\n30-08-2022,-500\n26-08-2022,-500\n25-08-2022,-500\n24-08-2022,-500\n23-08-2022,-500\n22-08-2022,-500\n19-08-2022,-500\n18-08-2022,-500\n17-08-2022,-500\n16-08-2022,-500\n15-08-2022,-500\n12-08-2022,-500\n11-08-2022,-500\n10-08-2022,-500\n09-08-2022,-500\n08-08-2022,-500\n05-08-2022,-500\n04-08-2022,-500\n03-08-2022,-500\n02-08-2022,-500\n01-08-2022,-500\n29-07-2022,-500\n28-07-2022,-500\n27-07-2022,-500\n26-07-2022,-500\n25-07-2022,-500\n22-07-2022,-500\n21-07-2022,-500\n20-07-2022,-500\n19-07-2022,-500\n18-07-2022,-500\n15-07-2022,-500\n14-07-2022,-500\n13-07-2022,-500\n12-07-2022,-500\n11-07-2022,-500\n08-07-2022,-500\n07-07-2022,-500\n06-07-2022,-500\n05-07-2022,-500\n04-07-2022,-500\n01-07-2022,-500\n30-06-2022,-500\n29-06-2022,-500\n28-06-2022,-500\n27-06-2022,-500\n24-06-2022,-500\n23-06-2022,-500\n22-06-2022,-500\n21-06-2022,-500\n20-06-2022,-500\n17-06-2022,-500\n16-06-2022,-500\n15-06-2022,-500\n14-06-2022,-500\n13-06-2022,-500\n10-06-2022,-500\n09-06-2022,-500\n08-06-2022,-500\n07-06-2022,-500\n06-06-2022,-500\n01-06-2022,-500\n31-05-2022,-500\n30-05-2022,-500\n27-05-2022,-500\n26-05-2022,-500\n25-05-2022,-500\n24-05-2022,-500\n23-05-2022,-500\n20-05-2022,-500\n19-05-2022,-500\n18-05-2022,-500\n17-05-2022,-500\n16-05-2022,-500\n13-05-2022,-500\n12-05-2022,-500\n11-05-2022,-500\n10-05-2022,-500\n09-05-2022,-500\n06-05-2022,-500\n05-05-2022,-500\n04-05-2022,-500\n03-05-2022,-500\n29-04-2022,-500\n28-04-2022,-500\n27-04-2022,-500\n26-04-2022,-500\n25-04-2022,-500\n22-04-2022,-500\n21-04-2022,-500\n20-04-2022,-500\n19-04-2022,-500\n14-04-2022,-500\n13-04-2022,-500\n12-04-2022,-500\n11-04-2022,-500\n08-04-2022,-500\n07-04-2022,-500\n06-04-2022,-500\n05-04-2022,-500\n04-04-2022,-500\n01-04-2022,-500\n31-03-2022,-500\n30-03-2022,-500\n29-03-2022,-500\n28-03-2022,-500\n25-03-2022,-500\n24-03-2022,-500\n23-03-2022,-500\n22-03-2022,-500\n21-03-2022,-500\n18-03-2022,-500\n17-03-2022,-500\n16-03-2022,-500\n15-03-2022,-500\n14-03-2022,-500\n11-03-2022,-500\n10-03-2022,-500\n09-03-2022,-500\n08-03-2022,-500\n07-03-2022,-500\n04-03-2022,-500\n03-03-2022,-500\n02-03-2022,-500\n01-03-2022,-500\n28-02-2022,-500\n25-02-2022,-500\n24-02-2022,-500\n23-02-2022,-500\n22-02-2022,-500\n21-02-2022,-500\n18-02-2022,-500\n17-02-2022,-500\n16-02-2022,-500\n15-02-2022,-500\n14-02-2022,-500\n11-02-2022,-500\n10-02-2022,-500\n09-02-2022,-500\n08-02-2022,-500\n07-02-2022,-500\n04-02-2022,-500\n03-02-2022,-500\n02-02-2022,-500\n01-02-2022,-500\n31-01-2022,-500\n28-01-2022,-500\n27-01-2022,-500\n26-01-2022,-500\n25-01-2022,-500\n24-01-2022,-500\n21-01-2022,-500\n20-01-2022,-500\n19-01-2022,-500\n18-01-2022,-500\n17-01-2022,-500\n14-01-2022,-500\n13-01-2022,-500\n12-01-2022,-500\n11-01-2022,-500\n10-01-2022,-500\n07-01-2022,-500\n06-01-2022,-500\n05-01-2022,-500\n04-01-2022,-500\n31-12-2021,-500\n30-12-2021,-500\n29-12-2021,-500\n24-12-2021,-500\n23-12-2021,-500\n22-12-2021,-500\n21-12-2021,-500\n20-12-2021,-500\n17-12-2021,-500\n16-12-2021,-500\n15-12-2021,-500\n14-12-2021,-500\n13-12-2021,-500\n10-12-2021,-500\n09-12-2021,-500\n08-12-2021,-500\n07-12-2021,-500\n06-12-2021,-500\n03-12-2021,-500\n02-12-2021,-500\n01-12-2021,-500\n30-11-2021,-500\n29-11-2021,-500\n26-11-2021,-500\n25-11-2021,-500\n24-11-2021,-500\n23-11-2021,-500\n22-11-2021,-500\n19-11-2021,-500\n18-11-2021,-500\n17-11-2021,-500\n16-11-2021,-500\n15-11-2021,-500\n12-11-2021,-500\n11-11-2021,-500\n10-11-2021,-500\n09-11-2021,-500\n08-11-2021,-500\n05-11-2021,-500\n04-11-2021,-500\n03-11-2021,-500\n02-11-2021,-500\n01-11-2021,-500\n29-10-2021,-500\n28-10-2021,-500\n27-10-2021,-500\n26-10-2021,-500\n25-10-2021,-500\n22-10-2021,-500\n21-10-2021,-500\n20-10-2021,-500\n19-10-2021,-500\n18-10-2021,-500\n15-10-2021,-500\n14-10-2021,-500\n13-10-2021,-500\n12-10-2021,-500\n11-10-2021,-500\n08-10-2021,-500\n07-10-2021,-500\n06-10-2021,-500\n05-10-2021,-500\n04-10-2021,-500\n01-10-2021,-500\n30-09-2021,-500\n29-09-2021,-500\n28-09-2021,-500\n27-09-2021,-500\n24-09-2021,-500\n23-09-2021,-500\n22-09-2021,-500\n21-09-2021,-500\n20-09-2021,-500\n17-09-2021,-500\n16-09-2021,-500\n15-09-2021,-500\n14-09-2021,-500\n13-09-2021,-500\n10-09-2021,-500\n09-09-2021,-500\n08-09-2021,-500\n07-09-2021,-500\n06-09-2021,-500\n03-09-2021,-500\n02-09-2021,-500\n01-09-2021,-500\n31-08-2021,-500\n27-08-2021,-500\n26-08-2021,-500\n25-08-2021,-500\n24-08-2021,-500\n23-08-2021,-500\n20-08-2021,-500\n19-08-2021,-500\n18-08-2021,-500\n17-08-2021,-500\n16-08-2021,-500\n13-08-2021,-500\n12-08-2021,-500\n11-08-2021,-500\n10-08-2021,-500\n09-08-2021,-500\n06-08-2021,-500\n05-08-2021,-500\n04-08-2021,-500\n03-08-2021,-500\n02-08-2021,-500\n30-07-2021,-500\n29-07-2021,-500\n28-07-2021,-500\n27-07-2021,-500\n26-07-2021,-500\n23-07-2021,-500\n22-07-2021,-500\n21-07-2021,-500\n20-07-2021,-500\n19-07-2021,-500\n16-07-2021,-500\n15-07-2021,-500\n14-07-2021,-500\n13-07-2021,-500\n12-07-2021,-500\n09-07-2021,-500\n08-07-2021,-500\n07-07-2021,-500\n06-07-2021,-500\n05-07-2021,-500\n02-07-2021,-500\n01-07-2021,-500\n30-06-2021,-500\n29-06-2021,-500\n28-06-2021,-500\n25-06-2021,-500\n24-06-2021,-500\n23-06-2021,-500\n22-06-2021,-500\n21-06-2021,-500\n18-06-2021,-500\n17-06-2021,-500\n16-06-2021,-500\n15-06-2021,-500\n14-06-2021,-500\n11-06-2021,-500\n10-06-2021,-500\n09-06-2021,-500\n08-06-2021,-500\n07-06-2021,-500\n04-06-2021,-500\n03-06-2021,-500\n02-06-2021,-500\n01-06-2021,-500\n28-05-2021,-500\n27-05-2021,-500\n26-05-2021,-500\n25-05-2021,-500\n24-05-2021,-500\n21-05-2021,-500\n20-05-2021,-500\n19-05-2021,-500\n18-05-2021,-500\n17-05-2021,-500\n14-05-2021,-500\n13-05-2021,-500\n12-05-2021,-500\n11-05-2021,-500\n10-05-2021,-500\n07-05-2021,-500\n06-05-2021,-500\n05-05-2021,-500\n04-05-2021,-500\n30-04-2021,-500\n29-04-2021,-500\n28-04-2021,-500\n27-04-2021,-500\n26-04-2021,-500\n23-04-2021,-500\n22-04-2021,-500\n21-04-2021,-500\n20-04-2021,-500\n19-04-2021,-500\n16-04-2021,-500\n15-04-2021,-500\n14-04-2021,-500\n13-04-2021,-500\n12-04-2021,-500\n09-04-2021,-500\n08-04-2021,-500\n07-04-2021,-500\n06-04-2021,-500\n01-04-2021,-500\n31-03-2021,-500\n30-03-2021,-500\n29-03-2021,-500\n26-03-2021,-500\n25-03-2021,-500\n24-03-2021,-500\n23-03-2021,-500\n22-03-2021,-500\n19-03-2021,-500\n18-03-2021,-500\n17-03-2021,-500\n16-03-2021,-500\n15-03-2021,-500\n12-03-2021,-500\n11-03-2021,-500\n10-03-2021,-500\n09-03-2021,-500\n08-03-2021,-500\n05-03-2021,-500\n04-03-2021,-500\n03-03-2021,-500\n02-03-2021,-500\n01-03-2021,-500\n26-02-2021,-500\n25-02-2021,-500\n24-02-2021,-500\n23-02-2021,-500\n22-02-2021,-500\n19-02-2021,-500\n18-02-2021,-500\n17-02-2021,-500\n16-02-2021,-500\n15-02-2021,-500\n12-02-2021,-500\n11-02-2021,-500\n10-02-2021,-500\n09-02-2021,-500\n08-02-2021,-500\n05-02-2021,-500\n04-02-2021,-500\n03-02-2021,-500\n02-02-2021,-500\n01-02-2021,-500\n29-01-2021,-500\n28-01-2021,-500\n27-01-2021,-500\n26-01-2021,-500\n25-01-2021,-500\n22-01-2021,-500\n21-01-2021,-500\n20-01-2021,-500\n19-01-2021,-500\n18-01-2021,-500\n15-01-2021,-500\n14-01-2021,-500\n13-01-2021,-500\n12-01-2021,-500\n11-01-2021,-500\n08-01-2021,-500\n07-01-2021,-500\n06-01-2021,-500\n05-01-2021,-500\n04-01-2021,-500\n31-12-2020,-500\n30-12-2020,-500\n29-12-2020,-500\n24-12-2020,-500\n23-12-2020,-500\n22-12-2020,-500\n21-12-2020,-500\n18-12-2020,-500\n17-12-2020,-500\n16-12-2020,-500\n15-12-2020,-500\n14-12-2020,-500\n11-12-2020,-500\n10-12-2020,-500\n09-12-2020,-500\n08-12-2020,-500\n07-12-2020,-500\n04-12-2020,-500\n03-12-2020,-500\n02-12-2020,-500\n01-12-2020,-500\n30-11-2020,-500\n27-11-2020,-500\n26-11-2020,-500\n25-11-2020,-500\n24-11-2020,-500\n23-11-2020,-500\n20-11-2020,-500\n19-11-2020,-500\n18-11-2020,-500\n17-11-2020,-500\n16-11-2020,-500\n13-11-2020,-500\n12-11-2020,-500\n11-11-2020,-500\n10-11-2020,-500\n09-11-2020,-500\n06-11-2020,-500\n05-11-2020,-500\n04-11-2020,-500\n03-11-2020,-500\n02-11-2020,-500\n30-10-2020,-500\n29-10-2020,-500\n28-10-2020,-500\n27-10-2020,-500\n26-10-2020,-500\n23-10-2020,-500\n22-10-2020,-500\n21-10-2020,-500\n20-10-2020,-500\n19-10-2020,-500\n16-10-2020,-500\n15-10-2020,-500\n14-10-2020,-500\n13-10-2020,-500\n12-10-2020,-500\n09-10-2020,-500\n08-10-2020,-500\n07-10-2020,-500\n06-10-2020,-500\n05-10-2020,-500\n02-10-2020,-500\n01-10-2020,-500\n30-09-2020,-500\n29-09-2020,-500\n28-09-2020,-500\n25-09-2020,-500\n24-09-2020,-500\n23-09-2020,-500\n22-09-2020,-500\n21-09-2020,-500\n18-09-2020,-500\n17-09-2020,-500\n16-09-2020,-500\n15-09-2020,-500\n14-09-2020,-500\n11-09-2020,-500\n10-09-2020,-500\n09-09-2020,-500\n08-09-2020,-500\n07-09-2020,-500\n04-09-2020,-500\n03-09-2020,-500\n02-09-2020,-500\n01-09-2020,-500\n28-08-2020,-500\n27-08-2020,-500\n26-08-2020,-500\n25-08-2020,-500\n24-08-2020,-500\n21-08-2020,-500\n20-08-2020,-500\n19-08-2020,-500\n18-08-2020,-500\n17-08-2020,-500\n14-08-2020,-500\n13-08-2020,-500\n12-08-2020,-500\n11-08-2020,-500\n10-08-2020,-500\n07-08-2020,-500\n06-08-2020,-500\n05-08-2020,-500\n04-08-2020,-500\n03-08-2020,-500\n31-07-2020,-500\n30-07-2020,-500\n29-07-2020,-500\n28-07-2020,-500\n27-07-2020,-500\n24-07-2020,-500\n23-07-2020,-500\n22-07-2020,-500\n21-07-2020,-500\n20-07-2020,-500\n17-07-2020,-500\n16-07-2020,-500\n15-07-2020,-500\n14-07-2020,-500\n13-07-2020,-500\n10-07-2020,-500\n09-07-2020,-500\n08-07-2020,-500\n07-07-2020,-500\n06-07-2020,-500\n03-07-2020,-500\n02-07-2020,-500\n01-07-2020,-500\n30-06-2020,-500\n29-06-2020,-500\n26-06-2020,-500\n25-06-2020,-500\n24-06-2020,-500\n23-06-2020,-500\n22-06-2020,-500\n19-06-2020,-500\n18-06-2020,-500\n17-06-2020,-500\n16-06-2020,-500\n15-06-2020,-500\n12-06-2020,-500\n11-06-2020,-500\n10-06-2020,-500\n09-06-2020,-500\n08-06-2020,-500\n05-06-2020,-500\n04-06-2020,-500\n03-06-2020,-500\n02-06-2020,-500\n01-06-2020,-500\n29-05-2020,-500\n28-05-2020,-500\n27-05-2020,-500\n26-05-2020,-500\n22-05-2020,-500\n21-05-2020,-500\n20-05-2020,-500\n19-05-2020,-500\n18-05-2020,-500\n15-05-2020,-500\n14-05-2020,-500\n13-05-2020,-500\n12-05-2020,-500\n11-05-2020,-500\n07-05-2020,-500\n06-05-2020,-500\n05-05-2020,-500\n04-05-2020,-500\n01-05-2020,-500\n30-04-2020,-500\n29-04-2020,-500\n28-04-2020,-500\n27-04-2020,-500\n24-04-2020,-500\n23-04-2020,-500\n22-04-2020,-500\n21-04-2020,-500\n20-04-2020,-500\n17-04-2020,-500\n16-04-2020,-500\n15-04-2020,-500\n14-04-2020,-500\n09-04-2020,-500\n08-04-2020,-500\n07-04-2020,-500\n06-04-2020,-500\n03-04-2020,-500\n02-04-2020,-500\n01-04-2020,-500\n31-03-2020,-500\n30-03-2020,-500\n27-03-2020,-500\n26-03-2020,-500\n25-03-2020,-500\n24-03-2020,-500\n23-03-2020,-500\n20-03-2020,-500\n19-03-2020,-500\n18-03-2020,-500\n17-03-2020,-500\n16-03-2020,-500\n13-03-2020,-500\n12-03-2020,-500\n11-03-2020,-500\n10-03-2020,-500\n09-03-2020,-500\n06-03-2020,-500\n05-03-2020,-500\n04-03-2020,-500\n03-03-2020,-500\n02-03-2020,-500\n28-02-2020,-500\n27-02-2020,-500\n26-02-2020,-500\n25-02-2020,-500\n24-02-2020,-500\n21-02-2020,-500\n20-02-2020,-500\n19-02-2020,-500\n18-02-2020,-500\n17-02-2020,-500\n14-02-2020,-500\n13-02-2020,-500\n12-02-2020,-500\n11-02-2020,-500\n10-02-2020,-500\n07-02-2020,-500\n06-02-2020,-500\n05-02-2020,-500\n04-02-2020,-500\n03-02-2020,-500\n31-01-2020,-500\n30-01-2020,-500\n29-01-2020,-500\n28-01-2020,-500\n27-01-2020,-500\n24-01-2020,-500\n23-01-2020,-500\n22-01-2020,-500\n21-01-2020,-500\n20-01-2020,-500\n17-01-2020,-500\n16-01-2020,-500\n15-01-2020,-500\n14-01-2020,-500\n13-01-2020,-500\n10-01-2020,-500\n09-01-2020,-500\n08-01-2020,-500\n07-01-2020,-500\n06-01-2020,-500\n03-01-2020,-500\n02-01-2020,-500\n31-12-2019,-500\n30-12-2019,-500\n27-12-2019,-500\n24-12-2019,-500\n23-12-2019,-500\n20-12-2019,-500\n19-12-2019,-500\n18-12-2019,-500\n17-12-2019,-500\n16-12-2019,-500\n13-12-2019,-500\n12-12-2019,-500\n11-12-2019,-500\n10-12-2019,-500\n09-12-2019,-500\n06-12-2019,-500\n05-12-2019,-500\n04-12-2019,-500\n03-12-2019,-500\n02-12-2019,-500\n29-11-2019,-500\n28-11-2019,-500\n27-11-2019,-500\n26-11-2019,-500\n25-11-2019,-500\n22-11-2019,-500\n21-11-2019,-500\n20-11-2019,-500\n19-11-2019,-500\n18-11-2019,-500\n15-11-2019,-500\n14-11-2019,-500\n13-11-2019,-500\n12-11-2019,-500\n11-11-2019,-500\n08-11-2019,-500\n07-11-2019,-500\n06-11-2019,-500\n05-11-2019,-500\n04-11-2019,-500\n01-11-2019,-500\n31-10-2019,-500\n30-10-2019,-500\n29-10-2019,-500\n28-10-2019,-500\n25-10-2019,-500\n24-10-2019,-500\n23-10-2019,-500\n22-10-2019,-500\n21-10-2019,-500\n18-10-2019,-500\n17-10-2019,-500\n16-10-2019,-500\n15-10-2019,-500\n14-10-2019,-500\n11-10-2019,-500\n10-10-2019,-500\n09-10-2019,-500\n08-10-2019,-500\n07-10-2019,-500\n04-10-2019,-500\n03-10-2019,-500\n02-10-2019,-500\n01-10-2019,-500\n30-09-2019,-500\n27-09-2019,-500\n26-09-2019,-500\n25-09-2019,-500\n24-09-2019,-500\n23-09-2019,-500\n20-09-2019,-500\n19-09-2019,-500\n18-09-2019,-500\n17-09-2019,-500\n16-09-2019,-500\n13-09-2019,-500\n12-09-2019,-500\n11-09-2019,-500\n10-09-2019,-500\n09-09-2019,-500\n06-09-2019,-500\n05-09-2019,-500\n04-09-2019,-500\n03-09-2019,-500\n02-09-2019,-500\n30-08-2019,-500\n29-08-2019,-500\n28-08-2019,-500\n27-08-2019,-500\n23-08-2019,-500\n22-08-2019,-500\n21-08-2019,-500\n20-08-2019,-500\n19-08-2019,-500\n16-08-2019,-500\n15-08-2019,-500\n14-08-2019,-500\n13-08-2019,-500\n12-08-2019,-500\n09-08-2019,-500\n08-08-2019,-500\n07-08-2019,-500\n06-08-2019,-500\n05-08-2019,-500\n02-08-2019,-500\n01-08-2019,-500\n31-07-2019,-500\n30-07-2019,-500\n29-07-2019,-500\n26-07-2019,-500\n25-07-2019,-500\n24-07-2019,-500\n23-07-2019,-500\n22-07-2019,-500\n19-07-2019,-500\n18-07-2019,-500\n17-07-2019,-500\n16-07-2019,-500\n15-07-2019,-500\n12-07-2019,-500\n11-07-2019,-500\n10-07-2019,-500\n09-07-2019,-500\n08-07-2019,-500\n05-07-2019,-500\n04-07-2019,-500\n03-07-2019,-500\n02-07-2019,-500\n01-07-2019,-500\n28-06-2019,-500\n27-06-2019,-500\n26-06-2019,-500\n25-06-2019,-500\n24-06-2019,-500\n21-06-2019,-500\n20-06-2019,-500\n19-06-2019,-500\n18-06-2019,-500\n17-06-2019,-500\n14-06-2019,-500\n13-06-2019,-500\n12-06-2019,-500\n11-06-2019,-500\n10-06-2019,-500\n07-06-2019,-500\n06-06-2019,-500\n05-06-2019,-500\n04-06-2019,-500\n03-06-2019,-500\n31-05-2019,-500\n30-05-2019,-500\n29-05-2019,-500\n28-05-2019,-500\n24-05-2019,-500\n23-05-2019,-500\n22-05-2019,-500\n21-05-2019,-500\n20-05-2019,-500\n17-05-2019,-500\n16-05-2019,-500\n15-05-2019,-500\n14-05-2019,-500\n13-05-2019,-500\n10-05-2019,-500\n09-05-2019,-500\n08-05-2019,-500\n07-05-2019,-500\n03-05-2019,-500\n02-05-2019,-500\n01-05-2019,-500\n30-04-2019,-500\n29-04-2019,-500\n26-04-2019,-500\n25-04-2019,-500\n24-04-2019,-500\n23-04-2019,-500\n18-04-2019,-500\n17-04-2019,-500\n16-04-2019,-500\n15-04-2019,-500\n12-04-2019,-500\n11-04-2019,-500\n10-04-2019,-500\n09-04-2019,-500\n08-04-2019,-500\n05-04-2019,-500\n04-04-2019,-500\n03-04-2019,-500\n02-04-2019,-500\n01-04-2019,-500\n29-03-2019,-500\n28-03-2019,-500\n27-03-2019,-500\n26-03-2019,-500\n25-03-2019,-500\n22-03-2019,-500\n21-03-2019,-500\n20-03-2019,-500\n19-03-2019,-500\n18-03-2019,-500\n15-03-2019,-500\n14-03-2019,-500\n13-03-2019,-500\n12-03-2019,-500\n11-03-2019,-500\n08-03-2019,-500\n07-03-2019,-500\n06-03-2019,-500\n05-03-2019,-500\n04-03-2019,-500\n01-03-2019,-500\n28-02-2019,-500\n27-02-2019,-500\n26-02-2019,-500\n25-02-2019,-500\n22-02-2019,-500\n21-02-2019,-500\n20-02-2019,-500\n19-02-2019,-500\n18-02-2019,-500\n15-02-2019,-500\n14-02-2019,-500\n13-02-2019,-500\n12-02-2019,-500\n11-02-2019,-500\n08-02-2019,-500\n07-02-2019,-500\n06-02-2019,-500\n05-02-2019,-500\n04-02-2019,-500\n01-02-2019,-500\n31-01-2019,-500\n30-01-2019,-500\n29-01-2019,-500\n28-01-2019,-500\n25-01-2019,-500\n24-01-2019,-500\n23-01-2019,-500\n22-01-2019,-500\n21-01-2019,-500\n18-01-2019,-500\n17-01-2019,-500\n16-01-2019,-500\n15-01-2019,-500\n14-01-2019,-500\n11-01-2019,-500\n10-01-2019,-500\n09-01-2019,-500\n08-01-2019,-500\n07-01-2019,-500\n04-01-2019,-500\n03-01-2019,-500\n02-01-2019,-500\n31-12-2018,-500\n28-12-2018,-500\n27-12-2018,-500\n24-12-2018,-500\n21-12-2018,-500\n20-12-2018,-500\n19-12-2018,-500\n18-12-2018,-500\n17-12-2018,-500\n14-12-2018,-500\n13-12-2018,-500\n12-12-2018,-500\n11-12-2018,-500\n10-12-2018,-500\n07-12-2018,-500\n06-12-2018,-500\n05-12-2018,-500\n04-12-2018,-500\n03-12-2018,-500\n30-11-2018,-500\n29-11-2018,-500\n28-11-2018,-500\n27-11-2018,-500\n26-11-2018,-500\n23-11-2018,-500\n22-11-2018,-500\n21-11-2018,-500\n20-11-2018,-500\n19-11-2018,-500\n16-11-2018,-500\n15-11-2018,-500\n14-11-2018,-500\n13-11-2018,-500\n12-11-2018,-500\n09-11-2018,-500\n08-11-2018,-500\n07-11-2018,-500\n06-11-2018,-500\n05-11-2018,-500\n02-11-2018,-500\n01-11-2018,-500\n31-10-2018,-500\n30-10-2018,-500\n29-10-2018,-500\n26-10-2018,-500\n25-10-2018,-500\n24-10-2018,-500\n23-10-2018,-500\n22-10-2018,-500\n19-10-2018,-500\n18-10-2018,-500\n17-10-2018,-500\n16-10-2018,-500\n15-10-2018,-500\n12-10-2018,-500\n11-10-2018,-500\n10-10-2018,-500\n09-10-2018,-500\n08-10-2018,-500\n05-10-2018,-500\n04-10-2018,-500\n03-10-2018,-500\n02-10-2018,-500\n01-10-2018,-500\n28-09-2018,-500\n27-09-2018,-500\n26-09-2018,-500\n25-09-2018,-500\n24-09-2018,-500\n21-09-2018,-500\n20-09-2018,-500\n19-09-2018,-500\n18-09-2018,-500\n17-09-2018,-500\n14-09-2018,-500\n13-09-2018,-500\n12-09-2018,-500\n11-09-2018,-500\n10-09-2018,-500\n07-09-2018,-500\n06-09-2018,-500\n05-09-2018,-500\n04-09-2018,-500\n03-09-2018,-500\n31-08-2018,-500\n30-08-2018,-500\n29-08-2018,-500\n28-08-2018,-500\n24-08-2018,-500\n23-08-2018,-500\n22-08-2018,-500\n21-08-2018,-500\n20-08-2018,-500\n17-08-2018,-500\n16-08-2018,-500\n15-08-2018,-500\n14-08-2018,-500\n13-08-2018,-500\n10-08-2018,-500\n09-08-2018,-500\n08-08-2018,-500\n07-08-2018,-500\n06-08-2018,-500\n03-08-2018,-500\n02-08-2018,-500\n01-08-2018,-500\n31-07-2018,-500\n30-07-2018,-500\n27-07-2018,-500\n26-07-2018,-500\n25-07-2018,-500\n24-07-2018,-500\n23-07-2018,-500\n20-07-2018,-500\n19-07-2018,-500\n18-07-2018,-500\n17-07-2018,-500\n16-07-2018,-500\n13-07-2018,-500\n12-07-2018,-500\n11-07-2018,-500\n10-07-2018,-500\n09-07-2018,-500\n06-07-2018,-500\n05-07-2018,-500\n04-07-2018,-500\n03-07-2018,-500\n02-07-2018,-500\n29-06-2018,-500\n28-06-2018,-500\n27-06-2018,-500\n26-06-2018,-500\n25-06-2018,-500\n22-06-2018,-500\n21-06-2018,-500\n20-06-2018,-500\n19-06-2018,-500\n18-06-2018,-500\n15-06-2018,-500\n14-06-2018,-500\n13-06-2018,-500\n12-06-2018,-500\n11-06-2018,-500\n08-06-2018,-500\n07-06-2018,-500\n06-06-2018,-500\n05-06-2018,-500\n04-06-2018,-500\n01-06-2018,-500\n31-05-2018,-500\n30-05-2018,-500\n29-05-2018,-500\n25-05-2018,-500\n24-05-2018,-500\n23-05-2018,-500\n22-05-2018,-500\n21-05-2018,-500\n18-05-2018,-500\n17-05-2018,-500\n16-05-2018,-500\n15-05-2018,-500\n14-05-2018,-500\n11-05-2018,-500\n10-05-2018,-500\n09-05-2018,-500\n08-05-2018,-500\n04-05-2018,-500\n03-05-2018,-500\n02-05-2018,-500\n01-05-2018,-500\n30-04-2018,-500\n27-04-2018,-500\n26-04-2018,-500\n25-04-2018,-500\n24-04-2018,-500\n23-04-2018,-500\n20-04-2018,-500\n19-04-2018,-500\n18-04-2018,-500\n17-04-2018,-500\n16-04-2018,-500\n13-04-2018,-500\n12-04-2018,-500\n11-04-2018,-500\n10-04-2018,-500\n09-04-2018,-500\n06-04-2018,-500\n05-04-2018,-500\n04-04-2018,-500\n03-04-2018,-500\n29-03-2018,-500\n28-03-2018,-500\n27-03-2018,-500\n26-03-2018,-500\n23-03-2018,-500\n22-03-2018,-500\n21-03-2018,-500\n20-03-2018,-500\n19-03-2018,-500\n16-03-2018,-500\n15-03-2018,-500\n14-03-2018,-500\n13-03-2018,-500\n12-03-2018,-500\n09-03-2018,-500\n08-03-2018,-500\n07-03-2018,-500\n06-03-2018,-500\n05-03-2018,-500\n02-03-2018,-500\n01-03-2018,-500\n28-02-2018,-500\n27-02-2018,-500\n26-02-2018,-500\n23-02-2018,-500\n22-02-2018,-500\n21-02-2018,-500\n20-02-2018,-500\n19-02-2018,-500\n16-02-2018,-500\n15-02-2018,-500\n14-02-2018,-500\n13-02-2018,-500\n12-02-2018,-500\n09-02-2018,-500\n08-02-2018,-500\n07-02-2018,-500\n06-02-2018,-500\n05-02-2018,-500\n02-02-2018,-500\n01-02-2018,-500\n31-01-2018,-500\n30-01-2018,-500\n29-01-2018,-500\n26-01-2018,-500\n25-01-2018,-500\n24-01-2018,-500\n23-01-2018,-500\n22-01-2018,-500\n19-01-2018,-500\n18-01-2018,-500\n17-01-2018,-500\n16-01-2018,-500\n15-01-2018,-500\n12-01-2018,-500\n11-01-2018,-500\n10-01-2018,-500\n09-01-2018,-500\n08-01-2018,-500\n05-01-2018,-500\n04-01-2018,-500\n03-01-2018,-500\n02-01-2018,-500\n29-12-2017,-500\n28-12-2017,-500\n27-12-2017,-500\n22-12-2017,-500\n21-12-2017,-500\n20-12-2017,-500\n19-12-2017,-500\n18-12-2017,-500\n15-12-2017,-500\n14-12-2017,-500\n13-12-2017,-500\n12-12-2017,-500\n11-12-2017,-500\n08-12-2017,-500\n07-12-2017,-500\n06-12-2017,-500\n05-12-2017,-500\n04-12-2017,-500\n01-12-2017,-500\n30-11-2017,-500\n29-11-2017,-500\n28-11-2017,-500\n27-11-2017,-500\n24-11-2017,-500\n23-11-2017,-500\n22-11-2017,-500\n21-11-2017,-500\n20-11-2017,-500\n17-11-2017,-500\n16-11-2017,-500\n15-11-2017,-500\n14-11-2017,-500\n13-11-2017,-500\n10-11-2017,-500\n09-11-2017,-500\n08-11-2017,-500\n07-11-2017,-500\n06-11-2017,-500\n03-11-2017,-500\n02-11-2017,-500\n01-11-2017,-500\n31-10-2017,-500\n30-10-2017,-500\n27-10-2017,-500\n26-10-2017,-500\n25-10-2017,-500\n24-10-2017,-500\n23-10-2017,-500\n20-10-2017,-500\n19-10-2017,-500\n18-10-2017,-500\n17-10-2017,-500\n16-10-2017,-500\n13-10-2017,-500\n12-10-2017,-500\n11-10-2017,-500\n10-10-2017,-500\n09-10-2017,-500\n06-10-2017,-500\n05-10-2017,-500\n04-10-2017,-500\n03-10-2017,-500\n02-10-2017,-500\n29-09-2017,-500\n28-09-2017,-500\n27-09-2017,-500\n26-09-2017,-500\n25-09-2017,-500\n22-09-2017,-500\n21-09-2017,-500\n20-09-2017,-500\n19-09-2017,-500\n18-09-2017,-500\n15-09-2017,-500\n14-09-2017,-500\n13-09-2017,-500\n12-09-2017,-500\n11-09-2017,-500\n08-09-2017,-500\n07-09-2017,-500\n06-09-2017,-500\n05-09-2017,-500\n04-09-2017,-500\n01-09-2017,-500\n31-08-2017,-500\n30-08-2017,-500\n29-08-2017,-500\n25-08-2017,-500\n24-08-2017,-500\n23-08-2017,-500\n22-08-2017,-500\n21-08-2017,-500\n18-08-2017,-500\n17-08-2017,-500\n16-08-2017,-500\n15-08-2017,-500\n14-08-2017,-500\n11-08-2017,-500\n10-08-2017,-500\n09-08-2017,-500\n08-08-2017,-500\n07-08-2017,-500\n04-08-2017,-500\n03-08-2017,-500\n02-08-2017,-500\n01-08-2017,-500\n31-07-2017,-500\n28-07-2017,-500\n27-07-2017,-500\n26-07-2017,-500\n25-07-2017,-500\n24-07-2017,-500\n21-07-2017,-500\n20-07-2017,-500\n19-07-2017,-500\n18-07-2017,-500\n17-07-2017,-500\n14-07-2017,-500\n13-07-2017,-500\n12-07-2017,-500\n11-07-2017,-500\n10-07-2017,-500\n07-07-2017,-500\n06-07-2017,-500\n05-07-2017,-500\n04-07-2017,-500\n03-07-2017,-500\n30-06-2017,-500\n29-06-2017,-500\n28-06-2017,-500\n27-06-2017,-500\n26-06-2017,-500\n23-06-2017,-500\n22-06-2017,-500\n21-06-2017,-500\n20-06-2017,-500\n19-06-2017,-500\n16-06-2017,-500\n15-06-2017,-500\n14-06-2017,-500\n13-06-2017,-500\n12-06-2017,-500\n09-06-2017,-500\n08-06-2017,-500\n07-06-2017,-500\n06-06-2017,-500\n05-06-2017,-500\n02-06-2017,-500\n01-06-2017,-500\n31-05-2017,-500\n30-05-2017,-500\n26-05-2017,-500\n25-05-2017,-500\n24-05-2017,-500\n23-05-2017,-500\n22-05-2017,-500\n19-05-2017,-500\n18-05-2017,-500\n17-05-2017,-500\n16-05-2017,-500\n15-05-2017,-500\n12-05-2017,-500\n11-05-2017,-500\n10-05-2017,-500\n09-05-2017,-500\n08-05-2017,-500\n05-05-2017,-500\n04-05-2017,-500\n03-05-2017,-500\n02-05-2017,-500\n28-04-2017,-500\n27-04-2017,-500\n26-04-2017,-500\n25-04-2017,-500\n24-04-2017,-500\n21-04-2017,-500\n20-04-2017,-500\n19-04-2017,-500\n18-04-2017,-500\n13-04-2017,-500\n12-04-2017,-500\n11-04-2017,-500\n10-04-2017,-500\n07-04-2017,-500\n06-04-2017,-500\n05-04-2017,-500\n04-04-2017,-500\n03-04-2017,-500\n31-03-2017,-500\n30-03-2017,-500\n29-03-2017,-500\n28-03-2017,-500\n27-03-2017,-500\n24-03-2017,-500\n23-03-2017,-500\n22-03-2017,-500\n21-03-2017,-500\n20-03-2017,-500\n17-03-2017,-500\n16-03-2017,-500\n15-03-2017,-500\n14-03-2017,-500\n13-03-2017,-500\n10-03-2017,-500\n09-03-2017,-500\n08-03-2017,-500\n07-03-2017,-500\n06-03-2017,-500\n03-03-2017,-500\n02-03-2017,-500\n01-03-2017,-500\n28-02-2017,-500\n27-02-2017,-500\n24-02-2017,-500\n23-02-2017,-500\n22-02-2017,-500\n21-02-2017,-500\n20-02-2017,-500\n17-02-2017,-500\n16-02-2017,-500\n15-02-2017,-500\n14-02-2017,-500\n13-02-2017,-500\n10-02-2017,-500\n09-02-2017,-500\n08-02-2017,-500\n07-02-2017,-500\n06-02-2017,-500\n03-02-2017,-500\n02-02-2017,-500\n01-02-2017,-500\n31-01-2017,-500\n30-01-2017,-500\n27-01-2017,-500\n26-01-2017,-500\n25-01-2017,-500\n24-01-2017,-500\n23-01-2017,-500\n20-01-2017,-500\n19-01-2017,-500\n18-01-2017,-500\n17-01-2017,-500\n16-01-2017,-500\n13-01-2017,-500\n12-01-2017,-500\n11-01-2017,-500\n10-01-2017,-500\n09-01-2017,-500\n06-01-2017,-500\n05-01-2017,-500\n04-01-2017,-500\n03-01-2017,-500\n30-12-2016,-500\n29-12-2016,-500\n28-12-2016,-500\n23-12-2016,-500\n22-12-2016,-500\n21-12-2016,-500\n20-12-2016,-500\n19-12-2016,-500\n16-12-2016,-500\n15-12-2016,-500\n14-12-2016,-500\n13-12-2016,-500\n12-12-2016,-500\n09-12-2016,-500\n08-12-2016,-500\n07-12-2016,-500\n06-12-2016,-500\n05-12-2016,-500\n02-12-2016,-500\n01-12-2016,-500\n30-11-2016,-500\n29-11-2016,-500\n28-11-2016,-500\n25-11-2016,-500\n24-11-2016,-500\n23-11-2016,-500\n22-11-2016,-500\n21-11-2016,-500\n18-11-2016,-500\n17-11-2016,-500\n16-11-2016,-500\n15-11-2016,-500\n14-11-2016,-500\n11-11-2016,-500\n10-11-2016,-500\n09-11-2016,-500\n08-11-2016,-500\n07-11-2016,-500\n04-11-2016,-500\n03-11-2016,-500\n02-11-2016,-500\n01-11-2016,-500\n31-10-2016,-500\n28-10-2016,-500\n27-10-2016,-500\n26-10-2016,-500\n25-10-2016,-500\n24-10-2016,-500\n21-10-2016,-500\n20-10-2016,-500\n19-10-2016,-500\n18-10-2016,-500\n17-10-2016,-500\n14-10-2016,-500\n13-10-2016,-500\n12-10-2016,-500\n11-10-2016,-500\n10-10-2016,-500\n07-10-2016,-500\n06-10-2016,-500\n05-10-2016,-500\n04-10-2016,-500\n03-10-2016,-500\n30-09-2016,-500\n29-09-2016,-500\n28-09-2016,-500\n27-09-2016,-500\n26-09-2016,-500\n23-09-2016,-500\n22-09-2016,-500\n21-09-2016,-500\n20-09-2016,-500\n19-09-2016,-500\n16-09-2016,-500\n15-09-2016,-500\n14-09-2016,-500\n13-09-2016,-500\n12-09-2016,-500\n09-09-2016,-500\n08-09-2016,-500\n07-09-2016,-500\n06-09-2016,-500\n05-09-2016,-500\n02-09-2016,-500\n01-09-2016,-500\n31-08-2016,-500\n30-08-2016,-500\n26-08-2016,-500\n25-08-2016,-500\n24-08-2016,-500\n23-08-2016,-500\n22-08-2016,-500\n19-08-2016,-500\n18-08-2016,-500\n17-08-2016,-500\n16-08-2016,-500\n15-08-2016,-500\n12-08-2016,-500\n11-08-2016,-500\n10-08-2016,-500\n09-08-2016,-500\n08-08-2016,-500\n05-08-2016,-500\n04-08-2016,-500\n03-08-2016,-500\n02-08-2016,-500\n01-08-2016,-500\n29-07-2016,-500\n28-07-2016,-500\n27-07-2016,-500\n26-07-2016,-500\n25-07-2016,-500\n22-07-2016,-500\n21-07-2016,-500\n20-07-2016,-500\n19-07-2016,-500\n18-07-2016,-500\n15-07-2016,-500\n14-07-2016,-500\n13-07-2016,-500\n12-07-2016,-500\n11-07-2016,-500\n08-07-2016,-500\n07-07-2016,-500\n06-07-2016,-500\n05-07-2016,-500\n04-07-2016,-500\n01-07-2016,-500\n30-06-2016,-500\n29-06-2016,-500\n28-06-2016,-500\n27-06-2016,-500\n24-06-2016,-500\n23-06-2016,-500\n22-06-2016,-500\n21-06-2016,-500\n20-06-2016,-500\n17-06-2016,-500\n16-06-2016,-500\n15-06-2016,-500\n14-06-2016,-500\n13-06-2016,-500\n10-06-2016,-500\n09-06-2016,-500\n08-06-2016,-500\n07-06-2016,-500\n06-06-2016,-500\n03-06-2016,-500\n02-06-2016,-500\n01-06-2016,-500\n31-05-2016,-500\n27-05-2016,-500\n26-05-2016,-500\n25-05-2016,-500\n24-05-2016,-500\n23-05-2016,-500\n20-05-2016,-500\n19-05-2016,-500\n18-05-2016,-500\n17-05-2016,-500\n16-05-2016,-500\n13-05-2016,-500\n12-05-2016,-500\n11-05-2016,-500\n10-05-2016,-500\n09-05-2016,-500\n06-05-2016,-500\n05-05-2016,-500\n04-05-2016,-500\n03-05-2016,-500\n29-04-2016,-500\n28-04-2016,-500\n27-04-2016,-500\n26-04-2016,-500\n25-04-2016,-500\n22-04-2016,-500\n21-04-2016,-500\n20-04-2016,-500\n19-04-2016,-500\n18-04-2016,-500\n15-04-2016,-500\n14-04-2016,-500\n13-04-2016,-500\n12-04-2016,-500\n11-04-2016,-500\n08-04-2016,-500\n07-04-2016,-500\n06-04-2016,-500\n05-04-2016,-500\n04-04-2016,-500\n01-04-2016,-500\n31-03-2016,-500\n30-03-2016,-500\n29-03-2016,-500\n24-03-2016,-500\n23-03-2016,-500\n22-03-2016,-500\n21-03-2016,-500\n18-03-2016,-500\n17-03-2016,-500\n16-03-2016,-500\n15-03-2016,-500\n14-03-2016,-500\n11-03-2016,-500\n10-03-2016,-500\n09-03-2016,-500\n08-03-2016,-500\n07-03-2016,-500\n04-03-2016,-500\n03-03-2016,-500\n02-03-2016,-500\n01-03-2016,-500\n29-02-2016,-500\n26-02-2016,-500\n25-02-2016,-500\n24-02-2016,-500\n23-02-2016,-500\n22-02-2016,-500\n19-02-2016,-500\n18-02-2016,-500\n17-02-2016,-500\n16-02-2016,-500\n15-02-2016,-500\n12-02-2016,-500\n11-02-2016,-500\n10-02-2016,-500\n09-02-2016,-500\n08-02-2016,-500\n05-02-2016,-500\n04-02-2016,-500\n03-02-2016,-500\n02-02-2016,-500\n01-02-2016,-500\n29-01-2016,-500\n28-01-2016,-500\n27-01-2016,-500\n26-01-2016,-500\n25-01-2016,-500\n22-01-2016,-500\n21-01-2016,-500\n20-01-2016,-500\n19-01-2016,-500\n18-01-2016,-500\n15-01-2016,-500\n14-01-2016,-500\n13-01-2016,-500\n12-01-2016,-500\n11-01-2016,-500\n08-01-2016,-500\n07-01-2016,-500\n06-01-2016,-500\n05-01-2016,-500\n04-01-2016,-500\n31-12-2015,-500\n30-12-2015,-500\n29-12-2015,-500\n24-12-2015,-500\n23-12-2015,-500\n22-12-2015,-500\n21-12-2015,-500\n18-12-2015,-500\n17-12-2015,-500\n16-12-2015,-500\n15-12-2015,-500\n14-12-2015,-500\n11-12-2015,-500\n10-12-2015,-500\n09-12-2015,-500\n08-12-2015,-500\n07-12-2015,-500\n04-12-2015,-500\n03-12-2015,-500\n02-12-2015,-500\n01-12-2015,-500\n30-11-2015,-500\n27-11-2015,-500\n26-11-2015,-500\n25-11-2015,-500\n24-11-2015,-500\n23-11-2015,-500\n20-11-2015,-500\n19-11-2015,-500\n18-11-2015,-500\n17-11-2015,-500\n16-11-2015,-500\n13-11-2015,-500\n12-11-2015,-500\n11-11-2015,-500\n10-11-2015,-500\n09-11-2015,-500\n06-11-2015,-500\n05-11-2015,-500\n04-11-2015,-500\n03-11-2015,-500\n02-11-2015,-500\n30-10-2015,-500\n29-10-2015,-500\n28-10-2015,-500\n27-10-2015,-500\n26-10-2015,-500\n23-10-2015,-500\n22-10-2015,-500\n21-10-2015,-500\n20-10-2015,-500\n19-10-2015,-500\n16-10-2015,-500\n15-10-2015,-500\n14-10-2015,-500\n13-10-2015,-500\n12-10-2015,-500\n09-10-2015,-500\n08-10-2015,-500\n07-10-2015,-500\n06-10-2015,-500\n05-10-2015,-500\n02-10-2015,-500\n01-10-2015,-500\n30-09-2015,-500\n29-09-2015,-500\n28-09-2015,-500\n25-09-2015,-500\n24-09-2015,-500\n23-09-2015,-500\n22-09-2015,-500\n21-09-2015,-500\n18-09-2015,-500\n17-09-2015,-500\n16-09-2015,-500\n15-09-2015,-500\n14-09-2015,-500\n11-09-2015,-500\n10-09-2015,-500\n09-09-2015,-500\n08-09-2015,-500\n07-09-2015,-500\n04-09-2015,-500\n03-09-2015,-500\n02-09-2015,-500\n01-09-2015,-500\n28-08-2015,-500\n27-08-2015,-500\n26-08-2015,-500\n25-08-2015,-500\n24-08-2015,-500\n21-08-2015,-500\n20-08-2015,-500\n19-08-2015,-500\n18-08-2015,-500\n17-08-2015,-500\n14-08-2015,-500\n13-08-2015,-500\n12-08-2015,-500\n11-08-2015,-500\n10-08-2015,-500\n07-08-2015,-500\n06-08-2015,-500\n05-08-2015,-500\n04-08-2015,-500\n03-08-2015,-500\n31-07-2015,-500\n30-07-2015,-500\n29-07-2015,-500\n28-07-2015,-500\n27-07-2015,-500\n24-07-2015,-500\n23-07-2015,-500\n22-07-2015,-500\n21-07-2015,-500\n20-07-2015,-500\n17-07-2015,-500\n16-07-2015,-500\n15-07-2015,-500\n14-07-2015,-500\n13-07-2015,-500\n10-07-2015,-500\n09-07-2015,-500\n08-07-2015,-500\n07-07-2015,-500\n06-07-2015,-500\n03-07-2015,-500\n02-07-2015,-500\n01-07-2015,-500\n30-06-2015,-500\n29-06-2015,-500\n26-06-2015,-500\n25-06-2015,-500\n24-06-2015,-500\n23-06-2015,-500\n22-06-2015,-500\n19-06-2015,-500\n18-06-2015,-500\n17-06-2015,-500\n16-06-2015,-500\n15-06-2015,-500\n12-06-2015,-500\n11-06-2015,-500\n10-06-2015,-500\n09-06-2015,-500\n08-06-2015,-500\n05-06-2015,-500\n04-06-2015,-500\n03-06-2015,-500\n02-06-2015,-500\n01-06-2015,-500\n29-05-2015,-500\n28-05-2015,-500\n27-05-2015,-500\n26-05-2015,-500\n22-05-2015,-500\n21-05-2015,-500\n20-05-2015,-500\n19-05-2015,-500\n18-05-2015,-500\n15-05-2015,-500\n14-05-2015,-500\n13-05-2015,-500\n12-05-2015,-500\n11-05-2015,-500\n08-05-2015,-500\n07-05-2015,-500\n06-05-2015,-500\n05-05-2015,-500\n01-05-2015,-500\n30-04-2015,-500\n29-04-2015,-500\n28-04-2015,-500\n27-04-2015,-500\n24-04-2015,-500\n23-04-2015,-500\n22-04-2015,-500\n21-04-2015,-500\n20-04-2015,-500\n17-04-2015,-500\n16-04-2015,-500\n15-04-2015,-500\n14-04-2015,-500\n13-04-2015,-500\n10-04-2015,-500\n09-04-2015,-500\n08-04-2015,-500\n07-04-2015,-500\n02-04-2015,-500\n01-04-2015,-500\n31-03-2015,-500\n30-03-2015,-500\n27-03-2015,-500\n26-03-2015,-500\n25-03-2015,-500\n24-03-2015,-500\n23-03-2015,-500\n20-03-2015,-500\n19-03-2015,-500\n18-03-2015,-500\n17-03-2015,-500\n16-03-2015,-500\n13-03-2015,-500\n12-03-2015,-500\n11-03-2015,-500\n10-03-2015,-500\n09-03-2015,-500\n06-03-2015,-500\n05-03-2015,-500\n04-03-2015,-500\n03-03-2015,-500\n02-03-2015,-500\n27-02-2015,-500\n26-02-2015,-500\n25-02-2015,-500\n24-02-2015,-500\n23-02-2015,-500\n20-02-2015,-500\n19-02-2015,-500\n18-02-2015,-500\n17-02-2015,-500\n16-02-2015,-500\n13-02-2015,-500\n12-02-2015,-500\n11-02-2015,-500\n10-02-2015,-500\n09-02-2015,-500\n06-02-2015,-500\n05-02-2015,-500\n04-02-2015,-500\n03-02-2015,-500\n02-02-2015,-500\n30-01-2015,-500\n29-01-2015,-500\n28-01-2015,-500\n27-01-2015,-500\n26-01-2015,-500\n23-01-2015,-500\n22-01-2015,-500\n21-01-2015,-500\n20-01-2015,-500\n19-01-2015,-500\n16-01-2015,-500\n15-01-2015,-500\n14-01-2015,-500\n13-01-2015,-500\n12-01-2015,-500\n09-01-2015,-500\n08-01-2015,-500\n07-01-2015,-500\n06-01-2015,-500\n05-01-2015,-500\n02-01-2015,-500\n31-12-2014,-500\n30-12-2014,-500\n29-12-2014,-500\n24-12-2014,-500\n23-12-2014,-500\n22-12-2014,-500\n19-12-2014,-500\n18-12-2014,-500\n17-12-2014,-500\n16-12-2014,-500\n15-12-2014,-500\n12-12-2014,-500\n11-12-2014,-500\n10-12-2014,-500\n09-12-2014,-500\n08-12-2014,-500\n05-12-2014,-500\n04-12-2014,-500\n03-12-2014,-500\n02-12-2014,-500\n01-12-2014,-500\n28-11-2014,-500\n27-11-2014,-500\n26-11-2014,-500\n25-11-2014,-500\n24-11-2014,-500\n21-11-2014,-500\n20-11-2014,-500\n19-11-2014,-500\n18-11-2014,-500\n17-11-2014,-500\n14-11-2014,-500\n13-11-2014,-500\n12-11-2014,-500\n11-11-2014,-500\n10-11-2014,-500\n07-11-2014,-500\n06-11-2014,-500\n05-11-2014,-500\n04-11-2014,-500\n03-11-2014,-500\n31-10-2014,-500\n30-10-2014,-500\n29-10-2014,-500\n28-10-2014,-500\n27-10-2014,-500\n24-10-2014,-500\n23-10-2014,-500\n22-10-2014,-500\n21-10-2014,-500\n20-10-2014,-500\n17-10-2014,-500\n16-10-2014,-500\n15-10-2014,-500\n14-10-2014,-500\n13-10-2014,-500\n10-10-2014,-500\n09-10-2014,-500\n08-10-2014,-500\n07-10-2014,-500\n06-10-2014,-500\n03-10-2014,-500\n02-10-2014,-500\n01-10-2014,-500\n30-09-2014,-500\n29-09-2014,-500\n26-09-2014,-500\n25-09-2014,-500\n24-09-2014,-500\n23-09-2014,-500\n22-09-2014,-500\n19-09-2014,-500\n18-09-2014,-500\n17-09-2014,-500\n16-09-2014,-500\n15-09-2014,-500\n12-09-2014,-500\n11-09-2014,-500\n10-09-2014,-500\n09-09-2014,-500\n08-09-2014,-500\n05-09-2014,-500\n04-09-2014,-500\n03-09-2014,-500\n02-09-2014,-500\n01-09-2014,-500\n29-08-2014,-500\n28-08-2014,-500\n27-08-2014,-500\n26-08-2014,-500\n22-08-2014,-500\n21-08-2014,-500\n20-08-2014,-500\n19-08-2014,-500\n18-08-2014,-500\n15-08-2014,-500\n14-08-2014,-500\n13-08-2014,-500\n12-08-2014,-500\n11-08-2014,-500\n08-08-2014,-500\n07-08-2014,-500\n06-08-2014,-500\n05-08-2014,-500\n04-08-2014,-500\n01-08-2014,-500\n31-07-2014,-500\n30-07-2014,-500\n29-07-2014,-500\n28-07-2014,-500\n25-07-2014,-500\n24-07-2014,-500\n23-07-2014,-500\n22-07-2014,-500\n21-07-2014,-500\n18-07-2014,-500\n17-07-2014,-500\n16-07-2014,-500\n15-07-2014,-500\n14-07-2014,-500\n11-07-2014,-500\n10-07-2014,-500\n09-07-2014,-500\n08-07-2014,-500\n07-07-2014,-500\n04-07-2014,-500\n03-07-2014,-500\n02-07-2014,-500\n01-07-2014,-500\n30-06-2014,-500\n27-06-2014,-500\n26-06-2014,-500\n25-06-2014,-500\n24-06-2014,-500\n23-06-2014,-500\n20-06-2014,-500\n19-06-2014,-500\n18-06-2014,-500\n17-06-2014,-500\n16-06-2014,-500\n13-06-2014,-500\n12-06-2014,-500\n11-06-2014,-500\n10-06-2014,-500\n09-06-2014,-500\n06-06-2014,-500\n05-06-2014,-500\n04-06-2014,-500\n03-06-2014,-500\n02-06-2014,-500\n30-05-2014,-500\n29-05-2014,-500\n28-05-2014,-500\n27-05-2014,-500\n23-05-2014,-500\n22-05-2014,-500\n21-05-2014,-500\n20-05-2014,-500\n19-05-2014,-500\n16-05-2014,-500\n15-05-2014,-500\n14-05-2014,-500\n13-05-2014,-500\n12-05-2014,-500\n09-05-2014,-500\n08-05-2014,-500\n07-05-2014,-500\n06-05-2014,-500\n02-05-2014,-500\n01-05-2014,-500\n30-04-2014,-500\n29-04-2014,-500\n28-04-2014,-500\n25-04-2014,-500\n24-04-2014,-500\n23-04-2014,-500\n22-04-2014,-500\n17-04-2014,-500\n16-04-2014,-500\n15-04-2014,-500\n14-04-2014,-500\n11-04-2014,-500\n10-04-2014,-500\n09-04-2014,-500\n08-04-2014,-500\n07-04-2014,-500\n04-04-2014,-500\n03-04-2014,-500\n02-04-2014,-500\n01-04-2014,-500\n31-03-2014,-500\n28-03-2014,-500\n27-03-2014,-500\n26-03-2014,-500\n25-03-2014,-500\n24-03-2014,-500\n21-03-2014,-500\n20-03-2014,-500\n19-03-2014,-500\n18-03-2014,-500\n17-03-2014,-500\n14-03-2014,-500\n13-03-2014,-500\n12-03-2014,-500\n11-03-2014,-500\n10-03-2014,-500\n07-03-2014,-500\n06-03-2014,-500\n05-03-2014,-500\n04-03-2014,-500\n03-03-2014,-500\n28-02-2014,-500\n27-02-2014,-500\n26-02-2014,-500\n25-02-2014,-500\n24-02-2014,-500\n21-02-2014,-500\n20-02-2014,-500\n19-02-2014,-500\n18-02-2014,-500\n17-02-2014,-500\n14-02-2014,-500\n13-02-2014,-500\n12-02-2014,-500\n11-02-2014,-500\n10-02-2014,-500\n07-02-2014,-500\n06-02-2014,-500\n05-02-2014,-500\n04-02-2014,-500\n03-02-2014,-500\n31-01-2014,-500\n30-01-2014,-500\n29-01-2014,-500\n28-01-2014,-500\n27-01-2014,-500\n24-01-2014,-500\n23-01-2014,-500\n22-01-2014,-500\n21-01-2014,-500\n20-01-2014,-500\n17-01-2014,-500\n16-01-2014,-500\n15-01-2014,-500\n14-01-2014,-500\n13-01-2014,-500\n10-01-2014,-500\n09-01-2014,-500\n08-01-2014,-500\n07-01-2014,-500\n06-01-2014,-500\n03-01-2014,-500\n02-01-2014,-500\n31-12-2013,-500\n30-12-2013,-500\n27-12-2013,-500\n24-12-2013,-500\n23-12-2013,-500\n20-12-2013,-500\n19-12-2013,-500\n18-12-2013,-500\n17-12-2013,-500\n16-12-2013,-500\n13-12-2013,-500\n12-12-2013,-500\n11-12-2013,-500\n10-12-2013,-500\n09-12-2013,-500\n06-12-2013,-500\n05-12-2013,-500\n04-12-2013,-500\n03-12-2013,-500\n02-12-2013,-500\n29-11-2013,-500\n28-11-2013,-500\n27-11-2013,-500\n26-11-2013,-500\n25-11-2013,-500\n22-11-2013,-500\n21-11-2013,-500\n20-11-2013,-500\n19-11-2013,-500\n18-11-2013,-500\n15-11-2013,-500\n14-11-2013,-500\n13-11-2013,-500\n12-11-2013,-500\n11-11-2013,-500\n08-11-2013,-500\n07-11-2013,-500\n06-11-2013,-500\n05-11-2013,-500\n04-11-2013,-500\n01-11-2013,-500\n31-10-2013,-500\n30-10-2013,-500\n29-10-2013,-500\n28-10-2013,-500\n25-10-2013,-500\n24-10-2013,-500\n23-10-2013,-500\n22-10-2013,-500\n21-10-2013,-500\n18-10-2013,-500\n17-10-2013,-500\n16-10-2013,-500\n15-10-2013,-500\n14-10-2013,-500\n11-10-2013,-500\n10-10-2013,-500\n09-10-2013,-500\n08-10-2013,-500\n07-10-2013,-500\n04-10-2013,-500\n03-10-2013,-500\n02-10-2013,-500\n01-10-2013,-500\n30-09-2013,-500\n27-09-2013,-500\n26-09-2013,-500\n25-09-2013,-500\n24-09-2013,-500\n23-09-2013,-500\n20-09-2013,-500\n19-09-2013,-500\n18-09-2013,-500\n17-09-2013,-500\n16-09-2013,-500\n13-09-2013,-500\n12-09-2013,-500\n11-09-2013,-500\n10-09-2013,-500\n09-09-2013,-500\n06-09-2013,-500\n05-09-2013,-500\n04-09-2013,-500\n03-09-2013,-500\n02-09-2013,-500\n30-08-2013,-500\n29-08-2013,-500\n28-08-2013,-500\n27-08-2013,-500\n23-08-2013,-500\n22-08-2013,-500\n21-08-2013,-500\n20-08-2013,-500\n19-08-2013,-500\n16-08-2013,-500\n15-08-2013,-500\n14-08-2013,-500\n13-08-2013,-500\n12-08-2013,-500\n09-08-2013,-500\n08-08-2013,-500\n07-08-2013,-500\n06-08-2013,-500\n05-08-2013,-500\n02-08-2013,-500\n01-08-2013,-500\n31-07-2013,-500\n30-07-2013,-500\n29-07-2013,-500\n26-07-2013,-500\n25-07-2013,-500\n24-07-2013,-500\n23-07-2013,-500\n22-07-2013,-500\n19-07-2013,-500\n18-07-2013,-500\n17-07-2013,-500\n16-07-2013,-500\n15-07-2013,-500\n12-07-2013,-500\n11-07-2013,-500\n10-07-2013,-500\n09-07-2013,-500\n08-07-2013,-500\n05-07-2013,-500\n04-07-2013,-500\n03-07-2013,-500\n02-07-2013,-500\n01-07-2013,-500\n28-06-2013,-500\n27-06-2013,-500\n26-06-2013,-500\n25-06-2013,-500\n24-06-2013,-500\n21-06-2013,-500\n20-06-2013,-500\n19-06-2013,-500\n18-06-2013,-500\n17-06-2013,-500\n14-06-2013,-500\n13-06-2013,-500\n12-06-2013,-500\n11-06-2013,-500\n10-06-2013,-500\n07-06-2013,-500\n06-06-2013,-500\n05-06-2013,-500\n04-06-2013,-500\n03-06-2013,-500\n31-05-2013,-500\n30-05-2013,-500\n29-05-2013,-500\n28-05-2013,-500\n24-05-2013,-500\n23-05-2013,-500\n22-05-2013,-500\n21-05-2013,-500\n20-05-2013,-500\n17-05-2013,-500\n16-05-2013,-500\n15-05-2013,-500\n14-05-2013,-500\n13-05-2013,-500\n10-05-2013,-500\n09-05-2013,-500\n08-05-2013,-500\n07-05-2013,-500\n03-05-2013,-500\n02-05-2013,-500\n01-05-2013,-500\n30-04-2013,-500\n29-04-2013,-500\n26-04-2013,-500\n25-04-2013,-500\n24-04-2013,-500\n23-04-2013,-500\n22-04-2013,-500\n19-04-2013,-500\n18-04-2013,-500\n17-04-2013,-500\n16-04-2013,-500\n15-04-2013,-500\n12-04-2013,-500\n11-04-2013,-500\n10-04-2013,-500\n09-04-2013,-500\n08-04-2013,-500\n05-04-2013,-500\n04-04-2013,-500\n03-04-2013,-500\n02-04-2013,-500\n28-03-2013,-500\n27-03-2013,-500\n26-03-2013,-500\n25-03-2013,-500\n22-03-2013,-500\n21-03-2013,-500\n20-03-2013,-500\n19-03-2013,-500\n18-03-2013,-500\n15-03-2013,-500\n14-03-2013,-500\n13-03-2013,-500\n12-03-2013,-500\n11-03-2013,-500\n08-03-2013,-500\n07-03-2013,-500\n06-03-2013,-500\n05-03-2013,-500\n04-03-2013,-500\n01-03-2013,-500\n28-02-2013,-500\n27-02-2013,-500\n26-02-2013,-500\n25-02-2013,-500\n22-02-2013,-500\n21-02-2013,-500\n20-02-2013,-500\n19-02-2013,-500\n18-02-2013,-500\n15-02-2013,-500\n14-02-2013,-500\n13-02-2013,-500\n12-02-2013,-500\n11-02-2013,-500\n08-02-2013,-500\n07-02-2013,-500\n06-02-2013,-500\n05-02-2013,-500\n04-02-2013,-500\n01-02-2013,-500\n31-01-2013,-500\n30-01-2013,-500\n29-01-2013,-500\n28-01-2013,-500\n25-01-2013,-500\n24-01-2013,-500\n23-01-2013,-500\n22-01-2013,-500\n21-01-2013,-500\n18-01-2013,-500\n17-01-2013,-500\n16-01-2013,-500\n15-01-2013,-500\n14-01-2013,-500\n11-01-2013,-500\n10-01-2013,-500\n09-01-2013,-500\n08-01-2013,-500\n07-01-2013,-500\n04-01-2013,-500\n03-01-2013,-500\n02-01-2013,-500"
  },
  {
    "path": "python/rateslib/data/historical/swestr.csv",
    "content": "﻿reference_date,rate\n01-09-2021,-500\n02-09-2021,-500\n03-09-2021,-500\n06-09-2021,-500\n07-09-2021,-500\n08-09-2021,-500\n09-09-2021,-500\n10-09-2021,-500\n13-09-2021,-500\n14-09-2021,-500\n15-09-2021,-500\n16-09-2021,-500\n17-09-2021,-500\n20-09-2021,-500\n21-09-2021,-500\n22-09-2021,-500\n23-09-2021,-500\n24-09-2021,-500\n27-09-2021,-500\n28-09-2021,-500\n29-09-2021,-500\n30-09-2021,-500\n01-10-2021,-500\n04-10-2021,-500\n05-10-2021,-500\n06-10-2021,-500\n07-10-2021,-500\n08-10-2021,-500\n11-10-2021,-500\n12-10-2021,-500\n13-10-2021,-500\n14-10-2021,-500\n15-10-2021,-500\n18-10-2021,-500\n19-10-2021,-500\n20-10-2021,-500\n21-10-2021,-500\n22-10-2021,-500\n25-10-2021,-500\n26-10-2021,-500\n27-10-2021,-500\n28-10-2021,-500\n29-10-2021,-500\n01-11-2021,-500\n02-11-2021,-500\n03-11-2021,-500\n04-11-2021,-500\n05-11-2021,-500\n08-11-2021,-500\n09-11-2021,-500\n10-11-2021,-500\n11-11-2021,-500\n12-11-2021,-500\n15-11-2021,-500\n16-11-2021,-500\n17-11-2021,-500\n18-11-2021,-500\n19-11-2021,-500\n22-11-2021,-500\n23-11-2021,-500\n24-11-2021,-500\n25-11-2021,-500\n26-11-2021,-500\n29-11-2021,-500\n30-11-2021,-500\n01-12-2021,-500\n02-12-2021,-500\n03-12-2021,-500\n06-12-2021,-500\n07-12-2021,-500\n08-12-2021,-500\n09-12-2021,-500\n10-12-2021,-500\n13-12-2021,-500\n14-12-2021,-500\n15-12-2021,-500\n16-12-2021,-500\n17-12-2021,-500\n20-12-2021,-500\n21-12-2021,-500\n22-12-2021,-500\n23-12-2021,-500\n27-12-2021,-500\n28-12-2021,-500\n29-12-2021,-500\n30-12-2021,-500\n03-01-2022,-500\n04-01-2022,-500\n05-01-2022,-500\n07-01-2022,-500\n10-01-2022,-500\n11-01-2022,-500\n12-01-2022,-500\n13-01-2022,-500\n14-01-2022,-500\n17-01-2022,-500\n18-01-2022,-500\n19-01-2022,-500\n20-01-2022,-500\n21-01-2022,-500\n24-01-2022,-500\n25-01-2022,-500\n26-01-2022,-500\n27-01-2022,-500\n28-01-2022,-500\n31-01-2022,-500\n01-02-2022,-500\n02-02-2022,-500\n03-02-2022,-500\n04-02-2022,-500\n07-02-2022,-500\n08-02-2022,-500\n09-02-2022,-500\n10-02-2022,-500\n11-02-2022,-500\n14-02-2022,-500\n15-02-2022,-500\n16-02-2022,-500\n17-02-2022,-500\n18-02-2022,-500\n21-02-2022,-500\n22-02-2022,-500\n23-02-2022,-500\n24-02-2022,-500\n25-02-2022,-500\n28-02-2022,-500\n01-03-2022,-500\n02-03-2022,-500\n03-03-2022,-500\n04-03-2022,-500\n07-03-2022,-500\n08-03-2022,-500\n09-03-2022,-500\n10-03-2022,-500\n11-03-2022,-500\n14-03-2022,-500\n15-03-2022,-500\n16-03-2022,-500\n17-03-2022,-500\n18-03-2022,-500\n21-03-2022,-500\n22-03-2022,-500\n23-03-2022,-500\n24-03-2022,-500\n25-03-2022,-500\n28-03-2022,-500\n29-03-2022,-500\n30-03-2022,-500\n31-03-2022,-500\n01-04-2022,-500\n04-04-2022,-500\n05-04-2022,-500\n06-04-2022,-500\n07-04-2022,-500\n08-04-2022,-500\n11-04-2022,-500\n12-04-2022,-500\n13-04-2022,-500\n14-04-2022,-500\n19-04-2022,-500\n20-04-2022,-500\n21-04-2022,-500\n22-04-2022,-500\n25-04-2022,-500\n26-04-2022,-500\n27-04-2022,-500\n28-04-2022,-500\n29-04-2022,-500\n02-05-2022,-500\n03-05-2022,-500\n04-05-2022,-500\n05-05-2022,-500\n06-05-2022,-500\n09-05-2022,-500\n10-05-2022,-500\n11-05-2022,-500\n12-05-2022,-500\n13-05-2022,-500\n16-05-2022,-500\n17-05-2022,-500\n18-05-2022,-500\n19-05-2022,-500\n20-05-2022,-500\n23-05-2022,-500\n24-05-2022,-500\n25-05-2022,-500\n27-05-2022,-500\n30-05-2022,-500\n31-05-2022,-500\n01-06-2022,-500\n02-06-2022,-500\n03-06-2022,-500\n07-06-2022,-500\n08-06-2022,-500\n09-06-2022,-500\n10-06-2022,-500\n13-06-2022,-500\n14-06-2022,-500\n15-06-2022,-500\n16-06-2022,-500\n17-06-2022,-500\n20-06-2022,-500\n21-06-2022,-500\n22-06-2022,-500\n23-06-2022,-500\n27-06-2022,-500\n28-06-2022,-500\n29-06-2022,-500\n30-06-2022,-500\n01-07-2022,-500\n04-07-2022,-500\n05-07-2022,-500\n06-07-2022,-500\n07-07-2022,-500\n08-07-2022,-500\n11-07-2022,-500\n12-07-2022,-500\n13-07-2022,-500\n14-07-2022,-500\n15-07-2022,-500\n18-07-2022,-500\n19-07-2022,-500\n20-07-2022,-500\n21-07-2022,-500\n22-07-2022,-500\n25-07-2022,-500\n26-07-2022,-500\n27-07-2022,-500\n28-07-2022,-500\n29-07-2022,-500\n01-08-2022,-500\n02-08-2022,-500\n03-08-2022,-500\n04-08-2022,-500\n05-08-2022,-500\n08-08-2022,-500\n09-08-2022,-500\n10-08-2022,-500\n11-08-2022,-500\n12-08-2022,-500\n15-08-2022,-500\n16-08-2022,-500\n17-08-2022,-500\n18-08-2022,-500\n19-08-2022,-500\n22-08-2022,-500\n23-08-2022,-500\n24-08-2022,-500\n25-08-2022,-500\n26-08-2022,-500\n29-08-2022,-500\n30-08-2022,-500\n31-08-2022,-500\n01-09-2022,-500\n02-09-2022,-500\n05-09-2022,-500\n06-09-2022,-500\n07-09-2022,-500\n08-09-2022,-500\n09-09-2022,-500\n12-09-2022,-500\n13-09-2022,-500\n14-09-2022,-500\n15-09-2022,-500\n16-09-2022,-500\n19-09-2022,-500\n20-09-2022,-500\n21-09-2022,-500\n22-09-2022,-500\n23-09-2022,-500\n26-09-2022,-500\n27-09-2022,-500\n28-09-2022,-500\n29-09-2022,-500\n30-09-2022,-500\n03-10-2022,-500\n04-10-2022,-500\n05-10-2022,-500\n06-10-2022,-500\n07-10-2022,-500\n10-10-2022,-500\n11-10-2022,-500\n12-10-2022,-500\n13-10-2022,-500\n14-10-2022,-500\n17-10-2022,-500\n18-10-2022,-500\n19-10-2022,-500\n20-10-2022,-500\n21-10-2022,-500\n24-10-2022,-500\n25-10-2022,-500\n26-10-2022,-500\n27-10-2022,-500\n28-10-2022,-500\n31-10-2022,-500\n01-11-2022,-500\n02-11-2022,-500\n03-11-2022,-500\n04-11-2022,-500\n07-11-2022,-500\n08-11-2022,-500\n09-11-2022,-500\n10-11-2022,-500\n11-11-2022,-500\n14-11-2022,-500\n15-11-2022,-500\n16-11-2022,-500\n17-11-2022,-500\n18-11-2022,-500\n21-11-2022,-500\n22-11-2022,-500\n23-11-2022,-500\n24-11-2022,-500\n25-11-2022,-500\n28-11-2022,-500\n29-11-2022,-500\n30-11-2022,-500\n01-12-2022,-500\n02-12-2022,-500\n05-12-2022,-500\n06-12-2022,-500\n07-12-2022,-500\n08-12-2022,-500\n09-12-2022,-500\n12-12-2022,-500\n13-12-2022,-500\n14-12-2022,-500\n15-12-2022,-500\n16-12-2022,-500\n19-12-2022,-500\n20-12-2022,-500\n21-12-2022,-500\n22-12-2022,-500\n23-12-2022,-500\n27-12-2022,-500\n28-12-2022,-500\n29-12-2022,-500\n30-12-2022,-500\n02-01-2023,-500\n03-01-2023,-500\n04-01-2023,-500\n05-01-2023,-500\n09-01-2023,-500\n10-01-2023,-500\n11-01-2023,-500\n12-01-2023,-500\n13-01-2023,-500\n16-01-2023,-500\n17-01-2023,-500\n18-01-2023,-500\n19-01-2023,-500\n20-01-2023,-500\n23-01-2023,-500\n24-01-2023,-500\n25-01-2023,-500\n26-01-2023,-500\n27-01-2023,-500\n30-01-2023,-500\n31-01-2023,-500\n01-02-2023,-500\n02-02-2023,-500\n03-02-2023,-500\n06-02-2023,-500\n07-02-2023,-500\n08-02-2023,-500\n09-02-2023,-500\n10-02-2023,-500\n13-02-2023,-500\n14-02-2023,-500\n15-02-2023,-500\n16-02-2023,-500\n17-02-2023,-500\n20-02-2023,-500\n21-02-2023,-500\n22-02-2023,-500\n23-02-2023,-500\n24-02-2023,-500\n27-02-2023,-500\n28-02-2023,-500\n01-03-2023,-500\n02-03-2023,-500\n03-03-2023,-500\n06-03-2023,-500\n07-03-2023,-500\n08-03-2023,-500\n09-03-2023,-500\n10-03-2023,-500\n13-03-2023,-500\n14-03-2023,-500\n15-03-2023,-500\n16-03-2023,-500\n17-03-2023,-500\n20-03-2023,-500\n21-03-2023,-500\n22-03-2023,-500\n23-03-2023,-500\n24-03-2023,-500\n27-03-2023,-500\n28-03-2023,-500\n29-03-2023,-500\n30-03-2023,-500\n31-03-2023,-500\n03-04-2023,-500\n04-04-2023,-500\n05-04-2023,-500\n06-04-2023,-500\n11-04-2023,-500\n12-04-2023,-500\n13-04-2023,-500\n14-04-2023,-500\n17-04-2023,-500\n18-04-2023,-500\n19-04-2023,-500\n20-04-2023,-500\n21-04-2023,-500\n24-04-2023,-500\n25-04-2023,-500\n26-04-2023,-500\n27-04-2023,-500\n28-04-2023,-500\n02-05-2023,-500\n03-05-2023,-500\n04-05-2023,-500\n05-05-2023,-500\n08-05-2023,-500\n09-05-2023,-500\n10-05-2023,-500\n11-05-2023,-500\n12-05-2023,-500\n15-05-2023,-500\n16-05-2023,-500\n17-05-2023,-500\n19-05-2023,-500\n22-05-2023,-500\n23-05-2023,-500\n24-05-2023,-500\n25-05-2023,-500\n26-05-2023,-500\n29-05-2023,-500\n30-05-2023,-500\n31-05-2023,-500\n01-06-2023,-500\n02-06-2023,-500\n05-06-2023,-500\n07-06-2023,-500\n08-06-2023,-500\n09-06-2023,-500\n12-06-2023,-500\n13-06-2023,-500\n14-06-2023,-500\n15-06-2023,-500\n16-06-2023,-500\n19-06-2023,-500\n20-06-2023,-500\n21-06-2023,-500\n22-06-2023,-500\n26-06-2023,-500\n27-06-2023,-500\n28-06-2023,-500\n29-06-2023,-500\n30-06-2023,-500\n03-07-2023,-500\n04-07-2023,-500\n05-07-2023,-500\n06-07-2023,-500\n07-07-2023,-500\n10-07-2023,-500\n11-07-2023,-500\n12-07-2023,-500\n13-07-2023,-500\n14-07-2023,-500\n17-07-2023,-500\n18-07-2023,-500\n19-07-2023,-500\n20-07-2023,-500\n21-07-2023,-500\n24-07-2023,-500\n25-07-2023,-500\n26-07-2023,-500\n27-07-2023,-500\n28-07-2023,-500\n31-07-2023,-500\n01-08-2023,-500\n02-08-2023,-500"
  },
  {
    "path": "python/rateslib/data/historical/usd_rfr.csv",
    "content": "﻿reference_date,rate\n01-08-2019,2.19\n31-07-2019,2.55\n30-07-2019,2.39\n29-07-2019,2.4\n26-07-2019,2.41\n25-07-2019,2.42\n24-07-2019,2.41\n23-07-2019,2.4\n22-07-2019,2.4"
  },
  {
    "path": "python/rateslib/data/loader.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport os\nfrom abc import ABCMeta, abstractmethod\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nimport rateslib.errors as err\nfrom packaging import version\nfrom pandas import Series, read_csv\nfrom pandas import __version__ as pandas_version\nfrom rateslib.enums.generics import Err, NoInput, Ok\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Adjuster,\n        CalTypes,\n        DualTypes,\n        FloatRateSeries,\n        Result,\n        datetime_,\n        int_,\n    )\n\n\nclass _BaseFixingsLoader(metaclass=ABCMeta):\n    \"\"\"\n    Abstract base class to allow custom implementations of a fixings data loader.\n\n    Notes\n    -----\n    This class requires an implementation of ``__getitem__``, which should accept an\n    ``identifier`` and return a 3-tuple. The 3-tuple should include;\n\n    - an integer representing the state id of the loaded data, i.e. its hash or pseudo-hash.\n    - the data itself as a Series indexed by daily datetimes.\n    - a 2-tuple of datetimes indicating the min and max of the timeseries index.\n\n    If a valid Series object cannot be loaded for the ``identifier`` then this method\n    is required to raise a `ValeuError`.\n    \"\"\"\n\n    @abstractmethod\n    def __getitem__(self, name: str) -> tuple[int, Series[DualTypes], tuple[datetime, datetime]]:  # type: ignore[type-var]\n        \"\"\"\n        Get item method to load a fixing series and ist state id from a custom container object.\n\n        Parameters\n        ----------\n        name: str\n            The name of the fixing series to load.\n\n        Returns\n        -------\n        tuple of int, pandas Series, and tuple of datetime\n\n        Notes\n        -----\n        The first tuple element is a hash integer which represents the state of the Series object.\n        This is used to determine if the Series object has changed since it was last loaded,\n        and makes for more efficient fixings lookup calculations in *Periods*.\n\n        The second element is the timeseries object itself.\n\n        The third tuple element is a cached record of the first and last dates in the Series index.\n\n        If a valid Series object cannot be loaded this method **must** raise an `Exception`,\n        preferably a `ValueError`.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def add(self, name: str, series: Series[DualTypes], state: int_ = NoInput(0)) -> None:  # type: ignore[type-var]\n        \"\"\"\n        Add a timeseries to the data loader directly from Python.\n\n        Parameters\n        ----------\n        name: str\n            The string identifier for the timeseries.\n        series: Series[DualTypes]\n            The timeseries to add to static data.\n\n        Returns\n        -------\n        None\n\n        Examples\n        --------\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import fixings, dt\n           from pandas import Series\n\n        .. ipython:: python\n\n           ts = Series(index=[dt(2000, 1, 1)], data=[666.0])\n           fixings.add(\"my_timeseries\", ts)\n           fixings[\"my_timeseries\"]\n           fixings.pop(\"my_timeseries\")\n\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def pop(self, name: str) -> Series[DualTypes] | None:  # type: ignore[type-var]\n        \"\"\"\n        Remove a timeseries from the data loader.\n\n        Parameters\n        ----------\n        name: str\n            The string identifier for the timeseries.\n\n        Returns\n        -------\n        Series[DualTypes] or None\n\n        Notes\n        -----\n        If the ``name`` does not exist None will be returned.\n        \"\"\"\n        pass\n\n    def __try_getitem__(\n        self, name: str\n    ) -> Result[tuple[int, Series[DualTypes], tuple[datetime, datetime]]]:  # type: ignore[type-var]\n        try:\n            tuple_value = self.__getitem__(name)\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(tuple_value)\n\n    def __base_lookup__(\n        self,\n        fixing_series: Series[DualTypes],  # type: ignore[type-var]\n        lookup_date: datetime_,\n        bounds: tuple[datetime, datetime] | None = None,\n    ) -> Result[DualTypes]:\n        if bounds is not None:\n            left, right = bounds\n        else:\n            # default to slower mechanism of lookup\n            left, right = fixing_series.index[0], fixing_series.index[-1]\n\n        if isinstance(lookup_date, NoInput):\n            # program break, raise directly\n            raise ValueError(\"A `lookup_date` must be provided for fetching fixings from Series.\")\n        if lookup_date < left or lookup_date > right:\n            return Err(FixingRangeError(lookup_date, (left, right)))\n\n        if lookup_date not in fixing_series.index:\n            return Err(FixingMissingDataError(lookup_date, (left, right)))\n        else:\n            return Ok(fixing_series.loc[lookup_date])\n\n    def get_stub_ibor_fixings(\n        self,\n        value_start_date: datetime,\n        value_end_date: datetime,\n        fixing_date: datetime,\n        fixing_calendar: CalTypes,\n        fixing_modifier: Adjuster,\n        fixing_identifier: str,\n    ) -> tuple[list[str], list[datetime], list[DualTypes | None]]:\n        \"\"\"\n        Return the tenors available in the :class:`~rateslib.defaults.Fixings` object for\n        determining an IBOR type stub period.\n\n        Parameters\n        ----------\n        value_start_date: datetime\n            The value start date of the IBOR period.\n        value_end_date: datetime\n            The value end date of the current stub period.\n        fixing_date: datetime\n            The index date to examine from the fixing series.\n        fixing_calendar: Cal, UnionCal, NamedCal,\n            The calendar to derive IBOR value end dates.\n        fixing_modifier: Adjuster\n            The date adjuster to derive IBOR value end dates.\n        fixing_identifier: str\n            The fixing name, prior to the addition of tenor, e.g. \"EUR_EURIBOR\"\n\n        Returns\n        -------\n        tuple of list[string tenors] and list[evaluated end dates]\n        \"\"\"\n\n        def _is_available(tenor: str) -> bool:\n            try:\n                self.__getitem__(f\"{fixing_identifier.upper()}_{tenor}\")\n            except Exception:  # noqa: S112\n                return False\n            else:\n                return True\n\n        tenors = [\"1D\", \"1B\", \"2B\", \"1W\", \"2W\", \"3W\", \"4W\"] + [\n            \"1M\",\n            \"2M\",\n            \"3M\",\n            \"4M\",\n            \"5M\",\n            \"6M\",\n            \"7M\",\n            \"8M\",\n            \"9M\",\n            \"10M\",\n            \"11M\",\n            \"12M\",\n            \"1Y\",\n        ]\n\n        available_tenors = [tenor for tenor in tenors if _is_available(tenor)]\n        from rateslib.data.fixings import FloatRateSeries\n\n        neighbouring_tenors = _find_neighbouring_tenors(\n            end=value_end_date,\n            start=value_start_date,\n            tenors=available_tenors,\n            rate_series=FloatRateSeries(\n                lag=0, calendar=fixing_calendar, convention=\"1\", modifier=fixing_modifier, eom=False\n            ),\n        )\n\n        values: list[DualTypes | None] = []\n        for tenor in neighbouring_tenors[0]:\n            try:\n                val: DualTypes = self.__getitem__(f\"{fixing_identifier.upper()}_{tenor}\")[1][\n                    fixing_date\n                ]\n            except KeyError:\n                values.append(None)\n            else:\n                values.append(val)\n        return neighbouring_tenors + (values,)\n\n\nclass DefaultFixingsLoader(_BaseFixingsLoader):\n    \"\"\"\n    The :class:`~rateslib.data.loader._BaseFixingsLoader` implemented by default.\n\n    This loader searches a particular local directory for CSV files.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self._directory = os.path.dirname(os.path.abspath(__file__)) + \"/historical\"\n        self._loaded: dict[str, tuple[int, Series[DualTypes], tuple[datetime, datetime]]] = {}  # type: ignore[type-var]\n\n    @property\n    def directory(self) -> str:\n        \"\"\"The local directory in which data CSV files may be located.\"\"\"\n        return self._directory\n\n    @directory.setter\n    def directory(self, val: str) -> None:\n        self._directory = val\n\n    @property\n    def loaded(self) -> dict[str, tuple[int, Series[DualTypes], tuple[datetime, datetime]]]:  # type: ignore[type-var]\n        \"\"\"A dictionary of the (state id, timeseries, data range) keyed by identifiers.\"\"\"\n        return self._loaded\n\n    @staticmethod\n    def _load_csv(directory: str, path: str) -> Series[DualTypes]:  # type: ignore[type-var]\n        target = os.path.join(directory, path)\n        if version.parse(pandas_version) < version.parse(\"2.0\"):  # pragma: no cover\n            # this is tested by the minimum version gitflow actions.\n            # TODO (low:dependencies) remove when pandas min version is bumped to 2.0\n            df = read_csv(target)\n            df[\"reference_date\"] = df[\"reference_date\"].map(\n                lambda x: datetime.strptime(x, \"%d-%m-%Y\"),\n            )\n            df = df.set_index(\"reference_date\")\n        else:\n            df = read_csv(target, index_col=0, parse_dates=[0], date_format=\"%d-%m-%Y\")\n        return df[\"rate\"].sort_index(ascending=True)\n\n    def __getitem__(self, name: str) -> tuple[int, Series[DualTypes], tuple[datetime, datetime]]:  # type: ignore[type-var]\n        name_ = name.upper()\n        if name_ in self.loaded:\n            return self.loaded[name_]\n\n        try:\n            s: Series[DualTypes] = self._load_csv(self.directory, f\"{name}.csv\")  # type: ignore[type-var]\n        except FileNotFoundError:\n            raise ValueError(\n                f\"Fixing data for the index '{name}' has been attempted, but there is no file:\\n\"\n                f\"'{name}.csv' located in the search directory.\\n\"\n                \"For further info see the documentation section regarding `Fixings`.\",\n            )\n\n        data = (hash(os.urandom(8)), s, (s.index[0], s.index[-1]))\n        self.loaded[name_] = data\n        return data\n\n    def add(self, name: str, series: Series[DualTypes], state: int_ = NoInput(0)) -> None:  # type: ignore[type-var]\n        if name in self.loaded:\n            raise ValueError(f\"Fixing data for the index '{name}' has already been loaded.\")\n        s = series.sort_index(ascending=True)\n        s.index.name = \"reference_date\"\n        s.name = \"rate\"\n        name_ = name.upper()\n        if isinstance(state, NoInput):\n            state_: int = hash(os.urandom(64))\n        else:\n            state_ = state\n        self.loaded[name_] = (state_, s, (s.index[0], s.index[-1]))\n\n    def pop(self, name: str) -> Series[DualTypes] | None:  # type: ignore[type-var]\n        name_ = name.upper()\n        popped = self.loaded.pop(name_, None)\n        if popped is not None:\n            return popped[1]  # return the Series object only\n        else:\n            return None\n\n\nclass Fixings(_BaseFixingsLoader):\n    \"\"\"\n    Object to store and load fixing data to populate *Leg* and *Period* calculations.\n\n    .. warning::\n\n       You must maintain and populate your own fixing data.\n\n       *Rateslib* does not come pre-packaged with accurate, nor upto date fixing data.\n       1) It does not have data licensing to distribute such data.\n       2) It is a statically uploaded code package will become immediately out of date.\n\n    .. attention::\n\n       This object is loaded **once** by *rateslib* and in its global module,\n       under the attribute `fixings`.\n       Only this object is referenced internally and other instantiations of this class\n       will be ignored.\n\n    Notes\n    -----\n    The ``loader`` is initialised as the :class:`DefaultFixingsLoader`. This can be set as\n    a user implemented :class:`_BaseFixingsLoader`.\n\n    This class maintains a dictionary of financial fixing Series indexed by string identifiers.\n\n    **Fixing Population**\n\n    This dictionary can be populated in one of two ways:\n\n    - Either by maintaining a set of CSV files in the source lookup directory (whose path is\n      visible/settable by calling `fixings.directory`)\n    - Or creating a pandas *Series* and using the :meth:`~rateslib.default.Fixings.add` to\n      add this object to the dictionary.\n\n    **Fixing Lookup**\n\n    Lookup of a fixing *Series* is performed, for example using the get item pattern. If an\n    object does not already exist in the dictionary it will be attempted to load from source CSV\n    file. If neither exists it will raise a `ValueError`.\n\n    .. ipython:: python\n       :suppress:\n\n       from pandas import Series\n       from datetime import datetime as dt\n       from rateslib import fixings\n\n    .. ipython:: python\n\n       cpi = Series(\n           index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)],\n           data=[100.0, 101.2, 102.2]\n       )\n       fixings.add(\"MY_CPI\", cpi)\n       fixings[\"MY_CPI\"]\n\n    .. ipython:: python\n\n       try:\n           fixings[\"NON_EXISTENT_SERIES\"]\n       except ValueError as e:\n           print(e)\n\n    \"\"\"\n\n    _instance = None\n\n    def __new__(cls) -> Fixings:\n        if cls._instance is None:\n            # Singleton pattern creates only one instance: TODO (low) might not be thread safe\n            cls._instance = super(_BaseFixingsLoader, cls).__new__(cls)  # noqa: UP008\n\n            cls._loader: _BaseFixingsLoader = DefaultFixingsLoader()\n\n        return cls._instance\n\n    def __getitem__(self, name: str) -> tuple[int, Series[DualTypes], tuple[datetime, datetime]]:  # type: ignore[type-var]\n        return self.loader.__getitem__(name)\n\n    @property\n    def loader(self) -> _BaseFixingsLoader:\n        \"\"\"\n        Object responsible for fetching data from external sources.\n        \"\"\"\n        return self._loader\n\n    @loader.setter\n    def loader(self, loader: _BaseFixingsLoader) -> None:\n        self._loader = loader\n\n    def add(self, name: str, series: Series[DualTypes], state: int_ = NoInput(0)) -> None:  # type: ignore[type-var]\n        \"\"\"\n        Add a Series to the Fixings object directly from Python\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        name: str, :red:`required`\n            The string identifier key for the timeseries.\n        series: Series, :red:`required`\n            The timeseries indexed by datetime.\n        state: int, :green:`optional`\n            The state id to be used upon insertion of the Series.\n\n        Returns\n        -------\n        None\n        \"\"\"\n        return self.loader.add(name, series, state)\n\n    def pop(self, name: str) -> Series[DualTypes] | None:  # type: ignore[type-var]\n        \"\"\"\n        Remove a Series from the Fixings object.\n\n        .. role:: red\n\n        Parameters\n        ----------\n        name: str, :red:`required`\n            The string identifier key for the timeseries.\n\n        Returns\n        -------\n        Series, or None (if name not found)\n        \"\"\"\n        return self.loader.pop(name)\n\n\nclass FixingRangeError(Exception):\n    def __init__(self, date: datetime, boundary: tuple[datetime, datetime]) -> None:\n        super().__init__(\n            f\"Fixing lookup for date '{date}' failed.\\n\"\n            f\"The fixings series has range [{boundary[0]}, {boundary[1]}]\"\n        )\n        self.date = date\n        self.boundary = boundary\n\n\nclass FixingMissingDataError(Exception):\n    def __init__(self, date: datetime, boundary: tuple[datetime, datetime]) -> None:\n        super().__init__(\n            f\"Fixing lookup for date '{date}' failed.\\n\"\n            f\"The requested date falls within the fixings series range \"\n            f\"[{boundary[0]}, {boundary[1]}] but was not found.\"\n        )\n        self.date = date\n        self.boundary = boundary\n\n\nclass FixingMissingForecasterError(Exception):\n    def __init__(self) -> None:\n        super().__init__(err.VE_NEEDS_RATE_TO_FORECAST_RFR)\n\n\ndef _find_neighbouring_tenors(\n    end: datetime,\n    start: datetime,\n    tenors: list[str],\n    rate_series: FloatRateSeries,\n) -> tuple[list[str], list[datetime]]:\n    \"\"\"\n    Given a list of string tenors find the two, measured from `start`, that encompass `end`\n    on neighbouring sides. If outside, find the closest single tenor.\n    \"\"\"\n    from rateslib.scheduling import add_tenor\n\n    left: tuple[str | None, datetime] = (None, datetime(1, 1, 1))\n    right: tuple[str | None, datetime] = (None, datetime(9999, 1, 1))\n\n    for tenor in tenors:\n        sample_end = add_tenor(\n            start=start,\n            tenor=tenor,\n            modifier=rate_series.modifier,\n            calendar=rate_series.calendar,\n        )\n        if sample_end <= end and sample_end > left[1]:\n            left = (tenor, sample_end)\n        if sample_end >= end and sample_end < right[1]:\n            right = (tenor, sample_end)\n            break\n\n    ret: tuple[list[str], list[datetime]] = ([], [])\n    if left[0] is not None:\n        ret[0].append(left[0].upper())\n        ret[1].append(left[1])\n    if right[0] is not None:\n        ret[0].append(right[0].upper())\n        ret[1].append(right[1])\n    return ret\n\n\n__all__ = [\"Fixings\", \"DefaultFixingsLoader\", \"_BaseFixingsLoader\"]\n"
  },
  {
    "path": "python/rateslib/default.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom copy import deepcopy\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nimport matplotlib.dates as mdates\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom rateslib._spec_loader import INSTRUMENT_SPECS\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.rs import Adjuster, Convention, Frequency, NamedCal\n\nPlotOutput = tuple[plt.Figure, plt.Axes, list[plt.Line2D]]  # type: ignore[name-defined]\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalTypes,\n    )\n\n\nDEFAULTS = dict(\n    stub=\"SHORTFRONT\",\n    stub_length=\"SHORT\",\n    eval_mode=\"swaps_align\",\n    modifier=\"MF\",\n    eom=False,\n    eom_fx=True,\n    # Instrument parameterisation\n    metric={\n        \"SBS\": \"leg1\",\n    },\n    convention=\"ACT360\",\n    notional=1.0e6,\n    index_lag=3,\n    index_lag_curve=0,\n    index_method=\"daily\",\n    payment_lag=2,\n    payment_lag_exchange=0,\n    payment_lag_specific={\n        \"Fee\": 0,\n        \"Loan\": 0,\n        \"IRS\": 2,\n        \"STIRFuture\": 0,\n        \"IIRS\": 2,\n        \"YoYIS\": 2,\n        \"ZCS\": 2,\n        \"ZCIS\": 0,\n        \"FXSwap\": 0,\n        \"SBS\": 2,\n        \"Swap\": 2,\n        \"XCS\": 2,\n        \"NDXCS\": 2,\n        \"FixedRateBond\": 0,\n        \"IndexFixedRateBond\": 0,\n        \"FloatRateNote\": 0,\n        \"Bill\": 0,\n        \"FRA\": 0,\n        \"CDS\": 0,\n        \"NDF\": 2,\n    },\n    fixing_method=\"rfr_payment_delay\",\n    spread_compound_method=\"none_simple\",\n    index_base_type=\"initial\",\n    base_currency=\"usd\",\n    fx_delivery_lag=2,\n    fx_delta_type=\"spot\",\n    fx_option_metric=\"pips\",\n    ir_option_metric=\"black_vol_shift_0\",\n    ir_option_settlement=\"physical\",\n    cds_premium_accrued=True,\n    cds_recovery_rate=0.40,\n    cds_protection_discretization=23,\n    # Curves\n    interpolation={\n        \"dfs\": \"log_linear\",\n        \"values\": \"linear\",\n    },\n    endpoints=\"natural\",\n    multi_csa_steps=[\n        2,\n        5,\n        10,\n        20,\n        30,\n        50,\n        77,\n        81,\n        86,\n        91,\n        96,\n        103,\n        110,\n        119,\n        128,\n        140,\n        153,\n        169,\n        188,\n        212,\n        242,\n        281,\n        332,\n        401,\n        498,\n        636,\n        835,\n        1104,\n        1407,\n        1646,\n        1766,\n        1808,\n        1821,\n        1824,\n        1825,\n    ],\n    multi_csa_min_step=1,\n    multi_csa_max_step=1825,\n    curve_caching=True,\n    curve_caching_max=1000,\n    # Solver\n    tag=\"v\",\n    algorithm=\"levenberg_marquardt\",\n    curve_not_in_solver=\"ignore\",  # or \"warn\" or \"raise\"\n    ini_lambda=(1000.0, 0.25, 2.0),\n    # bonds\n    calc_mode={\n        \"FixedRateBond\": \"uk_gb\",\n        \"FloatRateNote\": \"uk_gb\",\n        \"Bill\": \"us_gbb\",\n        \"IndexFixedRateBond\": \"uk_gb\",\n    },\n    settle=1,\n    ex_div=1,\n    calc_mode_futures=\"ytm\",\n    # misc\n    pool=1,\n    no_fx_fixings_for_xcs=\"warn\",  # or \"raise\" or \"ignore\"\n    headers={\n        \"type\": \"Type\",\n        \"stub_type\": \"Period\",\n        \"u_acc_start\": \"Unadj Acc Start\",\n        \"u_acc_end\": \"Unadj Acc End\",\n        \"a_acc_start\": \"Acc Start\",\n        \"a_acc_end\": \"Acc End\",\n        \"payment\": \"Payment\",\n        \"convention\": \"Convention\",\n        \"dcf\": \"DCF\",\n        \"df\": \"DF\",\n        \"notional\": \"Notional\",\n        \"reference_currency\": \"Reference Ccy\",\n        \"currency\": \"Ccy\",\n        \"fx_fixing\": \"FX Fixing\",\n        \"fx_fixing_date\": \"FX Fix Date\",\n        \"rate\": \"Rate\",\n        \"spread\": \"Spread\",\n        \"npv\": \"NPV\",\n        \"cashflow\": \"Cashflow\",\n        \"fx\": \"FX Rate\",\n        \"npv_fx\": \"NPV Ccy\",\n        \"base\": \"Base Ccy\",\n        \"unindexed_cashflow\": \"Unindexed Cashflow\",\n        \"index_fix_date\": \"Index Fix Date\",\n        \"index_value\": \"Index Val\",\n        \"index_ratio\": \"Index Ratio\",\n        \"index_base\": \"Index Base\",\n        \"collateral\": \"Collateral\",\n        # Options headers\n        \"pair\": \"Pair\",\n        \"expiry\": \"Expiry\",\n        \"t_e\": \"Time to Expiry\",\n        \"delivery\": \"Delivery\",\n        \"model\": \"Model\",\n        \"vol\": \"Vol\",\n        \"strike\": \"Strike\",\n        # CDS headers\n        \"survival\": \"Survival\",\n        \"recovery\": \"Recovery\",\n    },\n    _global_ad_order=1,\n    oaspread_func_tol=1e-6,\n    oaspread_conv_tol=1e-8,\n    # Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n    # Commercial use of this code, and/or copying and redistribution is prohibited.\n    # Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n    spec=INSTRUMENT_SPECS,\n    fx_index={\n        # ISDA values determined from the ISDA MTM Matrix documentation\n        \"eurusd\": dict(\n            pair=\"eurusd\",\n            calendar=NamedCal(\"tgt|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=False,\n        ),\n        \"eurgbp\": dict(\n            pair=\"eurgbp\",\n            calendar=NamedCal(\"ldn,tgt|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        \"eursek\": dict(\n            pair=\"eursek\",\n            calendar=NamedCal(\"tgt,stk|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"tgt,stk\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=False,\n        ),\n        \"gbpusd\": dict(\n            pair=\"gbpusd\",\n            calendar=NamedCal(\"ldn|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=False,\n        ),\n        \"usdcad\": dict(\n            pair=\"usdcad\",\n            calendar=NamedCal(\"tro|fed\"),\n            settle=Adjuster.BusDaysLagSettle(1),\n            isda_mtm_calendar=NamedCal(\"tro,nyc,ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=False,\n        ),\n        \"gbpcad\": dict(\n            pair=\"gbpcad\",\n            calendar=NamedCal(\"tro,ldn|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"tro,nyc,ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        \"usdnok\": dict(\n            pair=\"usdnok\",\n            calendar=NamedCal(\"osl|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"osl\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        \"usdsek\": dict(\n            pair=\"usdsek\",\n            calendar=NamedCal(\"stk|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"stk\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        \"chfsek\": dict(\n            pair=\"chfsek\",\n            calendar=NamedCal(\"stk,zur|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"stk,zur,ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        \"usdchf\": dict(\n            pair=\"usdchf\",\n            calendar=NamedCal(\"zur|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        \"seknok\": dict(\n            pair=\"seknok\",\n            calendar=NamedCal(\"stk,osl|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"stk,osl\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        \"audusd\": dict(\n            pair=\"audusd\",\n            calendar=NamedCal(\"syd|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"syd,nyc,ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=False,\n        ),\n        \"usdjpy\": dict(\n            pair=\"usdjpy\",\n            calendar=NamedCal(\"tyo|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"tyo,nyc,ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=False,\n        ),\n        \"nzdusd\": dict(\n            pair=\"nzdusd\",\n            calendar=NamedCal(\"wlg|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"wlg,nyc,ldn\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        # The following are not defined in the ISDA MTM Matrix\n        \"audnzd\": dict(\n            pair=\"audnzd\",\n            calendar=NamedCal(\"wlg,syd|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"wlg,syd,ldn,nyc\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=True,\n        ),\n        \"usdinr\": dict(\n            pair=\"usdinr\",\n            calendar=NamedCal(\"mum|fed\"),\n            settle=Adjuster.BusDaysLagSettle(2),\n            isda_mtm_calendar=NamedCal(\"mum\"),\n            isda_mtm_settle=Adjuster.BusDaysLagSettle(-2),\n            allow_cross=False,\n        ),\n    },\n    irs_series={\n        \"eur_irs6\": dict(\n            currency=\"eur\",\n            settle=Adjuster.BusDaysLagSettle(2),\n            calendar=\"tgt\",\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.ThirtyE360,\n            leg2_convention=Convention.Act360,\n            frequency=Frequency.Months(12, None),\n            leg2_frequency=Frequency.Months(6, None),\n            leg2_fixing_method=\"ibor(2)\",\n            eom=False,\n            payment_lag=Adjuster.BusDaysLagSettle(0),\n        ),\n        \"eur_irs3\": dict(\n            currency=\"eur\",\n            settle=Adjuster.BusDaysLagSettle(2),\n            calendar=\"tgt\",\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.ThirtyE360,\n            leg2_convention=Convention.Act360,\n            frequency=Frequency.Months(12, None),\n            leg2_frequency=Frequency.Months(3, None),\n            leg2_fixing_method=\"ibor(2)\",\n            eom=False,\n            payment_lag=Adjuster.BusDaysLagSettle(0),\n        ),\n        \"usd_irs\": dict(\n            currency=\"usd\",\n            settle=Adjuster.BusDaysLagSettle(2),\n            calendar=\"nyc\",\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act360,\n            frequency=Frequency.Months(12, None),\n            leg2_fixing_method=\"rfr_payment_delay\",\n            eom=False,\n            payment_lag=Adjuster.BusDaysLagSettle(2),\n        ),\n    },\n    float_series={\n        \"usd_ibor\": dict(\n            lag=2,\n            calendar=NamedCal(\"nyc\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act360,\n            eom=False,\n            tenors=[\"1B\", \"1W\", \"1M\", \"2M\", \"3M\", \"6M\", \"12M\"],\n        ),\n        \"usd_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"nyc\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act360,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"gbp_ibor\": dict(\n            lag=0,\n            calendar=NamedCal(\"ldn\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act365F,\n            eom=True,\n            tenors=[\"1B\", \"1W\", \"1M\", \"2M\", \"3M\", \"6M\", \"12M\"],\n        ),\n        \"gbp_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"ldn\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act365F,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"sek_ibor\": dict(\n            lag=2,\n            calendar=NamedCal(\"ldn\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act360,\n            eom=True,\n            tenors=[\"2B\", \"1W\", \"1M\", \"2M\", \"3M\", \"6M\"],\n        ),\n        \"sek_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"ldn\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act360,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"eur_ibor\": dict(\n            lag=2,\n            calendar=NamedCal(\"tgt\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act360,\n            eom=False,\n            tenors=[\"1W\", \"1M\", \"3M\", \"6M\", \"12M\"],\n        ),\n        \"eur_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"tgt\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act360,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"nok_ibor\": dict(\n            lag=2,\n            calendar=NamedCal(\"osl\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act360,\n            eom=False,\n            tenors=[\"1W\", \"1M\", \"2M\", \"3M\", \"6M\"],\n        ),\n        \"nok_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"osl\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act365F,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"chf_ibor\": dict(\n            lag=2,\n            calendar=NamedCal(\"zur\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act360,\n            eom=False,\n            tenors=[\"1B\", \"1W\", \"1M\", \"2M\", \"3M\", \"6M\", \"12M\"],\n        ),\n        \"chf_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"zur\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act360,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"cad_ibor\": dict(\n            lag=2,\n            calendar=NamedCal(\"tro\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act365F,\n            eom=False,\n            tenors=[\"1M\", \"2M\", \"3M\", \"6M\", \"12M\"],\n        ),\n        \"cad_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"tro\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act365F,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"jpy_ibor\": dict(\n            lag=2,\n            calendar=NamedCal(\"tyo\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act365F,\n            eom=False,\n            tenors=[\"1M\", \"3M\", \"6M\"],\n        ),\n        \"jpy_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"tyo\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act365F,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"aud_ibor\": dict(\n            lag=0,\n            calendar=NamedCal(\"syd\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act365F,\n            eom=True,\n            tenors=[\"1M\", \"2M\", \"3M\", \"4M\", \"5M\", \"6M\"],\n        ),\n        \"aud_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"syd\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act365F,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n        \"nzd_ibor\": dict(\n            lag=0,\n            calendar=NamedCal(\"wlg\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            convention=Convention.Act365F,\n            eom=True,\n            tenors=[\"1M\", \"3M\", \"6M\"],\n        ),\n        \"nzd_rfr\": dict(\n            lag=0,\n            calendar=NamedCal(\"wlg\"),\n            modifier=Adjuster.Following(),\n            convention=Convention.Act365F,\n            eom=False,\n            tenors=[\"1b\"],\n        ),\n    },\n)\n\n\nclass Defaults:\n    \"\"\"\n    The *defaults* object used by initialising objects. Values are printed below:\n\n    .. ipython:: python\n\n       from rateslib import defaults\n       print(defaults.print())\n\n    \"\"\"\n\n    _instance = None\n\n    stub: str\n    stub_length: str\n    eval_mode: str\n    modifier: str\n    calendars: dict[str, CalTypes]\n    eom: bool\n    eom_fx: bool\n\n    metric: dict[str, str]\n    convention: str\n    notional: float\n    index_lag: int\n    index_lag_curve: int\n    index_method: str\n    payment_lag: int\n    payment_lag_exchange: int\n    payment_lag_specific: dict[str, int]\n    fixing_method: str\n    spread_compound_method: str\n    index_base_type: str\n    base_currency: str\n    fx_delivery_lag: int\n    fx_delta_type: str\n    fx_option_metric: str\n    ir_option_metric: str\n    ir_option_settlement: str\n    cds_premium_accrued: bool\n    cds_recovery_rate: float\n    cds_protection_discretization: int\n\n    interpolation: dict[str, str]\n    endpoints: str\n    multi_csa_steps: list[int]\n    multi_csa_min_step: int\n    multi_csa_max_step: int\n    curve_caching: bool\n    curve_caching_max: int\n\n    tag: str\n    algorithm: str\n    curve_not_in_solver: str\n    ini_lambda: tuple[int, float, float]\n\n    calc_mode: dict[str, str]\n    settle: int\n    ex_div: int\n    calc_mode_futures: str\n\n    pool: int\n    no_fx_fixings_for_xcs: str\n    headers: dict[str, str]\n    _global_ad_order: int\n    oaspread_func_tol: float\n    oaspread_conv_tol: float\n    # Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n    # Commercial use of this code, and/or copying and redistribution is prohibited.\n    # Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n    spec: dict[str, dict[str, Any]]\n    fx_index: dict[str, Any]\n    irs_series: dict[str, Any]\n    float_series: dict[str, Any]\n\n    def __new__(cls) -> Defaults:\n        if cls._instance is None:\n            # Singleton pattern creates only one instance: TODO (low) might not be thread safe\n            cls._instance = super(Defaults, cls).__new__(cls)  # noqa: UP008\n\n            for k, v in DEFAULTS.items():\n                setattr(cls._instance, k, deepcopy(v))\n\n        return cls._instance\n\n    def reset_defaults(self) -> None:\n        \"\"\"\n        Revert defaults back to their initialisation status.\n\n        Examples\n        --------\n        .. ipython:: python\n\n           from rateslib import defaults\n           defaults.reset_defaults()\n        \"\"\"\n        attrs = [\n            v\n            for v in dir(self)\n            if \"__\" not in v and not callable(getattr(self, v)) and v != \"_instance\"\n        ]\n        for attr in attrs:\n            delattr(self, attr)\n\n        for k, v in DEFAULTS.items():\n            setattr(self, k, deepcopy(v))\n\n    def print(self) -> str:\n        \"\"\"\n        Return a string representation of the current values in the defaults object.\n        \"\"\"\n\n        def _t_n(v: str) -> str:  # teb-newline\n            return f\"\\t{v}\\n\"\n\n        _: str = f\"\"\"\\\nScheduling:\\n\n{\n            \"\".join(\n                [\n                    _t_n(f\"{attribute}: {getattr(self, attribute)}\")\n                    for attribute in [\n                        \"stub\",\n                        \"stub_length\",\n                        \"modifier\",\n                        \"eom\",\n                        \"eom_fx\",\n                        \"eval_mode\",\n                    ]\n                ]\n            )\n        }\nInstruments:\\n\n{\n            \"\".join(\n                [\n                    _t_n(f\"{attribute}: {getattr(self, attribute)}\")\n                    for attribute in [\n                        \"convention\",\n                        \"payment_lag\",\n                        \"payment_lag_exchange\",\n                        \"payment_lag_specific\",\n                        \"notional\",\n                        \"fixing_method\",\n                        \"spread_compound_method\",\n                        \"base_currency\",\n                        \"fx_delivery_lag\",\n                        \"fx_delta_type\",\n                        \"fx_option_metric\",\n                        \"cds_premium_accrued\",\n                        \"cds_recovery_rate\",\n                        \"cds_protection_discretization\",\n                    ]\n                ]\n            )\n        }\nCurves:\\n\n{\n            \"\".join(\n                [\n                    _t_n(f\"{attribute}: {getattr(self, attribute)}\")\n                    for attribute in [\n                        \"interpolation\",\n                        \"endpoints\",\n                        \"multi_csa_steps\",\n                        \"curve_caching\",\n                    ]\n                ]\n            )\n        }\nSolver:\\n\n{\n            \"\".join(\n                [\n                    _t_n(f\"{attribute}: {getattr(self, attribute)}\")\n                    for attribute in [\n                        \"algorithm\",\n                        \"tag\",\n                        \"curve_not_in_solver\",\n                    ]\n                ]\n            )\n        }\nMiscellaneous:\\n\n{\n            \"\".join(\n                [\n                    _t_n(f\"{attribute}: {getattr(self, attribute)}\")\n                    for attribute in [\n                        \"headers\",\n                        \"no_fx_fixings_for_xcs\",\n                        \"pool\",\n                    ]\n                ]\n            )\n        }\n\"\"\"  # noqa: W291\n        return _\n\n\ndef plot(\n    x: list[list[Any]], y: list[list[Any]], labels: list[str] | NoInput = NoInput(0)\n) -> PlotOutput:\n    labels = _drb([], labels)\n    fig, ax = plt.subplots(1, 1)\n    lines = []\n    for _x, _y in zip(x, y, strict=True):\n        (line,) = ax.plot(_x, _y)\n        lines.append(line)\n    if not isinstance(labels, NoInput) and len(labels) == len(lines):\n        ax.legend(lines, labels)\n\n    ax.grid(True)\n\n    if isinstance(x[0][0], datetime):\n        years = mdates.YearLocator()  # type: ignore[no-untyped-call]\n        months = mdates.MonthLocator()  # type: ignore[no-untyped-call]\n        yearsFmt = mdates.DateFormatter(\"%Y\")  # type: ignore[no-untyped-call]\n        ax.xaxis.set_major_locator(years)\n        ax.xaxis.set_major_formatter(yearsFmt)\n        ax.xaxis.set_minor_locator(months)\n        fig.autofmt_xdate()\n    return fig, ax, lines\n\n\ndef plot3d(\n    x: list[Any], y: list[Any], z: np.ndarray[tuple[int, int], np.dtype[np.float64]]\n) -> tuple[plt.Figure, plt.Axes, None]:  # type: ignore[name-defined]\n    import matplotlib.pyplot as plt\n    from matplotlib import cm\n\n    # import matplotlib.dates as mdates  # type: ignore[import]\n\n    fig, ax = plt.subplots(subplot_kw={\"projection\": \"3d\"})\n\n    X, Y = np.meshgrid(x, y)\n    # Plot the surface.\n    ax.plot_surface(X, Y, z, cmap=cm.coolwarm, linewidth=0, antialiased=False)  # type: ignore[attr-defined]\n    return fig, ax, None\n\n\ndef _make_py_json(json: str, class_name: str) -> str:\n    \"\"\"Modifies the output JSON output for Rust structs wrapped by Python classes.\"\"\"\n    return '{\"PyWrapped\":' + json + \"}\"\n\n\n__all__ = [\"Defaults\"]\n"
  },
  {
    "path": "python/rateslib/dual/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nfrom rateslib.dual.ift import ift_1dim\nfrom rateslib.dual.newton import newton_1dim, newton_ndim\nfrom rateslib.dual.quadratic import quadratic_eqn\nfrom rateslib.dual.utils import (\n    dual_exp,\n    dual_inv_norm_cdf,\n    dual_log,\n    dual_norm_cdf,\n    dual_norm_pdf,\n    dual_solve,\n    gradient,\n    set_order,\n    set_order_convert,\n)\nfrom rateslib.dual.variable import Variable\nfrom rateslib.rs import ADOrder, Dual, Dual2\n\nDual.__doc__ = \"\"\"\nDual number data type to perform first derivative automatic differentiation.\n\nParameters\n----------\nreal : float\n   The real coefficient of the dual number: its value.\nvars : tuple/list of str\n   The labels of the variables for which to record derivatives. If empty,\n   the dual number represents a constant, equivalent to a float.\ndual : list of float\n   First derivative information contained as coefficient of linear manifold.\n   Defaults to an array of ones the length of ``vars`` if empty.\n\nSee Also\n---------\n\n.. seealso::\n   :class:`~rateslib.dual.Dual2`: Dual number data type to perform second derivative automatic differentiation.\n\nExamples\n---------\n.. ipython:: python\n   :suppress:\n\n   from rateslib.dual import Dual, gradient\n\n.. ipython:: python\n\n   def func(x, y):\n       return 5 * x**2 + 10 * y**3\n\n   x = Dual(1.0, [\"x\"], [])\n   y = Dual(1.0, [\"y\"], [])\n   gradient(func(x,y), [\"x\", \"y\"])\n\n\"\"\"  # noqa: E501\n\nDual2.__doc__ = \"\"\"\nDual number data type to perform second derivative automatic differentiation.\n\nParameters\n-----------\nreal : float\n   The real coefficient of the dual number: its value.\nvars : tuple/list of str\n   The labels of the variables for which to record derivatives. If empty,\n   the dual number represents a constant, equivalent to a float.\ndual : list of float\n   First derivative information contained as coefficient of linear manifold.\n   Defaults to an array of ones the length of ``vars`` if empty.\ndual2 : list of float\n   Second derivative information contained as coefficients of quadratic manifold.\n   Defaults to a 2d array of zeros the size of ``vars`` if empty.\n   These values represent a 2d array but must be given as a 1d list of values in row-major order,\n   which is reshaped.\n\nSee Also\n--------\n\n.. seealso::\n   :class:`~rateslib.dual.Dual`: Dual number data type to perform first derivative automatic differentiation.\n\nExamples\n---------\n\n.. ipython:: python\n\n   from rateslib.dual import Dual2, gradient\n   def func(x, y):\n       return 5 * x**2 + 10 * y**3\n\n   x = Dual2(1.0, [\"x\"], [], [])\n   y = Dual2(1.0, [\"y\"], [], [])\n   gradient(func(x,y), [\"x\", \"y\"], order=2)\n\n\"\"\"  # noqa: E501\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n__all__ = [\n    \"ADOrder\",\n    \"Dual\",\n    \"Dual2\",\n    \"Variable\",\n    \"dual_log\",\n    \"dual_exp\",\n    \"dual_solve\",\n    \"dual_norm_pdf\",\n    \"dual_norm_cdf\",\n    \"dual_inv_norm_cdf\",\n    \"gradient\",\n    \"set_order_convert\",\n    \"set_order\",\n    \"newton_ndim\",\n    \"newton_1dim\",\n    \"ift_1dim\",\n    \"quadratic_eqn\",\n]\n"
  },
  {
    "path": "python/rateslib/dual/ift.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nfrom collections.abc import Callable\nfrom time import time\nfrom typing import TYPE_CHECKING, Any, ParamSpec\n\nimport numpy as np\n\nfrom rateslib.dual.newton import _dual_float_or_unchanged, _solver_result\nfrom rateslib.dual.utils import _dual_float, _get_order_of, gradient, set_order\nfrom rateslib.rs import Dual, Dual2\n\nif TYPE_CHECKING:\n    from rateslib.local_types import DualTypes, Number\n\nP = ParamSpec(\"P\")\n\n\ndef ift_1dim(\n    s: Callable[[DualTypes], DualTypes],\n    s_tgt: DualTypes,\n    h: Callable[P, tuple[float, float, int, tuple[Any, ...]]] | str,\n    ini_h_args: tuple[Any, ...] = (),\n    max_iter: int = 50,\n    func_tol: float = 1e-14,\n    conv_tol: float = 1e-9,\n    raise_on_fail: bool = True,\n) -> dict[str, Any]:\n    r\"\"\"\n    A **one** dimensional root solver using the inverse function theorem to capture\n    AD sensitivities.\n\n    This method can be used to find the value of :math:`g(s)` for a given :math:`s_{tgt}`, where:\n\n    - :math:`g(s)` is **not** analytical and hence requires iterations to determine.\n    - :math:`s(g)` is a known analytical inverse of :math:`g`.\n\n    This problem is framed by finding the root of :math:`f(g) = s(g) - s_{tgt} = 0`.\n\n    Parameters\n    ----------\n    s: Callable[DualTypes, DualTypes]\n        The known inverse function of *g* such that *g(s(x))=x*. Of the signature: `s(x)`.\n    s_tgt: DualTypes\n        The value of *s* for which *g* is to be found.\n    h: Callable, string\n        The iterative function to use to determine the solution g. See notes.\n    ini_h_args:\n        Initial arguments passed to the iterative function, ``h``.\n    max_iter: int > 1\n        Number of maximum iterations to perform.\n    func_tol: float, optional\n        The absolute function tolerance to reach before exiting.\n    conv_tol: float, optional\n        The convergence tolerance for subsequent iterations of *g*, passed to ``h`` to implement.\n    raise_on_fail: bool, optional\n        If *False* will return a solver result dict with state and message indicating failure.\n\n    Notes\n    ------\n    **Available iterative methods**\n\n    The iteration algorithm to find the root can be given directly as a callable ``h`` or\n    can be specified from one of the below pre-implemented algorithms:\n\n    - **'bisection'**: repeatedly halves an interval and selects the interval in which the root\n      falls, until convergence. Requires ``ini_h_args`` to be a tuple of two floats defining\n      the interval whose *f* values have opposite signs.\n    - **'modified_dekker'**: enhances the *'bisection'* method to include a *'secant'* step when\n      it produces a better iterate. Requires ``ini_h_args`` to be a tuple of two floats defining\n      the interval whose *f* values have opposite signs. For info see\n      :download:`Halving Interval for Dekker<_static/modified-dekker.pdf>`.\n    - **'modified_brent'**: enhances the *'modified_dekker'* method to also permit\n      inverse quadratic interpolation within an iteration.\n      Requires ``ini_h_args`` to be a tuple of two floats defining\n      the interval whose *f* values have opposite signs. For info see\n      :download:`Halving Interval for Dekker<_static/modified-dekker.pdf>`.\n    - **'ytm_quadratic'**: Requires ``ini_h_args`` to be a tuple of three floats defining the\n      interval and interior point. This algorithm utilises successive parabolic approximations\n      for *g(f)* and is specifically tuned for solving bond yield-to-maturity efficiently.\n\n    **What is ``h``**\n\n    *h()* is a function that is used to perform iterations to determine *g* from *s*. If\n    a custom function is provided, it must conform to the following signature:\n\n    `h(s, s_target, conv_tol, *h_args) -> (g_i, f_i, state, *h_args_i)`\n\n    The input parameters provide:\n\n    - *s*: The inverse function of *g* such that *g(s(x))=x*.\n    - *s_target*: The target value of *s* for which *g* is to be found.\n    - *conv_tol*: The convergence tolerance which is measured internally by *h*.\n    - *h_args*: Additional arguments passed to *h* which facilitate its internal operation.\n\n    The output parameters provide:\n\n    - *g_i*: The value of *g* at the current iteration, representative of :math:`g(s_i)`.\n    - *f_i*: A measure of error in the iteration\n    - *state*: A state flag return from the iteration as indicator to the controlling process.\n    - *h_args_i*: Arguments passed to the next iteration of *h*.\n\n    ``state`` flag returns are:\n\n    - -2: The algorithm failed for an internal reason.\n    - 1: `conv_tol` has been satisfied and the solution is considered to have converged.\n    - None: The algorithm has not yet converged and will continue.\n\n    **AD Implementation**\n\n    The AD order of the solution is determined by the AD order of the ``s_tgt`` input.\n\n    Examples\n    --------\n    The most prevalent use of this technique in *rateslib* is to solve bond yield-to-maturity from\n    a given price. Suppose we develop a formula, *s(g)* which determines the price (*s*) of a\n    2y bond with 3% annual coupon given its ytm (*g*):\n\n    .. math::\n\n       s(g) = \\frac{3}{1+g/100} + \\frac{103}{(1+g/100)^2}\n\n    Then we use the *bisection* method to discover the ytm given a price of 101:\n\n    .. ipython:: python\n\n       from rateslib.dual import ift_1dim, Dual\n\n       def s(g):\n            return 3 / (1 + g / 100) + 103 / (1 + g / 100) ** 2\n\n       # solve for a bond price of 101 with lower and upper ytm bounds of 2.0 and 3.0.\n       result = ift_1dim(s, Dual(101.0, [\"price\"], []), \"bisection\", (2.0, 3.0))\n       print(result)\n\n    For **traditional root solving** the function :math:`s(g)` is given with the :math:`s_{tgt}`\n    set to zero, therefore the returned *g* will be the root of *s(g)*.\n\n    .. ipython:: python\n\n       def s(g):\n            return g ** 2 - 2\n\n       result = ift_1dim(s, 0.0, \"modified_brent\", (-2.0, 0.0))\n       print(result)\n\n    \"\"\"\n    if isinstance(h, str):\n        h_: Callable[P, tuple[float, float, int, tuple[Any, ...]]] = ift_map[h]\n    else:\n        h_ = h\n\n    t0 = time()\n    i = 1\n\n    float_ini_hargs = tuple(_dual_float_or_unchanged(_) for _ in ini_h_args)\n    s0_: float = _dual_float(s_tgt)\n\n    g0, f0, state, *hargs = h_(s, s0_, conv_tol, *float_ini_hargs)  # type: ignore[call-arg, arg-type]\n    while i < max_iter:\n        if state == 1:\n            g1 = g0\n            break\n        elif state == -2:\n            if raise_on_fail:\n                raise ValueError(\n                    \"The internal iterative function `h` has reported a iteration failure.\"\n                )\n            else:\n                return _solver_result(-2, i, g0, time() - t0, log=True, algo=\"ift_1dim\")\n        if abs(f0) < func_tol:\n            state = 2\n            g1 = g0\n            break\n        g1, f1, state, *hargs = h_(s, s0_, conv_tol, *hargs)  # type: ignore[call-arg, arg-type]\n        i += 1\n        g0 = g1\n        f0 = f1\n\n    if i == max_iter:\n        if raise_on_fail:\n            raise ValueError(\n                f\"`max_iter`: {max_iter} exceeded in 'ift_1dim' algorithm'.\\n\"\n                f\"Last iteration values:\\nf0: {f0}\\nf1: {f1}\\ng0: {g0}\"\n            )\n        else:\n            return _solver_result(-1, i, g1, time() - t0, log=True, algo=\"ift_1dim\")\n\n    # # IFT to preserve AD  # TODO: this uses `set_order` to handle Variable, maybe `_to_number`?\n    ad_order = _get_order_of(s_tgt)\n    if ad_order == 0:\n        # return g1 as is.\n        ret: Number = g1\n    elif ad_order == 1:\n        s_: Dual | Dual2 = s(Dual(g1, [\"x\"], []))  # type: ignore[assignment]\n        ds_dx = gradient(s_, vars=[\"x\"])[0]\n        ret = Dual.vars_from(set_order(s_tgt, 1), g1, s_tgt.vars, 1.0 / ds_dx * s_tgt.dual)  # type: ignore[union-attr, arg-type]\n    else:  # ad_order == 2\n        s_ = s(Dual2(g1, [\"x\"], [], []))  # type: ignore[assignment]\n        ds_dx = gradient(s_, vars=[\"x\"])[0]\n        d2s_dx2 = gradient(s_, vars=[\"x\"], order=2)[0][0]\n        ret = Dual2.vars_from(\n            set_order(s_tgt, 2),  # type: ignore[arg-type]\n            g1,\n            s_tgt.vars,  # type: ignore[union-attr, arg-type]\n            1.0 / ds_dx * s_tgt.dual,  # type: ignore[union-attr]\n            np.ravel(\n                1.0 / ds_dx * s_tgt.dual2  # type: ignore[union-attr]\n                - 0.5 * d2s_dx2 * ds_dx**-3 * np.outer(s_tgt.dual, s_tgt.dual)  # type: ignore[union-attr]\n            ),\n        )\n\n    return _solver_result(state, i, ret, time() - t0, log=False, algo=\"ift_1dim\")\n\n\ndef _bisection(\n    s: Callable[[DualTypes], DualTypes],\n    s_tgt: float,\n    conv_tol: float,\n    g_lower: float,\n    g_upper: float,\n    s_lower: float | None = None,\n    s_upper: float | None = None,\n) -> tuple[float, float, int | None, float, float, float]:\n    \"\"\"\n    Perform an iteration by bisection.\n\n    The bounds `g` must yield values of `s` that are either side of the target value.\n\n    The interval will be bisected and the side kept that envelopes the target value.\n\n    All calculated values are returned to prevent re-calculation in the next iteration.\n\n    The `ini_hargs` needed for this method are only (g_lower, g_upper).\n\n    Returns\n    -------\n    g_i, f_i, state, *h_args_i\n    \"\"\"\n    if s_lower is None:\n        s_lower = _dual_float(s(g_lower))\n    if s_upper is None:\n        s_upper = _dual_float(s(g_upper))\n\n    f_lower = s_lower - s_tgt\n    f_upper = s_upper - s_tgt\n\n    if _dual_float(f_lower * f_upper) > 0:\n        # return a failed state because boundaries must be opposite sign to imply root.\n        return 0, 0, -2, 0, 0, 0\n\n    g_mid = (g_lower + g_upper) / 2.0\n    s_mid = _dual_float(s(g_mid))\n    f_mid = s_mid - s_tgt\n\n    if abs(g_mid - g_lower) < conv_tol:\n        state: int | None = 1\n    else:\n        state = None\n\n    if _dual_float(f_lower * f_mid) > 0:\n        # then lower and mid have same sign so must return upper interval\n        if abs(f_mid) < abs(f_upper):\n            return g_mid, f_mid, state, g_mid, g_upper, s_mid, s_upper  # type: ignore[return-value]\n        else:\n            return g_upper, f_upper, state, g_mid, g_upper, s_mid, s_upper  # type: ignore[return-value]\n    else:\n        # then lower and mid have opposite sign so return the lower interval\n        if abs(f_mid) < abs(f_lower):\n            # g_mid is closest to the target value with g_lower being the better side\n            return g_mid, f_mid, state, g_lower, g_mid, s_lower, s_mid  # type: ignore[return-value]\n        else:\n            return g_lower, f_lower, state, g_lower, g_mid, s_lower, s_mid  # type: ignore[return-value]\n\n\ndef _root_f(x: float, s: Callable[[DualTypes], DualTypes], s_tgt: float) -> float:\n    \"\"\"Root reformulation for Dekker's algorithm\"\"\"\n    return s(x) - s_tgt  # type: ignore[return-value]\n\n\ndef _dekker(\n    s: Callable[[DualTypes], DualTypes],\n    s_tgt: float,\n    conv_tol: float,\n    a_k: float,\n    b_k: float,\n    b_k_m1: float | None = None,\n    cached_f_a_k: float | None = None,\n    cached_f_b_k: float | None = None,\n    cached_f_b_k_m1: float | None = None,\n) -> tuple[float, float, int | None, float, float, float, float, float, float]:\n    \"\"\"\n    Alternative root solver.\n    See docs/source/_static/modified-dekker.pdf for details.\n\n    Cached values allow value transmission from one function to the next with many efficiencies.\n    \"\"\"\n    if b_k_m1 is None:  # (which is read b k minus 1)\n        # b_k_m1 is None only once. This indicates the first iteration so no caches are present.\n        f_a_k = _dual_float(_root_f(a_k, s, s_tgt))\n        f_b_k = _dual_float(_root_f(b_k, s, s_tgt))\n        if abs(f_a_k) < abs(f_b_k):\n            # switch to make b_k the 'closest' solution\n            f_a_k, f_b_k = f_b_k, f_a_k\n            a_k, b_k = b_k, a_k\n\n        # in the first iteration set b_k_m1 = a_k\n        b_k_m1 = a_k\n        f_b_k_m1 = f_a_k\n    else:\n        # subsequent iterations will contain all cached values\n        f_a_k = cached_f_a_k  # type: ignore[assignment]\n        f_b_k = cached_f_b_k  # type: ignore[assignment]\n        f_b_k_m1 = cached_f_b_k_m1  # type: ignore[assignment]\n\n    if abs(a_k - b_k) < conv_tol:\n        # the interval is within tolerance so report converged, b_k should be the 'best' solution.\n        return b_k, f_b_k, 1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0\n\n    m = (a_k + b_k) / 2.0  # midpoint\n    # secant\n    if abs(f_b_k - f_b_k_m1) < 1e-16:\n        # secant is divide by zero error\n        q = max(b_k, m) + 1.0\n    else:\n        q = b_k - f_b_k * (b_k - b_k_m1) / (f_b_k - f_b_k_m1)\n\n    if q >= min(b_k, m) and q <= max(b_k, m):\n        b_k_p1 = q  # accept the secant as an estimated better iterate\n    else:\n        b_k_p1 = m  # discard the secant as it is outside of the window\n    f_b_k_p1 = _dual_float(_root_f(b_k_p1, s, s_tgt))\n\n    # determine a_k_p1\n    a_k_p1 = a_k\n    f_a_k_p1 = f_a_k\n    if f_a_k * f_b_k_p1 > 0:\n        a_k_p1 = b_k\n        f_a_k_p1 = f_b_k\n    elif q >= min(b_k, m) and q <= max(b_k, m):\n        f_m = _dual_float(_root_f(m, s, s_tgt))\n        if f_m * f_b_k_p1 < 0:\n            a_k_p1 = m\n            f_a_k_p1 = f_m\n\n    if abs(f_a_k_p1) < abs(f_b_k_p1):\n        # switch to make b_k the 'best' solution\n        f_a_k_p1, f_b_k_p1 = f_b_k_p1, f_a_k_p1\n        a_k_p1, b_k_p1 = b_k_p1, a_k_p1\n\n        if abs(f_b_k_p1 - f_b_k) < 1e-15:\n            # also switch the existing values to avoid secant divide by zero errros\n            f_b_k, b_k = f_a_k, a_k\n            # f_a_k, f_b_k = f_b_k, f_a_k\n            # a_k, b_k = b_k, a_k\n\n    return b_k_p1, f_b_k_p1, None, a_k_p1, b_k_p1, b_k, f_a_k_p1, f_b_k_p1, f_b_k\n\n\ndef _brent(\n    s: Callable[[DualTypes], DualTypes],\n    s_tgt: float,\n    conv_tol: float,\n    a_k: float,\n    b_k: float,\n    b_k_m1: float | None = None,\n    cached_f_a_k: float | None = None,\n    cached_f_b_k: float | None = None,\n    cached_f_b_k_m1: float | None = None,\n) -> tuple[float, float, int | None, float, float, float, float, float, float]:\n    \"\"\"\n    Alternative root solver.\n    See docs/source/_static/modified-dekker.pdf for details.\n\n    Cached values allow value transmission from one function to the next with many efficiencies.\n    \"\"\"\n    if b_k_m1 is None:  # (which is read b k minus 1)\n        # b_k_m1 is None only once. This indicates the first iteration so no caches are present.\n        f_a_k = _dual_float(_root_f(a_k, s, s_tgt))\n        f_b_k = _dual_float(_root_f(b_k, s, s_tgt))\n        if abs(f_a_k) < abs(f_b_k):\n            # switch to make b_k the 'closest' solution\n            f_a_k, f_b_k = f_b_k, f_a_k\n            a_k, b_k = b_k, a_k\n\n        # in the first iteration set b_k_m1 = a_k\n        b_k_m1 = a_k\n        f_b_k_m1 = f_a_k\n    else:\n        # subsequent iterations will contain all cached values\n        f_a_k = cached_f_a_k  # type: ignore[assignment]\n        f_b_k = cached_f_b_k  # type: ignore[assignment]\n        f_b_k_m1 = cached_f_b_k_m1  # type: ignore[assignment]\n\n    if abs(a_k - b_k) < conv_tol:\n        return b_k, f_b_k, 1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0\n\n    m = (a_k + b_k) / 2.0\n    # provisional values for the next iteration\n    if f_a_k != f_b_k and f_a_k != f_b_k_m1 and f_b_k != f_b_k_m1:\n        # then all three function values are distinct: use inverse quadratic interpolation\n        fba = f_b_k / f_a_k\n        fbbm1 = f_b_k / f_b_k_m1\n        fabm1 = f_a_k / f_b_k_m1\n        numerator = fba * ((1.0 - fbbm1) * (a_k - b_k) + fabm1 * (fbbm1 - fabm1) * (b_k_m1 - b_k))\n        denominator = (fbbm1 - 1.0) * (fba - 1.0) * (fabm1 - 1.0)\n        q = b_k + numerator / denominator\n    else:\n        # use secant\n        if abs(f_b_k - f_b_k_m1) < 1e-16:\n            # secant is div by zero error this ensures bisection is chosen\n            q = min(b_k, (3.0 * a_k + b_k) / 4.0) - 1.0\n        else:\n            q = b_k - f_b_k * (b_k - b_k_m1) / (f_b_k - f_b_k_m1)\n    w = (min(b_k, (3.0 * a_k + b_k) / 4.0), max(b_k, (3.0 * a_k + b_k) / 4.0))\n    if q <= w[0] or q >= w[1]:\n        q = m\n\n    b_k_p1 = q\n    f_b_k_p1 = _dual_float(_root_f(b_k_p1, s, s_tgt))\n\n    a_k_p1 = a_k\n    f_a_k_p1 = f_a_k\n    if float(f_a_k * f_b_k_p1) > 0:\n        a_k_p1 = b_k\n        f_a_k_p1 = f_b_k\n    else:\n        f_m = _dual_float(_root_f(m, s, s_tgt))\n        if float(f_m * f_b_k_p1) < 0:\n            a_k_p1 = m\n            f_a_k_p1 = f_m\n\n    if abs(f_a_k_p1) < abs(f_b_k_p1):\n        # switch to make b_k the 'best' solution\n        f_a_k_p1, f_b_k_p1 = f_b_k_p1, f_a_k_p1\n        a_k_p1, b_k_p1 = b_k_p1, a_k_p1\n        # # also switch the existing values\n        # f_a_k, f_b_k = f_b_k, f_a_k\n        # a_k, b_k = b_k, a_k\n\n    return b_k_p1, f_b_k_p1, None, a_k_p1, b_k_p1, b_k, f_a_k_p1, f_b_k_p1, f_b_k\n\n\ndef _ytm_quadratic(\n    s: Callable[[DualTypes], DualTypes],\n    s_tgt: float,\n    conv_tol: float,\n    g0: float,\n    g1: float,\n    g2: float,\n    cached_f0: float | None = None,\n    cached_f1: float | None = None,\n    cached_f2: float | None = None,\n) -> tuple[float, float, int | None, float, float, float, float | None, float | None, float | None]:\n    \"\"\"\n    Alternative root solver.\n    See docs/source/_static/modified-dekker.pdf for details.\n\n    Cached values allow value transmission from one function to the next with many efficiencies.\n\n    Returns\n    -------\n    g_i, f_i=s_i-s_tgt, state, *h_args_i = (g0, g1, g2, f0, f1, f2)\n    \"\"\"\n\n    # Load cached values\n    f0: float = cached_f0 if cached_f0 is not None else _root_f(g0, s, s_tgt)\n    f1: float = cached_f1 if cached_f1 is not None else _root_f(g1, s, s_tgt)\n    f2: float = cached_f2 if cached_f2 is not None else _root_f(g2, s, s_tgt)\n\n    # Test interval: if all values are same sign translate the interval.\n    if f0 < 0 and f1 < 0 and f2 < 0:\n        # then g(s*) must be\n        g0_ = g0 - (g2 - g0)\n        g1_ = g1 - (g2 - g1)\n        g2_ = g0\n        return g1_, 1e9, None, g0_, g1_, g2_, None, None, None\n    elif f0 > 0 and f1 > 0 and f2 > 0:\n        g0_ = g2\n        g1_ = g1 + (g2 - g0)\n        g2_ = g2 + 2 * (g2 - g0)\n        return g1_, 1e9, None, g0_, g1_, g2_, None, None, None\n\n    # Solve g_new via quadratic approximation\n\n    # # Linear algebra solution\n    # _b = np.array([g0, g1, g2])[:, None]\n    # _A = np.array([[f0**2, f0, 1], [f1**2, f1, 1], [f2**2, f2, 1]])\n    # x = np.linalg.solve(_A, _b)\n    # g_new = x[2, 0]\n\n    # Analytical solution (via Gaussian elimination)\n    f012, f022, f01, f02, g01, g02 = (\n        f0**2 - f1**2,\n        f0**2 - f2**2,\n        f0 - f1,\n        f0 - f2,\n        g0 - g1,\n        g0 - g2,\n    )\n    x0 = (g01 * f02 - g02 * f01) / (f012 * f02 - f022 * f01)\n    x1 = (g01 - x0 * f012) / f01\n    x2 = g0 - x1 * f0 - x0 * f0**2\n    g_new = x2\n\n    # # Lagrange interpolation formula is a valid alternative solution\n    # g_new_compare = g0 * f1 * f2 / ((f0 - f1) * (f0 - f2))\n    # g_new_compare += g1 * f0 * f2 / ((f1 - f0) * (f1 - f2))\n    # g_new_compare += g2 * f0 * f1 / ((f2 - f0) * (f2 - f1))\n    # assert abs(g_new_compare - g_new) < 1e-8\n\n    if g_new < g0 or g_new > g2:\n        # if the quadratic approximation is outside the interval then use a bisection method\n        if f0 * f1 < 0:\n            # bisect in the left hand side\n            g_new = g0 + (g1 - g0) * f0 / (f0 - f1)\n        else:\n            # bisect in the right hand side\n            g_new = g1 - (g2 - g1) * f1 / (f2 - f1)\n\n    f_new = _root_f(g_new, s, s_tgt)\n    for g_ in [g0, g1, g2]:\n        if abs(g_ - g_new) < conv_tol:\n            return g_new, f_new, 1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0\n\n    if g0 < g_new and g_new < g1:\n        return g_new, f_new, None, g0, g_new, g1, f0, f_new, f1\n    else:  # g1 < g_new and g_new < g2:\n        return g_new, f_new, None, g1, g_new, g2, f1, f_new, f2\n    # else:\n    #     raise RuntimeError(\"Unexpected interval: this line should never be reached.\")\n\n\ndef _quadratic_approx(\n    s: Callable[[DualTypes], DualTypes],\n    s_tgt: float,\n    conv_tol: float,\n    g0: float,\n    g1: float,\n    g2: float,\n    cached_f0: float | None = None,\n    cached_f1: float | None = None,\n    cached_f2: float | None = None,\n) -> tuple[float, float, int | None, float, float, float, float | None, float | None, float | None]:\n    \"\"\"\n    Appro\n\n    Cached values allow value transmission from one function to the next with many efficiencies.\n\n    Returns\n    -------\n    g_i, f_i=s_i-s_tgt, state, *h_args_i = (g0, g1, g2, f0, f1, f2)\n    \"\"\"\n\n    # Load cached values\n    f0: float = cached_f0 if cached_f0 is not None else _root_f(g0, s, s_tgt)\n    f1: float = cached_f1 if cached_f1 is not None else _root_f(g1, s, s_tgt)\n    f2: float = cached_f2 if cached_f2 is not None else _root_f(g2, s, s_tgt)\n\n    # Test interval: if all values are same sign translate the guess interval.\n    if (f0 < 0 and f1 < 0 and f2 < 0) or (f0 > 0 and f1 > 0 and f2 > 0):\n        # Then all f = s-s_tgt are above or below zero and there is no crossing point.\n        # Shift the entire initial guesses lower or higher based the linear gradient.\n        if (f0 < 0 and f2 > f0) or (f0 > 0 and f2 < f0):\n            # Shift g to the right\n            g0_ = g2\n            g1_ = g1 + (g2 - g0)\n            g2_ = g2 + 2 * (g2 - g0)\n            return g1_, conv_tol, None, g0_, g1_, g2_, f2, None, None\n        else:\n            # Shift g to the left\n            g0_ = g0 - (g2 - g0)\n            g1_ = g1 - (g2 - g1)\n            g2_ = g0\n            return g1_, conv_tol, None, g0_, g1_, g2_, None, None, f0\n\n    # Solve g_new via quadratic approximation\n\n    # # Linear algebra solution\n    # _b = np.array([g0, g1, g2])[:, None]\n    # _A = np.array([[f0**2, f0, 1], [f1**2, f1, 1], [f2**2, f2, 1]])\n    # x = np.linalg.solve(_A, _b)\n    # g_new = x[2, 0]\n\n    # Analytical solution\n    f012, f022, f01, f02, g01, g02 = (\n        f0**2 - f1**2,\n        f0**2 - f2**2,\n        f0 - f1,\n        f0 - f2,\n        g0 - g1,\n        g0 - g2,\n    )\n    x0 = (g01 * f02 - g02 * f01) / (f012 * f02 - f022 * f01)\n    x1 = (g01 - x0 * f012) / f01\n    x2 = g0 - x1 * f0 - x0 * f0**2\n    g_new = x2\n\n    if g_new < g0 or g_new > g2:\n        # if the quadratic approximation is outside the interval then use a bisection method\n        if f0 * f1 < 0:\n            # bisect in the left hand side\n            g_new = g0 + (g1 - g0) * f0 / (f0 - f1)\n        else:\n            # bisect in the right hand side\n            g_new = g1 - (g2 - g1) * f1 / (f2 - f1)\n\n    f_new = _root_f(g_new, s, s_tgt)\n    for g_ in [g0, g1, g2]:\n        if abs(g_ - g_new) < conv_tol:\n            return g_new, f_new, 1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0\n\n    if g0 < g_new and g_new < g1:\n        return g_new, f_new, None, g0, g_new, g1, f0, f_new, f1\n    else:  # g1 < g_new and g_new < g2:\n        return g_new, f_new, None, g1, g_new, g2, f1, f_new, f2\n    # else:\n    #     raise RuntimeError(\"Unexpected interval: this line should never be reached.\")\n\n\nift_map: dict[str, Callable[P, tuple[float, float, int, tuple[Any, ...]]]] = {\n    \"bisection\": _bisection,  # type: ignore[dict-item]\n    \"modified_dekker\": _dekker,  # type: ignore[dict-item]\n    \"modified_brent\": _brent,  # type: ignore[dict-item]\n    \"ytm_quadratic\": _ytm_quadratic,  # type: ignore[dict-item]\n    \"quadratic_approx\": _quadratic_approx,  # type: ignore[dict-item]\n}\n"
  },
  {
    "path": "python/rateslib/dual/newton.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nfrom collections.abc import Callable, Sequence\nfrom time import time\nfrom typing import TYPE_CHECKING, Any, ParamSpec, TypeVar\n\nimport numpy as np\n\nfrom rateslib.dual.utils import _dual_float, dual_solve\nfrom rateslib.dual.variable import Variable\nfrom rateslib.rs import Dual, Dual2\n\nif TYPE_CHECKING:\n    from rateslib.local_types import DualTypes\nP = ParamSpec(\"P\")\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\nSTATE_MAP = {\n    1: [\"SUCCESS\", \"`conv_tol` reached\"],\n    2: [\"SUCCESS\", \"`func_tol` reached\"],\n    3: [\"SUCCESS\", \"closed form valid\"],\n    4: [\"SUCCESS\", \"`step_tol` reached\"],\n    5: [\"SUCCESS\", \"`grad_tol` reached\"],\n    -1: [\"FAILURE\", \"`max_iter` breached\"],\n    -2: [\"FAILURE\", \"internal iteration function failure\"],\n}\n\n\ndef _solver_result(\n    state: int, i: int, func_val: DualTypes, time: float, log: bool, algo: str\n) -> dict[str, Any]:\n    if log:\n        print(\n            f\"{STATE_MAP[state][0]}: {STATE_MAP[state][1]} after {i} iterations \"\n            f\"({algo}), `f_val`: {func_val}, \"\n            f\"`time`: {time:.4f}s\",\n        )\n    return {\n        \"status\": STATE_MAP[state][0],\n        \"state\": state,\n        \"g\": func_val,\n        \"iterations\": i,\n        \"time\": time,\n    }\n\n\nT = TypeVar(\"T\")\n\n\ndef _dual_float_or_unchanged(x: T | DualTypes) -> T | float:\n    \"\"\"If x is a DualType convert it to float otherwise leave it as is\"\"\"\n    if isinstance(x, float | Dual | Dual2 | Variable):\n        return _dual_float(x)\n    return x\n\n\ndef newton_1dim(\n    f: Callable[P, tuple[DualTypes, DualTypes]],\n    g0: DualTypes,\n    max_iter: int = 50,\n    func_tol: float = 1e-14,\n    conv_tol: float = 1e-9,\n    args: tuple[Any, ...] = (),\n    pre_args: tuple[Any, ...] = (),\n    final_args: tuple[Any, ...] = (),\n    raise_on_fail: bool = True,\n) -> dict[str, Any]:\n    \"\"\"\n    Use the Newton-Raphson algorithm to determine the root of a function searching **one** variable.\n\n    Parameters\n    ----------\n    f: callable\n        The function, *f*, to find the root of. Of the signature: `f(g, *args)`.\n        Must return a tuple where the second value is the derivative of *f* with respect to *g*.\n    g0: DualTypes\n        Initial guess of the root. Should be reasonable to avoid failure.\n    max_iter: int\n        The maximum number of iterations to try before exiting.\n    func_tol: float, optional\n        The absolute function tolerance to reach before exiting.\n    conv_tol: float, optional\n        The convergence tolerance for subsequent iterations of *g*.\n    args: tuple of float, Dual, Dual2 or str\n        Additional arguments passed to ``f``.\n    pre_args: tuple of float, Dual, Dual2 or str\n        Additional arguments passed to ``f`` used only in the float solve section of\n        the algorithm.\n        Functions are called with the signature `f(g, *(*args[as float], *pre_args))`.\n    final_args: tuple of float, Dual, Dual2 or str\n        Additional arguments passed to ``f`` in the final iteration of the algorithm\n        to capture AD sensitivities.\n        Functions are called with the signature `f(g, *(*args, *final_args))`.\n    raise_on_fail: bool, optional\n        If *False* will return a solver result dict with state and message indicating failure.\n\n    Returns\n    -------\n    dict\n\n    Notes\n    ------\n    Solves the root equation :math:`f(g; s_i)=0` for *g*. This method is AD-safe, meaning the\n    iteratively determined solution will preserve AD sensitivities, if the functions are suitable.\n    Functions which are not AD suitable, such as discontinuous functions or functions with\n    no derivative at given points, may yield spurious derivative results.\n\n    This method works by first solving in the domain of floats (which is typically faster\n    for most complex functions), and then performing final iterations in higher AD modes to\n    capture derivative sensitivities.\n\n    For special cases arguments can be passed separately to each of these modes using the\n    ``pre_args`` and ``final_args`` arguments, rather than generically supplying it to ``args``.\n\n    Examples\n    --------\n    Iteratively solve the equation: :math:`f(g, s) = g^2 - s = 0`. This has solution\n    :math:`g=\\\\pm \\\\sqrt{s}` and :math:`\\\\frac{dg}{ds} = \\\\frac{1}{2 \\\\sqrt{s}}`.\n    Thus for :math:`s=2` we expect the solution :code:`g=Dual(1.41.., [\"s\"], [0.35..])`.\n\n    .. ipython:: python\n\n       from rateslib.dual import newton_1dim\n\n       def f(g, s):\n           f0 = g**2 - s   # Function value\n           f1 = 2*g        # Analytical derivative is required\n           return f0, f1\n\n       s = Dual(2.0, [\"s\"], [])\n       newton_1dim(f, g0=1.0, args=(s,))\n    \"\"\"\n    t0 = time()\n    i = 0\n\n    # First attempt solution using faster float calculations\n    float_args = tuple(_dual_float_or_unchanged(_) for _ in args)\n    g0 = _dual_float(g0)\n    state = -1\n\n    while i < max_iter:\n        f0, f1 = f(*(g0, *float_args, *pre_args))  # type: ignore[call-arg]\n        i += 1\n        g1 = g0 - f0 / f1\n        if abs(f0) < func_tol:\n            state = 2\n            break\n        elif abs(g1 - g0) < conv_tol:\n            state = 1\n            break\n        g0 = g1\n\n    if i == max_iter:\n        if raise_on_fail:\n            raise ValueError(\n                f\"`max_iter`: {max_iter} exceeded in 'newton_1dim' algorithm'.\\n\"\n                f\"Last iteration values:\\nf0: {f0}\\nf1: {f1}\\ng0: {g0}\"\n            )\n        else:\n            return _solver_result(-1, i, g1, time() - t0, log=True, algo=\"newton_1dim\")\n\n    # # Final iteration method to preserve AD\n    f0, f1 = f(*(g1, *args, *final_args))  # type: ignore[call-arg]\n    if isinstance(f0, Dual | Dual2) or isinstance(f1, Dual | Dual2):\n        i += 1\n        g1 = g1 - f0 / f1\n    if isinstance(f0, Dual2) or isinstance(f1, Dual2):\n        f0, f1 = f(*(g1, *args, *final_args))  # type: ignore[call-arg]\n        i += 1\n        g1 = g1 - f0 / f1\n\n    # # Analytical approach to capture AD sensitivities\n    # f0, f1 = f(g1, *(*args, *final_args))\n    # if isinstance(f0, Dual):\n    #     g1 = Dual.vars_from(f0, float(g1), f0.vars, float(f1) ** -1 * -gradient(f0))\n    # if isinstance(f0, Dual2):\n    #     g1 = Dual2.vars_from(f0, float(g1), f0.vars, float(f1) ** -1 * -gradient(f0), [])\n    #     f02, f1 = f(g1, *(*args, *final_args))\n    #\n    #     #f0_beta = gradient(f0, order=1, vars=f0.vars, keep_manifold=True)\n    #\n    #     f0_gamma = gradient(f02, order=2)\n    #     f0_beta = gradient(f0, order=1)\n    #     # f1 = set_order_convert(g1, tag=[], order=2)\n    #     f1_gamma = gradient(f1, f0.vars, order=2)\n    #     f1_beta = gradient(f1, f0.vars, order=1)\n    #\n    #     g1_beta = -float(f1) ** -1 * f0_beta\n    #     g1_gamma = (\n    #         -float(f1)**-1 * f0_gamma +\n    #         float(f1)**-2 * (\n    #                 np.matmul(f0_beta[:, None], f1_beta[None, :]) +\n    #                 np.matmul(f1_beta[:, None], f0_beta[None, :]) +\n    #                 float(f0) * f1_gamma\n    #         ) -\n    #         2 * float(f1)**-3 * float(f0) * np.matmul(f1_beta[:, None], f1_beta[None, :])\n    #     )\n    #     g1 = Dual2.vars_from(f0, float(g1), f0.vars, g1_beta, g1_gamma.flatten())\n\n    return _solver_result(state, i, g1, time() - t0, log=False, algo=\"newton_1dim\")\n\n\ndef newton_ndim(\n    f: Callable[P, tuple[Any, Any]],\n    g0: Sequence[DualTypes],\n    max_iter: int = 50,\n    func_tol: float = 1e-14,\n    conv_tol: float = 1e-9,\n    args: tuple[Any, ...] = (),\n    pre_args: tuple[Any, ...] = (),\n    final_args: tuple[Any, ...] = (),\n    raise_on_fail: bool = True,\n) -> dict[str, Any]:\n    r\"\"\"\n    Use the Newton-Raphson algorithm to determine a function root searching **many** variables.\n\n    Solves the *n* root equations :math:`f_i(g_1, \\hdots, g_n; s_k)=0` for each :math:`g_j`.\n\n    Parameters\n    ----------\n    f: callable\n        The function, *f*, to find the root of. Of the signature: `f([g_1, .., g_n], *args)`.\n        Must return a tuple where the second value is the Jacobian of *f* with respect to *g*.\n    g0: Sequence of DualTypes\n        Initial guess of the root values. Should be reasonable to avoid failure.\n    max_iter: int\n        The maximum number of iterations to try before exiting.\n    func_tol: float, optional\n        The absolute function tolerance to reach before exiting.\n    conv_tol: float, optional\n        The convergence tolerance for subsequent iterations of *g*.\n    args: tuple of float, Dual or Dual2\n        Additional arguments passed to ``f``.\n    pre_args: tuple\n        Additional arguments passed to ``f`` only in the float solve section\n        of the algorithm.\n        Functions are called with the signature `f(g, *(*args[as float], *pre_args))`.\n    final_args: tuple of float, Dual, Dual2\n        Additional arguments passed to ``f`` in the final iteration of the algorithm\n        to capture AD sensitivities.\n        Functions are called with the signature `f(g, *(*args, *final_args))`.\n    raise_on_fail: bool, optional\n        If *False* will return a solver result dict with state and message indicating failure.\n\n    Returns\n    -------\n    dict\n\n    Examples\n    --------\n    Iteratively solve the equation system:\n\n    - :math:`f_0(\\mathbf{g}, s) = g_1^2 + g_2^2 + s = 0`.\n    - :math:`f_1(\\mathbf{g}, s) = g_1^2 - 2g_2^2 + s = 0`.\n\n    .. ipython:: python\n\n       from rateslib.dual import newton_ndim\n\n       def f(g, s):\n           # Function value\n           f0 = g[0] ** 2 + g[1] ** 2 + s\n           f1 = g[0] ** 2 - 2 * g[1]**2 - s\n           # Analytical derivative as Jacobian matrix is required\n           f00 = 2 * g[0]\n           f01 = 2 * g[1]\n           f10 = 2 * g[0]\n           f11 = -4 * g[1]\n           return [f0, f1], [[f00, f01], [f10, f11]]\n\n       s = Dual(-2.0, [\"s\"], [])\n       newton_ndim(f, g0=[1.0, 1.0], args=(s,))\n    \"\"\"\n    t0 = time()\n    i = 0\n    n = len(g0)\n\n    # First attempt solution using faster float calculations\n    float_args = tuple(_dual_float_or_unchanged(_) for _ in args)\n    g0_ = np.array([_dual_float(_) for _ in g0])\n    state = -1\n\n    while i < max_iter:\n        f0, f1 = f(*(g0_, *float_args, *pre_args))  # type: ignore[call-arg]\n        f0 = np.array(f0)[:, np.newaxis]\n        f1 = np.array(f1)\n\n        i += 1\n        g1 = g0_ - np.matmul(np.linalg.inv(f1), f0)[:, 0]\n        if all(abs(_) < func_tol for _ in f0[:, 0]):\n            state = 2\n            break\n        elif all(abs(g1[_] - g0_[_]) < conv_tol for _ in range(n)):\n            state = 1\n            break\n        g0_ = g1\n\n    if i == max_iter:\n        if raise_on_fail:\n            raise ValueError(f\"`max_iter`: {max_iter} exceeded in 'newton_ndim' algorithm'.\")\n        else:\n            return _solver_result(-1, i, g1, time() - t0, log=True, algo=\"newton_ndim\")\n\n    # Final iteration method to preserve AD\n    f0, f1 = f(*(g1, *args, *final_args))  # type: ignore[call-arg]\n    f1, f0 = np.array(f1), np.array(f0)\n\n    # get AD type\n    ad: int = 0\n    if _is_any_dual(f0) or _is_any_dual(f1):\n        ad = 1\n        DualType: type[Dual] | type[Dual2] = Dual\n    elif _is_any_dual2(f0) or _is_any_dual2(f1):\n        ad = 2\n        DualType = Dual2\n\n    if ad > 0:\n        i += 1\n        g1 = g0_ - dual_solve(f1, f0[:, None], allow_lsq=False, types=(DualType, DualType))[:, 0]\n    if ad == 2:\n        f0, f1 = f(*(g1, *args, *final_args))  # type: ignore[call-arg]\n        f1, f0 = np.array(f1), np.array(f0)\n        i += 1\n        g1 = g1 - dual_solve(f1, f0[:, None], allow_lsq=False, types=(DualType, DualType))[:, 0]\n\n    return _solver_result(state, i, g1, time() - t0, log=False, algo=\"newton_ndim\")\n\n\ndef _is_any_dual(arr: np.ndarray[tuple[int, ...], np.dtype[np.object_]]) -> bool:\n    return any(isinstance(_, Dual) for _ in arr.flatten())\n\n\ndef _is_any_dual2(arr: np.ndarray[tuple[int, ...], np.dtype[np.object_]]) -> bool:\n    return any(isinstance(_, Dual2) for _ in arr.flatten())\n"
  },
  {
    "path": "python/rateslib/dual/quadratic.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any\n\nfrom rateslib.dual.newton import _solver_result\n\nif TYPE_CHECKING:\n    from rateslib.local_types import DualTypes\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n\ndef quadratic_eqn(\n    a: DualTypes, b: DualTypes, c: DualTypes, x0: DualTypes, raise_on_fail: bool = True\n) -> dict[str, Any]:\n    \"\"\"\n    Solve the quadratic equation, :math:`ax^2 + bx +c = 0`, with error reporting.\n\n    Parameters\n    ----------\n    a: float, Dual Dual2\n        The *a* coefficient value.\n    b: float, Dual Dual2\n        The *b* coefficient value.\n    c: float, Dual Dual2\n        The *c* coefficient value.\n    x0: float, Dual, Dual2\n        The expected solution to discriminate between two possible solutions.\n    raise_on_fail: bool, optional\n        Whether to raise if unsolved or return a solver result in failed state.\n\n    Returns\n    -------\n    dict\n\n    Notes\n    -----\n    If ``a`` is evaluated to be less that 1e-15 in absolute terms then it is treated as zero and the\n    equation is solved as a linear equation in ``b`` and ``c`` only.\n\n    Examples\n    --------\n    .. ipython:: python\n\n       from rateslib.dual import quadratic_eqn\n\n       quadratic_eqn(a=1.0, b=1.0, c=Dual(-6.0, [\"c\"], []), x0=-2.9)\n\n    \"\"\"\n    discriminant = b**2 - 4 * a * c\n    if discriminant < 0.0:\n        if raise_on_fail:\n            raise ValueError(\"`quadratic_eqn` has failed to solve: discriminant is less than zero.\")\n        else:\n            return _solver_result(\n                state=-1,\n                i=0,\n                func_val=1e308,\n                time=0.0,\n                log=True,\n                algo=\"quadratic_eqn\",\n            )\n\n    if abs(a) > 1e-15:  # machine tolerance on normal float64 is 2.22e-16\n        sqrt_d = discriminant**0.5\n        _1 = (-b + sqrt_d) / (2 * a)\n        _2 = (-b - sqrt_d) / (2 * a)\n        if abs(x0 - _1) < abs(x0 - _2):\n            return _solver_result(\n                state=3,\n                i=1,\n                func_val=_1,\n                time=0.0,\n                log=False,\n                algo=\"quadratic_eqn\",\n            )\n        else:\n            return _solver_result(\n                state=3,\n                i=1,\n                func_val=_2,\n                time=0.0,\n                log=False,\n                algo=\"quadratic_eqn\",\n            )\n    else:\n        # 'a' is considered too close to zero for the quadratic eqn, solve the linear eqn\n        # to avoid division by zero errors\n        return _solver_result(\n            state=3,\n            i=1,\n            func_val=-c / b,\n            time=0.0,\n            log=False,\n            algo=\"quadratic_eqn->linear_eqn\",\n        )\n"
  },
  {
    "path": "python/rateslib/dual/utils.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport math\nfrom functools import partial\nfrom statistics import NormalDist\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\n\nfrom rateslib import defaults\nfrom rateslib.dual.variable import FLOATS, INTS, Variable\nfrom rateslib.enums.generics import Err, NoInput, Ok\nfrom rateslib.rs import ADOrder, Dual, Dual2, _dsolve1, _dsolve2, _fdsolve1, _fdsolve2\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        Arr1dF64,\n        Arr1dObj,\n        Arr2dF64,\n        Arr2dObj,\n        DualTypes,\n        Number,\n        Result,\n        Sequence,\n    )\n\nDual.__doc__ = \"Dual number data type to perform first derivative automatic differentiation.\"\nDual2.__doc__ = \"Dual number data type to perform second derivative automatic differentiation.\"\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n\ndef _dual_float(val: DualTypes) -> float:\n    \"\"\"Overload for the float() builtin to handle Pyo3 issues with Variable\"\"\"\n    try:\n        return float(val)  # type: ignore[arg-type]\n    except TypeError as e:  # val is not Number but a Variable\n        if isinstance(val, Variable):\n            #  This does not work well with rust.\n            #  See: https://github.com/PyO3/pyo3/issues/3672\n            #  and https://github.com/PyO3/pyo3/discussions/3911\n            return val.real\n        raise e\n\n\ndef _dual_round(val: DualTypes, ndigits: int) -> DualTypes:\n    \"\"\"Overload for the round() builtin to handle Duals: ONLY impacting the real quantity\"\"\"\n    try:\n        return round(val, ndigits)  # type: ignore[arg-type]\n    except TypeError as e:  # val is not Number but a Variable\n        if isinstance(val, Dual):\n            return Dual.vars_from(val, round(val.real, ndigits), val.vars, val.dual)\n        elif isinstance(val, Dual2):\n            return Dual2.vars_from(\n                val, round(val.real, ndigits), val.vars, val.dual, val.dual2.ravel()\n            )\n        elif isinstance(val, Variable):\n            return Variable(round(val.real, ndigits), vars=val.vars, dual=val.dual)\n        raise e\n\n\ndef _float_or_none(val: DualTypes | None | NoInput | Result[DualTypes]) -> float | None:\n    if val is None or isinstance(val, NoInput | Err):\n        return None\n    elif isinstance(val, Ok):\n        return _float_or_none(val.unwrap())\n    else:\n        return _dual_float(val)\n\n\ndef _abs_float(val: DualTypes) -> float:\n    \"\"\"Overload the abs() builtin to return the abs of the real component only\"\"\"\n    if isinstance(val, Dual | Dual2 | Variable):\n        return abs(val.real)\n    else:\n        return abs(val)\n\n\ndef _get_order_of(val: DualTypes) -> int:\n    \"\"\"Get the AD order of a DualType including checking the globals for the current order.\"\"\"\n    if isinstance(val, Dual):\n        ad_order: int = 1\n    elif isinstance(val, Dual2):\n        ad_order = 2\n    elif isinstance(val, Variable):\n        ad_order = defaults._global_ad_order\n    else:\n        ad_order = 0\n    return ad_order\n\n\ndef _to_number(val: DualTypes) -> Number:\n    \"\"\"Convert a DualType to a Number Type by casting a Variable to the required global AD order.\"\"\"\n    if isinstance(val, Variable):\n        return set_order(val, defaults._global_ad_order)\n    return val\n\n\ndef set_order(val: DualTypes, order: int) -> Number:\n    \"\"\"\n    Changes the order of a :class:`Dual` or :class:`Dual2` and a sets a :class:`Variable`.\n\n    Parameters\n    ----------\n    val : float, Dual, Dual2, Variable\n        The value to convert the order of.\n    order : int in [0, 1, 2]\n        The AD order to convert to. If ``val`` is float or int 0 will be used.\n\n    Returns\n    -------\n    float, Dual or Dual2\n\n    Notes\n    ------\n    **floats** are not affected by this function. There is no benefit to converting\n    one of these types to a dual number type with no tagged variable sensitivity.\n\n    If ``order`` is **zero**, all objects are converted to float.\n\n    If ``order`` is **one**, *Dual2* are converted to *Dual* by dropping second order gradients.\n\n    If ``order`` is **two**, *Dual* are converted to *DUal2* by setting second order gradients to\n    default zero values.\n    \"\"\"\n    if order == 0:\n        return _dual_float(val)\n    elif order == 1:\n        if isinstance(val, Dual):\n            return val\n        elif isinstance(val, Dual2 | Variable):\n            return val.to_dual()\n        return val  # as float\n    else:  # order == 2\n        if isinstance(val, Dual2):\n            return val\n        elif isinstance(val, Dual | Variable):\n            return val.to_dual2()\n        return val  # as float\n\n\ndef set_order_convert(\n    val: DualTypes, order: int, tag: list[str] | None, vars_from: Dual | Dual2 | None = None\n) -> Number:\n    \"\"\"\n    Convert a float, :class:`Dual` or :class:`Dual2` type to a specified alternate type with\n    tagged variables.\n\n    Parameters\n    ----------\n    val : float, Dual, Dual2, Variable\n        The value to convert.\n    order : int\n        The AD order to convert the value to if necessary.\n    tag : list of str, optional\n        The variable name(s) if upcasting a float to a Dual or Dual2\n    vars_from : optional, Dual or Dual2\n        A pre-existing Dual of correct order from which the Vars are extracted. Improves efficiency\n        when given.\n\n    Returns\n    -------\n    float, Dual, Dual2\n\n    Notes\n    -----\n    This function is used for AD variable management.\n\n    ``tag`` and ``vars_from`` are only used when floats are upcast and the variables need to be\n    specifically define.\n\n    \"\"\"\n    if isinstance(val, FLOATS | INTS):\n        _ = [] if tag is None else tag\n        if order == 0:\n            return float(val)\n        elif order == 1:\n            if vars_from is None:\n                return Dual(val, _, [])\n            elif isinstance(vars_from, Dual):\n                return Dual.vars_from(vars_from, val, _, [])\n            else:\n                raise TypeError(\"`vars_from` must be a Dual when converting to ADOrder:1.\")\n        elif order == 2:\n            if vars_from is None:\n                return Dual2(val, _, [], [])\n            elif isinstance(vars_from, Dual2):\n                return Dual2.vars_from(vars_from, val, _, [], [])\n            else:\n                raise TypeError(\"`vars_from` must be a Dual2 when converting to ADOrder:2.\")\n    # else val is Dual or Dual2 so convert directly\n    return set_order(val, order)\n\n\ndef gradient(\n    dual: DualTypes,\n    vars: Sequence[str] | None = None,  # noqa: A002\n    order: int = 1,\n    keep_manifold: bool = False,\n) -> Arr1dF64 | Arr2dF64:\n    \"\"\"\n    Return derivatives of a dual number.\n\n    Parameters\n    ----------\n    dual : Dual, Dual2, Variable, float\n        The dual variable from which to derive derivatives.\n    vars : str, tuple, list optional\n        Name of the variables which to return gradients for. If not given\n        defaults to all vars attributed to the instance.\n    order : {1, 2}\n        Whether to return the first or second derivative of the dual number.\n        Second order will raise if applied to a ``Dual`` and not ``Dual2`` instance.\n    keep_manifold : bool\n        If ``order`` is 1 and the type is ``Dual2`` one can return a ``Dual2``\n        where the ``dual2`` values are converted to ``dual`` values to represent\n        a first order manifold of the first derivative (and the ``dual2`` values\n        set to zero). Useful for propagation in iterations.\n\n    Returns\n    -------\n    float, ndarray, Dual2\n    \"\"\"\n    _validate_keep_manifold(keep_manifold, order, dual)\n\n    if order == 1:\n        if not isinstance(dual, Dual | Dual2 | Variable):\n            if isinstance(dual, float | int):\n                return np.zeros(shape=(len(vars) if vars is not None else 0,))\n            else:\n                raise TypeError(\"Can call `gradient` only on dual-type variables.\")\n        if isinstance(dual, Variable):\n            dual = Dual(dual.real, vars=dual.vars, dual=dual.dual)\n        if vars is None and not keep_manifold:\n            return dual.dual\n        elif vars is not None and not keep_manifold:\n            return dual.grad1(vars)\n        _ = dual.grad1_manifold(dual.vars if vars is None else vars)  # type: ignore[union-attr]\n        return np.asarray(_)\n\n    elif order == 2:\n        if not isinstance(dual, Dual | Dual2 | Variable):\n            if isinstance(dual, float | int):\n                n = len(vars) if vars is not None else 0\n                return np.zeros(shape=(n, n))\n            else:\n                raise TypeError(\"Can call `gradient` only on dual-type variables.\")\n        if isinstance(dual, Variable):\n            dual = Dual2(dual.real, vars=dual.vars, dual=dual.dual, dual2=[])\n        elif isinstance(dual, Dual):\n            raise TypeError(\"Dual type cannot derive second order automatic derivatives.\")\n\n        if vars is None:\n            return 2.0 * dual.dual2\n        else:\n            return dual.grad2(vars)\n    else:\n        raise ValueError(\"`order` must be in {1, 2} for gradient calculation.\")\n\n\ndef _validate_keep_manifold(keep_manifold: bool, order: int, dual: DualTypes) -> None:\n    \"\"\"Validate the keep_manifold argument for gradient.\"\"\"\n    if keep_manifold and not isinstance(dual, Dual2):\n        if isinstance(dual, Dual):\n            raise TypeError(\"Dual type cannot perform `keep_manifold`.\")\n        elif isinstance(dual, Variable):\n            raise TypeError(\"Variable type cannot perform `keep_manifold`.\")\n        else:\n            raise TypeError(\"Float type cannot perform `keep_manifold`.\")\n\n\ndef dual_exp(x: DualTypes) -> Number:\n    \"\"\"\n    Calculate the exponential value of a regular int or float or a dual number.\n\n    Parameters\n    ----------\n    x : int, float, Dual, Dual2, Variable\n        Value to calculate exponent of.\n\n    Returns\n    -------\n    float, Dual, Dual2\n    \"\"\"\n    if isinstance(x, Dual | Dual2 | Variable):\n        return x.__exp__()\n    return math.exp(x)\n\n\ndef dual_log(x: DualTypes, base: int | None = None) -> Number:\n    \"\"\"\n    Calculate the logarithm of a regular int or float or a dual number.\n\n    Parameters\n    ----------\n    x : int, float, Dual, Dual2, Variable\n        Value to calculate exponent of.\n    base : int, float, optional\n        Base of the logarithm. Defaults to e to compute natural logarithm\n\n    Returns\n    -------\n    float, Dual, Dual2\n    \"\"\"\n    if isinstance(x, Dual | Dual2 | Variable):\n        val = x.__log__()\n        if base is None:\n            return val\n        else:\n            return val * (1 / math.log(base))\n    elif base is None:\n        return math.log(x)\n    else:\n        return math.log(x, base)\n\n\ndef dual_norm_pdf(x: DualTypes) -> Number:\n    \"\"\"\n    Return the standard normal probability density function.\n\n    Parameters\n    ----------\n    x : float, Dual, Dual2, Variable\n\n    Returns\n    -------\n    float, Dual, Dual2\n    \"\"\"\n    return dual_exp(-0.5 * x**2) / math.sqrt(2.0 * math.pi)\n\n\ndef dual_norm_cdf(x: DualTypes) -> Number:\n    \"\"\"\n    Return the cumulative standard normal distribution for given value.\n\n    Parameters\n    ----------\n    x : float, Dual, Dual2, Variable\n\n    Returns\n    -------\n    float, Dual, Dual2\n    \"\"\"\n    if isinstance(x, Dual | Dual2 | Variable):\n        return x.__norm_cdf__()\n    else:\n        return NormalDist().cdf(x)\n\n\ndef dual_inv_norm_cdf(x: DualTypes) -> Number:\n    \"\"\"\n    Return the inverse cumulative standard normal distribution for given value.\n\n    Parameters\n    ----------\n    x : float, Dual, Dual2, Variable\n\n    Returns\n    -------\n    float, Dual, Dual2\n    \"\"\"\n    if isinstance(x, Dual | Dual2 | Variable):\n        return x.__norm_inv_cdf__()\n    else:\n        return NormalDist().inv_cdf(x)\n\n\ndef dual_solve(\n    A: Arr2dObj | Arr2dF64,\n    b: Arr1dObj | Arr1dF64,\n    allow_lsq: bool = False,\n    types: tuple[type[float] | type[Dual] | type[Dual2], type[float] | type[Dual] | type[Dual2]] = (\n        Dual,\n        Dual,\n    ),\n) -> Arr1dObj | Arr1dF64:\n    \"\"\"\n    Solve a linear system of equations involving dual number data types.\n\n    The `x` value is found for the equation :math:`Ax=b`.\n\n    .. warning::\n\n       This method has not yet implemented :class:`~rateslib.dual.Variable` types.\n\n    Parameters\n    ----------\n    A: 2-d array\n        Left side matrix of values.\n    b: 1-d array\n        Right side vector of values.\n    allow_lsq: bool\n        Whether to allow solutions for non-square `A`, i.e. when `len(b) > len(x)`.\n    types: tuple\n        Defining the input data type elements of `A` and `b`, e.g. (float, float) or (Dual, Dual).\n\n    Returns\n    -------\n    1-d array\n    \"\"\"\n    if types == (float, float):\n        # Use basic Numpy LinAlg\n        if allow_lsq:\n            return np.linalg.lstsq(A, b, rcond=None)[0]  # type: ignore[arg-type]\n        else:\n            return np.linalg.solve(A, b)  # type: ignore[arg-type]\n\n    # Move to Rust implementation\n    if types in [(Dual, float), (Dual2, float)]:\n        raise TypeError(\n            \"Not implemented for type crossing. Use (Dual, Dual) or (Dual2, Dual2). It is no less\"\n            \"efficient to preconvert `b` to dual types and then solve.\",\n        )\n\n    map_ = {float: 0, Dual: 1, Dual2: 2}\n    A_ = np.vectorize(partial(set_order_convert, tag=[], order=map_[types[0]], vars_from=None))(A)\n    b_ = np.vectorize(partial(set_order_convert, tag=[], order=map_[types[1]], vars_from=None))(b)\n\n    a_ = [item for sublist in A_.tolist() for item in sublist]  # 1D array of A_\n    b_ = b_[:, 0].tolist()\n\n    if types == (Dual, Dual):\n        return np.array(_dsolve1(a_, b_, allow_lsq))[:, None]\n    elif types == (Dual2, Dual2):\n        return np.array(_dsolve2(a_, b_, allow_lsq))[:, None]\n    elif types == (float, Dual):\n        return np.array(_fdsolve1(A_, b_, allow_lsq))[:, None]\n    elif types == (float, Dual2):\n        return np.array(_fdsolve2(A_, b_, allow_lsq))[:, None]\n    else:\n        raise TypeError(\n            \"Provided `types` argument are not permitted. Must be a 2-tuple with \"\n            \"elements from {float, Dual, Dual2}\"\n        )\n\n\ndef _get_adorder(order: int) -> ADOrder:\n    \"\"\"Convert int AD order to an ADOrder enum type.\"\"\"\n    if order == 1:\n        return ADOrder.One\n    elif order == 0:\n        return ADOrder.Zero\n    elif order == 2:\n        return ADOrder.Two\n    else:\n        raise ValueError(\"Order for AD can only be in {0,1,2}\")\n\n\ndef _set_ad_order_objects(order: list[int] | dict[int, int], objs: list[Any]) -> dict[int, int]:\n    \"\"\"\n    Set the order on multiple Objects, returning their previous order indexed my memory id.\n\n    Parameters\n    ----------\n    order: list[int] or dict[int,int]\n        A list of orders to set the objects to. If a dict indexed my memory id.\n    objs: list[Any]\n        A list of objects to convert the AD orders of.\n\n    Returns\n    -------\n    dict[int]\n\n    Notes\n    -----\n    If an Object does not have a `_set_ad_order` method then\n    it will simply be passed and return 0 for its associated\n    previous AD order.\n    \"\"\"\n    # this function catches duplicate objects that are identical by memory id\n    if isinstance(order, list) and len(order) != len(objs):\n        raise ValueError(\"`order` and `objs` must have the same length\")\n\n    original_order: dict[int, int] = {}\n    for i, obj in enumerate(objs):\n        if id(obj) in original_order:\n            continue  # object has already been parsed\n\n        _ad = getattr(obj, \"_ad\", None)\n        if _ad is None:\n            # object cannot be set_ad_order\n            continue\n\n        if isinstance(order, dict):\n            obj._set_ad_order(order[id(obj)])\n            original_order[id(obj)] = _ad\n        else:  # isinstance(order, list)\n            obj._set_ad_order(order[i])\n            original_order[id(obj)] = _ad\n\n    return original_order\n"
  },
  {
    "path": "python/rateslib/dual/variable.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport json\nimport math\nfrom collections.abc import Sequence\nfrom typing import TYPE_CHECKING, Any\n\nimport numpy as np\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.rs import Dual, Dual2\n\nif TYPE_CHECKING:\n    from rateslib.local_types import Arr1dF64\n\nPRECISION = 1e-14\nFLOATS = float | np.float16 | np.float32 | np.float64 | np.longdouble\nINTS = int | np.int8 | np.int16 | np.int32 | np.int32 | np.int64\n\n\nclass Variable:\n    \"\"\"\n    A user defined, exogenous variable that automatically converts to a\n    :class:`~rateslib.dual.Dual` or\n    :class:`~rateslib.dual.Dual2` type dependent upon the overall AD calculation order.\n\n    See :ref:`what is an exogenous variable? <cook-exogenous-doc>`\n\n    Parameters\n    ----------\n    real : float\n        The real coefficient of the underlying dual number.\n    vars : tuple of str, optional\n        The labels of the variables for which to record derivatives. If not given\n        the *Variable* represents a constant - it would be better to define just a float.\n    dual : 1d ndarray, optional\n        First derivative information contained as coefficient of linear manifold.\n        Defaults to an array of ones the length of ``vars`` if not given.\n\n    Attributes\n    ----------\n    real : float\n    vars : str, tuple of str\n    dual : 1d ndarray\n    \"\"\"\n\n    def __init__(\n        self,\n        real: float,\n        vars: Sequence[str] = (),  # noqa: A002\n        dual: list[float] | Arr1dF64 | NoInput = NoInput(0),\n    ):\n        self.real: float = float(real)\n        self.vars: tuple[str, ...] = tuple(vars)\n        n = len(self.vars)\n        if isinstance(dual, NoInput) or len(dual) == 0:\n            self.dual: Arr1dF64 = np.ones(n, dtype=np.float64)\n        else:\n            self.dual = np.asarray(dual.copy())\n\n    def _to_dual_type(self, order: int) -> Dual | Dual2:\n        if order == 1:\n            _: Dual | Dual2 = self.to_dual()\n            return _\n        elif order == 2:\n            _ = self.to_dual2()\n            return _\n        else:\n            raise TypeError(\n                f\"`Variable` can only be converted with `order` in [1, 2], got order: {order}.\"\n            )\n\n    def to_json(self) -> str:\n        \"\"\"\n        Serialize this object to JSON format.\n\n        The object can be deserialized using the :meth:`~rateslib.serialization.from_json` method.\n\n        Returns\n        -------\n        str\n        \"\"\"\n        obj = dict(\n            PyNative=dict(\n                Variable=dict(\n                    real=self.real,\n                    vars=self.vars,\n                    dual=list(self.dual),\n                )\n            )\n        )\n        return json.dumps(obj)\n\n    @classmethod\n    def _from_json(cls, loaded_json: dict[str, Any]) -> Variable:\n        return Variable(\n            real=loaded_json[\"real\"],\n            vars=loaded_json[\"vars\"],\n            dual=loaded_json[\"dual\"],\n        )\n\n    def to_dual(self) -> Dual:\n        return Dual(self.real, vars=self.vars, dual=self.dual)\n\n    def to_dual2(self) -> Dual2:\n        return Dual2(self.real, vars=self.vars, dual=self.dual, dual2=[])\n\n    def __eq__(self, argument: Any) -> bool:\n        \"\"\"\n        Compare an argument with a Variable for equality.\n        This does not account for variable ordering.\n        \"\"\"\n        if not isinstance(argument, type(self)):\n            return False\n        if self.vars == argument.vars:\n            return self.__eq_coeffs__(argument, PRECISION)\n        return False\n\n    def __lt__(self, other: Any) -> bool:\n        return self.real.__lt__(other)\n\n    def __le__(self, other: Any) -> bool:\n        return self.real.__le__(other)\n\n    def __gt__(self, other: Any) -> bool:\n        return self.real.__gt__(other)\n\n    def __ge__(self, other: Any) -> bool:\n        return self.real.__ge__(other)\n\n    def __eq_coeffs__(self, argument: Dual | Dual2 | Variable, precision: float) -> bool:\n        \"\"\"Compare the coefficients of two dual array numbers for equality.\"\"\"\n        return not (\n            not math.isclose(self.real, argument.real, abs_tol=precision)\n            or not np.all(np.isclose(self.dual, argument.dual, atol=precision))\n        )\n\n    # def __float__(self):\n    #  This does not work well with rust.\n    #  See: https://github.com/PyO3/pyo3/issues/3672\n    #  and https://github.com/PyO3/pyo3/discussions/3911\n    #     return self.real\n\n    def __abs__(self) -> float:\n        return abs(self.real)\n\n    def __neg__(self) -> Variable:\n        return Variable(-self.real, vars=self.vars, dual=-self.dual)\n\n    def __add__(self, other: Dual | Dual2 | float | Variable) -> Dual | Dual2 | Variable:\n        if isinstance(other, Variable):\n            _1 = self._to_dual_type(defaults._global_ad_order)\n            _2 = other._to_dual_type(defaults._global_ad_order)\n            return _1.__add__(_2)\n        elif isinstance(other, FLOATS | INTS):\n            return Variable(self.real + float(other), vars=self.vars, dual=self.dual)\n        elif isinstance(other, Dual):\n            return Dual(self.real, vars=self.vars, dual=self.dual).__add__(other)\n        elif isinstance(other, Dual2):\n            return Dual2(self.real, vars=self.vars, dual=self.dual, dual2=[]).__add__(other)\n        else:\n            raise TypeError(f\"No operation defined between `Variable` and type: `{type(other)}`\")\n\n    def __radd__(self, other: Dual | Dual2 | float | Variable) -> Dual | Dual2 | Variable:\n        return self.__add__(other)\n\n    def __rsub__(self, other: Dual | Dual2 | float | Variable) -> Dual | Dual2 | Variable:\n        return (self.__neg__()).__add__(other)\n\n    def __sub__(self, other: Dual | Dual2 | float | Variable) -> Dual | Dual2 | Variable:\n        return self.__add__(other.__neg__())\n\n    def __mul__(self, other: Dual | Dual2 | float | Variable) -> Dual | Dual2 | Variable:\n        if isinstance(other, Variable):\n            _1 = self._to_dual_type(defaults._global_ad_order)\n            _2 = other._to_dual_type(defaults._global_ad_order)\n            return _1.__mul__(_2)\n        elif isinstance(other, FLOATS | INTS):\n            return Variable(self.real * float(other), vars=self.vars, dual=self.dual * float(other))\n        elif isinstance(other, Dual):\n            return Dual(self.real, vars=self.vars, dual=self.dual).__mul__(other)\n        elif isinstance(other, Dual2):\n            return Dual2(self.real, vars=self.vars, dual=self.dual, dual2=[]).__mul__(other)\n        else:\n            raise TypeError(f\"No operation defined between `Variable` and type: `{type(other)}`\")\n\n    def __rmul__(self, other: Dual | Dual2 | float | Variable) -> Dual | Dual2 | Variable:\n        return self.__mul__(other)\n\n    def __truediv__(self, other: Dual | Dual2 | float | Variable) -> Dual | Dual2 | Variable:\n        if isinstance(other, Variable):\n            _1 = self._to_dual_type(defaults._global_ad_order)\n            _2 = other._to_dual_type(defaults._global_ad_order)\n            return _1.__truediv__(_2)\n        elif isinstance(other, FLOATS | INTS):\n            return Variable(self.real / float(other), vars=self.vars, dual=self.dual / float(other))\n        elif isinstance(other, Dual):\n            return Dual(self.real, vars=self.vars, dual=self.dual).__truediv__(other)\n        elif isinstance(other, Dual2):\n            return Dual2(self.real, vars=self.vars, dual=self.dual, dual2=[]).__truediv__(other)\n        else:\n            raise TypeError(f\"No operation defined between `Variable` and type: `{type(other)}`\")\n\n    def __rtruediv__(self, other: Dual | Dual2 | float | Variable) -> Dual | Dual2 | Variable:\n        if isinstance(other, Variable):\n            # cannot reach this line\n            raise TypeError(\"Impossible line execution - please report issue.\")  # pragma: no cover\n        elif isinstance(other, FLOATS | INTS):\n            _1 = Variable(other, ())\n            return _1 / self\n        elif isinstance(other, Dual):\n            _ = Dual(self.real, vars=self.vars, dual=self.dual)\n            return other.__truediv__(_)\n        elif isinstance(other, Dual2):\n            _ = Dual2(self.real, vars=self.vars, dual=self.dual, dual2=[])\n            return other.__truediv__(_)\n        else:\n            raise TypeError(f\"No operation defined between `Variable` and type: `{type(other)}`\")\n\n    def __exp__(self) -> Dual | Dual2:\n        _1 = self._to_dual_type(defaults._global_ad_order)\n        return _1.__exp__()\n\n    def __log__(self) -> Dual | Dual2:\n        _1 = self._to_dual_type(defaults._global_ad_order)\n        return _1.__log__()\n\n    def __norm_cdf__(self) -> Dual | Dual2:\n        _1 = self._to_dual_type(defaults._global_ad_order)\n        return _1.__norm_cdf__()\n\n    def __norm_inv_cdf__(self) -> Dual | Dual2:\n        _1 = self._to_dual_type(defaults._global_ad_order)\n        return _1.__norm_inv_cdf__()\n\n    def __pow__(self, exponent: float | Dual | Dual2, modulo: int | None = None) -> Dual | Dual2:\n        _1 = self._to_dual_type(defaults._global_ad_order)\n        return _1.__pow__(exponent, modulo)\n\n    def __repr__(self) -> str:\n        a = \", \".join(self.vars[:3])\n        b = \", \".join([str(_) for _ in self.dual[:3]])\n        if len(self.vars) > 3:\n            a += \", ...\"\n            b += \", ...\"\n        return f\"<Variable: {self.real:.6}, ({a}), [{b}]>\"\n"
  },
  {
    "path": "python/rateslib/enums/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom rateslib.enums.generics import Err, NoInput, Ok, Result\nfrom rateslib.enums.parameters import (\n    FloatFixingMethod,\n    FXDeltaMethod,\n    FXOptionMetric,\n    IndexMethod,\n    IROptionMetric,\n    LegIndexBase,\n    LegMtm,\n    OptionPricingModel,\n    OptionType,\n    SpreadCompoundMethod,\n    SwaptionSettlementMethod,\n)\n\n__all__ = [\n    \"FloatFixingMethod\",\n    \"SpreadCompoundMethod\",\n    \"IndexMethod\",\n    \"FXDeltaMethod\",\n    \"SwaptionSettlementMethod\",\n    \"FXOptionMetric\",\n    \"IROptionMetric\",\n    \"OptionPricingModel\",\n    \"OptionType\",\n    \"LegMtm\",\n    \"LegIndexBase\",\n    \"NoInput\",\n    \"Result\",\n    \"Ok\",\n    \"Err\",\n]\n"
  },
  {
    "path": "python/rateslib/enums/generics.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nfrom enum import Enum\nfrom typing import Any, Generic, NoReturn, TypeAlias, TypeVar\n\nT = TypeVar(\"T\")\n\n\nclass Err:\n    \"\"\"\n    Standard result class indicating **failure** and containing some *Exception* type.\n    \"\"\"\n\n    _exception: Exception\n\n    def __init__(self, exception: Exception) -> None:\n        self._exception = exception\n\n    def __repr__(self) -> str:\n        return f\"<rl.DelayedException at {hex(id(self))}>\"\n\n    @property\n    def is_err(self) -> bool:\n        return True\n\n    @property\n    def is_ok(self) -> bool:\n        return False\n\n    def unwrap(self) -> NoReturn:\n        raise self._exception\n\n\nclass Ok(Generic[T]):\n    \"\"\"Standard result class indicating **success** and containing some value.\"\"\"\n\n    _value: T\n\n    def __init__(self, value: T) -> None:\n        self._value = value\n\n    def __repr__(self) -> str:\n        return f\"<rl.Result {self._value.__repr__()}>\"\n\n    @property\n    def is_err(self) -> bool:\n        return False\n\n    @property\n    def is_ok(self) -> bool:\n        return True\n\n    def unwrap(self) -> T:\n        return self._value\n\n\nResult: TypeAlias = Ok[T] | Err\n\n\nclass NoInput(Enum):\n    \"\"\"\n    Enumerable type to handle setting default values.\n\n    See :ref:`default values <defaults-doc>`.\n    \"\"\"\n\n    blank = 0\n    inherit = 1\n    negate = -1\n\n\ndef _validate_obj_not_no_input(obj: T | NoInput, expected: str) -> T:\n    if isinstance(obj, NoInput):\n        raise ValueError(f\"Object of type `{expected}` must be supplied. Got NoInput.\")\n    return obj\n\n\ndef _try_validate_obj_not_no_input(obj: T | NoInput, expected: str) -> Result[T]:\n    if isinstance(obj, NoInput):\n        return Err(ValueError(f\"Object of type `{expected}` must be supplied. Got NoInput.\"))\n    else:\n        return Ok(obj)\n\n\ndef _drb(default: Any, possible_ni: Any | NoInput) -> Any:\n    \"\"\"(D)efault (r)eplaces (b)lank\"\"\"\n    return default if isinstance(possible_ni, NoInput) else possible_ni\n"
  },
  {
    "path": "python/rateslib/enums/parameters.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nfrom enum import Enum\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.rs import FloatFixingMethod, IROptionMetric, LegIndexBase\n\nif TYPE_CHECKING:\n    from typing import NoReturn  # TODO: convert to Never on Python >= 3.11\n\n\n# LegIndexBase.__doc__ = \"\"\"\n# Enumerable type for placement of ``index_base_date`` on each *Period* within a *Leg*.\n#\n# .. rubric:: Variants\n#\n# .. ipython:: python\n#    :suppress:\n#\n#    from rateslib.rs import LegIndexBase\n#    variants = [item for item in LegIndexBase.__dict__ if \\\\\n#        \"__\" != item[:2] and \\\\\n#        item not in ['to_json'] \\\n#    ]\n#\n# .. ipython:: python\n#\n#    variants\n#\n# This is a **simple** enum and does not require initialization with other parameters. For example\n#\n# .. ipython:: python\n#\n#    _ = LegIndexBase.Initial\n#\n# \"\"\"\n\n\nclass OptionType(float, Enum):\n    \"\"\"\n    Enumerable type to define option directions.\n    \"\"\"\n\n    Put = -1.0\n    Call = 1.0\n\n\nclass FXOptionMetric(Enum):\n    \"\"\"\n    Enumerable type for FXOption metrics.\n    \"\"\"\n\n    Pips = 0\n    Percent = 1\n\n\nclass OptionPricingModel(Enum):\n    \"\"\"\n    Enumerable type for option pricing models\n    \"\"\"\n\n    Black76 = 0\n    Bachelier = 1\n\n\nclass SwaptionSettlementMethod(Enum):\n    \"\"\"\n    Enumerable type for swaption settlement methods.\n    \"\"\"\n\n    Physical = 0\n    CashParTenor = 1\n    CashCollateralized = 2\n\n\nclass LegMtm(Enum):\n    \"\"\"\n    Enumerable type to define :class:`~rateslib.data.fixings.FXFixing` dates for non-deliverable\n    *Legs*.\n\n    For further information see non-deliverability **Notes** of :class:`~rateslib.legs.FixedLeg`.\n    \"\"\"\n\n    Initial = 0\n    XCS = 1\n    Payment = 2\n\n\nclass IndexMethod(Enum):\n    \"\"\"\n    Enumerable type to define determining the index value on some reference value date.\n\n    Notes\n    -----\n    ``Curve`` variant derives an index value directly from a *Curve* by using its discount factors\n    and its index base date.\n    \"\"\"\n\n    Daily = 0\n    Monthly = 1\n    Curve = 2\n\n    def __str__(self) -> str:\n        return self.name\n\n\nclass SpreadCompoundMethod(Enum):\n    \"\"\"\n    Enumerable type to define spread compounding methods for floating rates.\n    \"\"\"\n\n    NoneSimple = 0\n    ISDACompounding = 1\n    ISDAFlatCompounding = 2\n\n    def __str__(self) -> str:\n        return self.name\n\n\nclass FXDeltaMethod(Enum):\n    \"\"\"\n    Enumerable type to define the delta expression of an FX option.\n    \"\"\"\n\n    Forward = 0\n    Spot = 1\n    ForwardPremiumAdjusted = 2\n    SpotPremiumAdjusted = 3\n\n    def __str__(self) -> str:\n        return self.name\n\n\n_OPTION_PRICING_MAP = {\n    \"black76\": OptionPricingModel.Black76,\n    \"bachelier\": OptionPricingModel.Bachelier,\n    # aliases\n    \"black\": OptionPricingModel.Black76,\n    \"log_normal\": OptionPricingModel.Black76,\n    \"normal\": OptionPricingModel.Bachelier,\n    \"normal_vol\": OptionPricingModel.Bachelier,\n    \"log_normal_vol\": OptionPricingModel.Black76,\n    \"black_vol\": OptionPricingModel.Black76,\n    \"black_vol_shift\": OptionPricingModel.Black76,\n}\n\n\ndef _get_option_pricing_model(\n    method: str | OptionPricingModel,\n) -> OptionPricingModel:\n    if isinstance(method, OptionPricingModel):\n        return method\n    else:\n        try:\n            return _OPTION_PRICING_MAP[method.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"`pricing_model` as string: '{method}' is not a valid option. Please consult docs.\"\n            )\n\n\n_SWAPTION_SETTLEMENT_MAP = {\n    \"physical\": SwaptionSettlementMethod.Physical,\n    \"cash_par_tenor\": SwaptionSettlementMethod.CashParTenor,\n    \"cash_collateralized\": SwaptionSettlementMethod.CashCollateralized,\n    # aliases\n    \"cashcollateralized\": SwaptionSettlementMethod.CashCollateralized,\n    \"cashpartenor\": SwaptionSettlementMethod.CashParTenor,\n}\n\n\ndef _get_swaption_settlement_method(\n    method: str | SwaptionSettlementMethod,\n) -> SwaptionSettlementMethod:\n    if isinstance(method, SwaptionSettlementMethod):\n        return method\n    else:\n        try:\n            return _SWAPTION_SETTLEMENT_MAP[method.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"`swaption_settlement_method` as string: '{method}' is not a valid option. \"\n                f\"Please consult docs.\"\n            )\n\n\n_LEG_MTM_MAP = {\n    \"initial\": LegMtm.Initial,\n    \"xcs\": LegMtm.XCS,\n    \"payment\": LegMtm.Payment,\n}\n\n\ndef _get_leg_mtm(leg_mtm: str | LegMtm) -> LegMtm:\n    if isinstance(leg_mtm, LegMtm):\n        return leg_mtm\n    else:\n        try:\n            return _LEG_MTM_MAP[leg_mtm.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"`mtm` as string: '{leg_mtm}' is not a valid option. Please consult docs.\"\n            )\n\n\n_LEG_INDEX_BASE_MAP = {\n    \"initial\": LegIndexBase.Initial,\n    \"periodonperiod\": LegIndexBase.PeriodOnPeriod,\n    \"period_on_period\": LegIndexBase.PeriodOnPeriod,\n}\n\n\ndef _get_leg_index_base(leg_index: str | LegIndexBase) -> LegIndexBase:\n    if isinstance(leg_index, LegIndexBase):\n        return leg_index\n    else:\n        try:\n            return _LEG_INDEX_BASE_MAP[leg_index.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"`leg_index_base` as string: '{leg_index}' is not a valid option. \"\n                f\"Please consult docs.\"\n            )\n\n\n_INDEX_METHOD_MAP = {\n    \"daily\": IndexMethod.Daily,\n    \"monthly\": IndexMethod.Monthly,\n    \"curve\": IndexMethod.Curve,\n}\n\n\ndef _get_index_method(index_method: str | IndexMethod) -> IndexMethod:\n    if isinstance(index_method, IndexMethod):\n        return index_method\n    else:\n        try:\n            return _INDEX_METHOD_MAP[index_method.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"`index_method` as string: '{index_method}' is not a valid option. \"\n                f\"Please consult docs.\"\n            )\n\n\n_FIXING_METHOD_MAP: dict[str, type[FloatFixingMethod]] = {\n    \"ibor\": FloatFixingMethod.IBOR,\n    \"rfrpaymentdelay\": FloatFixingMethod.RFRPaymentDelay,\n    \"rfrobservationshift\": FloatFixingMethod.RFRObservationShift,\n    \"rfrlockout\": FloatFixingMethod.RFRLockout,\n    \"rfrlookback\": FloatFixingMethod.RFRLookback,\n    \"rfrpaymentdelayaverage\": FloatFixingMethod.RFRPaymentDelayAverage,\n    \"rfrobservationshiftaverage\": FloatFixingMethod.RFRObservationShiftAverage,\n    \"rfrlockoutaverage\": FloatFixingMethod.RFRLockoutAverage,\n    \"rfrlookbackaverage\": FloatFixingMethod.RFRLookbackAverage,\n    # legacy compatibility\n    \"rfr_payment_delay\": FloatFixingMethod.RFRPaymentDelay,\n    \"rfr_observation_shift\": FloatFixingMethod.RFRObservationShift,\n    \"rfr_lockout\": FloatFixingMethod.RFRLockout,\n    \"rfr_lookback\": FloatFixingMethod.RFRLookback,\n    \"rfr_payment_delay_avg\": FloatFixingMethod.RFRPaymentDelayAverage,\n    \"rfr_observation_shift_avg\": FloatFixingMethod.RFRObservationShiftAverage,\n    \"rfr_lockout_avg\": FloatFixingMethod.RFRLockoutAverage,\n    \"rfr_lookback_avg\": FloatFixingMethod.RFRLookbackAverage,\n}\n\n\ndef _get_float_fixing_method(method: str | FloatFixingMethod) -> FloatFixingMethod:\n    if isinstance(method, FloatFixingMethod):\n        return method\n    else:\n        if method.lower() in [\"rfrpaymentdelay\", \"rfr_payment_delay\"]:\n            return FloatFixingMethod.RFRPaymentDelay()\n        elif method.lower() in [\"rfrpaymentdelayaverage\", \"rfr_payment_delay_avg\"]:\n            return FloatFixingMethod.RFRPaymentDelayAverage()\n\n        if not (\"(\" in method and method[-1] == \")\"):\n            raise ValueError(\n                f\"`fixing_method` as string: '{method}' must have an associated parameter \"\n                f\"contained in parentheses, for example 'ibor(2)' or 'rfr_observation_shift(5)'. \"\n            )\n\n        method_, number_part = method[:-1].split(\"(\")\n        number = int(number_part)\n        try:\n            enum_ = _FIXING_METHOD_MAP[method_.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"`fixing_method` as string: '{method_}' is not a valid FloatFixingMethod.\"\n            )\n        return enum_(number)  # type: ignore[call-arg]\n\n\n_SPREAD_COMPOUNDING_METHOD_MAP = {\n    \"nonesimple\": SpreadCompoundMethod.NoneSimple,\n    \"isdacompounding\": SpreadCompoundMethod.ISDACompounding,\n    \"isdaflatcompounding\": SpreadCompoundMethod.ISDAFlatCompounding,\n    # legacy compatibility\n    \"none_simple\": SpreadCompoundMethod.NoneSimple,\n    \"isda_compounding\": SpreadCompoundMethod.ISDACompounding,\n    \"isda_flat_compounding\": SpreadCompoundMethod.ISDAFlatCompounding,\n}\n\n\ndef _get_spread_compound_method(method: str | SpreadCompoundMethod) -> SpreadCompoundMethod:\n    if isinstance(method, SpreadCompoundMethod):\n        return method\n    else:\n        try:\n            return _SPREAD_COMPOUNDING_METHOD_MAP[method.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"`spread_compound_method` as string: '{method}' is not a valid option. \"\n                f\"Please consult docs.\"\n            )\n\n\n_FX_DELTA_TYPE_MAP = {\n    \"forward\": FXDeltaMethod.Forward,\n    \"spot\": FXDeltaMethod.Spot,\n    \"forward_pa\": FXDeltaMethod.ForwardPremiumAdjusted,\n    \"spot_pa\": FXDeltaMethod.SpotPremiumAdjusted,\n    \"forwardpremkiumadjusted\": FXDeltaMethod.ForwardPremiumAdjusted,\n    \"spotpremiumadjusted\": FXDeltaMethod.SpotPremiumAdjusted,\n}\n\n\ndef _get_fx_delta_type(method: str | FXDeltaMethod) -> FXDeltaMethod:\n    if isinstance(method, FXDeltaMethod):\n        return method\n    else:\n        try:\n            return _FX_DELTA_TYPE_MAP[method.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"`delta_type` as string: '{method}' is not a valid option. Please consult docs.\"\n            )\n\n\n_FX_METRIC_MAP = {\n    \"pips\": FXOptionMetric.Pips,\n    \"percent\": FXOptionMetric.Percent,\n}\n\n\ndef _get_fx_option_metric(method: str | FXOptionMetric) -> FXOptionMetric:\n    if isinstance(method, FXOptionMetric):\n        return method\n    else:\n        try:\n            return _FX_METRIC_MAP[method.lower()]\n        except KeyError:\n            raise ValueError(\n                f\"FXOption `metric` as string: '{method}' is not a valid option. Please consult \"\n                f\"docs.\"\n            )\n\n\n_IR_METRIC_MAP: dict[str, type[IROptionMetric]] = {\n    \"normal_vol\": IROptionMetric.NormalVol,\n    \"premium\": IROptionMetric.Premium,\n    \"percent_notional\": IROptionMetric.PercentNotional,\n    \"black_vol_shift\": IROptionMetric.BlackVolShift,\n    # aliases\n    \"normalvol\": IROptionMetric.NormalVol,\n    \"percentnotional\": IROptionMetric.PercentNotional,\n    \"blackvolshift\": IROptionMetric.BlackVolShift,\n}\n\n\ndef _get_ir_option_metric(method: str | IROptionMetric) -> IROptionMetric:\n    if isinstance(method, IROptionMetric):\n        return method\n    else:\n        method = method.lower()\n        if \"shift\" in method:\n            idx = method.rfind(\"_\")\n            if idx < 0:\n                raise ValueError(\n                    \"The 'BlackVolShift' metric must have an underscore and shift, e.g. \"\n                    \"'black_vol_shift_100\"\n                )\n            else:\n                args: tuple[NoReturn, ...] | tuple[int] = (int(method[idx + 1 :]),)\n            method = method[:idx]\n        else:\n            args = tuple()\n\n        try:\n            return _IR_METRIC_MAP[method](*args)\n        except KeyError:\n            raise ValueError(\n                f\"IROption `metric` as string: '{method}' is not a valid option. Please consult \"\n                f\"documentation.\"\n            )\n\n\n__all__ = [\n    \"SpreadCompoundMethod\",\n    \"FloatFixingMethod\",\n    \"FXDeltaMethod\",\n    \"FXOptionMetric\",\n    \"IROptionMetric\",\n    \"LegMtm\",\n    \"LegIndexBase\",\n    \"OptionType\",\n    \"OptionPricingModel\",\n    \"IndexMethod\",\n]\n"
  },
  {
    "path": "python/rateslib/errors.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n# Arg Parsing\n\nVE_NEEDS_FREQUENCY = \"`frequency` as string or Frequency is needed to perform tenor calculations.\"\n\nVE_NEEDS_FIXEDRATE = \"A `fixed_rate` must be set for a cashflow to be determined.\"\n\nVE_ATTRIBUTE_IS_IMMUTABLE = (\n    \"The '{}' attribute is immutable to avoid conflicting calculations. Re-initialize the instance.\"\n)\n\nVE_ND_LEG_NEEDS_NO_EXCHANGES = (\n    \"An Leg defined as non-deliverable by some parameter, e.g. `pair` cannot have \"\n    \"notional exchanges.\"\n)\n\nVE_PAIR_AND_LEG_MTM = \"Setting `mtm` on a Leg requires a non-deliverable `pair` input.\"\n\n# Curve Parsing\n\nNI_NO_DISC_FROM_DICT = \"`disc_curve` cannot currently be parsed from a dictionary of curves.\"\n\nVE_NEEDS_DISC_CURVE = (\n    \"`disc_curve` is required but it has not been provided, or cannot be parsed from an external \"\n    \"`curves` argument.\"\n)\n\nVE_NO_DISC_FROM_VALUES = \"`disc_curve` cannot be inferred from a non-DF based curve.\"\n\nVE_BEFORE_INITIAL = \"The Curve initial node date is after the required forecasting date.\"\n\n# Period Parameters\n\nVE_NEEDS_INDEX_PARAMS = (\n    \"`{0}` must be initialised with index parameters, i.e. those for `_IndexParams`. See docs.\"\n)\n\nVE_HAS_INDEX_PARAMS = (\n    \"`{0}` must not be initialised with index parameters, i.e. those for `_IndexParams`. See docs.\"\n)\n\nVE_NEEDS_ND_CURRENCY_PARAMS = (\n    \"`{0}` must be initialised with non-deliverable currency parameters, i.e. those for \"\n    \"`_CurrencyParams`. See docs.\"\n)\n\nVE_HAS_ND_CURRENCY_PARAMS = (\n    \"`{0}` must not be initialised with non-deliverable currency parameters, i.e. those for \"\n    \"`_CurrencyParams`. See docs.\"\n)\n\nVE_MISMATCHED_FX_PAIR_ND_PAIR = (\n    \"Non-deliverable FXOptions into a third currency are not allowed.\\n\"\n    \"Got nd-currency: '{0}' and option index pair: '{1}'.\\n\"\n    \"FXOptions of this nature require quanto volatility adjustements that the basic models\"\n    \"do not include.\"\n)\n\n# Fixings\n\n# Tenors are now derived from a `fixing_series` and not a fixings timeseries\n# UW_NO_TENORS = (\n#     \"The IBORStubFixing has not detected any tenors under the identifier: '{0}' and \"\n#     \"will therefore never obtain any fixing value.\"\n# )\n\nTE_NO_FIXING_EXPOSURE_ON_OBJ = (\n    \"The object type '{0}' does not contain or have available methods to calculate fixings \"\n    \"exposure.\"\n)\n\nVE01_1 = (\n    \"Fixing data for the index '{0}' has been attempted, but none found.\\nEither there \"\n    \"is no data file ('{0}.csv') located in the searched data directory,\\nor a Series \"\n    \"has not been added manually by performing `fixings.add\"\n    \"('{0}', some_series)`.\\nTo create a CSV file in the searched data directory \"\n    \"use the exact template structure for the file between the hashes:\\n\"\n    \"###################\\n\"\n    \"reference_date,rate\\n26-08-2023,5.6152\\n27-08-2023,5.6335\\n##################\\n\"\n    \"For further info see 'Working with Fixings' in the documentation cookbook.\",\n)\n\nAE_NEEDS_PAIR_TO_FORECAST = (\n    \"A currency `pair` is required for non-deliverable `fx_fixing` forecasting.\"\n)\n\nVE_NEEDS_FX_FORWARDS = (\n    \"An FXForwards object for `fx` is required for instrument pricing.\\n\"\n    \"If this instrument is part of a Solver, have you omitted the `fx` input?\",\n)\n\nVE_NEEDS_FX_FORWARDS_BAD_TYPE = (\n    \"An FXForwards object for `fx` is required for instrument pricing.\\n\"\n    \"The given type, '{0}', cannot be used here.\"\n)\n\nFW_FIXINGS_AS_SERIES = (\n    \"Setting any `fixings` argument as a Series directly is currently supported, but not \"\n    \"recommended and may be removed in future versions.\\n\"\n    \"Best practice is to add the fixings object to the default _BaseFixingsLoader and then \"\n    \"reference that object by Series name.\\n\"\n    \"For example, change: `rate_fixings`=my_series_object` to\\n\"\n    \"`fixings.add('EURIBOR_3M', my_series_object)`\\n\"\n    \"`fixings.add('EURIBOR_6M', another_series_object)`\\n\"\n    \"`rate_fixings='EURIBOR'`\\n\"\n    \"See cookbook article 'Working with Fixings' for more information.\"\n)\n\nVE_INDEX_FIXINGS_AS_STR_OR_VALUE = (\n    \"`index_fixings` must be specified either as a scalar value or a string identifier for a \"\n    \"fixings set in the _BaseFixingsLoader. Got type: {0}.\"\n)\n\nVE_INDEX_LAG_MUST_BE_ZERO = (\n    \"`index_lag` must be zero when using a 'Curve' `index_method`.\\n\"\n    \"`index_date`: {0}, is in Series but got `index_lag`: {1}.\"\n)\n\nVE_EMPTY_SERIES = \"An fixing value cannot be derived from an `fixings` Series having no entries.\"\n\nVE_INDEX_BASE_NO_STR = (\n    \"`index_base` argument cannot be initialised as string.\\n If seeking to determine its \"\n    \"value with a Fixings series then do not provide any `index_base` value and use \"\n    \"`index_fixings` instead.\\nOr use the 'index_value' method to separately determine a \"\n    \"scalar value to enter directly as the `index_base` argument.\"\n)\n\n# VE_NEEDS_INDEX_BASE_DATE = (\n#     \"An `index_base` forecast value requires an `index_base_date` to be provided.\"\n# )\n\n# 08: periods/components/parameters.py\n\nVE08_0 = (\n    \"The `index_base` is not an explicitly provided value for the Period.\\n\"\n    \"`index_base_date` must therefore be provided to forecast `index_base` from an `index_curve` \"\n    \"or `index_fixings`.\"\n)\n\nVE08_1 = (\n    \"Must supply an `index_date` from which to forecast if `index_fixings` is not provided.\\n\"\n    \"This error usually arises when an `index_base` value is not provided for a Period and \"\n    \"there is no `index_base_date`,\\nor if there are no `index_fixings` and there is no \"\n    \"`index_reference_date` is combination.\"\n)\n\n\nVE_NEEDS_STRIKE = \"An FXOptionPeriod cashflow cannot be determined without setting a `strike`.\"\n\n\n# VE_NEEDS_FIXING_SERIES = (\n#     \"A `fixing_series` must be supplied for floating rate parameters.\"\n# )\n\n# VE_NEEDS_FIXING_FREQUENCY = \"A `fixing_frequency` must be supplied for floating rate parameters.\"\n\n# 02: periods/components/float_rate.py\n\nVE_NEEDS_RATE_CURVE = \"A `rate_curve` must be provided to this method.\"\n\nVE_MISMATCHED_ND_PAIR = (\n    \"A non-deliverable pair must contain the settlement currency.\\nGot '{0}' and '{1}'.\"\n)\n\nMISMATCH_RATE_INDEX_PARAMETERS = (\n    \"A `rate_curve` and `rate_index` have been supplied with conflicting parameters.\\n\"\n    \"Specifically for the attribute '{0}'\\n\"\n    \"Got: '{1}' and '{2}'.\"\n)\n\nVE_NEEDS_CURVE_OR_INDEX = (\n    \"Either `rate_curve` or `rate_index` must be provided so that the \"\n    \"conventions for the floating rate, such as the fixing calendar and the accrual \"\n    \"convention can be determined.\"\n)\n\nVE_NEEDS_RATE_TO_FORECAST_RFR = (\n    \"A `rate_curve` is required to forecast missing RFR rates.\\n\"\n    \"This may be observed as a direct argument input, or this error may by a result of \"\n    \"incorrectly supplying the `curves` argument to any Instrument class.\"\n)\n\nVE_NEEDS_RATE_TO_FORECAST_STUB_IBOR = (\n    \"A `rate_curve` is required to forecast missing IBOR rate.\\n\"\n    \"`rate_curve` might be specifically omitted or an external `curves` argument may be \"\n    \"malformed.\\nNote that forecasting an IBOR stub from a single curve is bad practice and \"\n    \"a more accurate calculation will likely be obtained from a dict of curves, e.g.\\n\"\n    \"'{'1m': curve1, '3m': curve2, '6m': curve3}'\"\n)\n\nVE_NEEDS_RATE_TO_FORECAST_TENOR_IBOR = (\n    \"A `rate_curve` is required to forecast missing IBOR rate.\\n\"\n    \"`rate_curve` might be specifically omitted or an external `curves` argument may be \"\n    \"malformed.\"\n)\n\nVE_FIXINGS_BAD_TYPE = (\n    \"`.._fixings` should be a single value or a string labelling a fixing set in the \"\n    \"`fixings` container. It cannot be a list or Series.\\n\"\n    \"To migrate from the legacy implementation where a Series could be supplied directly \"\n    \"use the following:\\nAdd your Series to defaults: `default.fixings.add('EURIBOR_3M', \"\n    \"my_series_obj)`\\nAnd then reference this fixing set directly: `rate_fixings='EURIBOR'`.\\nThe\"\n    \"suffix '_3M' will be added directly internally (based on the Frequency) and will adjust for \"\n    \"stub fixings. RFR fixings will have the '_1B' suffix added, so use for example:\\n\"\n    \"Add an RFR Series: `fixings.add('SOFR_1B', my_series_obj)`\\n\"\n    \"And reference this set directly: `rate_fixings='SOFR'`.\\n\"\n    \"For further details see the cookbook documentation entitled 'Working with Fixings'.\"\n)\n\nVE02_1 = (\n    \"RFR Observation and Accrual DCF dates do not align.\\nThis is usually the result of a \"\n    \"'rfr_lookback' Period which does not adhere to the holiday calendar of the `curve`.\\n\"\n    \"start date: {0} is curve holiday? => {1}\\nend date: {2} is curve holiday? => {3}\\n\"\n)\nVE02_2 = (\n    \"The accrual `start` and `end` dates ({0} and/or {1}) for the period do not align with \"\n    \"business days under the `fixing_calendar`.\\nRFR Periods need to align with valid fixing\"\n    \"days.\"\n)\nVE02_3 = (\n    \"Providing `rate_fixings` as a scalar value for an RFR type `fixing_method` is not \"\n    \"permitted due to ambiguity, particularly in combination with the `float_spread`.\\n\"\n    \"Consider adding a Series to `defaults`: `fixings.add('MY_RFR_1B', \"\n    \"some_series)`\\nAnd then referencing this fixings collection: `rate_fixings='MY_RFR'\\n\"\n    \"For an RFR type fixing method the suffix added internally is always '_1B'.\"\n)\n\nVE_SPREAD_METHOD_RFR = (\n    \"The `spread_compound_method` must be the 'NoneSimple' variant when using a \"\n    \"`fixing_method` which defines an RFR Averaging type calculation.\\nGot: {0}\"\n)\n\nVE02_5 = (\n    \"The fixings series '{0}' for the RFR 1B rates is missing a value expected by the fixings \"\n    \"calendar.\\n\"\n    \"Specifically '{1}' is expected, yet '{2}' is provided implying a data entry is missing.\"\n)\n\nVE_NEEDS_RATE_POPULATE_FIXINGS = (\n    \"A `rate_curve` is required to forecast missing RFR fixings in a floating rate calculation.\\n\"\n    \"This may be a direct input or the input to an Instrument's `curves` argument may be incorrect.\"\n    \"\\nThe missing data is shown below for this calculation:\\n\"\n    \"{0}\"\n)\n\nVE_LOCKOUT_METHOD_PARAM = (\n    \"The `method_param` for an RFR Lockout type `fixing_method` must not exceed the length of the \"\n    \"period.\\nGot: '{0}' for the following fixing rates:\\n{1}\"\n)\n\nW02_0 = (\n    \"The fixings series '{0}' for the RFR 1B rates contains more fixings than are expected from \"\n    \"the fixings calendar.\\n\"\n    \"Specifically, the extra data item lies within the fixings window: '{1}':'{2}'.\"\n)\n"
  },
  {
    "path": "python/rateslib/fx/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom rateslib.fx.fx_forwards import FXForwards, forward_fx\nfrom rateslib.fx.fx_rates import FXRates\n\n__all__ = (\"FXForwards\", \"forward_fx\", \"FXRates\")\n"
  },
  {
    "path": "python/rateslib/fx/fx_forwards.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport json\nimport warnings\nfrom dataclasses import replace\nfrom datetime import datetime, timedelta\nfrom itertools import combinations, product\nfrom typing import TYPE_CHECKING, Any, TypeAlias\n\nimport numpy as np\nfrom pandas import DataFrame, Series\n\nfrom rateslib import defaults\nfrom rateslib.curves import Curve, MultiCsaCurve, ProxyCurve\nfrom rateslib.curves.utils import _CurveType\nfrom rateslib.data.fixings import FXIndex\nfrom rateslib.default import PlotOutput, plot\nfrom rateslib.dual import Dual, Dual2, Variable, gradient\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.fx.fx_rates import FXRates\nfrom rateslib.mutability import (\n    _clear_cache_post,\n    _new_state_post,\n    _validate_states,\n    _WithCache,\n    _WithState,\n)\nfrom rateslib.scheduling import add_tenor\n\nif TYPE_CHECKING:\n    from rateslib.local_types import Number, _BaseCurve, datetime_\nDualTypes: TypeAlias = (\n    \"Dual | Dual2 | Variable | float\"  # required for non-cyclic import on _WithCache\n)\n\n\n\"\"\"\n.. ipython:: python\n   :suppress:\n\n   from rateslib.curves import Curve\n   from datetime import datetime as dt\n\"\"\"\n\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n\nclass FXForwards(_WithState, _WithCache[tuple[str, datetime], DualTypes]):\n    \"\"\"\n    Class for storing and calculating FX forward rates.\n\n    Parameters\n    ----------\n    fx_rates : FXRates, or list of such\n        An ``FXRates`` object with an associated settlement date. If multiple settlement\n        dates are relevant, e.g. GBPUSD (T+2) and USDCAD(T+1), then a list of\n        ``FXRates`` object is allowed to create a no arbitrage framework.\n    fx_curves : dict\n        A dict of DF ``Curve`` objects defined by keys of two currency labels. First, by\n        the currency in which cashflows occur (3-digit code), combined with the\n        currency by which the future cashflow is collateralised in a derivatives sense\n        (3-digit code). There must also be a curve in each currency for\n        local discounting, i.e. where the cashflow and collateral currency are the\n        same. See examples.\n    base : str, optional\n        The base currency (3-digit code). If not given defaults to the base currency\n        of the first ``fx_rates`` object.\n\n    Notes\n    -----\n\n    .. math::\n\n       f_{DOMFOR,i} &= \\\\text{Forward domestic-foreign FX rate fixing on maturity date, }m_i \\\\\\\\\n       F_{DOMFOR,0} &= \\\\text{Immediate settlement market domestic-foreign FX rate} \\\\\\\\\n       v_{dom:dom,i} &= \\\\text{Local domestic-currency DF on maturity date, }m_i \\\\\\\\\n       w_{dom:for,i} &= \\\\text{XCS adjusted domestic-currency DF on maturity date, }m_i \\\\\\\\\n\n    Examples\n    --------\n    The most basic ``FXForwards`` object is created from a spot ``FXRates`` object and\n    two local currency discount curves.\n\n    .. ipython:: python\n\n       from rateslib.fx import FXRates, FXForwards\n       from rateslib.curves import Curve\n\n    .. ipython:: python\n\n       fxr = FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 3))\n       eur_local = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.91})\n       usd_local = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.95})\n       fxf = FXForwards(fxr, {\"usdusd\": usd_local, \"eureur\": eur_local, \"eurusd\": eur_local})\n\n    Note that in the above the ``eur_local`` curve has also been used as the curve\n    for EUR cashflows collateralised in USD, which is necessary for calculation\n    of forward FX rates and cross-currency basis. With this assumption the\n    cross-currency basis is implied to be zero at all points along the curve.\n\n    Attributes\n    ----------\n    fx_rates : FXRates or list\n    fx_curves : dict\n    immediate : datetime\n    currencies: dict\n    q : int\n    currencies_list : list\n    transform : ndarray\n    base : str\n    fx_rates_immediate : FXRates\n    \"\"\"\n\n    _mutable_by_association = True\n\n    # @_new_state_post # handled internally\n    @_clear_cache_post\n    def update(self, fx_rates: list[dict[str, float]] | NoInput = NoInput(0)) -> None:\n        \"\"\"\n        Update the FXForward object with the latest FX rates and FX curves values.\n\n        The update method is primarily used to allow synchronous updating within a\n        ``Solver``.\n\n        Parameters\n        ----------\n        fx_rates: list of dict, optional\n            A list of dictionaries with new rates to update the associated\n            :class:`~rateslib.fx.FXRates` objects associated with the *FXForwards* object.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n        An *FXForwards* object contains associations to external objects, those being\n        :class:`~rateslib.fx.FXRates` and :class:`~rateslib.curves.Curve`, and its purpose is\n        to be able to combine those objects to yield FX forward rates.\n\n        When those external objects have themselves been updated the *FXForwards* class\n        will detect this via *rateslib's* cache management and will automatically update\n        the *FXForwards* object. Manually calling this update on the *FXForwards* class\n        also allows those associated *FXRates* classes to be updated with new market data.\n\n        .. ipython:: python\n\n           fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3), base=\"usd\")\n           fx_curves = {\n               \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.965}),\n               \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n               \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n           }\n           fxf = FXForwards(fxr, fx_curves)\n           fxf.rate(\"eurusd\", dt(2022, 8, 15))\n\n        .. ipython:: python\n\n           fxr.update({\"eurusd\": 2.0})  # <-- update the associated FXRates object.\n           fxf.rate(\"eurusd\", dt(2022, 8, 15))  # <-- rate has changed, fxf has auto-updated.\n\n        It is possible to update an *FXRates* object directly from the *FXForwards* object, via\n        the ``fx_rates`` argument.\n\n        .. ipython:: python\n\n           fxf.update([{\"eurusd\": 1.50}])\n           fxf.rate(\"eurusd\", dt(2022, 8, 15))\n\n        The :class:`~rateslib.solver.Solver` also automatically updates *FXForwards* objects\n        when it mutates and solves the *Curves*.\n        \"\"\"\n        # does not require cache validation because resets the cache_id at end of method.\n        if not isinstance(fx_rates, NoInput):\n            self_fx_rates = self.fx_rates if isinstance(self.fx_rates, list) else [self.fx_rates]\n            if not isinstance(fx_rates, list) or len(self_fx_rates) != len(fx_rates):\n                raise ValueError(\n                    \"`fx_rates` must be a list of dicts with length equal to the number of FXRates \"\n                    f\"objects associated with the *FXForwards* object: {len(self_fx_rates)}.\"\n                )\n            for fxr_obj, fxr_up in zip(self_fx_rates, fx_rates, strict=True):\n                fxr_obj.update(fxr_up)\n\n        if self._state != self._get_composited_state():\n            self._calculate_immediate_rates(base=self.base, init=False)\n            self._set_new_state()\n\n    @_new_state_post\n    @_clear_cache_post\n    def __init__(\n        self,\n        fx_rates: FXRates | list[FXRates],\n        fx_curves: dict[str, _BaseCurve],\n        base: str | NoInput = NoInput(0),\n    ) -> None:\n        self._ad = 1\n        self._validate_fx_curves(fx_curves)\n        self._fx_proxy_curves: dict[str, ProxyCurve] = {}\n        self.fx_rates: FXRates | list[FXRates] = fx_rates\n        self._calculate_immediate_rates(base, init=True)\n        assert self.currencies_list == self.fx_rates_immediate.currencies_list  # noqa: S101\n\n    @property\n    def fx_proxy_curves(self) -> dict[str, ProxyCurve]:\n        \"\"\"\n        A dict of cached :class:`~rateslib.curves.ProxyCurve` associated with this object.\n        \"\"\"\n        return self._fx_proxy_curves\n\n    def _get_composited_state(self) -> int:\n        self_fx_rates = [self.fx_rates] if not isinstance(self.fx_rates, list) else self.fx_rates\n        total = sum(curve._state for curve in self.fx_curves.values()) + sum(\n            fxr._state for fxr in self_fx_rates\n        )\n        return hash(total)\n\n    def _validate_state(self) -> None:\n        if self._state != self._get_composited_state():\n            self.update()\n\n    def _validate_fx_curves(self, fx_curves: dict[str, _BaseCurve]) -> None:\n        self.fx_curves: dict[str, _BaseCurve] = {k.lower(): v for k, v in fx_curves.items()}\n\n        self.terminal: datetime = datetime(2200, 1, 1)\n        for flag, (k, curve) in enumerate(self.fx_curves.items()):\n            try:  # to label curve meta with collateral\n                curve._meta = replace(curve._meta, _collateral=k[3:6])  # type: ignore[misc]\n            except AttributeError:\n                if curve._meta.collateral is not None and curve._meta.collateral != k[3:6]:\n                    warnings.warn(\n                        \"Constructing an FXForwards with curve operation objects is possible.\\n\"\n                        \"However, these objects reference other curve meta data, and a collateral \"\n                        f\"clash has been detected.\\n \"\n                        f\"Curve.meta.collateral: '{curve._meta.collateral}'\\n\"\n                        f\"Actual collateral: '{k[3:6]}'\",\n                        UserWarning,\n                    )\n                else:\n                    # collateral is None so ignore, or it is correct anyway so pass\n                    pass\n\n            if flag == 0:\n                self.immediate: datetime = curve.nodes.keys[0]\n            elif self.immediate != curve.nodes.keys[0]:\n                raise ValueError(\"`fx_curves` do not have the same initial date.\")\n            if curve._base_type == _CurveType.values:\n                raise TypeError(\"`fx_curves` must be DF based, not type LineCurve.\")\n            if curve.nodes.final < self.terminal:\n                self.terminal = curve.nodes.final\n\n    def _calculate_immediate_rates(self, base: str | NoInput, init: bool) -> None:\n        if not isinstance(self.fx_rates, list):\n            # if in initialisation phase (and not update phase) populate immutable values\n            if init:\n                self.currencies = self.fx_rates.currencies\n                self.q = len(self.currencies.keys())\n                self.currencies_list: list[str] = self.fx_rates.currencies_list\n                self.transform = _get_curves_indicator_array(\n                    self.q,\n                    self.currencies,\n                    self.fx_curves,\n                )\n                self._paths = _create_initial_mapping(self.transform)\n                self.base: str = self.fx_rates.base if isinstance(base, NoInput) else base\n                self.pairs = self.fx_rates.pairs\n                self.variables = tuple(f\"fx_{pair}\" for pair in self.pairs)\n                self.pairs_settlement = self.fx_rates.pairs_settlement\n            self.fx_rates_immediate = self._calculate_immediate_rates_same_settlement_frame()\n        else:\n            # Get values for the first FXRates in the list\n            sub_curves = self._get_curves_for_currencies(\n                self.fx_curves,\n                self.fx_rates[0].currencies_list,\n            )\n            acyclic_fxf: FXForwards = FXForwards(\n                fx_rates=self.fx_rates[0],\n                fx_curves=sub_curves,\n            )\n            settlement_pairs = dict.fromkeys(self.fx_rates[0].pairs, self.fx_rates[0].settlement)\n\n            # Now iterate through the remaining FXRates objects and patch them into the fxf\n            for fx_rates_obj in self.fx_rates[1:]:\n                # create sub FXForwards for each FXRates instance and re-combine.\n                # This reuses the arg validation of a single FXRates object and\n                # dependency of FXRates with fx_curves.\n\n                # calculate additional FX rates from previous objects\n                # in the same settlement frame.\n                overlapping_currencies = [\n                    ccy\n                    for ccy in fx_rates_obj.currencies_list\n                    if ccy in acyclic_fxf.currencies_list\n                ]\n                pre_currencies = [\n                    ccy\n                    for ccy in acyclic_fxf.currencies_list\n                    if ccy not in fx_rates_obj.currencies_list\n                ]\n                pre_rates = {\n                    f\"{overlapping_currencies[0]}{ccy}\": acyclic_fxf._rate_without_validation(\n                        f\"{overlapping_currencies[0]}{ccy}\",\n                        fx_rates_obj.settlement,\n                    )\n                    for ccy in pre_currencies\n                }\n                combined_fx_rates = FXRates(\n                    fx_rates={**fx_rates_obj.fx_rates, **pre_rates},\n                    settlement=fx_rates_obj.settlement,\n                )\n                sub_curves = self._get_curves_for_currencies(\n                    self.fx_curves,\n                    fx_rates_obj.currencies_list + pre_currencies,\n                )\n                acyclic_fxf = FXForwards(fx_rates=combined_fx_rates, fx_curves=sub_curves)\n                settlement_pairs.update(\n                    dict.fromkeys(fx_rates_obj.pairs, fx_rates_obj.settlement),\n                )\n\n            if not isinstance(base, NoInput):\n                acyclic_fxf.base = base.lower()\n\n            for attr in [\n                \"currencies\",\n                \"q\",\n                \"currencies_list\",\n                \"transform\",\n                \"base\",\n                \"fx_rates_immediate\",\n                \"pairs\",\n                \"_paths\",\n            ]:\n                setattr(self, attr, getattr(acyclic_fxf, attr))\n            self.pairs_settlement = settlement_pairs\n\n    def _calculate_immediate_rates_same_settlement_frame(self) -> FXRates:\n        \"\"\"\n        Calculate the immediate FX rates values given current Curves and input FXRates obj.\n\n        Notes\n        -----\n        Searches the non-diagonal elements of transformation matrix, once it has\n        found a pair uses the relevant curves and the FX rate to determine the\n        immediate FX rate for that pair.\n        \"\"\"\n        # this method can only be performed on an FXForwards object that is associated to a\n        # single FXRates obj (hence the use of the acyclic_fxf)\n        # since this is an internal method this line is used for testing\n        assert not isinstance(self.fx_rates, list)  # noqa: S101\n\n        fx_rates_immediate: dict[str, DualTypes] = {}\n        for row in range(self.q):\n            for col in range(self.q):\n                if row == col or self.transform[row, col] == 0:\n                    continue\n                cash_ccy = self.currencies_list[row]\n                coll_ccy = self.currencies_list[col]\n                settlement = self.fx_rates.settlement\n                if isinstance(settlement, NoInput) or settlement is None:\n                    raise ValueError(\n                        \"`fx_rates` as FXRates supplied to FXForwards must contain a \"\n                        \"`settlement` argument.\",\n                    )\n                v_i = self.fx_curves[f\"{coll_ccy}{coll_ccy}\"][settlement]\n                v_0 = self.fx_curves[f\"{coll_ccy}{coll_ccy}\"][self.immediate]\n                w_i = self.fx_curves[f\"{cash_ccy}{coll_ccy}\"][settlement]\n                w_0 = self.fx_curves[f\"{cash_ccy}{coll_ccy}\"][self.immediate]\n                pair = f\"{cash_ccy}{coll_ccy}\"\n                fx_rates_immediate.update(\n                    {pair: self.fx_rates.fx_array[row, col] * v_i / w_i * w_0 / v_0}\n                )\n\n        fx_rates_immediate_ = FXRates(fx_rates_immediate, self.immediate, self.currencies_list[0])\n        return fx_rates_immediate_.restate(self.fx_rates.pairs, keep_ad=True)\n\n    def __repr__(self) -> str:\n        if len(self.currencies_list) > 5:\n            return (\n                f\"<rl.FXForwards:[{','.join(self.currencies_list[:2])},\"\n                f\"+{len(self.currencies_list) - 2} others] at {hex(id(self))}>\"\n            )\n        else:\n            return f\"<rl.FXForwards:[{','.join(self.currencies_list)}] at {hex(id(self))}>\"\n\n    @staticmethod\n    def _get_curves_for_currencies(\n        fx_curves: dict[str, _BaseCurve], currencies: list[str]\n    ) -> dict[str, _BaseCurve]:\n        \"\"\"produces a complete subset of fx curves given a list of currencies\"\"\"\n        ps = product(currencies, currencies)\n        ret = {p[0] + p[1]: fx_curves[p[0] + p[1]] for p in ps if p[0] + p[1] in fx_curves}\n        return ret\n\n    # Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n    # Commercial use of this code, and/or copying and redistribution is prohibited.\n    # Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n    @_validate_states\n    def rate(\n        self,\n        pair: FXIndex | str,\n        settlement: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return the fx forward rate for a currency pair.\n\n        Parameters\n        ----------\n        pair : FXIndex, str\n            The FX pair in usual domestic:foreign convention (6 digit code).\n        settlement : datetime, optional\n            The settlement date of currency exchange. If not given defaults to\n            immediate settlement.\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Notes\n        -----\n        Uses the formula,\n\n        .. math::\n\n           f_{DOMFOR, i} = \\\\frac{w_{dom:for, i}}{v_{for:for, i}} F_{DOMFOR,0} = \\\\frac{v_{dom:dom, i}}{w_{for:dom, i}} F_{DOMFOR,0}\n\n        where :math:`v` is a local currency discount curve and :math:`w` is a discount\n        curve collateralised with an alternate currency.\n\n        If required curves do not exist in the relevant currencies then forwards rates are chained\n        using those calculable from available curves. The chain is found using a search algorithm.\n\n        .. math::\n\n           f_{DOMFOR, i} = f_{DOMALT, i} ...  f_{ALTFOR, i}\n\n        \"\"\"  # noqa: E501\n        if isinstance(pair, FXIndex):\n            pair = pair.pair\n\n        return self._rate_without_validation(pair, settlement)\n\n    def _rate_without_validation(self, pair: str, settlement: datetime_ = NoInput(0)) -> DualTypes:\n        settlement_: datetime = _drb(self.immediate, settlement)\n        if defaults.curve_caching and (pair, settlement_) in self._cache:\n            return self._cache[(pair, settlement_)]\n\n        if settlement_ < self.immediate:\n            raise ValueError(\"`settlement` cannot be before immediate FX rate date.\")\n\n        if settlement_ == self.immediate:\n            # get FX rate directly from the immediate object\n            return self._cached_value((pair, settlement_), self.fx_rates_immediate.rate(pair))\n        elif isinstance(self.fx_rates, FXRates) and settlement_ == self.fx_rates.settlement:\n            # get FX rate directly from the spot object\n            return self._cached_value((pair, settlement_), self.fx_rates.rate(pair))\n\n        ccy_lhs = pair[0:3].lower()\n        ccy_rhs = pair[3:6].lower()\n        if ccy_lhs == ccy_rhs:\n            return 1.0  # then return identity\n\n        if (self.currencies[ccy_lhs], self.currencies[ccy_rhs]) not in self._paths:\n            # then paths have not been recursively determined, so determine them and cache now.\n            self._paths = _recursive_pair_population(self.transform, self._paths)[1]\n\n        via_idx = self._paths[(self.currencies[ccy_lhs], self.currencies[ccy_rhs])]\n        if via_idx == -1:\n            # then a rate is directly available\n            return self._rate_direct(ccy_lhs, ccy_rhs, settlement_)\n        else:\n            # recursively determine from FX-crosses\n            via_ccy = self.currencies_list[via_idx]\n            ret = self.rate(f\"{ccy_lhs}{via_ccy}\", settlement_) * self.rate(\n                f\"{via_ccy}{ccy_rhs}\", settlement_\n            )\n            return self._cached_value((pair, settlement_), ret)\n\n    def _rate_direct(\n        self,\n        ccy_lhs: str,\n        ccy_rhs: str,\n        settlement: datetime,\n    ) -> DualTypes:\n        \"\"\"Return a forward FX rate conditional on curves existing directly between the\n        given currency indexes.\"\"\"\n        ccy_lhs_idx = self.currencies[ccy_lhs]\n        ccy_rhs_idx = self.currencies[ccy_rhs]\n        if self.transform[ccy_lhs_idx, ccy_rhs_idx] == 1:\n            # f_ab = w_ab / v_bb * F_ab\n            w_ab = self.fx_curves[f\"{ccy_lhs}{ccy_rhs}\"][settlement]\n            v_bb = self.fx_curves[f\"{ccy_rhs}{ccy_rhs}\"][settlement]\n            scalar = w_ab / v_bb\n        elif self.transform[ccy_rhs_idx, ccy_lhs_idx] == 1:\n            # f_ab = v_aa / w_ba * F_ab\n            v_aa = self.fx_curves[f\"{ccy_lhs}{ccy_lhs}\"][settlement]\n            w_ba = self.fx_curves[f\"{ccy_rhs}{ccy_lhs}\"][settlement]\n            scalar = v_aa / w_ba\n        else:\n            raise ValueError(\"`fx_curves` do not exist to create a direct FX rate for the pair.\")\n        f = self.fx_rates_immediate.rate(f\"{ccy_lhs}{ccy_rhs}\")\n        return self._cached_value((f\"{ccy_lhs}{ccy_rhs}\", settlement), scalar * f)\n\n    @_validate_states\n    def positions(\n        self, value: Number, base: str | NoInput = NoInput(0), aggregate: bool = False\n    ) -> Series[float] | DataFrame:\n        \"\"\"\n        Convert a base value with FX rate sensitivities into an array of cash positions\n        by settlement date.\n\n        Parameters\n        ----------\n        value : float or Dual\n            The amount expressed in base currency to convert to cash positions.\n        base : str, optional\n            The base currency in which ``value`` is given (3-digit code). If not given\n            assumes the ``base`` of the object.\n        aggregate : bool, optional\n            Whether to aggregate positions across all settlement dates and yield\n            a single column Series.\n\n        Returns\n        -------\n        DataFrame or Series\n\n        Examples\n        --------\n        .. ipython:: python\n\n           fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n           fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n           fxf = FXForwards(\n               fx_rates=[fxr1, fxr2],\n               fx_curves={\n                   \"usdusd\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n                   \"eureur\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n                   \"cadcad\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n                   \"usdeur\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n                   \"cadusd\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n               }\n           )\n           fxf.positions(\n               value=Dual(100000, [\"fx_eurusd\", \"fx_usdcad\"], [-100000, -150000]),\n               base=\"usd\",\n           )\n\n        \"\"\"\n        if isinstance(value, float | int):\n            value = Dual(value, [], [])\n        base_: str = self.base if isinstance(base, NoInput) else base.lower()\n        _ = np.array(\n            [0 if ccy != base_ else float(value) for ccy in self.currencies_list],\n        )  # this is an NPV so is assumed to be immediate settlement\n\n        if isinstance(self.fx_rates, list):\n            fx_rates = self.fx_rates\n        else:\n            fx_rates = [self.fx_rates]\n\n        dates = list({fxr.settlement for fxr in fx_rates})\n        if self.immediate not in dates:\n            dates.insert(0, self.immediate)\n        df = DataFrame(0.0, index=self.currencies_list, columns=dates)\n        df.loc[base_, self.immediate] = float(value)\n        for pair in value.vars:\n            if pair[:3] == \"fx_\":\n                dom_, for_ = pair[3:6], pair[6:9]\n                for fxr in fx_rates:\n                    if dom_ in fxr.currencies_list and for_ in fxr.currencies_list:\n                        delta = gradient(value, [pair])[0]\n                        _ = fxr._get_positions_from_delta(delta, pair[3:], base_)\n                        _ = Series(_, index=fxr.currencies_list, name=fxr.settlement)\n                        df = df.add(_.to_frame(), fill_value=0.0)\n\n        if aggregate:\n            _s: Series[float] = df.sum(axis=1).rename(dates[0])\n            return _s\n        else:\n            _d: DataFrame = df.sort_index(axis=1)\n            return _d\n\n    @_validate_states\n    def convert(\n        self,\n        value: DualTypes,\n        domestic: str,\n        foreign: str | NoInput = NoInput(0),\n        settlement: datetime | NoInput = NoInput(0),\n        value_date: datetime | NoInput = NoInput(0),\n        collateral: str | NoInput = NoInput(0),\n        on_error: str = \"ignore\",\n    ) -> DualTypes | None:\n        \"\"\"\n        Convert an amount of a domestic currency, as of a settlement date\n        into a foreign currency, valued on another date.\n\n        Parameters\n        ----------\n        value : float or Dual\n            The amount of the domestic currency to convert.\n        domestic : str\n            The domestic currency (3-digit code).\n        foreign : str, optional\n            The foreign currency to convert to (3-digit code). Uses instance\n            ``base`` if not given.\n        settlement : datetime, optional\n            The date of the assumed domestic currency cashflow. If not given is\n            assumed to be ``immediate`` settlement.\n        value_date : datetime, optional\n            The date for which the domestic cashflow is to be projected to. If not\n            given is assumed to be equal to the ``settlement``.\n        collateral : str, optional\n            The collateral currency to project the cashflow if ``value_date`` is\n            different to ``settlement``. If they are the same this is not needed.\n            If not given defaults to ``domestic``.\n        on_error : str in {\"ignore\", \"warn\", \"raise\"}\n            The action taken if either ``domestic`` or ``foreign`` are not contained\n            in the FX framework. `\"ignore\"` and `\"warn\"` will still return `None`.\n\n        Returns\n        -------\n        Dual or None\n\n        Examples\n        --------\n\n        .. ipython:: python\n\n           fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n           fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n           fxf = FXForwards(\n               fx_rates=[fxr1, fxr2],\n               fx_curves={\n                   \"usdusd\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n                   \"eureur\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n                   \"cadcad\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n                   \"usdeur\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n                   \"cadusd\": Curve({dt(2022, 1, 1):1.0, dt(2022, 2, 1): 0.999}),\n               }\n           )\n           fxf.convert(1000, \"usd\", \"cad\")\n\n        \"\"\"\n        foreign_ = _drb(self.base, foreign).lower()\n        domestic_ = domestic.lower()\n        collateral_ = _drb(domestic_, collateral).lower()\n        for ccy in [domestic_, foreign_]:\n            if ccy not in self.currencies:\n                if on_error == \"ignore\":\n                    return None\n                elif on_error == \"warn\":\n                    warnings.warn(\n                        f\"'{ccy}' not in FXForwards.currencies: returning None.\",\n                        UserWarning,\n                    )\n                    return None\n                else:\n                    raise ValueError(f\"'{ccy}' not in FXForwards.currencies.\")\n\n        settlement_: datetime = _drb(self.immediate, settlement)\n        value_date_: datetime = _drb(settlement_, value_date)\n\n        fx_rate: DualTypes = self.rate(domestic_ + foreign_, settlement_)\n        if value_date_ == settlement_:\n            return fx_rate * value\n        else:\n            crv = self.curve(foreign_, collateral_)\n            return fx_rate * value * crv[settlement_] / crv[value_date_]\n\n    @_validate_states\n    # this is technically unnecessary since calls pre-cached method: convert\n    def convert_positions(\n        self,\n        array: np.ndarray[tuple[int], np.dtype[np.float64]]\n        | list[float]\n        | DataFrame\n        | Series[float],\n        base: str | NoInput = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Convert an input of currency cash positions into a single base currency value.\n\n        Parameters\n        ----------\n        array : list, 1d ndarray of floats, or Series, or DataFrame\n            The cash positions to simultaneously convert to base currency value.\n            If a DataFrame, must be indexed by currencies (3-digit lowercase) and the\n            column headers must be settlement dates.\n            If a Series, must be indexed by currencies (3-digit lowercase).\n            If a 1d array or sequence, must\n            be ordered by currency as defined in the attribute ``FXForward.currencies``.\n        base : str, optional\n            The currency to convert to (3-digit code). Uses instance ``base`` if not\n            given.\n\n        Returns\n        -------\n        Dual\n\n        Examples\n        --------\n\n        .. ipython:: python\n           :suppress:\n\n           from pandas import DataFrame\n\n        .. ipython:: python\n\n           fxr = FXRates({\"usdnok\": 8.0}, settlement=dt(2022, 1, 1))\n           usdusd = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n           noknok = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995})\n           fxf = FXForwards(fxr, {\"usdusd\": usdusd, \"noknok\": noknok, \"nokusd\": noknok})\n           fxf.currencies\n           fxf.convert_positions([0, 1000000], \"usd\")\n\n        .. ipython:: python\n\n           fxr.convert_positions(Series([1000000, 0], index=[\"nok\", \"usd\"]), \"usd\")\n\n        .. ipython:: python\n\n           positions = DataFrame(index=[\"usd\", \"nok\"], data={\n               dt(2022, 6, 2): [0, 1000000],\n               dt(2022, 9, 7): [0, -1000000],\n           })\n           fxf.convert_positions(positions, \"usd\")\n        \"\"\"\n        base = _drb(self.base, base).lower()\n\n        if isinstance(array, Series):\n            array_: DataFrame = array.to_frame(name=self.immediate)\n        elif isinstance(array, DataFrame):\n            array_ = array\n        else:\n            array_ = DataFrame({self.immediate: np.asarray(array)}, index=self.currencies_list)\n\n        # j = self.currencies[base]\n        # return np.sum(array_ * self.fx_array[:, j])\n        sum_: DualTypes = 0.0\n        for d in array_.columns:\n            d_sum: DualTypes = 0.0\n            for ccy in array_.index:\n                # typing d is a datetime by default.\n                value_: DualTypes | None = self.convert(array_.loc[ccy, d], ccy, base, d)  # type: ignore[arg-type]\n                d_sum += 0.0 if value_ is None else value_\n            if abs(d_sum) < 1e-2:\n                sum_ += d_sum\n            else:  # only discount if there is a real value\n                value_ = self.convert(d_sum, base, base, d, self.immediate)  # type: ignore[arg-type]\n                sum_ += 0.0 if value_ is None else value_\n        return sum_\n\n    @_validate_states\n    def swap(\n        self,\n        pair: FXIndex | str,\n        settlements: list[datetime],\n    ) -> DualTypes:\n        \"\"\"\n        Return the FXSwap mid-market rate for the given currency pair.\n\n        Parameters\n        ----------\n        pair : FXIndex, str\n            The FX pair in usual domestic:foreign convention (6-digit code).\n        settlements : list of datetimes,\n            The settlement date of currency exchanges.\n\n        Returns\n        -------\n        Dual\n        \"\"\"\n        if isinstance(pair, FXIndex):\n            pair = pair.pair\n\n        fx0 = self._rate_without_validation(pair, settlements[0])\n        fx1 = self._rate_without_validation(pair, settlements[1])\n        return (fx1 - fx0) * 10000\n\n    @_validate_states\n    def _full_curve(self, cashflow: str, collateral: str) -> _BaseCurve:\n        \"\"\"\n        Calculate a cash collateral curve.\n\n        Parameters\n        ----------\n        cashflow : str\n            The currency in which cashflows are represented (3-digit code).\n        collateral : str\n            The currency of the CSA against which cashflows are collateralised (3-digit\n            code).\n\n        Returns\n        -------\n        Curve\n\n        Notes\n        -----\n        Uses the formula,\n\n        .. math::\n\n           w_{DOM:FOR,i} = \\\\frac{f_{DOMFOR,i}}{F_{DOMFOR,0}} v_{FOR:FOR,i}\n\n        The returned curve has each DF uniquely specified on each date.\n        \"\"\"\n        cash_ccy, coll_ccy = cashflow.lower(), collateral.lower()\n        cash_idx, coll_idx = self.currencies[cash_ccy], self.currencies[coll_ccy]\n        end = self.fx_curves[f\"{coll_ccy}{coll_ccy}\"].nodes.final\n        days = (end - self.immediate).days\n        nodes = {\n            k: (\n                self._rate_without_validation(f\"{cash_ccy}{coll_ccy}\", k)\n                / self.fx_rates_immediate.fx_array[cash_idx, coll_idx]\n                * self.fx_curves[f\"{coll_ccy}{coll_ccy}\"][k]\n            )\n            for k in [self.immediate + timedelta(days=i) for i in range(days + 1)]\n        }\n        c_: _BaseCurve = Curve(nodes)\n        return c_\n\n    # Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n    # Commercial use of this code, and/or copying and redistribution is prohibited.\n    # Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n    # @_validate_states: function does not determine values, just links to contained objects.\n    def curve(\n        self,\n        cashflow: str,\n        collateral: str | list[str] | tuple[str, ...],\n        id: str | NoInput = NoInput(0),  # noqa: A002\n    ) -> _BaseCurve:\n        \"\"\"\n        Return a cash collateral *Curve*.\n\n        Parameters\n        ----------\n        cashflow : str\n            The currency in which cashflows are represented (3-digit code).\n        collateral : str, or list/tuple of such\n            The currency of the CSA against which cashflows are collateralised (3-digit\n            code). If a list or tuple will return a CompositeCurve in multi-CSA mode.\n        id : str, optional\n            The identifier attached to any constructed :class:`~rateslib.fx.ProxyCurve`.\n\n        Returns\n        -------\n        Curve, ProxyCurve or MultiCsaCurve\n\n        Notes\n        -----\n        If the :class:`~rateslib.curves.Curve` already exists within the attribute\n        ``fx_curves`` that *Curve* will be returned directly.\n\n        If a :class:`~rateslib.curves.ProxyCurve` already exists with the attribute\n        ``fx_proxy_curves`` that *Curve* will be returned.\n\n        Otherwise, creates and returns a :class:`~rateslib.curves.ProxyCurve` which determines rates\n        and DFs via the chaining method and the below formula,\n\n        .. math::\n\n           w_{dom:for,i} = \\\\frac{f_{DOMFOR,i}}{F_{DOMFOR,0}} v_{for:for,i}\n\n        For multiple collateral currencies returns a :class:`~rateslib.curves.MultiCsaCurve`.\n\n        The :class:`~rateslib.curves._CurveMeta` inherits values from the local cash\n        :class:`~rateslib.curves.Curve`, and the ``collateral`` value is set as the defined\n        collateral currency.\n        \"\"\"\n        if isinstance(collateral, list | tuple):\n            # TODO add this curve to fx_proxy_curves and lexsort the collateral\n            curves = []\n            for coll in collateral:\n                curves.append(self.curve(cashflow, coll))\n            curve: _BaseCurve = MultiCsaCurve(curves=curves, id=id)\n            curve._meta = replace(curve.meta, _collateral=\",\".join([_.lower() for _ in collateral]))  # type: ignore[misc]\n            return curve\n\n        cash_ccy, coll_ccy = cashflow.lower(), collateral.lower()\n        pair = f\"{cash_ccy}{coll_ccy}\"\n\n        if pair in self.fx_curves:\n            return self.fx_curves[pair]\n        elif pair in self._fx_proxy_curves:\n            return self._fx_proxy_curves[pair]\n        else:\n            curve_: ProxyCurve = ProxyCurve(\n                cashflow=cash_ccy,\n                collateral=coll_ccy,\n                fx_forwards=self,\n                id=id,\n            )\n            self._fx_proxy_curves[pair] = curve_\n            return curve_\n\n    @_validate_states\n    def plot(\n        self,\n        pair: FXIndex | str,\n        right: datetime | str | NoInput = NoInput(0),\n        left: datetime | str | NoInput = NoInput(0),\n        fx_swap: bool = False,\n    ) -> PlotOutput:\n        \"\"\"\n        Plot given forward FX rates.\n\n        Parameters\n        ----------\n        pair : FXIndex, str\n            The FX pair to determine rates for (6-digit code).\n        right : datetime or str, optional\n            The right bound of the graph. If given as str should be a tenor format\n            defining a point measured from the initial node date of the curve.\n            Defaults to the terminal date of the FXForwards object.\n        left : datetime or str, optional\n            The left bound of the graph. If given as str should be a tenor format\n            defining a point measured from the initial node date of the curve.\n            Defaults to the immediate FX settlement date.\n        fx_swap : bool\n            Whether to plot as the FX rate or as FX swap points relative to the\n            initial FX rate on the left side of the chart.\n            Default is `False`.\n\n        Returns\n        -------\n        (fig, ax, line) : Matplotlib.Figure, Matplotplib.Axes, Matplotlib.Lines2D\n        \"\"\"\n        if isinstance(pair, FXIndex):\n            pair = pair.pair\n\n        if isinstance(left, NoInput):\n            left_: datetime = self.immediate\n        elif isinstance(left, str):\n            left_ = add_tenor(self.immediate, left, \"NONE\", NoInput(0))\n        elif isinstance(left, datetime):\n            left_ = left\n        else:\n            raise ValueError(\"`left` must be supplied as datetime or tenor string.\")\n\n        if isinstance(right, NoInput):\n            right_: datetime = self.terminal\n        elif isinstance(right, str):\n            right_ = add_tenor(self.immediate, right, \"NONE\", NoInput(0))\n        elif isinstance(right, datetime):\n            right_ = right\n        else:\n            raise ValueError(\"`right` must be supplied as datetime or tenor string.\")\n\n        points: int = (right_ - left_).days\n        x = [left_ + timedelta(days=i) for i in range(points)]\n        rates: list[DualTypes] = [self._rate_without_validation(pair, _) for _ in x]\n        if not fx_swap:\n            y: list[list[DualTypes]] = [rates]\n        else:\n            y = [[(rate - rates[0]) * 10000 for rate in rates]]\n        return plot([x] * len(y), y)\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        # does not require cache validation because updates the cache_id at end of method\n        self._ad = order\n        for curve in self.fx_curves.values():\n            curve._set_ad_order(order)\n\n        if isinstance(self.fx_rates, list):\n            for fx_rates in self.fx_rates:\n                fx_rates._set_ad_order(order)\n        else:\n            self.fx_rates._set_ad_order(order)\n        self.fx_rates_immediate._set_ad_order(order)\n\n    @_validate_states\n    def to_json(self) -> str:\n        if isinstance(self.fx_rates, list):\n            fx_rates: list[str] | str = [_.to_json() for _ in self.fx_rates]\n        else:\n            fx_rates = self.fx_rates.to_json()\n        container = {\n            \"base\": self.base,\n            \"fx_rates\": fx_rates,\n            \"fx_curves\": {k: v.to_json() for k, v in self.fx_curves.items()},  # type: ignore[attr-defined]\n        }\n        return json.dumps(container, default=str)\n\n    @classmethod\n    def from_json(cls, fx_forwards: str, **kwargs) -> FXForwards:  # type: ignore[no-untyped-def]\n        \"\"\"\n        Loads an FXForwards object from JSON.\n\n        Parameters\n        ----------\n        fx_forwards : str\n            JSON string describing the FXForwards class. Typically constructed with\n            :meth:`to_json`.\n\n        Returns\n        -------\n        FXForwards\n\n        Notes\n        -----\n        This method also creates new ``FXRates`` and ``Curve`` objects from JSON.\n        These new objects can be accessed from the attributes of the ``FXForwards``\n        instance.\n        \"\"\"\n        from rateslib.serialization import from_json\n\n        serial = json.loads(fx_forwards)\n\n        if isinstance(serial[\"fx_rates\"], list):\n            fx_rates = [from_json(_) for _ in serial[\"fx_rates\"]]\n        else:\n            fx_rates = from_json(serial[\"fx_rates\"])\n\n        fx_curves = {k: from_json(v) for k, v in serial[\"fx_curves\"].items()}\n        base = serial[\"base\"]\n        return FXForwards(fx_rates, fx_curves, base)\n\n    def __eq__(self, other: Any) -> bool:\n        \"\"\"Test two FXForwards are identical\"\"\"\n        if type(self) is not type(other):\n            return False\n        for attr in [\"base\"]:\n            if getattr(self, attr, None) != getattr(other, attr, None):\n                return False\n        if self.fx_rates_immediate != other.fx_rates_immediate:\n            return False\n\n        # it is sufficient to check that FX immediate and curves are equivalent.\n\n        # if type(self.fx_rates) != type(other.fx_rates):\n        #     return False\n        # if isinstance(self.fx_rates, list):\n        #     if len(self.fx_rates) != len(other.fx_rates):\n        #         return False\n        #     for i in range(len(self.fx_rates)):\n        #         # this tests FXRates are also ordered in the same on each object\n        #         if self.fx_rates[i] != other.fx_rates[i]:\n        #             return False\n        # else:\n        #     if self.fx_rates != other.fx_rates:\n        #         return False\n\n        for k, curve in self.fx_curves.items():\n            if k not in other.fx_curves:\n                return False\n            if curve != other.fx_curves[k]:\n                return False\n\n        return True\n\n    def __ne__(self, other: Any) -> bool:\n        return not self.__eq__(other)\n\n    # @_validate_state: unused because it is redirected to a cache_validated method (to_json)\n    def copy(self) -> FXForwards:\n        \"\"\"\n        An FXForwards copy creates a new object with copied references.\n        \"\"\"\n        return self.from_json(self.to_json())\n\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n\ndef forward_fx(\n    date: datetime,\n    curve_domestic: _BaseCurve,\n    curve_foreign: _BaseCurve,\n    fx_rate: DualTypes,\n    fx_settlement: datetime | NoInput = NoInput(0),\n) -> DualTypes:\n    \"\"\"\n    Return a forward FX rate based on interest rate parity.\n\n    Parameters\n    ----------\n    date : datetime\n        The target date to determine the adjusted FX rate for.\n    curve_domestic : Curve\n        The discount curve for the domestic currency. Should be collateral adjusted.\n    curve_foreign : Curve\n        The discount curve for the foreign currency. Should be collateral consistent\n        with ``domestic curve``.\n    fx_rate : float or Dual\n        The known FX rate, typically spot FX given with a spot settlement date.\n    fx_settlement : datetime, optional\n        The date the given ``fx_rate`` will settle, i.e. spot T+2. If `None` is assumed\n        to be immediate settlement, i.e. date upon which both ``curves`` have a DF\n        of precisely 1.0. Method is more efficient if ``fx_rate`` is given for\n        immediate settlement.\n\n    Returns\n    -------\n    float, Dual, Dual2\n\n    Notes\n    -----\n    We use the formula,\n\n    .. math::\n\n       (EURUSD) f_i = \\\\frac{(EUR:USD-CSA) w^*_i}{(USD:USD-CSA) v_i} F_0 = \\\\frac{(EUR:EUR-CSA) v^*_i}{(USD:EUR-CSA) w_i} F_0\n\n    where :math:`w` is a collateral adjusted discount curve and :math:`v` is the\n    locally derived discount curve in a given currency, and `*` denotes the domestic\n    currency. :math:`F_0` is the immediate FX rate, i.e. aligning with the initial date\n    on curves such that discounts factors are precisely 1.0.\n\n    This implies that given the dates and rates supplied,\n\n    .. math::\n\n       f_i = \\\\frac{w^*_iv_j}{v_iw_j^*} f_j = \\\\frac{v^*_iw_j}{w_iv_j^*} f_j\n\n    where `j` denotes the settlement date provided.\n\n    Examples\n    --------\n    Using this function directly.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.fx.fx_forwards import forward_fx\n\n    .. ipython:: python\n\n       domestic_curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.96})\n       foreign_curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n       forward_fx(\n           date=dt(2022, 7, 1),\n           curve_domestic=domestic_curve,\n           curve_foreign=foreign_curve,\n           fx_rate=2.0,\n           fx_settlement=dt(2022, 1, 3)\n       )\n\n    Best practice is to use :class:`FXForwards` classes but this method provides\n    an efficient alternative and is occasionally used internally in the library.\n\n    .. ipython:: python\n\n       fxr = FXRates({\"usdgbp\": 2.0}, settlement=dt(2022, 1, 3))\n       fxf = FXForwards(fxr, {\n           \"usdusd\": domestic_curve,\n           \"gbpgbp\": foreign_curve,\n           \"gbpusd\": foreign_curve,\n       })\n       fxf.rate(\"usdgbp\", dt(2022, 7, 1))\n    \"\"\"  # noqa: E501\n    if date == fx_settlement:  # noqa: SIM114\n        return fx_rate  # noqa: SIM114\n    elif date == curve_domestic.nodes.initial and isinstance(fx_settlement, NoInput):  # noqa: SIM114\n        return fx_rate  # noqa: SIM114\n\n    _: DualTypes = curve_domestic[date] / curve_foreign[date]\n    if not isinstance(fx_settlement, NoInput):\n        _ *= curve_foreign[fx_settlement] / curve_domestic[fx_settlement]\n    # else: fx_settlement is deemed to be immediate hence DF are both equal to 1.0\n    _ *= fx_rate\n    return _\n\n\ndef _get_curves_indicator_array(\n    q: int, currencies: dict[str, int], fx_curves: dict[str, _BaseCurve]\n) -> np.ndarray[tuple[int, int], np.dtype[np.int_]]:\n    \"\"\"\n    Constructs an indicator array identifying which cash-collateral curves are available in the\n    `fx_curves` dictionary.\n    \"\"\"\n    # Define the transformation matrix with unit elements in each valid pair.\n    T = np.zeros((q, q), dtype=int)\n    for k, _ in fx_curves.items():\n        cash, coll = k[:3].lower(), k[3:].lower()\n        try:\n            cash_idx, coll_idx = currencies[cash], currencies[coll]\n        except KeyError:\n            raise ValueError(f\"`fx_curves` contains an unexpected currency: {cash} or {coll}\")\n        T[cash_idx, coll_idx] = 1\n\n    _validate_curves_indicator_array(T)\n    return T\n\n\ndef _validate_curves_indicator_array(T: np.ndarray[tuple[int, int], np.dtype[np.int_]]) -> None:\n    \"\"\"\n    Performs checks to ensure the indicator array of cash-collateral curves contains the\n    appropriate number of curves required by an FXForwards object.\n    \"\"\"\n    q = T.shape[0]\n    if T.sum() > (2 * q) - 1:\n        raise ValueError(\n            f\"`fx_curves` is overspecified. {2 * q - 1} curves are expected \"\n            f\"but {T.sum()} provided.\",\n        )\n    elif T.sum() < (2 * q) - 1:\n        raise ValueError(\n            f\"`fx_curves` is underspecified. {2 * q - 1} curves are expected \"\n            f\"but {T.sum()} provided.\",\n        )\n    elif T.diagonal().sum() != q:\n        raise ValueError(\n            \"`fx_curves` must contain local cash-collateral curves for each and every currency.\"\n        )\n    elif np.linalg.matrix_rank(T) != q:\n        raise ValueError(\"`fx_curves` contains co-dependent rates.\")\n\n\ndef _recursive_pair_population(\n    arr: np.ndarray[tuple[int, int], np.dtype[np.int_]],\n    mapping: dict[tuple[int, int], int] | None = None,\n) -> tuple[np.ndarray[tuple[int, int], np.dtype[np.int_]], dict[tuple[int, int], int]]:\n    \"\"\"\n    Recursively scan through an indicator matrix and populate new entries.\n\n    This identifies existing FX pairs and attempts to derive new FX pairs from those values.\n\n    Parameters\n    ----------\n    arr: 2d-ndarray\n        An square indicator matrix consisting only of zeros and ones.\n\n    Notes\n    -----\n    ``arr`` should satify the following:\n\n    - be a square matrix,\n    - be an indicator matrix containing only zero and ones,\n    - have unit diagonal,\n    - sum to 2n - 1, so that the correct number of prior rates are supplied,\n    - be a full rank matrix so no pairs are degenerate\n    \"\"\"\n    # Build the initial mapping if none exists\n    if mapping is None:\n        _mapping: dict[tuple[int, int], int] = _create_initial_mapping(arr)\n    else:\n        _mapping = mapping\n\n    # loop through currencies and find new pairs\n    _arr = arr.copy()\n    for i in range(len(_arr)):\n        ccy_idxs = [_ for _ in range(len(_arr)) if _arr[i, _] == 1]\n        pairs = combinations(ccy_idxs, 2)\n        for pair in pairs:\n            if _arr[pair[0], pair[1]] == 1 and _arr[pair[1], pair[0]] == 1:\n                # then the rate and its inverse are already attainable\n                continue\n            elif _arr[pair[0], pair[1]] == 1:\n                # then the inverse is directly attainable\n                _mapping[pair[1], pair[0]] = _mapping[pair[0], pair[1]]\n                _arr[pair[1], pair[0]] = 1\n            elif _arr[pair[1], pair[0]] == 1:\n                # then the inverse is directly attainable\n                _mapping[pair[0], pair[1]] = _mapping[pair[1], pair[0]]\n                _arr[pair[0], pair[1]] = 1\n            else:\n                _arr[pair[0], [pair[1]]] = 1\n                _arr[pair[1], [pair[0]]] = 1\n                _mapping[(pair[0], pair[1])] = i\n                _mapping[(pair[1], pair[0])] = i\n\n    if np.all(_arr == arr) or np.sum(_arr, axis=None) == len(_arr) ** 2:\n        return _arr, _mapping\n    else:\n        return _recursive_pair_population(_arr, _mapping)\n\n\ndef _create_initial_mapping(\n    arr: np.ndarray[tuple[int, int], np.dtype[np.int_]],\n) -> dict[tuple[int, int], int]:\n    \"\"\"Detect the mappings immediately available and denote these with the value '-1'.\"\"\"\n    _mapping: dict[tuple[int, int], int] = {}\n    for i in range(len(arr)):\n        for j in range(len(arr)):\n            if i == j:\n                continue\n            if arr[i, j] == 1:\n                _mapping[(i, j)] = -1\n                _mapping[(j, i)] = -1\n    return _mapping\n"
  },
  {
    "path": "python/rateslib/fx/fx_rates.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations\n\nimport warnings\nfrom datetime import datetime\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, Any\n\nimport numpy as np\nfrom pandas import DataFrame, Series\n\nfrom rateslib import defaults\nfrom rateslib.data.fixings import FXIndex\nfrom rateslib.default import (\n    _make_py_json,\n)\nfrom rateslib.dual import Dual, gradient\nfrom rateslib.dual.utils import _get_adorder\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.mutability import (\n    _clear_cache_post,\n    _new_state_post,\n    _WithState,\n)\nfrom rateslib.rs import Ccy, FXRate\nfrom rateslib.rs import FXRates as FXRatesObj\n\nif TYPE_CHECKING:\n    from rateslib.local_types import Arr1dF64, Arr1dObj, Arr2dObj, DualTypes, Number\n\n\"\"\"\n.. ipython:: python\n   :suppress:\n\n   from rateslib.curves import Curve\n   from rateslib.fx import FXRates\n   from datetime import datetime as dt\n\"\"\"\n\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n\n\nclass FXRates(_WithState):\n    \"\"\"\n    Object to store and calculate FX rates for a consistent settlement date.\n\n    Parameters\n    ----------\n    fx_rates : dict[str, float]\n        Dict whose keys are 6-character currency pairs, and whose\n        values are the relevant rates.\n    settlement : datetime, optional\n        The settlement date for the FX rates.\n    base : str, optional\n        The base currency (3-digit code). If not given defaults to either:\n\n        - the base currency defined in `defaults`, if it is present in the list of currencies,\n        - the first currency detected.\n\n    Notes\n    -----\n\n    .. note::\n       When this class uses ``Dual`` numbers to represent sensitivities of values to\n       certain FX rates the variable names are called `\"fx_cc1cc2\"` where `\"cc1\"`\n       is left hand currency and `\"cc2\"` is the right hand currency in the currency pair.\n       See the examples contained in class methods for clarification.\n\n    Examples\n    --------\n    An FX rates market of *n* currencies is completely defined by *n-1*\n    independent FX pairs.\n\n    Below we define an FX rates market in 4 currencies with 3 FX pairs,\n\n    .. ipython:: python\n\n       fxr = FXRates({\"eurusd\": 1.1, \"gbpusd\": 1.25, \"usdjpy\": 100})\n       fxr.currencies\n       fxr.rate(\"gbpjpy\")\n\n    Ill defined FX markets will raise ``ValueError`` and are either **overspecified**,\n\n    .. ipython:: python\n\n       try:\n           FXRates({\"eurusd\": 1.1, \"gbpusd\": 1.25, \"usdjpy\": 100, \"gbpjpy\": 125})\n       except ValueError as e:\n           print(e)\n\n    or are **underspecified**,\n\n    .. ipython:: python\n\n       try:\n           FXRates({\"eurusd\": 1.1, \"gbpjpy\": 125})\n       except ValueError as e:\n           print(e)\n\n    or use redundant, co-dependent information,\n\n    .. ipython:: python\n\n       try:\n           FXRates({\"eurusd\": 1.1, \"usdeur\": 0.90909, \"gbpjpy\": 125})\n       except ValueError as e:\n           print(e)\n\n    \"\"\"\n\n    def __init__(\n        self,\n        fx_rates: dict[str, DualTypes],\n        settlement: datetime | NoInput = NoInput(0),\n        base: str | NoInput = NoInput(0),\n    ):\n        # Temporary declaration - will be overwritten\n        self._currencies: dict[str, int] = {}\n\n        settlement_: datetime | None = _drb(None, settlement)\n        fx_rates_ = [FXRate(k[0:3], k[3:6], v, settlement_) for k, v in fx_rates.items()]\n        if isinstance(base, NoInput):\n            default_ccy = defaults.base_currency.lower()\n            if any(default_ccy in k.lower() for k in fx_rates):\n                base_ = Ccy(defaults.base_currency)\n            else:\n                base_ = None\n        else:\n            base_ = Ccy(base)\n        self.obj = FXRatesObj(fx_rates_, base_)\n        self.__init_post_obj__()\n        self._clear_cache()\n        self._set_new_state()\n\n    @classmethod\n    def __init_from_obj__(cls, obj: FXRatesObj) -> FXRates:\n        \"\"\"Construct the class instance from a given rust object which is wrapped.\"\"\"\n        # create a default instance and overwrite it\n        new = cls({\"usdeur\": 1.0}, datetime(2000, 1, 1))\n        new.obj = obj\n        new.__init_post_obj__()\n        return new\n\n    def __init_post_obj__(self) -> None:\n        self._currencies = {ccy.name: i for (i, ccy) in enumerate(self.obj.currencies)}\n\n    def __eq__(self, other: Any) -> bool:\n        if isinstance(other, FXRates):\n            return self.obj == other.obj\n        return False\n\n    def __ne__(self, other: Any) -> bool:\n        return not self.__eq__(other)\n\n    def __copy__(self) -> FXRates:\n        obj = FXRates.__init_from_obj__(self.obj.__copy__())\n        obj.__init_post_obj__()\n        return obj\n\n    def __repr__(self) -> str:\n        if len(self.currencies_list) > 5:\n            return (\n                f\"<rl.FXRates:[{','.join(self.currencies_list[:2])},\"\n                f\"+{len(self.currencies_list) - 2} others] at {hex(id(self))}>\"\n            )\n        else:\n            return f\"<rl.FXRates:[{','.join(self.currencies_list)}] at {hex(id(self))}>\"\n\n    @cached_property\n    def fx_array(self) -> Arr2dObj:\n        \"\"\"An array containing all of the FX pairs/crosses available on the object.\"\"\"\n        # caching this prevents repetitive data transformations between Rust/Python\n        return np.array(self.obj.fx_array)\n\n    def _fx_array_el(self, i: int, j: int) -> Number:\n        # this is for typing since this numpy object array can only hold float | Dual | Dual2\n        return self.fx_array[i, j]  # type: ignore\n\n    @property\n    def base(self) -> str:\n        \"\"\"The assumed base currency of the object which may be used as the default ``base``\n        currency in ``npv`` calculations when otherwise omitted.\n\n        The base currency has index 0 in the ``currencies`` dict and is that which the ``fx_vector``\n        is defined relative to.\n        \"\"\"\n        return self.obj.base.name\n\n    @property\n    def settlement(self) -> datetime:\n        \"\"\"The settlement date of the FX rates that define the object.\"\"\"\n        return self.obj.fx_rates[0].settlement\n\n    @property\n    def pairs(self) -> list[str]:\n        \"\"\"A list of the currency pairs that define the object. The number of pairs is one\n        less than ``q``.\"\"\"\n        return [fxr.pair for fxr in self.obj.fx_rates]\n\n    @property\n    def fx_rates(self) -> dict[str, DualTypes]:\n        \"\"\"The dict of currency pairs and their FX rates that define the object.\"\"\"\n        return {fxr.pair: fxr.rate for fxr in self.obj.fx_rates}\n\n    @property\n    def currencies_list(self) -> list[str]:\n        \"\"\"An list of currencies available in the object. Aligns with ``currencies``.\"\"\"\n        return [ccy.name for ccy in self.obj.currencies]\n\n    @property\n    def currencies(self) -> dict[str, int]:\n        \"\"\"A dict whose keys are the currencies contained in the object and the value is the\n        ordered index of that currencies in other attributes such as ``fx_array`` and\n        ``currencies_list``.\"\"\"\n        return self._currencies\n\n    @property\n    def q(self) -> int:\n        \"\"\"The number of currencies contained in the object.\"\"\"\n        return len(self.obj.currencies)\n\n    @property\n    def fx_vector(self) -> Arr1dObj:\n        \"\"\"A vector of currency FX rates all relative to the stated ``base`` currency.\"\"\"\n        return self.fx_array[0, :]\n\n    @property\n    def pairs_settlement(self) -> dict[str, datetime]:\n        \"\"\"A dict aggregating each FX pair and its settlement date. In an *FXRates* object\n        all pairs settle on the same settlement date.\"\"\"\n        return dict.fromkeys(self.pairs, self.settlement)\n\n    @property\n    def variables(self) -> tuple[str, ...]:\n        \"\"\"The names of the variables associated with the object for automatic differentiation (AD)\n        purposes.\"\"\"\n        return tuple(f\"fx_{pair}\" for pair in self.pairs)\n\n    @property\n    def _ad(self) -> int:\n        return self.obj.ad\n\n    def rate(self, pair: FXIndex | str) -> Number:\n        \"\"\"\n        Return a specified FX rate for a given currency pair.\n\n        Parameters\n        ----------\n        pair : FXIndex, str\n            The FX pair in usual domestic:foreign convention (6 digit code).\n\n        Returns\n        -------\n        Dual\n\n        Examples\n        --------\n\n        .. ipython:: python\n\n           fxr = FXRates({\"usdeur\": 2.0, \"usdgbp\": 2.5})\n           fxr.rate(\"eurgbp\")\n        \"\"\"\n        if isinstance(pair, FXIndex):\n            pair = pair.pair\n\n        domi, fori = self.currencies[pair[:3].lower()], self.currencies[pair[3:].lower()]\n        return self._fx_array_el(domi, fori)\n\n    def restate(self, pairs: list[str], keep_ad: bool = False) -> FXRates:\n        \"\"\"\n        Create a new :class:`FXRates` class using other (or fewer) currency pairs as majors.\n\n        Parameters\n        ----------\n        pairs : list of str\n            The new currency pairs with which to define the ``FXRates`` class.\n        keep_ad : bool, optional\n            Keep the original derivative exposures defined by ``Dual``, instead\n            of redefinition. It is advised against setting this to *True*, it is mainly used\n            internally.\n\n        Returns\n        --------\n        FXRates\n\n        Notes\n        -----\n        This will redefine the pairs to which delta risks are expressed in ``Dual``\n        outputs.\n\n        If ``pairs`` match the existing object and ``keep_ad`` is\n        requested then the existing object is returned unchanged as new copy.\n\n        Examples\n        --------\n        Re-expressing an *FXRates* class with new majors, to which *Dual* sensitivities are\n        measured.\n\n        .. ipython:: python\n\n           fxr = FXRates({\"eurgbp\": 0.9, \"gbpjpy\": 125, \"usdjpy\": 100})\n           fxr.convert(100, \"gbp\", \"usd\")\n           fxr2 = fxr.restate([\"eurusd\", \"gbpusd\", \"usdjpy\"])\n           fxr2.convert(100, \"gbp\", \"usd\")\n\n        Extracting an *FXRates* subset from a larger object.\n\n        .. ipython:: python\n\n           fxr = FXRates({\"eurgbp\": 0.9, \"gbpjpy\": 125, \"usdjpy\": 100, \"audusd\": 0.85})\n           fxr2 = fxr.restate({\"eurusd\", \"gbpusd\"})\n           fxr2.rates_table()\n        \"\"\"\n        if pairs == self.pairs and keep_ad:\n            return self.__copy__()  # no restate needed but return new instance\n\n        restated_fx_rates = FXRates(\n            {pair: self.rate(pair) if keep_ad else self.rate(pair).real for pair in pairs},\n            settlement=self.settlement,\n            base=self.base,\n        )\n        return restated_fx_rates\n\n    def convert(\n        self,\n        value: DualTypes,\n        domestic: str,\n        foreign: str | NoInput = NoInput(0),\n        on_error: str = \"ignore\",\n    ) -> DualTypes | None:\n        \"\"\"\n        Convert an amount of a domestic currency into a foreign currency.\n\n        Parameters\n        ----------\n        value : float or Dual\n            The amount of the domestic currency to convert.\n        domestic : str\n            The domestic currency (3-digit code).\n        foreign : str, optional\n            The foreign currency to convert to (3-digit code). Uses instance\n            ``base`` if not given.\n        on_error : str in {\"ignore\", \"warn\", \"raise\"}\n            The action taken if either ``domestic`` or ``foreign`` are not contained\n            in the FX framework. `\"ignore\"` and `\"warn\"` will still return `None`.\n\n        Returns\n        -------\n        Dual or None\n\n        Examples\n        --------\n\n        .. ipython:: python\n\n           fxr = FXRates({\"usdnok\": 8.0})\n           fxr.convert(1000000, \"nok\", \"usd\")\n           fxr.convert(1000000, \"nok\", \"inr\")  # <- returns None, \"inr\" not in fxr.\n\n        \"\"\"\n        foreign = self.base if isinstance(foreign, NoInput) else foreign.lower()\n        domestic = domestic.lower()\n        for ccy in [domestic, foreign]:\n            if ccy not in self.currencies:\n                if on_error == \"ignore\":\n                    return None\n                elif on_error == \"warn\":\n                    warnings.warn(\n                        f\"'{ccy}' not in FXRates.currencies: returning None.\",\n                        UserWarning,\n                    )\n                    return None\n                else:\n                    raise ValueError(f\"'{ccy}' not in FXRates.currencies.\")\n\n        i, j = self.currencies[domestic.lower()], self.currencies[foreign.lower()]\n        return value * self._fx_array_el(i, j)\n\n    def convert_positions(\n        self,\n        array: Arr1dF64 | list[float],\n        base: str | NoInput = NoInput(0),\n    ) -> Number:\n        \"\"\"\n        Convert an array of currency cash positions into a single base currency.\n\n        Parameters\n        ----------\n        array : list, 1d ndarray of floats, or Series\n            The cash positions to simultaneously convert in the base currency. **Must**\n            be ordered by currency as defined in the attribute ``FXRates.currencies``.\n        base : str, optional\n            The currency to convert to (3-digit code). Uses instance ``base`` if not\n            given.\n\n        Returns\n        -------\n        Dual\n\n        Examples\n        --------\n\n        .. ipython:: python\n\n           fxr = FXRates({\"usdnok\": 8.0})\n           fxr.currencies\n           fxr.convert_positions([0, 1000000], \"usd\")\n        \"\"\"\n        base = self.base if isinstance(base, NoInput) else base.lower()\n        array_ = np.asarray(array)\n        j = self.currencies[base]\n        return np.sum(array_ * self.fx_array[:, j])  # type: ignore[no-any-return]\n\n    def positions(\n        self,\n        value: DualTypes,\n        base: str | NoInput = NoInput(0),\n    ) -> Series[float]:\n        \"\"\"\n        Convert a base value with FX rate sensitivities into an array of cash positions.\n\n        Parameters\n        ----------\n        value : float or Dual\n            The amount expressed in base currency to convert to cash positions.\n        base : str, optional\n            The base currency in which ``value`` is given (3-digit code). If not given\n            assumes the ``base`` of the object.\n\n        Returns\n        -------\n        Series\n\n        Examples\n        --------\n        .. ipython:: python\n\n           fxr = FXRates({\"usdnok\": 8.0})\n           fxr.positions(Dual(125000, [\"fx_usdnok\"], [-15625]), \"usd\")\n           fxr.positions(100, base=\"nok\")\n\n        \"\"\"\n        if isinstance(value, float | int):\n            value = Dual(value, [], [])\n        base_: str = self.base if isinstance(base, NoInput) else base.lower()\n        _ = np.array([0 if ccy != base_ else value.real for ccy in self.currencies_list])\n        for pair in value.vars:\n            if pair[:3] == \"fx_\":\n                delta = gradient(value, [pair])[0]\n                _ += self._get_positions_from_delta(delta, pair[3:], base_)\n        return Series(_, index=self.currencies_list)\n\n    def _get_positions_from_delta(\n        self, delta: float, pair: str, base: str\n    ) -> np.ndarray[tuple[int], np.dtype[np.float64]]:\n        \"\"\"Return an array of cash positions determined from an FX pair delta risk.\"\"\"\n        b_idx = self.currencies[base]\n        domestic, foreign = pair[:3], pair[3:]\n        d_idx, f_idx = self.currencies[domestic], self.currencies[foreign]\n        _: np.ndarray[tuple[int], np.dtype[np.float64]] = np.zeros(self.q, dtype=np.float64)\n\n        # f_val = -delta * float(self.fx_array[b_idx, d_idx]) * float(self.fx_array[d_idx,f_idx])**2\n        # _[f_idx] = f_val\n        # _[d_idx] = -f_val / float(self.fx_array[d_idx, f_idx])\n        # return _\n        f_val = delta * float(self._fx_array_el(b_idx, f_idx))\n        _[d_idx] = f_val\n        _[f_idx] = -f_val / float(self._fx_array_el(f_idx, d_idx))\n        return _  # calculation is more efficient from a domestic pov than foreign\n\n    def rates_table(self) -> DataFrame:\n        \"\"\"\n        Return a DataFrame of all FX rates in the object.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        return DataFrame(\n            np.vectorize(float)(self.fx_array),\n            index=self.currencies_list,\n            columns=self.currencies_list,\n        )\n\n    # Cache management\n\n    def _clear_cache(self) -> None:\n        \"\"\"\n        Clear the cache ID so the fx_array can be fetched and cached from Rust object.\n        \"\"\"\n        # the fx_array is a cached property.\n        self.__dict__.pop(\"fx_array\", None)\n\n    # Mutation\n\n    @_new_state_post\n    @_clear_cache_post\n    def update(self, fx_rates: dict[str, float] | NoInput = NoInput(0)) -> None:\n        \"\"\"\n        Update all or some of the FX rates of the instance with new market data.\n\n        Parameters\n        ----------\n        fx_rates : dict, optional\n            Dict whose keys are 6-character domestic-foreign currency pairs and\n            which are present in FXRates.pairs, and whose\n            values are the relevant rates to update. An empty dict will be ignored and\n            perform no update.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of an *FXRates* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *FXRates* instance.\n           This class is labelled as a **mutable on update** object.\n\n        Suppose an *FXRates* class has been instantiated and resides in memory.\n\n        .. ipython:: python\n\n           fxr = FXRates({\"eurusd\": 1.05, \"gbpusd\": 1.25}, settlement=dt(2022, 1, 3), base=\"usd\")\n           id(fxr)\n\n        This object may be linked to others, probably an :class:`~rateslib.fx.FXForwards` class.\n        It can be updated with some new market data. This will preserve its memory id and\n        association with other objects. Any :class:`~rateslib.fx.FXForwards` objects referencing\n        this will detect this change and will also lazily update via *rateslib's* state\n        management.\n\n        .. ipython:: python\n\n           linked_obj = fxr\n           fxr.update({\"eurusd\": 1.06})\n           id(fxr)  # <- SAME as above\n           linked_obj.rate(\"eurusd\")\n\n        Examples\n        --------\n\n        .. ipython:: python\n\n           fxr = FXRates({\"usdeur\": 0.9, \"eurnok\": 8.5})\n           fxr.rate(\"usdnok\")\n           fxr.update({\"usdeur\": 1.0})\n           fxr.rate(\"usdnok\")\n        \"\"\"\n        if isinstance(fx_rates, NoInput) or len(fx_rates) == 0:\n            return None\n        fx_rates_ = [FXRate(k[0:3], k[3:6], v, self.settlement) for k, v in fx_rates.items()]\n        self.obj.update(fx_rates_)\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        \"\"\"\n        Change the node values to float, Dual or Dual2 based on input parameter.\n        \"\"\"\n        self.obj.set_ad_order(_get_adorder(order))\n\n    # Serialization\n\n    def to_json(self) -> str:\n        \"\"\"Return a JSON representation of the object.\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return _make_py_json(self.obj.to_json(), \"FXRates\")\n\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n"
  },
  {
    "path": "python/rateslib/instruments/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.instruments.bonds import (\n    Bill,\n    BillCalcMode,\n    BondCalcMode,\n    BondFuture,\n    FixedRateBond,\n    FloatRateNote,\n    IndexFixedRateBond,\n    _BaseBondInstrument,\n)\nfrom rateslib.instruments.cds import CDS\nfrom rateslib.instruments.fee import Fee\nfrom rateslib.instruments.fly import Fly\nfrom rateslib.instruments.fra import FRA\nfrom rateslib.instruments.fx_forward import FXForward\nfrom rateslib.instruments.fx_options import (\n    FXBrokerFly,\n    FXCall,\n    FXPut,\n    FXRiskReversal,\n    FXStraddle,\n    FXStrangle,\n    FXVolValue,\n    _BaseFXOption,\n    _BaseFXOptionStrat,\n)\nfrom rateslib.instruments.fx_swap import FXSwap\nfrom rateslib.instruments.iirs import IIRS\nfrom rateslib.instruments.ir_options import (\n    IRSCall,\n    IRSPut,\n    IRSRiskReversal,\n    IRSStraddle,\n    IRSStrangle,\n    IRVolValue,\n    _BaseIRSOption,\n    _BaseIRSOptionStrat,\n)\nfrom rateslib.instruments.irs import IRS\nfrom rateslib.instruments.loan import Loan\nfrom rateslib.instruments.ndf import NDF\nfrom rateslib.instruments.ndxcs import NDXCS\nfrom rateslib.instruments.portfolio import Portfolio\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.sbs import SBS\nfrom rateslib.instruments.spread import Spread\nfrom rateslib.instruments.stir_future import STIRFuture\nfrom rateslib.instruments.value import Value\nfrom rateslib.instruments.xcs import XCS\nfrom rateslib.instruments.yoyis import YoYIS\nfrom rateslib.instruments.zcis import ZCIS\nfrom rateslib.instruments.zcs import ZCS\n\n__all__ = [\n    # derivatives\n    \"IRS\",\n    \"FRA\",\n    \"SBS\",\n    \"STIRFuture\",\n    \"ZCS\",\n    # cross currency\n    \"XCS\",\n    \"NDXCS\",\n    \"NDF\",\n    \"FXSwap\",\n    \"FXForward\",\n    # inflation\n    \"ZCIS\",\n    \"IIRS\",\n    \"YoYIS\",\n    # credit\n    \"CDS\",\n    # securities\n    \"FixedRateBond\",\n    \"FloatRateNote\",\n    \"IndexFixedRateBond\",\n    \"BondFuture\",\n    \"Bill\",\n    \"Fee\",\n    \"Loan\",\n    # fx options\n    \"FXPut\",\n    \"FXCall\",\n    \"FXRiskReversal\",\n    \"FXStraddle\",\n    \"FXStrangle\",\n    \"FXBrokerFly\",\n    # ir options\n    \"IRSPut\",\n    \"IRSCall\",\n    \"IRSRiskReversal\",\n    \"IRSStraddle\",\n    \"IRSStrangle\",\n    # generics\n    \"Portfolio\",\n    \"Fly\",\n    \"Spread\",\n    \"Value\",\n    \"FXVolValue\",\n    \"IRVolValue\",\n    \"BondCalcMode\",\n    \"BillCalcMode\",\n    \"_BaseInstrument\",\n    \"_BaseBondInstrument\",\n    \"_BaseFXOption\",\n    \"_BaseFXOptionStrat\",\n    \"_BaseIRSOption\",\n    \"_BaseIRSOptionStrat\",\n]\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.instruments.bonds.bill import Bill\nfrom rateslib.instruments.bonds.bond_future import BondFuture\nfrom rateslib.instruments.bonds.conventions import BillCalcMode, BondCalcMode\nfrom rateslib.instruments.bonds.fixed_rate_bond import FixedRateBond\nfrom rateslib.instruments.bonds.float_rate_note import FloatRateNote\nfrom rateslib.instruments.bonds.index_fixed_rate_bond import IndexFixedRateBond\nfrom rateslib.instruments.bonds.protocols import _BaseBondInstrument\n\n__all__ = [\n    \"FixedRateBond\",\n    \"IndexFixedRateBond\",\n    \"BondFuture\",\n    \"Bill\",\n    \"FloatRateNote\",\n    \"BillCalcMode\",\n    \"BondCalcMode\",\n    \"_BaseBondInstrument\",\n]\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/bill.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual import Variable, gradient\nfrom rateslib.dual.utils import _dual_float, _to_number\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.bonds.conventions import (\n    BillCalcMode,\n    _get_bill_calc_mode,\n)\nfrom rateslib.instruments.bonds.fixed_rate_bond import FixedRateBond\nfrom rateslib.instruments.bonds.protocols import _BaseBondInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg\nfrom rateslib.scheduling import Schedule\nfrom rateslib.scheduling.frequency import _get_frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        Number,\n        RollDay,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass Bill(_BaseBondInstrument):\n    \"\"\"\n    A *bill*, or discount security, composed of a :class:`~rateslib.legs.FixedLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import Bill\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       bill = Bill(\n           effective=dt(2000, 1, 1),\n           termination=\"3y\",\n           spec=\"us_gbb\",\n       )\n       bill.cashflows()\n\n    .. rubric:: Pricing\n\n    A *Bill* requires one *disc curve*. The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = {\"disc_curve\": disc_curve}  # dict form is explicit\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    calc_mode : str or BillCalcMode\n        A calculation mode for dealing with bonds under different conventions. See notes.\n    settle: int\n        The number of days by which to lag 'today' to arrive at standard settlement.\n    metric : str, :green:`optional` (set as 'price')\n        The pricing metric returned by :meth:`~rateslib.instruments.FixedRateBond.rate`.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    \"\"\"\n\n    _rate_scalar = 1.0\n\n    @property\n    def leg1(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: str_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        settle: int_ = NoInput(0),\n        calc_mode: BillCalcMode | str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str = \"price\",\n    ):\n        user_args = dict(\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            ex_div=ex_div,\n            roll=roll,\n            eom=eom,\n            notional=notional,\n            currency=currency,\n            convention=convention,\n            settle=settle,\n            calc_mode=calc_mode,\n            curves=self._parse_curves(curves),\n            metric=metric,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            initial_exchange=False,\n            final_exchange=True,\n            fixed_rate=0.0,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            calc_mode=defaults.calc_mode[type(self).__name__],\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_specific[type(self).__name__],\n            ex_div=defaults.ex_div,\n            settle=defaults.settle,\n        )\n\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"calc_mode\", \"settle\", \"metric\", \"frequency\", \"vol\"],\n        )\n        self.kwargs.meta[\"calc_mode\"] = _get_bill_calc_mode(self.kwargs.meta[\"calc_mode\"])\n        if isinstance(self.kwargs.leg1[\"termination\"], str):\n            s_ = Schedule(\n                effective=self.kwargs.leg1[\"effective\"],\n                termination=self.kwargs.leg1[\"termination\"],\n                frequency=self.kwargs.leg1[\"termination\"],\n                modifier=self.kwargs.leg1[\"modifier\"],\n                calendar=self.kwargs.leg1[\"calendar\"],\n                roll=self.kwargs.leg1[\"roll\"],\n                eom=self.kwargs.leg1[\"eom\"],\n            )\n            self._kwargs.leg1[\"termination\"] = s_.termination\n        self._kwargs.leg1[\"frequency\"] = \"Z\"\n        self._kwargs.meta[\"frequency\"] = _drb(\n            self.kwargs.meta[\"calc_mode\"]._ytm_clone_kwargs[\"frequency\"],\n            self.kwargs.meta[\"frequency\"],\n        )\n\n        self._leg1 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._legs = [self.leg1]\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        A Bill has one curve requirements: a disc_curve.\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 1:\n                return _Curves(\n                    disc_curve=curves[0],\n                )\n            elif len(curves) == 2:\n                return _Curves(\n                    disc_curve=curves[1],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 1 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return various pricing metrics of the security calculated from\n        :class:`~rateslib.curves.Curve` s.\n\n        Parameters\n        ----------\n        curves : Curve, str or list of such\n            A single :class:`Curve` or id or a list of such. A list defines the\n            following curves in the order:\n\n              - Forecasting :class:`Curve` for ``leg1``.\n              - Discounting :class:`Curve` for ``leg1``.\n        solver : Solver, optional\n            The numerical :class:`Solver` that constructs ``Curves`` from calibrating\n            instruments.\n        fx : float, FXRates, FXForwards, optional\n            The immediate settlement FX rate that will be used to convert values\n            into another currency. A given `float` is used directly. If giving a\n            ``FXRates`` or ``FXForwards`` object, converts from local currency\n            into ``base``.\n        base : str, optional\n            The base currency to convert cashflows into (3-digit code), set by default.\n            Only used if ``fx`` is an ``FXRates`` or ``FXForwards`` object.\n        metric : str in {\"price\", \"discount_rate\", \"ytm\", \"simple_rate\"}\n            Metric returned by the method. Uses the *Instrument* default if not given.\n\n        Returns\n        -------\n        float, Dual, Dual2\n        \"\"\"\n        c = _parse_curves(self, curves, solver)\n        disc_curve_ = _get_curve(\"disc_curve\", False, False, *c)\n\n        settlement_ = self._maybe_get_settlement(settlement=settlement, disc_curve=disc_curve_)\n\n        # scale price to par 100 and make a fwd adjustment according to curve\n        price = (\n            self.npv(curves=curves, solver=solver, local=False)  # type: ignore[operator]\n            * 100\n            / (-self.leg1.settlement_params.notional * disc_curve_[settlement_])\n        )\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n        if metric_ in [\"price\", \"clean_price\", \"dirty_price\"]:\n            return price\n        elif metric_ == \"discount_rate\":\n            return self.discount_rate(price, settlement_)\n        elif metric_ == \"simple_rate\":\n            return self.simple_rate(price, settlement_)\n        elif metric_ == \"ytm\":\n            return self.ytm(price, settlement_, NoInput(0))\n        raise ValueError(\"`metric` must be in {'price', 'discount_rate', 'ytm', 'simple_rate'}\")\n\n    def simple_rate(self, price: DualTypes, settlement: datetime) -> DualTypes:\n        \"\"\"\n        Return the simple rate of the security from its ``price``.\n\n        Parameters\n        ----------\n        price : float, Dual, or Dual2\n            The price of the security.\n        settlement : datetime\n            The settlement date of the security.\n\n        Returns\n        -------\n        float, Dual, or Dual2\n        \"\"\"\n        acc_frac = self.kwargs.meta[\"calc_mode\"]._settle_accrual(self, settlement, 0)\n        dcf = (1 - acc_frac) * self.leg1._regular_periods[0].period_params.dcf\n        return ((100 / price - 1) / dcf) * 100  # type: ignore[no-any-return]\n\n    def discount_rate(self, price: DualTypes, settlement: datetime) -> DualTypes:\n        \"\"\"\n        Return the discount rate of the security from its ``price``.\n\n        Parameters\n        ----------\n        price : float, Dual, or Dual2\n            The price of the security.\n        settlement : datetime\n            The settlement date of the security.\n\n        Returns\n        -------\n        float, Dual, or Dual2\n        \"\"\"\n        acc_frac = self.kwargs.meta[\"calc_mode\"]._settle_accrual(self, settlement, 0)\n        dcf = (1 - acc_frac) * self.leg1._regular_periods[0].period_params.dcf\n        rate = ((1 - price / 100) / dcf) * 100\n        return rate  # type: ignore[no-any-return]\n\n    def price(\n        self,\n        rate: DualTypes,\n        settlement: datetime,\n        dirty: bool = False,\n        calc_mode: str_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return the price of the bill given the ``discount_rate``.\n\n        Parameters\n        ----------\n        rate : float\n            The rate used by the pricing formula.\n        settlement : datetime\n            The settlement date.\n        dirty : bool, not required\n            Discount securities have no coupon, the concept of clean or dirty is not\n            relevant. Argument is included for signature consistency with\n            :meth:`FixedRateBond.price<rateslib.instruments.FixedRateBond.price>`.\n        calc_mode : str, optional\n            A calculation mode to force, which is used instead of that attributed the\n            *Bill* instance.\n\n        Returns\n        -------\n        float, Dual, Dual2\n        \"\"\"\n        calc_mode_ = _get_bill_calc_mode(_drb(self.kwargs.meta[\"calc_mode\"], calc_mode))\n        price_func = getattr(self, f\"_price_{calc_mode_._price_type}\")\n        return price_func(rate, settlement)  # type: ignore[no-any-return]\n\n    def _price_discount(self, rate: DualTypes, settlement: datetime) -> DualTypes:\n        acc_frac = self.kwargs.meta[\"calc_mode\"]._settle_accrual(self, settlement, 0)\n        dcf = (1 - acc_frac) * self.leg1._regular_periods[0].period_params.dcf\n        return 100 - rate * dcf  # type: ignore[no-any-return]\n\n    def _price_simple(self, rate: DualTypes, settlement: datetime) -> DualTypes:\n        acc_frac = self.kwargs.meta[\"calc_mode\"]._settle_accrual(self, settlement, 0)\n        dcf = (1 - acc_frac) * self.leg1._regular_periods[0].period_params.dcf\n        return 100 / (1 + rate * dcf / 100)  # type: ignore[no-any-return]\n\n    def ytm(  # type: ignore[override]\n        self,\n        price: DualTypes,\n        settlement: datetime,\n        calc_mode: BillCalcMode | str_ = NoInput(0),\n    ) -> Number:\n        \"\"\"\n        Calculate the yield-to-maturity on an equivalent bond with a coupon of 0%.\n\n        Parameters\n        ----------\n        price: float, Dual, Dual2\n            The price of the *Bill*.\n        settlement: datetime\n            The settlement date of the *Bill*.\n        calc_mode : str, optional\n            A calculation mode to force, which is used instead of that attributed the\n            *Bill* instance.\n\n        Notes\n        -----\n        Maps the following *Bill* ``calc_mode`` to the following *Bond* specifications:\n\n        - *NoInput* -> \"ust\"\n        - *\"ustb\"* -> \"ust\"\n        - *\"uktb\"* -> \"ukt\"\n        - *\"sgbb\"* -> \"sgb\"\n\n        This method calculates by constructing a :class:`~rateslib.instruments.FixedRateBond`\n        with a regular 0% coupon measured from the termination date of the bill.\n        \"\"\"\n        calc_mode_ = _get_bill_calc_mode(_drb(self.kwargs.meta[\"calc_mode\"], calc_mode))\n        freq = calc_mode_._ytm_clone_kwargs[\"frequency\"]\n\n        frequency = _get_frequency(\n            freq, self.leg1.schedule.utermination.day, self.leg1.schedule.calendar\n        )\n        quasi_ustart = frequency.uprevious(self.leg1.schedule.uschedule[-1])\n        while quasi_ustart > settlement:\n            quasi_ustart = frequency.uprevious(quasi_ustart)\n\n        equiv_bond = FixedRateBond(  # type: ignore[abstract]\n            effective=quasi_ustart,\n            termination=self.leg1.schedule.utermination,\n            fixed_rate=0.0,\n            **calc_mode_._ytm_clone_kwargs,  # type: ignore[arg-type]\n        )\n        return equiv_bond.ytm(price, settlement)\n\n    def duration(self, ytm: DualTypes, settlement: datetime, metric: str = \"risk\") -> float:\n        \"\"\"\n        Return the duration of the *Bill*. See\n        :class:`~rateslib.instruments.FixedRateBond.duration` for arguments.\n\n        Notes\n        ------\n\n        .. warning::\n\n           This function returns a *duration* that is consistent with a\n           *FixedRateBond* yield-to-maturity definition. It currently does not use the\n           specified ``convention`` of the *Bill*, and can be sensitive to the\n           ``frequency`` of the representative *FixedRateBond* equivalent.\n\n        .. ipython:: python\n\n           bill = Bill(effective=dt(2024, 2, 29), termination=dt(2024, 8, 29), spec=\"us_gbb\")\n           bill.duration(settlement=dt(2024, 5, 30), ytm=5.2525, metric=\"duration\")\n\n           bill = Bill(effective=dt(2024, 2, 29), termination=dt(2024, 8, 29), spec=\"us_gbb\", frequency=\"A\")\n           bill.duration(settlement=dt(2024, 5, 30), ytm=5.2525, metric=\"duration\")\n\n        \"\"\"  # noqa: E501\n        # TODO: this is not AD safe: returns only float\n        ytm_: float = _dual_float(ytm)\n        if metric == \"duration\":\n            price_ = _to_number(self.price(Variable(ytm_, [\"y\"]), settlement, dirty=True))\n            freq = _get_frequency(\n                self.kwargs.meta[\"frequency\"],\n                self.leg1.schedule.utermination.day,\n                self.leg1.schedule.calendar,\n            )\n            f = freq.periods_per_annum()\n            v = 1 + ytm_ / (100 * f)\n            _: float = -gradient(price_, [\"y\"])[0] / _dual_float(price_) * v * 100\n            return _\n        else:\n            return super().duration(ytm, settlement, metric)\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/bond_future.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame\n\nfrom rateslib import defaults\nfrom rateslib.curves import Curve\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument, _KWArgs\nfrom rateslib.instruments.protocols.pricing import _Curves, _get_curve, _parse_curves, _Vol\nfrom rateslib.periods.utils import (\n    _maybe_local,\n)\nfrom rateslib.rs import Adjuster, Cal, RollDay\nfrom rateslib.scheduling import add_tenor\nfrom rateslib.scheduling.calendars import _get_years_and_months\nfrom rateslib.solver import Solver\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        CurvesT_,\n        DualTypes,\n        FixedRateBond,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass ConversionFactorFunction(Protocol):\n    # Callable type for Conversion Factor Functions\n    def __call__(self, bond: FixedRateBond) -> DualTypes: ...\n\n\nclass BondFuture(_BaseInstrument):\n    \"\"\"\n    A *bond future* derivative containing a basket of :class:`~rateslib.instruments.FixedRateBond`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import BondFuture, dt, FixedRateBond\n\n    .. ipython:: python\n\n       bf = BondFuture(\n            delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n            coupon=7.0,\n            basket=[\n                FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), spec=\"uk_gb\", fixed_rate=5.75),\n                FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), spec=\"uk_gb\", fixed_rate=9.00),\n            ],\n            nominal=100000,\n            currency=\"gbp\",\n            calc_mode=\"ytm\"\n       )\n       bf.cfs\n\n    .. rubric:: Pricing\n\n    The ``curves`` on individual bonds can be set directly on those *Instruments*, or the\n    ``curves`` for the *BondFuture* will act, if given, as an override.\n\n    Any *FixedRateBond* requires one *disc curve*. The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = {\"disc_curve\": disc_curve}  # dict form is explicit\n\n    Parameters\n    ----------\n    coupon: float\n        The nominal coupon rate set on the contract specifications.\n    delivery: datetime or 2-tuple of datetimes\n        The delivery window first and last delivery day, or a single delivery day.\n    basket: tuple of FixedRateBond\n        The bonds that are available as deliverables.\n    nominal: float, optional\n        The nominal amount of the contract.\n    contracts: int, optional\n        The number of contracts owned or short.\n    calendar: str, optional\n        The calendar to define delivery days within the delivery window.\n    currency: str, optional\n        The currency (3-digit code) of the settlement contract.\n    calc_mode : str or BondCalcMode\n        A calculation mode for determining conversion factors. See notes.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* digital methods' ``curves`` argument.\n        See **Pricing**.\n    metric : str, :green:`optional` (set as 'clean_price')\n        The pricing metric returned by :meth:`~rateslib.instruments.FixedRateBond.rate`.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    Notes\n    -----\n    Conversion factors (CFs) ``calc_mode`` are:\n\n    - *\"ytm\"* which calculates the CF as the clean price percent of par with the bond having a\n      yield-to-maturity on the first delivery day in the delivery window.\n    - *\"ust_short\"* which applies to CME 2y, 3y and 5y treasury futures. See\n      :download:`CME Treasury Conversion Factors<_static/us-treasury-cfs.pdf>`.\n    - *\"ust_long\"* which applies to CME 10y and 30y treasury futures.\n    - *\"eurex_eur\"* which applies to EUREX EUR denominated government bond futures, except\n      Italian BTPs which require a different CF formula.\n    - *\"eurex_chf\"* which applies to EUREX CHF denominated government bond futures.\n    - *\"ice_gbp\"* which applies to ICE Gilt futures.\n\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        coupon: float_ = NoInput(0),\n        delivery: datetime_ | tuple[datetime, datetime] = NoInput(0),\n        basket: tuple[FixedRateBond] | NoInput = NoInput(0),\n        nominal: float_ = NoInput(0),\n        contracts: int_ = NoInput(0),\n        calendar: str_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        calc_mode: str_ = NoInput(0),\n        # meta\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ):\n        user_args = dict(\n            coupon=coupon,\n            delivery=delivery,\n            basket=basket,\n            nominal=nominal,\n            contracts=contracts,\n            calendar=calendar,\n            currency=currency,\n            calc_mode=calc_mode,\n            metric=metric,\n            curves=self._parse_curves(curves),\n        )\n        instrument_args: dict[str, Any] = dict(\n            vol=_Vol(),\n        )\n        # set defaults for missing values\n        default_args = dict(\n            calc_mode=defaults.calc_mode_futures,\n            currency=defaults.base_currency,\n            nominal=defaults.notional,\n            contracts=1,\n            metric=\"future_price\",\n        )\n\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\n                \"coupon\",\n                \"delivery\",\n                \"basket\",\n                \"nominal\",\n                \"contracts\",\n                \"calendar\",\n                \"currency\",\n                \"calc_mode\",\n                \"metric\",\n                \"curves\",\n                \"vol\",\n            ],\n        )\n\n        kw = self.kwargs.meta\n        if isinstance(kw[\"delivery\"], datetime):\n            kw[\"delivery\"] = (kw[\"delivery\"], kw[\"delivery\"])\n        elif isinstance(kw[\"delivery\"], NoInput):\n            raise ValueError(\"`delivery` must be a datetime or sequence of datetimes.\")\n        else:\n            kw[\"delivery\"] = tuple(kw[\"delivery\"])\n\n        if isinstance(kw[\"coupon\"], NoInput):\n            raise ValueError(\"`coupon` must be value.\")\n\n        self._cfs: tuple[DualTypes, ...] | NoInput = NoInput(0)\n\n    def __repr__(self) -> str:\n        return f\"<rl.BondFuture at {hex(id(self))}>\"\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An FRB has one curve requirements: a disc_curve.\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 1:\n                return _Curves(\n                    disc_curve=curves[0],\n                )\n            elif len(curves) == 2:\n                return _Curves(\n                    disc_curve=curves[1],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 1 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    @property\n    def notional(self) -> DualTypes:\n        \"\"\"\n        The effective notional: the number of contracts multiplied by contract nominal.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        nominal: DualTypes = self.kwargs.meta[\"nominal\"]\n        contracts: DualTypes = self.kwargs.meta[\"contracts\"]\n        _: DualTypes = nominal * contracts * -1\n        return _  # long positions is negative notn\n\n    @property\n    def cfs(self) -> tuple[DualTypes, ...]:\n        \"\"\"\n        Return the conversion factors for each bond in the ordered ``basket``.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, BondFuture, FixedRateBond\n\n        .. ipython:: python\n\n           bf = BondFuture(\n               delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n               coupon=7.0,\n               basket=[\n                   FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, spec=\"uk_gb\"),\n                   FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, spec=\"uk_gb\"),\n                   FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, spec=\"uk_gb\"),\n                   FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, spec=\"uk_gb\"),\n               ]\n           )\n           bf.cfs\n\n        Returns\n        -------\n        tuple\n\n        Notes\n        -----\n        The determination of conversion factors depend upon the ``calc_mode`` given\n        at initialization. These values, under the appropriate method, can be compared with\n        officially published exchange data such as that for UK gilts under the \"ytm\" method:\n        :download:`ICE-LIFFE Jun23 Long Gilt<_static/long_gilt_initial_jun23.pdf>`, and values\n        under the 'eurex_eur' see\n        :download:`EUREX Jun23 Bond Futures<_static/eurex_bond_conversion_factors.csv>`.\n        \"\"\"\n        if isinstance(self._cfs, NoInput):\n            self._cfs = self._conversion_factors()\n        return self._cfs\n\n    @property\n    def _cf_funcs(self) -> dict[str, ConversionFactorFunction]:\n        return {\n            \"ytm\": self._cfs_ytm,\n            \"ust_short\": self._cfs_ust_short,\n            \"ust_long\": self._cfs_ust_long,\n            \"eurex_eur\": self._cfs_eurex_eur,\n            \"eurex_chf\": self._cfs_eurex_chf,\n            \"ice_gbp\": self._cfs_ice_gbp,\n        }\n\n    def _conversion_factors(self) -> tuple[DualTypes, ...]:\n        calc_mode: str = self.kwargs.meta[\"calc_mode\"].lower()\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        try:\n            return tuple(self._cf_funcs[calc_mode](bond) for bond in basket)\n        except KeyError:\n            raise ValueError(\"`calc_mode` must be in {'ytm', 'ust_short', 'ust_long'}\")\n\n    def _cfs_ytm(self, bond: FixedRateBond) -> DualTypes:\n        coupon: DualTypes = self.kwargs.meta[\"coupon\"]\n        delivery: tuple[datetime, datetime] = self.kwargs.meta[\"delivery\"]\n        return bond.price(coupon, delivery[0]) / 100\n\n    def _cfs_ust(self, bond: FixedRateBond, short: bool) -> float:\n        # TODO: This method is not AD safe: it uses \"round\" function which destroys derivatives\n        # See CME pdf in doc Notes for formula.\n        coupon = _dual_float(bond.fixed_rate / 100.0)  # type: ignore[operator]  # fixed rate is given\n        delivery: datetime = self.kwargs.meta[\"delivery\"][0]\n        n, z = _get_years_and_months(delivery, bond.leg1.schedule.termination)\n        if not short:\n            mapping = {\n                0: 0,\n                1: 0,\n                2: 0,\n                3: 3,\n                4: 3,\n                5: 3,\n                6: 6,\n                7: 6,\n                8: 6,\n                9: 9,\n                10: 9,\n                11: 9,\n            }\n            z = mapping[z]  # round down number of months to quarters\n        if z < 7:\n            v = z\n        elif short:\n            v = z - 6\n        else:\n            v = 3\n        a = 1 / 1.03 ** (v / 6.0)\n        b = (coupon / 2) * (6 - v) / 6.0\n        if z < 7:\n            c = 1 / 1.03 ** (2 * n)\n        else:\n            c = 1 / 1.03 ** (2 * n + 1)\n        d = (coupon / 0.06) * (1 - c)\n        factor = a * ((coupon / 2) + c + d) - b\n        _: float = round(factor, 4)\n        return _\n\n    def _cfs_ust_short(self, bond: FixedRateBond) -> float:\n        return self._cfs_ust(bond, True)\n\n    def _cfs_ust_long(self, bond: FixedRateBond) -> float:\n        return self._cfs_ust(bond, False)\n\n    def _cfs_eurex_eur(self, bond: FixedRateBond) -> float:\n        # TODO: This method is not AD safe: it uses \"round\" function which destroys derivatives\n        # See EUREX specs\n        dd: datetime = self.kwargs.meta[\"delivery\"][1]\n        i = bond.leg1._period_index(dd)\n        ncd = bond.leg1._regular_periods[i].period_params.end\n        ncd1y = add_tenor(ncd, \"-1y\", \"none\")\n        ncd2y = add_tenor(ncd, \"-2y\", \"none\")\n        lcd = bond.leg1._regular_periods[i].period_params.start\n\n        d_e = float((ncd1y - dd).days)\n        if d_e < 0:\n            act1 = float((ncd - ncd1y).days)\n        else:\n            act1 = float((ncd1y - ncd2y).days)\n\n        d_i = float((ncd1y - lcd).days)\n        if d_i < 0:\n            act2 = float((ncd - ncd1y).days)\n        else:\n            act2 = float((ncd1y - ncd2y).days)\n\n        f = 1.0 + d_e / act1\n        c: DualTypes = bond.fixed_rate  # type: ignore[assignment]\n        n = round((bond.leg1.schedule.termination - ncd).days / 365.25)\n        not_: DualTypes = self.kwargs.meta[\"coupon\"]\n\n        _ = 1.0 + not_ / 100\n\n        cf = 1 / _**f * (c / 100.0 * d_i / act2 + c / not_ * (_ - 1 / _**n) + 1 / _**n)\n        cf -= c / 100.0 * (d_i / act2 - d_e / act1)\n        return round(_dual_float(cf), 6)\n\n    def _cfs_eurex_chf(self, bond: FixedRateBond) -> float:\n        # TODO: This method is not AD safe: it uses \"round\" function which destroys derivatives\n        # See EUREX specs\n\n        dd: datetime = self.kwargs.meta[\"delivery\"][1]\n        mat = bond.leg1.schedule.termination\n        # get full years and full months\n        cal = Cal([], [])\n        n = mat.year - dd.year - 1\n        _date = datetime(dd.year + n, dd.month, dd.day)\n        f = -1.0\n        while _date < mat:\n            f += 1\n            _date = cal.add_months(_date, 1, Adjuster.Actual(), RollDay.Day(dd.day))\n            if f == 12:\n                f = 0\n                n += 1\n\n        ## Using only Python calendar methods\n        # n = mat.year - dd.year\n        # f = (mat.month - dd.month)\n        # if f < 0:\n        #     n = n - 1\n        # f = f % 12\n        #\n        # if f < 0:\n        #     n = n - 1\n        # f = f % 12\n        #\n        # if mat.day < dd.day:\n        #     if f == 0:\n        #         n = n - 1\n        #         f = 11\n        #     else:\n        #         f = f - 1\n        #\n        # if f == 0:\n        #     f = 12\n        #     n = n - 1\n\n        f = f / 12.0\n        c: DualTypes = bond.fixed_rate  # type: ignore[assignment]\n        not_: DualTypes = self.kwargs.meta[\"coupon\"]\n\n        v = 1.0 / (1.0 + not_ / 100.0)\n        cf = v**f * (c / not_ * (1.0 + not_ / 100.0 - v**n) + v**n) - c * (1 - f) / 100.0\n        return round(_dual_float(cf), 6)\n\n    def _cfs_ice_gbp(self, bond: FixedRateBond) -> float:\n        # TODO: This method is not AD safe: it uses \"round\" function which destroys derivatives\n        # See ICE specs: uses a YTM method for the first delivery date as settlement, rounded\n        d: datetime = self.kwargs.meta[\"delivery\"][0]\n        price = bond.price(ytm=self.kwargs.meta[\"coupon\"], settlement=datetime(d.year, d.month, 1))\n        return round(_dual_float(price / 100.0), 7)\n\n    def dlv(\n        self,\n        future_price: DualTypes,\n        prices: list[DualTypes],\n        repo_rate: DualTypes | tuple[DualTypes, ...],\n        settlement: datetime,\n        delivery: datetime | NoInput = NoInput(0),\n        convention: str | NoInput = NoInput(0),\n        dirty: bool = False,\n    ) -> DataFrame:\n        \"\"\"\n        Return an aggregated DataFrame of deliverable (dlv) metrics.\n\n        .. rubric:: Examples\n\n        This example replicates the screen print in the publication\n        *The Futures Bond Basis: Second Edition (p77)* by Moorad Choudhry. To replicate\n        that publication exactly no calendar has been provided. Using the London business day\n        calendar and would affect the metrics of the third bond to a small degree (i.e.\n        set `calendar=\"ldn\"`)\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import BondFuture, Solver, FixedRateBond, dt\n\n        .. ipython:: python\n\n           future = BondFuture(\n                delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n                coupon=7.0,\n                basket=[\n                    FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), spec=\"uk_gb\", fixed_rate=5.75, calendar=\"bus\"),\n                    FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), spec=\"uk_gb\", fixed_rate=9.00, calendar=\"bus\"),\n                    FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), spec=\"uk_gb\", fixed_rate=6.25, calendar=\"bus\"),\n                    FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), spec=\"uk_gb\", fixed_rate=9.00, calendar=\"bus\"),\n                ],\n                nominal=100000,\n                contracts=10,\n                currency=\"gbp\",\n           )\n           future.dlv(\n               future_price=112.98,\n               prices=[102.732, 131.461, 107.877, 134.455],\n               repo_rate=6.24,\n               settlement=dt(2000, 3, 16),\n               convention=\"Act365f\",\n           )\n\n        Parameters\n        ----------\n        future_price: float, Dual, Dual2\n            The price of the future.\n        prices: sequence of float, Dual, Dual2\n            The prices of the bonds in the deliverable basket (ordered).\n        repo_rate: float, Dual, Dual2 or list/tuple of such\n            The repo rates of the bonds to delivery.\n        settlement: datetime\n            The settlement date of the bonds.\n        delivery: datetime, optional\n            The date of the futures delivery. If not given uses the final delivery\n            day.\n        convention: str, optional\n            The day count convention applied to the repo rates.\n        dirty: bool\n            Whether the bond prices are given including accrued interest. Default is *False*.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"  # noqa: E501\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        if not isinstance(repo_rate, tuple | list):\n            r_ = (repo_rate,) * len(basket)\n        else:\n            r_ = tuple(repo_rate)\n\n        df = DataFrame(\n            columns=[\n                \"Bond\",\n                \"Price\",\n                \"YTM\",\n                \"C.Factor\",\n                \"Gross Basis\",\n                \"Implied Repo\",\n                \"Actual Repo\",\n                \"Net Basis\",\n            ],\n            index=range(len(basket)),\n        )\n        df[\"Price\"] = prices  # type: ignore[assignment]\n        df[\"YTM\"] = [bond.ytm(prices[i], settlement, dirty=dirty) for i, bond in enumerate(basket)]  # type: ignore[assignment]\n        df[\"C.Factor\"] = self.cfs  # type: ignore[assignment]\n        df[\"Gross Basis\"] = self.gross_basis(future_price, prices, settlement, dirty=dirty)  # type: ignore[assignment]\n        df[\"Implied Repo\"] = self.implied_repo(  # type: ignore[assignment]\n            future_price,\n            prices,\n            settlement,\n            delivery,\n            convention,\n            dirty=dirty,\n        )\n        df[\"Actual Repo\"] = r_  # type: ignore[assignment]\n        df[\"Net Basis\"] = self.net_basis(  # type: ignore[assignment]\n            future_price,\n            prices,\n            r_,\n            settlement,\n            delivery,\n            convention,\n            dirty=dirty,\n        )\n        df[\"Bond\"] = [\n            f\"{bond.fixed_rate:,.3f}% {bond.leg1.schedule.termination.strftime('%d-%m-%Y')}\"\n            for bond in basket\n        ]\n        return df\n\n    def cms(\n        self,\n        prices: Sequence[float],\n        settlement: datetime,\n        shifts: Sequence[float],\n        delivery: datetime | NoInput = NoInput(0),\n        dirty: bool = False,\n    ) -> DataFrame:\n        \"\"\"\n        Perform CTD multi-security analysis.\n\n        Parameters\n        ----------\n        prices: sequence of float, Dual, Dual2\n            The prices of the bonds in the deliverable basket (ordered).\n        settlement: datetime\n            The settlement date of the bonds.\n        shifts : Sequence[float]\n            The scenarios to analyse.\n        delivery: datetime, optional\n            The date of the futures delivery. If not given uses the final delivery\n            day.\n        dirty: bool\n            Whether the bond prices are given including accrued interest. Default is *False*.\n\n        Returns\n        -------\n        DataFrame\n\n        Notes\n        -----\n        This method only operates when the CTD basket has multiple securities\n        \"\"\"\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        if len(basket) == 1:\n            raise ValueError(\"Multi-security analysis cannot be performed with one security.\")\n        delivery = _drb(self.kwargs.meta[\"delivery\"][1], delivery)\n\n        # build a curve for pricing\n        today = basket[0].leg1.schedule.calendar.lag_bus_days(\n            settlement,\n            -basket[0].kwargs.meta[\"settle\"],\n            False,\n        )\n        unsorted_nodes = {\n            today: 1.0,\n            **{_.leg1.schedule.termination: 1.0 for _ in basket},\n        }\n        bcurve = Curve(\n            nodes=dict(sorted(unsorted_nodes.items(), key=lambda _: _[0])),\n            convention=\"act365f\",  # use the most natural DCF without scaling\n        )\n        if dirty:\n            metric = \"dirty_price\"\n        else:\n            metric = \"clean_price\"\n        solver = Solver(\n            curves=[bcurve],\n            instruments=[(_, {\"curves\": bcurve, \"metric\": metric}) for _ in basket],  # type: ignore[misc]\n            s=prices,\n        )\n        if solver.result[\"status\"] != \"SUCCESS\":\n            raise ValueError(\n                \"A bond curve could not be solved for analysis. \"\n                \"See 'Cookbook: Bond Future CTD Multi-Security Analysis'.\",\n            )\n        bcurve._set_ad_order(order=0)  # turn off AD for efficiency\n\n        data: dict[str | float, Any] = {\n            \"Bond\": [\n                f\"{bond.fixed_rate:,.3f}% {bond.leg1.schedule.termination.strftime('%d-%m-%Y')}\"\n                for bond in basket\n            ],\n        }\n        for shift in shifts:\n            _curve = bcurve.shift(shift)\n            future_price = self.rate(curves=_curve, metric=\"future_price\")\n            data.update(\n                {\n                    shift: tuple(\n                        bond.rate(curves=_curve, metric=\"clean_price\", settlement=delivery)\n                        - self.cfs[i] * future_price\n                        for i, bond in enumerate(basket)\n                    ),\n                },\n            )\n\n        _: DataFrame = DataFrame(data=data)\n        return _\n\n    def gross_basis(\n        self,\n        future_price: DualTypes,\n        prices: list[DualTypes],\n        settlement: datetime | NoInput = NoInput(0),\n        dirty: bool = False,\n    ) -> tuple[DualTypes, ...]:\n        \"\"\"\n        Calculate the gross basis of each bond in the basket.\n\n        .. rubric:: Exmaples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import BondFuture, Solver, FixedRateBond, dt\n\n        .. ipython:: python\n\n           bf = BondFuture(\n                delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n                coupon=7.0,\n                basket=[\n                    FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), spec=\"uk_gb\", fixed_rate=5.75, calendar=\"bus\"),\n                    FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), spec=\"uk_gb\", fixed_rate=9.00, calendar=\"bus\"),\n                    FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), spec=\"uk_gb\", fixed_rate=6.25, calendar=\"bus\"),\n                    FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), spec=\"uk_gb\", fixed_rate=9.00, calendar=\"bus\"),\n                ],\n                nominal=100000,\n                contracts=10,\n                currency=\"gbp\",\n           )\n           bf.gross_basis(\n               future_price=112.98,\n               prices=[102.732, 131.461, 107.877, 134.455],\n               settlement=dt(2000, 3, 16),\n           )\n\n        Parameters\n        ----------\n        future_price: float, Dual, Dual2\n            The price of the future.\n        prices: sequence of float, Dual, Dual2\n            The prices of the bonds in the deliverable basket (ordered).\n        settlement: datetime\n            The settlement date of the bonds, required only if ``dirty`` is *True*.\n        dirty: bool\n            Whether the bond prices are given including accrued interest.\n\n        Returns\n        -------\n        tuple\n        \"\"\"  # noqa: E501\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        if dirty:\n            if isinstance(settlement, NoInput):\n                raise ValueError(\"`settlement` must be specified if `dirty` is True.\")\n            prices_: Sequence[DualTypes] = tuple(\n                prices[i] - bond.accrued(settlement) for i, bond in enumerate(basket)\n            )\n        else:\n            prices_ = prices\n        return tuple(prices_[i] - self.cfs[i] * future_price for i in range(len(basket)))\n\n    def net_basis(\n        self,\n        future_price: DualTypes,\n        prices: Sequence[DualTypes],\n        repo_rate: DualTypes | Sequence[DualTypes],\n        settlement: datetime,\n        delivery: datetime | NoInput = NoInput(0),\n        convention: str | NoInput = NoInput(0),\n        dirty: bool = False,\n    ) -> tuple[DualTypes, ...]:\n        \"\"\"\n        Calculate the net basis of each bond in the basket via the proceeds\n        method of repo.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, BondFuture, FixedRateBond\n\n        .. ipython:: python\n\n           bf = BondFuture(\n               delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n               coupon=7.0,\n               basket=[\n                   FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, spec=\"uk_gb\"),\n                   FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, spec=\"uk_gb\"),\n                   FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, spec=\"uk_gb\"),\n                   FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, spec=\"uk_gb\"),\n               ]\n           )\n           bf.net_basis(\n               future_price=112.98,\n               prices=[102.732, 131.461, 107.877, 134.455],\n               settlement=dt(2000, 3, 16),\n               repo_rate=6.24,\n               convention=\"Act365F\",\n           )\n\n        Parameters\n        ----------\n        future_price: float, Dual, Dual2\n            The price of the future.\n        prices: sequence of float, Dual, Dual2\n            The prices of the bonds in the deliverable basket (ordered).\n        repo_rate: float, Dual, Dual2 or list/tuple of such\n            The repo rates of the bonds to delivery.\n        settlement: datetime\n            The settlement date of the bonds, required only if ``dirty`` is *True*.\n        delivery: datetime, optional\n            The date of the futures delivery. If not given uses the final delivery\n            day.\n        convention: str, optional\n            The day count convention applied to the repo rates.\n        dirty: bool\n            Whether the bond prices are given including accrued interest.\n\n        Returns\n        -------\n        tuple\n        \"\"\"  # noqa: E501\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        f_settlement: datetime = _drb(self.kwargs.meta[\"delivery\"][1], delivery)\n\n        if not isinstance(repo_rate, Sequence):\n            r_: Sequence[DualTypes] = (repo_rate,) * len(basket)\n        else:\n            r_ = repo_rate\n\n        if dirty:\n            net_basis_ = tuple(\n                bond.fwd_from_repo(\n                    prices[i],\n                    settlement,\n                    f_settlement,\n                    r_[i],\n                    convention,\n                    dirty=dirty,\n                )\n                - self.cfs[i] * future_price\n                - bond.accrued(f_settlement)\n                for i, bond in enumerate(basket)\n            )\n        else:\n            net_basis_ = tuple(\n                bond.fwd_from_repo(\n                    prices[i],\n                    settlement,\n                    f_settlement,\n                    r_[i],\n                    convention,\n                    dirty=dirty,\n                )\n                - self.cfs[i] * future_price\n                for i, bond in enumerate(basket)\n            )\n        return net_basis_\n\n    def implied_repo(\n        self,\n        future_price: DualTypes,\n        prices: Sequence[DualTypes],\n        settlement: datetime,\n        delivery: datetime | NoInput = NoInput(0),\n        convention: str | NoInput = NoInput(0),\n        dirty: bool = False,\n    ) -> tuple[DualTypes, ...]:\n        \"\"\"\n        Calculate the implied repo of each bond in the basket using the proceeds\n        method.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import BondFuture, Solver, FixedRateBond, dt\n\n        .. ipython:: python\n\n           bf = BondFuture(\n                delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n                coupon=7.0,\n                basket=[\n                    FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), spec=\"uk_gb\", fixed_rate=5.75),\n                    FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), spec=\"uk_gb\", fixed_rate=9.00),\n                    FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), spec=\"uk_gb\", fixed_rate=6.25),\n                    FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), spec=\"uk_gb\", fixed_rate=9.00),\n                ],\n           )\n           future.implied_repo(\n               future_price=112.98,\n               prices=[102.732, 131.461, 107.877, 134.455],\n               settlement=dt(2000, 3, 16),\n               convention=\"Act365F\",\n           )\n\n        Parameters\n        ----------\n        future_price: float, Dual, Dual2\n            The price of the future.\n        prices: sequence of float, Dual, Dual2\n            The prices of the bonds in the deliverable basket (ordered).\n        settlement: datetime\n            The settlement date of the bonds.\n        delivery: datetime, optional\n            The date of the futures delivery. If not given uses the final delivery\n            day.\n        convention: str, optional\n            The day count convention used in the rate.\n        dirty: bool\n            Whether the bond prices are given including accrued interest.\n\n        Returns\n        -------\n        tuple\n        \"\"\"  # noqa: E501\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        f_settlement: datetime = _drb(self.kwargs.meta[\"delivery\"][1], delivery)\n\n        implied_repos: tuple[DualTypes, ...] = tuple()\n        for i, bond in enumerate(basket):\n            invoice_price = future_price * self.cfs[i]\n            implied_repos += (\n                bond.repo_from_fwd(\n                    price=prices[i],\n                    settlement=settlement,\n                    forward_settlement=f_settlement,\n                    forward_price=invoice_price,\n                    convention=convention,\n                    dirty=dirty,\n                ),\n            )\n        return implied_repos\n\n    def ytm(\n        self,\n        future_price: DualTypes,\n        delivery: datetime | NoInput = NoInput(0),\n    ) -> tuple[DualTypes, ...]:\n        \"\"\"\n        Calculate the yield-to-maturity of the bond future.\n\n        The relevant ytm should be selected according to the CTD index.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import BondFuture, Solver, FixedRateBond, dt\n\n        .. ipython:: python\n\n           bf = BondFuture(\n                delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n                coupon=7.0,\n                basket=[\n                    FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), spec=\"uk_gb\", fixed_rate=5.75),\n                    FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), spec=\"uk_gb\", fixed_rate=9.00),\n                    FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), spec=\"uk_gb\", fixed_rate=6.25),\n                    FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), spec=\"uk_gb\", fixed_rate=9.00),\n                ],\n           )\n           bf.ytm(future_price=112.98)\n\n        Parameters\n        ----------\n        future_price : float, Dual, Dual2\n            The price of the future.\n        delivery : datetime, optional\n            The future delivery day on which to calculate the yield. If not given aligns\n            with the last delivery day specified on the future.\n\n        Returns\n        -------\n        tuple\n        \"\"\"\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        settlement: datetime = _drb(self.kwargs.meta[\"delivery\"][1], delivery)\n        adjusted_prices = [future_price * cf for cf in self.cfs]\n        yields = tuple(bond.ytm(adjusted_prices[i], settlement) for i, bond in enumerate(basket))\n        return yields\n\n    def duration(\n        self,\n        future_price: DualTypes,\n        metric: str = \"risk\",\n        delivery: datetime | NoInput = NoInput(0),\n    ) -> tuple[float, ...]:\n        \"\"\"\n        Return the (negated) derivative of ``price`` w.r.t. ``ytm`` .\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import BondFuture, Solver, FixedRateBond, dt\n\n        .. ipython:: python\n\n           bf = BondFuture(\n                delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n                coupon=7.0,\n                basket=[\n                    FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), spec=\"uk_gb\", fixed_rate=5.75),\n                    FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), spec=\"uk_gb\", fixed_rate=9.00),\n                    FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), spec=\"uk_gb\", fixed_rate=6.25),\n                    FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), spec=\"uk_gb\", fixed_rate=9.00),\n                ],\n           )\n           bf.duration(future_price=112.98)\n\n        Parameters\n        ----------\n        future_price : float\n            The price of the future.\n        metric : str\n            The specific duration calculation to return. See notes.\n        delivery : datetime, optional\n            The delivery date of the contract.\n\n        Returns\n        -------\n        float\n\n        See Also\n        --------\n        FixedRateBond.duration: Calculation the risk of a FixedRateBond.\n\n        Example\n        -------\n        .. ipython:: python\n\n           risk = future.duration(112.98)\n           risk\n\n        The difference in yield is shown to be 1bp for the CTD (index: 0)\n        when the futures price is adjusted by the risk amount.\n\n        .. ipython:: python\n\n           future.ytm(112.98)\n           future.ytm(112.98 + risk[0] / 100)\n        \"\"\"\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        f_settlement: datetime = _drb(self.kwargs.meta[\"delivery\"][1], delivery)\n\n        _: tuple[float, ...] = ()\n        for i, bond in enumerate(basket):\n            invoice_price = future_price * self.cfs[i]\n            ytm = bond.ytm(invoice_price, f_settlement)\n            if metric == \"risk\":\n                _ += (_dual_float(bond.duration(ytm, f_settlement, \"risk\") / self.cfs[i]),)\n            else:\n                __ = (bond.duration(ytm, f_settlement, metric),)\n                _ += __\n        return _\n\n    def convexity(\n        self,\n        future_price: DualTypes,\n        delivery: datetime | NoInput = NoInput(0),\n    ) -> tuple[float, ...]:\n        \"\"\"\n        Return the second derivative of ``price`` w.r.t. ``ytm`` .\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import BondFuture, Solver, FixedRateBond, dt\n\n        .. ipython:: python\n\n           bf = BondFuture(\n                delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n                coupon=7.0,\n                basket=[\n                    FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), spec=\"uk_gb\", fixed_rate=5.75),\n                    FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), spec=\"uk_gb\", fixed_rate=9.00),\n                    FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), spec=\"uk_gb\", fixed_rate=6.25),\n                    FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), spec=\"uk_gb\", fixed_rate=9.00),\n                ],\n           )\n           bf.convexity(future_price=112.98)\n\n        Parameters\n        ----------\n        future_price : float\n            The price of the future.\n        delivery : datetime, optional\n            The delivery date of the contract. If not given uses the last delivery day\n            in the delivery window.\n\n        Returns\n        -------\n        float\n\n        See Also\n        --------\n        FixedRateBond.convexity: Calculate the convexity of a FixedRateBond.\n\n        \"\"\"\n        # TODO: Not AD safe becuase dependent convexity method is not AD safe. Returns float.\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        f_settlement: datetime = _drb(self.kwargs.meta[\"delivery\"][1], delivery)\n\n        _: tuple[float, ...] = ()\n        for i, bond in enumerate(basket):\n            invoice_price = future_price * self.cfs[i]\n            ytm = bond.ytm(invoice_price, f_settlement)\n            _ += (_dual_float(bond.convexity(ytm, f_settlement) / self.cfs[i]),)\n        return _\n\n    def ctd_index(\n        self,\n        future_price: DualTypes,\n        prices: Sequence[DualTypes],\n        settlement: datetime,\n        delivery: datetime | NoInput = NoInput(0),\n        dirty: bool = False,\n        ordered: bool = False,\n    ) -> int | list[int]:\n        \"\"\"\n        Determine the index (base 0) of the CTD in the basket from implied repo rate.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import BondFuture, Solver, FixedRateBond, dt\n\n        .. ipython:: python\n\n           future = BondFuture(\n                delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n                coupon=7.0,\n                basket=[\n                    FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), spec=\"uk_gb\", fixed_rate=5.75),\n                    FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), spec=\"uk_gb\", fixed_rate=9.00),\n                    FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), spec=\"uk_gb\", fixed_rate=6.25),\n                    FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), spec=\"uk_gb\", fixed_rate=9.00),\n                ],\n                nominal=100000,\n                contracts=10,\n                currency=\"gbp\",\n           )\n           future.ctd_index(\n               future_price=112.98,\n               prices=[102.732, 131.461, 107.877, 134.455],\n               settlement=dt(2000, 3, 16),\n               ordered=True,\n           )\n\n        Parameters\n        ----------\n        future_price : float\n            The price of the future.\n        prices: sequence of float, Dual, Dual2\n            The prices of the bonds in the deliverable basket (ordered).\n        settlement: datetime\n            The settlement date of the bonds.\n        delivery: datetime, optional\n            The date of the futures delivery. If not given uses the final delivery\n            day.\n        dirty: bool\n            Whether the bond prices are given including accrued interest.\n        ordered : bool, optional\n            Whether to return the sorted order of CTD indexes and not just a single index for\n            the specific CTD.\n\n        Returns\n        -------\n        int or list[int]\n        \"\"\"\n        implied_repo = self.implied_repo(\n            future_price,\n            prices,\n            settlement,\n            delivery,\n            \"Act365F\",  # to determine CTD only require a consistent comparison\n            dirty,\n        )\n        if not ordered:\n            ctd_index_ = implied_repo.index(max(implied_repo))\n            return ctd_index_\n        else:\n            _: dict[int, DualTypes] = dict(zip(range(len(implied_repo)), implied_repo, strict=True))\n            _ = dict(sorted(_.items(), key=lambda item: -item[1]))\n            return list(_.keys())\n\n    # Digital Methods\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return various pricing metrics of the security calculated from\n        :class:`~rateslib.curves.Curve` s.\n\n        Parameters\n        ----------\n        curves : Curve, str or list of such\n            A single :class:`Curve` or id or a list of such. A list defines the\n            following curves in the order:\n\n              - Forecasting :class:`Curve` for ``leg1``.\n              - Discounting :class:`Curve` for ``leg1``.\n        solver : Solver, optional\n            The numerical :class:`Solver` that constructs ``Curves`` from calibrating\n            instruments.\n        fx : float, FXRates, FXForwards, optional\n            The immediate settlement FX rate that will be used to convert values\n            into another currency. A given `float` is used directly. If giving a\n            ``FXRates`` or ``FXForwards`` object, converts from local currency\n            into ``base``.\n        base : str, optional\n            The base currency to convert cashflows into (3-digit code), set by default.\n            Only used if ``fx`` is an ``FXRates`` or ``FXForwards`` object.\n        metric : str in {\"future_price\", \"ytm\"}, optional\n            Metric returned by the method.\n        delivery: datetime, optional\n            The date of the futures delivery. If not given uses the final delivery\n            day.\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Notes\n        -----\n        This method determines the *'futures_price'* and *'ytm'*  by assuming a net\n        basis of zero and pricing from the cheapest to delivery (CTD).\n        \"\"\"\n        c = _parse_curves(self, curves, solver)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        basket: tuple[FixedRateBond, ...] = self.kwargs.meta[\"basket\"]\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n        if metric_ not in [\"future_price\", \"ytm\"]:\n            raise ValueError(\"`metric` must be in {'future_price', 'ytm'}.\")\n\n        f_settlement = _drb(self.kwargs.meta[\"delivery\"][1], settlement)\n        prices_: list[DualTypes] = [\n            bond.rate(\n                curves={\"disc_curve\": disc_curve},  # type: ignore[arg-type]\n                solver=solver,\n                fx=fx,\n                base=base,\n                metric=\"clean_price\",\n                settlement=f_settlement,\n            )\n            for bond in basket\n        ]\n        future_prices_: list[DualTypes] = [price / self.cfs[i] for i, price in enumerate(prices_)]\n        future_price: DualTypes = min(future_prices_)\n        ctd_index: int = future_prices_.index(min(future_prices_))\n\n        if metric_ == \"future_price\":\n            return future_price\n        else:  # metric == \"ytm\":\n            return basket[ctd_index].ytm(future_price * self.cfs[ctd_index], f_settlement)\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Determine the monetary value of the bond future position.\n\n        This method is mainly included to calculate risk sensitivities. The\n        monetary value of bond futures is not usually a metric worth considering.\n        The profit or loss of a position based on entry level is a more common\n        metric, however the initial value of the position does not affect the risk.\n\n        See :meth:`BaseDerivative.npv`.\n        \"\"\"\n        future_price = self.rate(\n            curves=curves, solver=solver, fx=fx, base=base, metric=\"future_price\"\n        )\n        local_npv = future_price / 100 * -self.notional\n        return _maybe_local(\n            value=local_npv,\n            local=local,\n            currency=self.kwargs.meta[\"currency\"].lower(),\n            fx=fx,\n            base=base,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/conventions/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.instruments.bonds.conventions.accrued import ACC_FRAC_FUNCS\nfrom rateslib.instruments.bonds.conventions.discounting import (\n    C_FUNCS,\n    V1_FUNCS,\n    V2_FUNCS,\n    V3_FUNCS,\n)\n\nif TYPE_CHECKING:\n    from rateslib.instruments.bonds.conventions.accrued import AccrualFunction  # pragma: no cover\n    from rateslib.instruments.bonds.conventions.discounting import (  # pragma: no cover\n        CashflowFunction,\n        YtmDiscountFunction,\n        YtmStubDiscountFunction,\n    )\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n    )\n\n\nclass BondCalcMode:\n    \"\"\"\n    Define calculation conventions for :class:`~rateslib.instruments.FixedRateBond`,\n    :class:`~rateslib.instruments.IndexFixedRateBond` and\n    :class:`~rateslib.instruments.FloatRateNote` types.\n\n    For a list of :class:`~rateslib.instruments.BondCalcMode` that have already\n    been pre-defined see :ref:`Securities Defaults <defaults-securities-input>`.\n    \n    Parameters\n    ----------\n    settle_accrual: str or Callable\n        The calculation type for accrued interest for physical settlement. See notes.\n    ytm_accrual: str or Callable\n        The calculation method for accrued interest used in the YTM formula. Often the same\n        as above but not always (e.g. Canadian GBs). See notes.\n    v1: str or Callable\n        The calculation function that defines discounting of the first period of the YTM formula.\n    v2: str or Callable\n        The calculation function that defines discounting of the regular periods of the YTM formula.\n    v3: str or Callable\n        The calculation function that defines discounting of the last period of the YTM formula.\n    c1: str or Callable\n        The calculation function that determines the cashflow amount in the first period of the\n        YTM formula.\n    ci: str or Callable\n        The calculation function that determines the cashflow amount in the interim periods of the\n        YTM formula.\n    cn: str or Callable\n        The calculation function that determines the cashflow amount in the final period of the\n        YTM formula.\n\n    Notes\n    -------\n    \n    For an example custom implementation of a *BondCalcMode* see the cookbook article:\n    `Cookbook: Understanding and Customising FixedRateBond Conventions <../z_bond_conventions.html>`_\n    \n    The :class:`~rateslib.instruments.BondCalcMode` is used to configure the calculations for\n    **accrued interest** and **yield-to-maturity** for a variety of different bonds.\n    \n    The bottom of this page enumerates all of the notation for formulae.\n\n    Accrued Interest\n    ****************\n    \n    *Rateslib* makes two types of accrued interest calculations:\n    \n    - Physically settleable accrued interest, *AI*, returned from the\n      :meth:`~rateslib.instruments.FixedRateBond.accrued` method by default.\n    \n      .. math::\n    \n         &AI = \\\\xi c_i \\\\qquad \\\\text{if not ex-dividend} \\\\\\\\\n         &AI = (\\\\xi - 1) c_i \\\\qquad \\\\text{if ex-dividend} \\\\\\\\\n         \n    - Accrued interest for the purpose of determining accurate YTM calculations.\n    \n      .. math::\n    \n         &AI_y = \\\\xi_y c_i \\\\qquad \\\\text{if not ex-dividend} \\\\\\\\\n         &AI_y = (\\\\xi_y - 1) c_i \\\\qquad \\\\text{if ex-dividend} \\\\\\\\\n\n    Where in both these formulae :math:`c_i` currently always uses the real ``cashflow`` method\n    (see below).\n\n    These two methods are almost always the same, but for an example where they differ consider\n    Canadian government bonds. The calculation mode relies on determining the :math:`\\\\xi` and\n    :math:`\\\\xi_y` values, known as the **accrual fraction**. This is achieved by using the\n    following functions:\n    \n    **Accrual Functions**\n\n    Accrual functions must be supplied to the ``settle_accrual`` and ``ytm_accrual``\n    arguments. The available values are:\n\n    - ``linear_days``: A calendar day, linear proportion used in any period.\n    \n      .. math::\n      \n         \\\\xi = r_u / s_u\n    \n    - ``linear_days_long_front_split``: A modified version of the above which, **only for long\n      stub** periods, uses a different formula treating the first quasi period as part of the\n      long stub differently. This adjustment is then scaled according to the length of the period.\n      (Used by UK and German GBs and is the Treasury method for US Treasuries,\n      see Section 31B ii A.356, Code of Federal Regulations)\n      \n      .. math::\n      \n         \\\\xi = (\\\\bar{r}_u / \\\\bar{s}_u + r_u / s_u) / ( d_i * f )\n      \n    - ``30e360_backward``: For **stubs** this method reverts to ``linear_days``. Otherwise,\n      determines the DCF, under *'30e360'* convention, of the remaining part of the coupon\n      period from settlement and deducts this from the full accrual fraction.\n      \n      .. math::\n      \n         \\\\xi = 1 - \\\\bar{d_u} f\n      \n    - ``30u360_forward``: Calculates the DCF between last accrual coupon and settlement,\n      and compares this with DCF between accrual coupon dates, both measured using *'30u360'*\n      (See MSRB Rule G-33):\n      \n      .. math::\n      \n         \\\\xi = DCF(prior, settlement) / DCF(prior, next)\n           \n    - ``act365f_1y``: For **stubs** this method reverts to ``linear_days``. Otherwise,\n      determines the accrual fraction using an approach that uses ACT365F convention.\n      (Used by Canadian GBs)\n      \n      .. math::\n      \n         \\\\xi = \\\\left \\\\{ \\\\begin{matrix} 1.0 & \\\\text{if, } r_u = s_u \\\\\\\\ 1.0 - f(s_u - r_u) / 365 & \\\\text{if, } r_u \\\\ge 365 / f \\\\\\\\ fr_u / 365 & \\\\text{if, } r_u < 365 / f \\\\\\\\ \\\\end{matrix} \\\\right .\n\n    **Custom accrual functions** can also be supplied where the input arguments signature should\n    accept the bond object, the settlement date, and the index relating to the period in which\n    the relevant coupon period falls. It should return an accrual fraction upto settlement.\n    As an example the code below shows the implementation of the\n    *\"linear_days\"* accrual function:\n\n    .. ipython:: python\n\n       def _linear_days(obj, settlement, acc_idx, *args) -> float:\n            sch = obj.leg1.schedule  # <- obj is always the Bond itself\n            r_u = (settlement - sch.aschedule[acc_idx]).days  # <- acc_idx accesses the correct date\n            s_u = (sch.aschedule[acc_idx + 1] - sch.aschedule[acc_idx]).days\n            return r_u / s_u\n    \n    Yield-To-Maturity\n    -----------------\n    \n    Yield-to-maturity in *rateslib*, for **every bond**, is calculated using the below formula.\n    The specific discounting and cashflow generating functions must be provided to determine\n    values based on the conventions of that specific bond. The cases where the number of remaining\n    coupons are 1, 2, or generically >2 are outlined explicitly:\n\n    .. math::\n\n       P &= v_1 \\\\left ( c_1 + 100 \\\\right ), \\\\quad n = 1 \\\\\\\\\n       P &= v_1 \\\\left ( c_1 + v_3 (c_n + 100) \\\\right ), \\\\quad n = 2 \\\\\\\\\n       P &= v_1 \\\\left ( c_1 + \\\\sum_{i=2}^{n-1} c_i v_2^{i-2} v_{2,i} + c_nv_2^{n-2}v_3 + 100 v_2^{n-2}v_3 \\\\right ), \\\\quad n > 2  \\\\\\\\\n       Q &= P - AI_y\n\n    where,\n\n    .. math::\n\n       P &= \\\\text{Dirty price}, \\\\; Q = \\\\text{Clean Price} \\\\\\\\\n       n &= \\\\text{Coupon periods remaining} \\\\\\\\\n       c_1 &= \\\\text{Cashflow (per 100) on next coupon date (may be zero if ex-dividend)} \\\\\\\\\n       c_i &= i \\\\text{'th cashflow (per 100) on subsequent coupon dates} \\\\\\\\\n       v_1 &= \\\\text{Discount value for the initial, possibly stub, period} \\\\\\\\\n       v_2 &= \\\\text{General discount value for the interim regular periods} \\\\\\\\\n       v_{2,i} &= \\\\text{Specific discount value for the i'th interim regular period} \\\\\\\\\n       v_3 &= \\\\text{Discount value for the final, possibly stub, period} \\\\\\\\\n\n    **v2 Functions**\n    \n    *v2* forms the core, regular part of discounting the cashflows. *v2* functions are required when\n    a bond has more than two coupon remaining. This reflects coupon periods that are\n    never stubs. The available functions are described below:\n\n    - ``regular``: uses the traditional discounting function matching the actual frequency of\n      coupons:\n\n      .. math::\n\n         v_2 = \\\\frac{1}{1 + y/f}\n\n    - ``annual``: assumes an annually expressed YTM disregarding the actual coupon frequency:\n\n      .. math::\n\n         v_2 = \\\\left ( \\\\frac{1}{1 + y} \\\\right ) ^ {1/f}\n         \n    - ``annual_pay_adjust``: an extension to ``annual`` that adjusts the period in scope to\n      account for a delay between its unadjusted coupon end date and the actual payment date. (Used\n      by Italian BTPs)\n      \n      .. math::\n      \n         v_2 = \\\\left ( \\\\frac{1}{1 + y} \\\\right ) ^ {1/f}, \\\\qquad \\\\text{and in the current period} \\\\qquad v_{2,i} = v_2 ^ {(1 + p_d / p_D)}\n              \n    **v1 Functions**\n\n    *v1* functions are required for every bond. Its value may, or may not, be dependent upon *v2*.\n    *v1* functions have to handle the cases whereby the coupon period in which *settlement* falls\n    is\n    \n    - The first coupon period, **and** it may be a **stub**,\n    - A regular interim coupon period,\n    - The final coupon period **and** it may be a **stub**.\n     \n    The two most common functions for determining *v1* are described below:\n    \n    - ``compounding``: If a **stub** then scaled by the length of\n      the stub. At issue, or on a coupon date, for a regular period, *v1* converges to *v2*.\n         \n      .. math::\n      \n         v_1 =  v_2^{g(\\\\xi_y)}  \\\\quad \\\\text{where,} \\\\quad g(\\\\xi_y) = \\\\left \\\\{ \\\\begin{matrix} 1-\\\\xi_y & \\\\text{if regular,} \\\\\\\\ (1-\\\\xi_y) f d_i & \\\\text{if stub,} \\\\\\\\ \\\\end{matrix} \\\\right . \\\\\\\\\n\n    - ``simple``: calculation uses a simple interest formula. At issue, or on a coupon date,\n      for a regular period, *v1* converges to a *'regular'* style *v2*.\n    \n      .. math::\n      \n         v_1 = \\\\frac{1}{1 + g(\\\\xi_y) y / f}  \\\\quad \\\\text{where, } g(\\\\xi_y) \\\\text{ defined as above}\n      \n    Combinations, or extensions, of the two above functions are also required for some\n    bond conventions:\n\n    - ``simple_act365f``: uses simple interest with a DCF calculated under Act365F convention,\n      irrespective of the bond’s underlying convention.\n      \n      .. math::\n      \n         v_1 = \\\\frac{1}{1 +  \\\\bar{d_u} y}\n         \n    - ``compounding_final_simple``: uses ``compounding``, unless settlement occurs in the final\n      period of the bond (and in which case n=1) and then the ``simple`` method is applied.\n    - ``compounding_final_simple_act365f``: uses ``compounding``, unless settlement occurs in the\n      final period of the bond (and in which case n=1) and then the ``simple_act365f`` method is\n      applied.\n    - ``compounding_stub_act365f``: uses ``compounding``, unless settlement occurs in a stub\n      period in which case Act365F convention derives the exponent.\n      \n      .. math::\n      \n         v_1 = v_2^{\\\\bar{d}_u} \\\\qquad \\\\text{if stub.}\n\n    - ``simple_long_stub_compounding``: uses ``simple`` formula **except** for long stubs,\n      and the calculation is only different if settlement falls before the quasi-coupon.\n      If settlement occurs before the quasi-coupon date then the entire quasi-coupon period\n      applies regular *v2* discounting, and the preliminary component has *simple* method\n      applied.\n      \n      .. math::\n\n         v_1 = v_2 \\\\frac{1}{1 + [f d_i(1 - \\\\xi_y) - 1] y / f} \\\\qquad \\\\text{if settlement before quasi-coupon in long stub}\n\n    - ``simple_pay_adjust``: adjusts the *'simple'* method to account for the payment date.\n    \n      .. math::\n      \n         v_1 = \\\\frac{1}{1 + g_p(\\\\xi_y) y / f} \\\\quad \\\\text{where,} \\\\quad g_p(\\\\xi_y) = \\\\left \\\\{ \\\\begin{matrix} 1-\\\\xi_y + p_d / p_D & \\\\text{if regular,} \\\\\\\\ (1-\\\\xi_y + p_d / p_D) f d_i & \\\\text{if stub,} \\\\\\\\ \\\\end{matrix} \\\\right .\n    \n    - ``compounding_pay_adjust``: adjusts the *'compounding'* method to account for payment date.\n    \n      .. math::\n      \n         v_1 = v_2^{g_p(\\\\xi_y)}  \\\\quad \\\\text{where, } g_p(\\\\xi_y) \\\\text{ defined as above}\n         \n    - ``compounding_final_simple_pay_adjust``: uses ``compounding`` unless settlement\n      occurs in the final period of the bond (and in which case n=1) and then the\n      ``simple_pay_adjust`` method is applied.\n    \n    \n    **v3 Functions**\n\n    *v3* functions will never have a settlement mid period, and are only used in the case\n    of 2 or more remaining coupon periods. The available functions are:\n\n    - ``compounding``: is identical to *v1 'compounding'* where :math:`\\\\xi_y` is set to zero.\n    - ``compounding_pay_adjust``: is identical to *v1 'compounding_pay_adjust'* where :math:`\\\\xi_y` is set to zero.\n    - ``simple``: is identical to *v1 'simple'* where :math:`\\\\xi_y` is set to zero.\n    - ``simple_pay_adjust``: is identical to *v1 'simple_pay_adjust'* where :math:`\\\\xi_y`\n      is set to zero.\n    - ``simple_30e360``: uses simple interest with a DCF calculated\n      under 30e360 convention, irrespective of the bond's underlying convention.\n      \n      .. math::\n\n         v_3 = \\\\frac{1}{1+\\\\bar{d}_n y}\n\n    **Custom discount functions** can also be supplied where the input arguments signature\n    is shown in the below example. It should return a discount factor. The example\n    shows the implementation of the *\"regular\"* discount function:\n\n    .. ipython:: python\n\n       def _v2_(\n           obj,         # the bond object\n           ytm,         # y as defined\n           f,           # f as defined\n           settlement,  # datetime\n           acc_idx,     # the index of the period in which settlement occurs\n           v2,          # the numeric value of v2 already calculated\n           accrual,     # the ytm_accrual function to return accrual fractions\n       ):\n           return 1 / (1 + ytm / (100 * f))\n           \n    **Cashflow Generating Functions**\n    \n    Most of the time, for the cashflows shown above in the YTM formula, the actual cashflows, as\n    determined by the native *schedule* and *convention* on the bond itself, can be used.\n    \n    This is because the cashflow often aligns with a *typical* expected amount,\n    i.e. *coupon / frequency*. Since this is by definition under the *ActActICMA* convention\n    and unadjusted *30360* will also tend to return standardised coupons.\n    \n    However, some bonds use a *convention* which does not lead to standardised\n    coupons, but have YTM formula definitions which do require standardised coupons. An example\n    is Thai Government Bonds.\n    \n    The available functions here are:\n    \n    - ``cashflow``: determine the cashflow for the period by using the native cashflow calculation\n      under the *schedule* and *convention* on the bond.\n    - ``full_coupon``: determine the cashflow as a full coupon payment, irrespective of period\n      dates, based on the notional of the period and the coupon rate of the bond. This method is\n      only for fixed rate bonds.\n      \n      .. math::\n      \n         c_i = \\\\frac{-N_i C}{f}\n\n    Notation\n    --------\n    \n    The notation used above is described:\n    \n    - :math:`\\\\xi`: The **accrual fraction** is a float, typically, in [0, 1] which defines the\n      amount of a bond's current cashflow period that is paid at *settlement* as accrued interest.\n    - :math:`\\\\xi_y`: The **accrual fraction** determined in a secondary method, used only in YTM\n      calculations and **not** for physical settlement.\n      (Almost always :math:`\\\\xi_y` and :math:`\\\\xi` are the same, for an exception see\n      Canadian GBs)\n    - :math:`r_u`: The number of calendar days between the last accrual coupon date and\n      settlement. If a **long stub** this is either; zero if settlement falls before the\n      accrual quasi-coupon date, or the number of calendar days between\n      those dates.\n    - :math:`s_u`: The number of calendar days between the last accrual coupon date and the\n      next accrual coupon date, i.e the number of calendar days in the accrual coupon\n      period. If a **long stub** this is the number of calendar days in the accrual\n      quasi-coupon period.\n    - :math:`\\\\bar{r}_u`: If a **long stub**, the number of calendar days between the accrual\n      effective date and either; the next accrual quasi-coupon date, or settlement date,\n      whichever is earliest.\n    - :math:`\\\\bar{s}_u`: If a **long stub**, the number of calendar days between the prior\n      accrual quasi-coupon date and the accrual next quasi-coupon date surrounding the\n      accrual effective date.\n    - :math:`d_i`: The full DCF of coupon period, *i*, calculated with the convention which\n      determines the physical cashflows.\n    - :math:`f`: The number of coupon periods per annum, e.g. 1-annually, 2-semi, 3-tertiary,\n      4-quarterly, 6-bi-monthly, 12-monthly.\n    - :math:`\\\\bar{d}_u`: The DCF between settlement and the next accrual coupon date\n      determined with the convention of the accrual function (which may be different to the\n      convention for determining physical bond cashflows)\n    - :math:`c_i`: A coupon cashflow monetary amount, **per 100 nominal**, for coupon period, *i*.\n    - :math:`p_d`: Number of days between unadjusted coupon date and payment date in a coupon\n      period, i.e. the pay delay.\n    - :math:`p_D` = Number of days between previous payment date and current payment date, in a\n      coupon period.\n    - :math:`C`: The nominal annual coupon rate for the bond.\n    - :math:`y`: The yield-to-maturity for a given bond. The expression of which, i.e. annually\n      or semi-annually is derived from the calculation context.\n    \n    \"\"\"  # noqa: E501, W293\n\n    _settle_accrual: AccrualFunction\n    # _settle_accrual_rounding: int | None\n    _ytm_accrual: AccrualFunction\n    _v1: YtmStubDiscountFunction\n    _v2: YtmDiscountFunction\n    _v3: YtmStubDiscountFunction\n    _c1: CashflowFunction\n    _ci: CashflowFunction\n    _cn: CashflowFunction\n\n    def __init__(\n        self,\n        settle_accrual: str | AccrualFunction,\n        ytm_accrual: str | AccrualFunction,\n        v1: str | YtmStubDiscountFunction,\n        v2: str | YtmDiscountFunction,\n        v3: str | YtmStubDiscountFunction,\n        c1: str | CashflowFunction,\n        ci: str | CashflowFunction,\n        cn: str | CashflowFunction,\n        # settle_accrual_rounding: int_ = NoInput(0),\n    ):\n        self._kwargs: dict[str, str] = {}\n        for name, func, _map in zip(\n            [\"settle_accrual\", \"ytm_accrual\", \"v1\", \"v2\", \"v3\", \"c1\", \"ci\", \"cn\"],\n            [settle_accrual, ytm_accrual, v1, v2, v3, c1, ci, cn],\n            [\n                ACC_FRAC_FUNCS,\n                ACC_FRAC_FUNCS,\n                V1_FUNCS,\n                V2_FUNCS,\n                V3_FUNCS,\n                C_FUNCS,\n                C_FUNCS,\n                C_FUNCS,\n            ],\n            strict=False,\n        ):\n            if isinstance(func, str):\n                setattr(self, f\"_{name}\", _map[func.lower()])  # type: ignore[index]\n                self._kwargs[name] = func\n            else:\n                setattr(self, f\"_{name}\", func)\n                self._kwargs[name] = \"custom\"\n\n        # if isinstance(settle_accrual_rounding, NoInput):\n        #     self._settle_accrual_rounding = None\n        #     self._kwargs[\"settle_accrual_rounding\"] = \"none\"\n        # else:\n        #     self._settle_accrual_rounding = settle_accrual_rounding\n        #     self._kwargs[\"settle_accrual_rounding\"] = str(settle_accrual_rounding)\n\n    @property\n    def kwargs(self) -> dict[str, str]:\n        \"\"\"String representation of the parameters for the calculation convention.\"\"\"\n        return self._kwargs\n\n\nclass BillCalcMode:\n    \"\"\"\n    Define calculation conventions for :class:`~rateslib.instruments.Bill` type.\n\n    Parameters\n    ----------\n    price_type: str in {\"simple\", \"discount\"}\n        The default calculation convention for the rate of the bill.\n    ytm_clone_kwargs: dict | str,\n        A list of bond keyword arguments, or the ``spec`` for a given bond for which\n        a replicable zero coupon bond is constructed and its YTM calculated as comparison.\n\n    Notes\n    ------\n\n    - *\"simple\"*: uses simple interest formula:\n\n      .. math::\n\n         P = \\\\frac{100}{1+r_{simple}d}\n\n    - *\"discount*\": uses a discount rate:\n\n      .. math::\n\n         P = 100 ( 1 - r_{discount} d )\n    \"\"\"\n\n    def __init__(\n        self,\n        price_type: str,\n        # price_accrual_type: str,\n        # accrual type uses \"linear days\" by default. This correctly scales ACT365f and ACT360\n        # DCF conventions and prepares for any non-standard DCFs.\n        # currently no identified cases where anything else is needed. Revise as necessary.\n        ytm_clone_kwargs: dict[str, str] | str,\n    ):\n        self._price_type = price_type\n        price_accrual_type = \"linear_days\"\n        self._settle_accrual = ACC_FRAC_FUNCS[price_accrual_type.lower()]\n        if isinstance(ytm_clone_kwargs, dict):\n            self._ytm_clone_kwargs = ytm_clone_kwargs\n        else:\n            self._ytm_clone_kwargs = defaults.spec[ytm_clone_kwargs]\n        self._kwargs: dict[str, str] = {\n            \"price_type\": price_type,\n            \"price_accrual_type\": price_accrual_type,\n            \"ytm_clone\": \"Custom dict\" if isinstance(ytm_clone_kwargs, dict) else ytm_clone_kwargs,\n        }\n\n    @property\n    def kwargs(self) -> dict[str, str]:\n        \"\"\"String representation of the parameters for the calculation convention.\"\"\"\n        return self._kwargs\n\n\nUK_GB = BondCalcMode(\n    # UK government bond conventions\n    settle_accrual=\"linear_days_long_front_split\",\n    ytm_accrual=\"linear_days_long_front_split\",\n    v1=\"compounding\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nCN_GB = BondCalcMode(\n    # Chinese government bond conventions\n    settle_accrual=\"linear_days\",\n    ytm_accrual=\"linear_days\",\n    v1=\"compounding\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nNZ_GB = BondCalcMode(\n    # New Zealand government bond conventions\n    settle_accrual=\"linear_days\",\n    ytm_accrual=\"linear_days\",\n    v1=\"compounding_final_simple_act365f\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nAU_GB = BondCalcMode(\n    # Australian government bond conventions\n    settle_accrual=\"linear_days\",\n    # settle_accrual_rounding=3,\n    ytm_accrual=\"linear_days\",\n    v1=\"compounding_final_simple_act365f\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nUS_GB = BondCalcMode(\n    # US Treasury street convention\n    settle_accrual=\"linear_days_long_front_split\",\n    ytm_accrual=\"linear_days_long_front_split\",\n    v1=\"compounding_final_simple\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nUS_GB_TSY = BondCalcMode(\n    # US Treasury treasury convention\n    settle_accrual=\"linear_days_long_front_split\",\n    ytm_accrual=\"linear_days_long_front_split\",\n    v1=\"simple_long_stub_compounding\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nUS_CORP = BondCalcMode(\n    # US Corporate bond street convention\n    settle_accrual=\"30u360_forward\",\n    ytm_accrual=\"30u360_forward\",\n    v1=\"compounding_final_simple\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nUS_MUNI = BondCalcMode(\n    # US Corporate bond street convention\n    settle_accrual=\"30u360_forward\",\n    ytm_accrual=\"30u360_forward\",\n    v1=\"compounding_final_simple\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nSE_GB = BondCalcMode(\n    # Swedish government bonds\n    settle_accrual=\"30e360_backward\",\n    ytm_accrual=\"30e360_backward\",\n    v1=\"compounding_final_simple\",\n    v2=\"regular\",\n    v3=\"simple_30e360\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nCA_GB = BondCalcMode(\n    # Canadian government bonds\n    settle_accrual=\"act365f_1y\",\n    ytm_accrual=\"linear_days\",\n    v1=\"compounding\",\n    v2=\"regular\",\n    v3=\"simple_30e360\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nDE_GB = BondCalcMode(\n    # German government bonds\n    settle_accrual=\"linear_days_long_front_split\",\n    ytm_accrual=\"linear_days_long_front_split\",\n    v1=\"compounding_final_simple\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nFR_GB = BondCalcMode(\n    # French OATs\n    settle_accrual=\"linear_days\",\n    ytm_accrual=\"linear_days\",\n    v1=\"compounding\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nIT_GB = BondCalcMode(\n    # Italian GBs\n    settle_accrual=\"linear_days\",\n    ytm_accrual=\"linear_days\",\n    v1=\"compounding_final_simple_pay_adjust\",\n    v2=\"annual_pay_adjust\",\n    v3=\"compounding_pay_adjust\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nNO_GB = BondCalcMode(\n    # Norwegian GBs\n    settle_accrual=\"act365f_1y\",\n    ytm_accrual=\"act365f_1y\",\n    v1=\"compounding_stub_act365f\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nNL_GB = BondCalcMode(\n    # Dutch GBs\n    settle_accrual=\"linear_days_long_front_split\",\n    ytm_accrual=\"linear_days_long_front_split\",\n    v1=\"compounding_final_simple\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nCH_GB = BondCalcMode(\n    # Swiss GBs\n    settle_accrual=\"30e360_backward\",\n    ytm_accrual=\"30e360_backward\",\n    v1=\"compounding\",\n    v2=\"regular\",\n    v3=\"compounding\",\n    c1=\"cashflow\",\n    ci=\"cashflow\",\n    cn=\"cashflow\",\n)\n\nUK_GBB = BillCalcMode(\n    # UK T-bills\n    price_type=\"simple\",\n    # price_accrual_type=\"linear_days\",\n    ytm_clone_kwargs=\"uk_gb\",\n)\n\nUS_GBB = BillCalcMode(\n    # US T-bills\n    price_type=\"discount\",\n    # price_accrual_type=\"linear_days\",\n    ytm_clone_kwargs=\"us_gb\",\n)\n\nSE_GBB = BillCalcMode(\n    # Swedish T-bills\n    price_type=\"simple\",\n    # price_accrual_type=\"linear_days\",\n    ytm_clone_kwargs=\"se_gb\",\n)\n\nNO_GBB = BillCalcMode(\n    # Norwegian T-bills\n    price_type=\"discount\",\n    # price_accrual_type=\"linear_days\",\n    ytm_clone_kwargs=\"no_gb\",\n)\n\nBOND_MODE_MAP = {\n    \"uk_gb\": UK_GB,\n    \"nz_gb\": NZ_GB,\n    \"au_gb\": AU_GB,\n    \"cn_gb\": CN_GB,\n    \"us_gb\": US_GB,\n    \"de_gb\": DE_GB,\n    \"fr_gb\": FR_GB,\n    \"nl_gb\": NL_GB,\n    \"ch_gb\": CH_GB,\n    \"no_gb\": NO_GB,\n    \"se_gb\": SE_GB,\n    \"us_gb_tsy\": US_GB_TSY,\n    \"us_corp\": US_CORP,\n    \"us_muni\": US_MUNI,\n    \"it_gb\": IT_GB,\n    \"ca_gb\": CA_GB,\n    # aliases\n    \"ukg\": UK_GB,\n    \"cadgb\": CA_GB,\n    \"ust\": US_GB,\n    \"ust_31bii\": US_GB_TSY,\n    \"sgb\": SE_GB,\n}\n\nBILL_MODE_MAP = {\n    \"uk_gbb\": UK_GBB,\n    \"us_gbb\": US_GBB,\n    \"se_gbb\": SE_GBB,\n    \"no_gbb\": NO_GBB,\n    # aliases\n    \"ustb\": US_GBB,\n    \"uktb\": UK_GBB,\n    \"sgbb\": SE_GBB,\n}\n\n\ndef _get_bond_calc_mode(calc_mode: str | BondCalcMode) -> BondCalcMode:\n    if isinstance(calc_mode, str):\n        return BOND_MODE_MAP[calc_mode.lower()]\n    return calc_mode\n\n\ndef _get_bill_calc_mode(calc_mode: str | BillCalcMode) -> BillCalcMode:\n    if isinstance(calc_mode, str):\n        return BILL_MODE_MAP[calc_mode.lower()]\n    return calc_mode\n\n\ndef _get_calc_mode_for_class(\n    obj: Any, calc_mode: str | BondCalcMode | BillCalcMode\n) -> BondCalcMode | BillCalcMode:\n    if isinstance(calc_mode, str):\n        map_: dict[str, dict[str, BondCalcMode] | dict[str, BillCalcMode]] = {\n            \"FixedRateBond\": BOND_MODE_MAP,\n            \"Bill\": BILL_MODE_MAP,\n            \"FloatRateNote\": BOND_MODE_MAP,\n            \"IndexFixedRateBond\": BOND_MODE_MAP,\n        }\n        klass: str = type(obj).__name__\n        return map_[klass][calc_mode.lower()]\n    return calc_mode\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/conventions/accrued.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.scheduling import dcf\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        _SupportsFixedFloatLeg1,\n    )\n\n\"\"\"\nAll functions in this module are designed to take a Bond object and return the **fraction**\nof the current coupon period associated with the given settlement.\n\nThis fraction is used to assess the total accrued calculation at a subsequent stage.\n\"\"\"\n\n\nclass AccrualFunction(Protocol):\n    # Callable type for Accrual Functions\n    def __call__(\n        self, obj: _SupportsFixedFloatLeg1, settlement: datetime, acc_idx: int, *args: Any\n    ) -> float: ...\n\n\ndef _acc_linear_proportion_by_days(\n    obj: _SupportsFixedFloatLeg1, settlement: datetime, acc_idx: int, *args: Any\n) -> float:\n    \"\"\"\n    Return the fraction of an accrual period between start and settlement.\n\n    Method: a linear proportion of actual days between start, settlement and end.\n    Measures between unadjusted coupon dates.\n\n    This is a general method, used by many types of bonds, for example by UK Gilts,\n    German Bunds.\n    \"\"\"\n    r = (settlement - obj.leg1.schedule.aschedule[acc_idx]).days\n    s = (obj.leg1.schedule.aschedule[acc_idx + 1] - obj.leg1.schedule.aschedule[acc_idx]).days\n    return float(r / s)\n\n\ndef _acc_linear_proportion_by_days_long_stub_split(\n    obj: _SupportsFixedFloatLeg1,\n    settlement: datetime,\n    acc_idx: int,\n    *args: Any,\n) -> float:\n    \"\"\"\n    For long stub periods this splits the accrued interest into two components.\n    Otherwise, returns the regular linear proportion.\n    [Designed primarily for US Treasuries]\n    \"\"\"\n    # TODO: handle this union attribute by segregating Securities periods into different\n    # categories, perhaps when also integrating deterministic amortised bonds.\n    if obj.leg1._regular_periods[acc_idx].period_params.stub:\n        f = obj.leg1.schedule.periods_per_annum\n        freq = obj.leg1.schedule.frequency_obj\n        adjuster = obj.leg1.schedule.accrual_adjuster\n        calendar = obj.leg1.schedule.calendar\n\n        if obj.leg1._regular_periods[acc_idx].period_params.dcf * f > 1:\n            # long stub\n\n            if acc_idx > 0:\n                # then stub is implied to be at the back, must roll forwards\n                ustart = obj.leg1.schedule.uschedule[acc_idx]\n                astart = obj.leg1.schedule.aschedule[acc_idx]\n                quasi_ucoupon = freq.unext(ustart)\n                quasi_acoupon = adjuster.adjust(quasi_ucoupon, calendar)\n                quasi_uend = freq.unext(quasi_ucoupon)\n                quasi_aend = adjuster.adjust(quasi_uend, calendar)\n                s_bar_u = (quasi_acoupon - astart).days\n\n                if settlement <= quasi_acoupon:\n                    #\n                    # |--------------------------|-----------------|---------|\n                    # s                     *    qc                e         qe\n                    # <-----------s_bar_u-------->\n                    # <---r_bar_u----------->                        ==>  (r_bar_u / s_bar_u) / (df)\n                    r_bar_u = (settlement - astart).days\n                    r_u = 0.0\n                    s_u = 1.0\n                else:\n                    #\n                    # |--------------------------|-----------------|---------|\n                    # s                          qc             *  e         qe\n                    # <-----------s_bar_u--------><------s_u----------------->\n                    # <--------r_bar_u-----------><----r_u------>\n                    #                                    ==>  (r_bar_u / s_bar_u + r_u / s_u) / (df)\n                    r_u = (settlement - quasi_acoupon).days\n                    s_u = (quasi_aend - quasi_acoupon).days\n                    r_bar_u = (quasi_acoupon - astart).days\n            else:\n                # then stub is implied to be at the front, must roll backwards\n                uend = obj.leg1.schedule.uschedule[acc_idx + 1]\n                aend = obj.leg1.schedule.aschedule[acc_idx + 1]\n                quasi_ucoupon = freq.uprevious(uend)\n                quasi_acoupon = adjuster.adjust(quasi_ucoupon, calendar)\n                quasi_ustart = freq.uprevious(quasi_ucoupon)\n                quasi_astart = adjuster.adjust(quasi_ustart, calendar)\n                s_bar_u = (quasi_acoupon - quasi_astart).days\n\n                if settlement <= quasi_acoupon:\n                    #\n                    # |--------|-------------------|--------------------------|\n                    # qs       s             *     qc                         e\n                    # <-----------s_bar_u--------->\n                    #          <---r_bar_u--->                       ==>  (r_bar_u / s_bar_u) / (df)\n                    r_bar_u = (settlement - obj.leg1.schedule.aschedule[acc_idx]).days\n                    r_u = 0.0\n                    s_u = 1.0\n                else:\n                    #\n                    # |--------|-------------------|--------------------------|\n                    # qs       s                   qc             *           e\n                    # <-----------s_bar_u---------><------------s_u----------->\n                    #          <-------r_bar_u----><------r_u----->\n                    #\n                    #                                    ==>  (r_bar_u / s_bar_u + r_u / s_u) / (df)\n                    r_u = (settlement - quasi_acoupon).days\n                    s_u = (aend - quasi_acoupon).days\n                    r_bar_u = (quasi_acoupon - obj.leg1.schedule.aschedule[acc_idx]).days\n\n            return (r_bar_u / s_bar_u + r_u / s_u) / (\n                obj.leg1._regular_periods[acc_idx].period_params.dcf * f\n            )\n\n    return _acc_linear_proportion_by_days(obj, settlement, acc_idx, *args)\n\n\ndef _acc_30e360_backward(\n    obj: _SupportsFixedFloatLeg1, settlement: datetime, acc_idx: int, *args: Any\n) -> float:\n    \"\"\"\n    Ignoring the convention on the leg uses \"30E360\" to determine the accrual fraction.\n    Measures between unadjusted date and settlement.\n    [Designed primarily for Swedish Government Bonds]\n\n    If stub revert to linear proportioning.\n    \"\"\"\n    if obj.leg1._regular_periods[acc_idx].period_params.stub:\n        return _acc_linear_proportion_by_days(obj, settlement, acc_idx)\n    f = obj.leg1.schedule.periods_per_annum\n    _: float = (\n        dcf(\n            start=settlement,\n            end=obj.leg1.schedule.aschedule[acc_idx + 1],\n            convention=\"30e360\",\n            frequency=obj.leg1.schedule.frequency_obj,\n        )\n        * f\n    )\n    _ = 1 - _\n    return _\n\n\ndef _acc_30u360_forward(\n    obj: _SupportsFixedFloatLeg1, settlement: datetime, acc_idx: int, *args: Any\n) -> float:\n    \"\"\"\n    Ignoring the convention on the leg uses \"30U360\" to determine the accrual fraction.\n    Measures between unadjusted dates and settlement.\n    [Designed primarily for US Corporate/Muni Bonds]\n    \"\"\"\n    sch = obj.leg1.schedule\n    accrued = dcf(\n        start=sch.aschedule[acc_idx],\n        end=settlement,\n        convention=\"30u360\",\n        frequency=sch.frequency_obj,\n    )\n    period = dcf(\n        start=sch.aschedule[acc_idx],\n        end=sch.aschedule[acc_idx + 1],\n        convention=\"30u360\",\n        frequency=sch.frequency_obj,\n    )\n    return accrued / period\n\n\ndef _acc_act365_with_1y_and_stub_adjustment(\n    obj: _SupportsFixedFloatLeg1, settlement: datetime, acc_idx: int, *args: Any\n) -> float:\n    \"\"\"\n    Ignoring the convention on the leg uses \"Act365f\" to determine the accrual fraction.\n    Measures between unadjusted date and settlement.\n    Special adjustment if number of days is greater than 365.\n    If the period is a stub reverts to a straight line interpolation\n    [this is primarily designed for Canadian Government Bonds]\n    \"\"\"\n    if obj.leg1._regular_periods[acc_idx].period_params.stub:\n        return _acc_linear_proportion_by_days(obj, settlement, acc_idx)\n    f = obj.leg1.schedule.periods_per_annum\n    r = (settlement - obj.leg1.schedule.aschedule[acc_idx]).days\n    s = (obj.leg1.schedule.aschedule[acc_idx + 1] - obj.leg1.schedule.aschedule[acc_idx]).days\n    if r == s:\n        _: float = 1.0  # then settlement falls on the coupon date\n    elif r >= 365.0 / f:\n        _ = 1.0 - ((s - r) * f) / 365.0  # counts remaining days\n    else:\n        _ = f * r / 365.0\n    return _\n\n\nACC_FRAC_FUNCS: dict[str, AccrualFunction] = {\n    \"linear_days\": _acc_linear_proportion_by_days,\n    \"linear_days_long_front_split\": _acc_linear_proportion_by_days_long_stub_split,\n    \"30e360_backward\": _acc_30e360_backward,\n    \"30u360_forward\": _acc_30u360_forward,\n    \"act365f_1y\": _acc_act365_with_1y_and_stub_adjustment,\n}\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/conventions/discounting.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.scheduling import dcf\n\nif TYPE_CHECKING:\n    from rateslib.instruments.bonds.conventions.accrued import AccrualFunction\n    from rateslib.local_types import (\n        CurveOption_,\n        DualTypes,\n        _SupportsFixedFloatLeg1,\n    )\n\n\"\"\"\nThe calculations for v2 (the interim, regular period discount value) are more standardised\nthan the other calculations because they exclude the scenarios for stub handling.\n\"\"\"\n\n\nclass YtmDiscountFunction(Protocol):\n    # Callable Type for discount functions in YTM formula\n    def __call__(\n        self,\n        obj: _SupportsFixedFloatLeg1,\n        ytm: DualTypes,\n        f: float,\n        settlement: datetime,\n        acc_idx: int,\n        v2: DualTypes | None,\n        accrual: AccrualFunction,\n        period_idx: int,\n    ) -> DualTypes: ...\n\n\nclass YtmStubDiscountFunction(Protocol):\n    # Callable Type for discount functions in YTM formula\n    # This is same as above, except v2 must be pre-calculated and cannot be None\n    def __call__(\n        self,\n        obj: _SupportsFixedFloatLeg1,\n        ytm: DualTypes,\n        f: float,\n        settlement: datetime,\n        acc_idx: int,\n        v2: DualTypes,\n        accrual: AccrualFunction,\n        period_idx: int,\n    ) -> DualTypes: ...\n\n\nclass CashflowFunction(Protocol):\n    # Callable Type for cashflow generation in YTM formula\n    def __call__(\n        self,\n        obj: _SupportsFixedFloatLeg1,\n        ytm: DualTypes,\n        f: float,\n        acc_idx: int,\n        p_idx: int,\n        n: int,\n        curve: CurveOption_,\n    ) -> DualTypes: ...\n\n\n\"\"\"\nThe calculations for v2:\n\"\"\"\n\n\ndef _v2_(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes | None,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Default method for a single regular period discounted in the regular portion of bond.\n    Implies compounding at the same frequency as the coupons.\n    \"\"\"\n    if v2 is None:\n        return 1 / (1 + ytm / (100 * f))\n    else:\n        return v2\n\n\ndef _v2_annual(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes | None,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    ytm is expressed annually but coupon payments are on another frequency\n    \"\"\"\n    if v2 is None:\n        return (1 / (1 + ytm / 100)) ** (1 / f)\n    else:\n        return v2\n\n\ndef _v2_annual_pay_adjust(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes | None,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    if v2 is None:\n        # This is the initial, regular determination of v2\n        return (1 / (1 + ytm / 100)) ** (1 / f)\n    else:\n        return v2 ** (1.0 + _pay_adj(obj, period_idx))\n\n\n\"\"\"\nThe calculations for v1:\n\"\"\"\n\n\ndef _v1_compounded(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Determine the discount factor for the first cashflow after settlement.\n\n    The parameter \"v2\" is a generic discount function which is normally :math:`1/(1+y/f)`\n\n    Method: compounds \"v2\" with exponent in terms of the accrual fraction of the period.\n    \"\"\"\n    acc_frac = accrual(obj, settlement, acc_idx)\n    if obj.leg1.periods[acc_idx].period_params.stub:  # type: ignore[attr-defined]\n        # If it is a stub then the remaining fraction must be scaled by the relative size of the\n        # stub period compared with a regular period.\n        fd0 = obj.leg1.periods[acc_idx].period_params.dcf * f * (1 - acc_frac)  # type: ignore[attr-defined]\n    else:\n        # 1 minus acc_fra is the fraction of the period remaining until the next cashflow.\n        fd0 = 1 - acc_frac\n    return v2**fd0  # type: ignore[no-any-return]\n\n\ndef _v1_simple(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Use simple rates with a yield which matches the frequency of the coupon.\n    \"\"\"\n    acc_frac = accrual(obj, settlement, acc_idx)\n    if obj.leg1.periods[acc_idx].period_params.stub:  # type: ignore[attr-defined]\n        # is a stub so must account for discounting in a different way.\n        fd0 = obj.leg1.periods[acc_idx].period_params.dcf * f * (1 - acc_frac)  # type: ignore[attr-defined]\n    else:\n        fd0 = 1 - acc_frac\n\n    v_ = 1 / (1 + fd0 * ytm / (100 * f))\n    return v_  # type: ignore[no-any-return]\n\n\ndef _v1_simple_act365f(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Use simple rates with the DCF determined by Act365F.\n    \"\"\"\n    fd0 = dcf(settlement, obj.leg1.schedule.aschedule[acc_idx + 1], \"Act365F\")\n    v_ = 1 / (1 + fd0 * ytm / 100.0)\n    return v_\n\n\ndef _v1_simple_pay_adjust(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    acc_frac = accrual(obj, settlement, acc_idx)\n    if obj.leg1._regular_periods[acc_idx].period_params.stub:\n        # is a stub so must account for discounting in a different way.\n        fd0 = (\n            obj.leg1.periods[acc_idx].period_params.dcf  # type: ignore[attr-defined]\n            * f\n            * (1 - acc_frac + _pay_adj(obj, period_idx))\n        )\n    else:\n        fd0 = 1 - acc_frac + _pay_adj(obj, period_idx)\n\n    v_ = 1 / (1 + fd0 * ytm / (100 * f))\n    return v_  # type: ignore[no-any-return]\n\n\ndef _v1_compounded_pay_adjust(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    acc_frac = accrual(obj, settlement, acc_idx)\n    if obj.leg1._regular_periods[acc_idx].period_params.stub:\n        # If it is a stub then the remaining fraction must be scaled by the relative size of the\n        # stub period compared with a regular period.\n        fd0 = (\n            obj.leg1.periods[acc_idx].period_params.dcf  # type: ignore[attr-defined]\n            * f\n            * (1 - acc_frac + _pay_adj(obj, period_idx))\n        )\n    else:\n        # 1 minus acc_fra is the fraction of the period remaining until the next cashflow.\n        fd0 = 1 - acc_frac + _pay_adj(obj, period_idx)\n    return v2**fd0  # type: ignore[no-any-return]\n\n\ndef _v1_compounded_final_simple(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Uses regular fractional compounding except if it is last period, when simple money-mkt\n    yield is used instead.\n    Introduced for German Bunds.\n    \"\"\"\n    if acc_idx == obj.leg1.schedule.n_periods - 1:\n        # or \\\n        # settlement == self.leg1.schedule.aschedule[acc_idx + 1]:\n        # then settlement is in last period use simple interest.\n        return _v1_simple(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n    else:\n        return _v1_compounded(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n\n\ndef _v1_compounded_final_simple_act365f(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Uses regular fractional compounding except if it is last period, when simple money-mkt\n    yield is used instead with discounting Act365F.\n    Introduced for New Zealand Government Bonds.\n    \"\"\"\n    if acc_idx == obj.leg1.schedule.n_periods - 1:\n        # or \\\n        # settlement == self.leg1.schedule.aschedule[acc_idx + 1]:\n        # then settlement is in last period use simple interest.\n        return _v1_simple_act365f(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n    else:\n        return _v1_compounded(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n\n\ndef _v1_compounded_final_simple_pay_adjust(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Uses regular fractional compounding except if it is last period, when simple money-mkt\n    yield is used instead.\n    Both methods are adjusted to account for pay delays.\n    \"\"\"\n    if acc_idx == obj.leg1.schedule.n_periods - 1:\n        return _v1_simple_pay_adjust(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n    else:\n        # Pay adjustment is not applied if it is not the final period\n        return _v1_compounded(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n\n\ndef _v1_comp_stub_act365f(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"Compounds the yield. In a stub period the act365f DCF is used\"\"\"\n    if not obj.leg1.periods[acc_idx].period_params.stub:  # type: ignore[attr-defined]\n        return _v1_compounded(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n    else:\n        fd0 = dcf(settlement, obj.leg1.schedule.aschedule[acc_idx + 1], \"Act365F\")\n        return v2**fd0\n\n\ndef _v1_simple_long_stub(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Use simple rates with a yield which matches the frequency of the coupon.\n\n    If the stub period is long, then discount the regular part of the stub with the regular\n    discount param ``v``.\n    \"\"\"\n    if (\n        obj.leg1._regular_periods[acc_idx].period_params.stub\n        and obj.leg1._regular_periods[acc_idx].period_params.dcf * f > 1\n    ):\n        # long stub\n        acc_frac = accrual(obj, settlement, acc_idx)\n        fd0 = obj.leg1.periods[acc_idx].period_params.dcf * f * (1 - acc_frac)  # type: ignore[attr-defined]\n        if fd0 > 1.0:\n            # then there is a whole quasi-coupon period until payment of next cashflow\n            v_ = v2 * 1 / (1 + (fd0 - 1) * ytm / (100 * f))\n        else:\n            # this is standard _v1_simple formula\n            v_ = 1 / (1 + fd0 * ytm / (100 * f))\n        return v_  # type: ignore[no-any-return]\n    else:\n        return _v1_simple(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n\n\n\"\"\"\nThe calculations for v3:\n\"\"\"\n\n\ndef _v3_compounded(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Final period uses a compounding approach where the power is determined by the DCF of that\n    period under the bond's specified convention.\n    \"\"\"\n    if obj.leg1.periods[acc_idx].period_params.stub:  # type: ignore[attr-defined]\n        # If it is a stub then the remaining fraction must be scaled by the relative size of the\n        # stub period compared with a regular period.\n        fd0 = obj.leg1.periods[acc_idx].period_params.dcf * f  # type: ignore[attr-defined]\n    else:\n        fd0 = 1\n    return v2**fd0  # type: ignore[no-any-return]\n\n\ndef _v3_compounded_pay_adjust(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    Final period uses a compounding approach where the power is determined by the DCF of that\n    period under the bond's specified convention.\n    \"\"\"\n    regular_v3 = _v3_compounded(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx)\n    return regular_v3 ** (1.0 + _pay_adj(obj, period_idx))\n\n\ndef _v3_30e360_u_simple(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    \"\"\"\n    The final period is discounted by a simple interest method under a 30E360 convention.\n\n    The YTM is assumed to have the same frequency as the coupons.\n    \"\"\"\n    d_ = dcf(\n        obj.leg1._regular_periods[acc_idx].period_params.start,\n        obj.leg1._regular_periods[acc_idx].period_params.end,\n        \"30E360\",\n    )\n    return 1 / (1 + d_ * ytm / 100)  # simple interest\n\n\ndef _v3_simple(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    if obj.leg1.periods[acc_idx].period_params.stub:  # type: ignore[attr-defined]\n        # is a stub so must account for discounting in a different way.\n        fd0 = obj.leg1.periods[acc_idx].period_params.dcf * f  # type: ignore[attr-defined]\n    else:\n        fd0 = 1.0\n\n    v_ = 1 / (1 + fd0 * ytm / (100 * f))\n    return v_  # type: ignore[no-any-return]\n\n\ndef _v3_simple_pay_adjust(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    settlement: datetime,\n    acc_idx: int,\n    v2: DualTypes,\n    accrual: AccrualFunction,\n    period_idx: int,\n) -> DualTypes:\n    if obj.leg1.periods[acc_idx].period_params.stub:  # type: ignore[attr-defined]\n        # is a stub so must account for discounting in a different way.\n        fd0 = (1.0 + _pay_adj(obj, period_idx)) * obj.leg1.periods[acc_idx].period_params.dcf * f  # type: ignore[attr-defined]\n    else:\n        fd0 = 1.0 + _pay_adj(obj, period_idx)\n\n    v_ = 1 / (1 + fd0 * ytm / (100 * f))\n    return v_  # type: ignore[no-any-return]\n\n\nV1_FUNCS: dict[str, YtmStubDiscountFunction] = {\n    \"compounding\": _v1_compounded,\n    \"compounding_pay_adjust\": _v1_compounded_pay_adjust,\n    \"simple\": _v1_simple,\n    \"simple_pay_adjust\": _v1_simple_pay_adjust,\n    \"compounding_final_simple\": _v1_compounded_final_simple,\n    \"compounding_final_simple_pay_adjust\": _v1_compounded_final_simple_pay_adjust,  # noqa: E501\n    \"compounding_stub_act365f\": _v1_comp_stub_act365f,\n    \"simple_long_stub_compounding\": _v1_simple_long_stub,\n    \"compounding_final_simple_act365f\": _v1_compounded_final_simple_act365f,\n    \"simple_act365f\": _v1_simple_act365f,\n}\n\nV2_FUNCS: dict[str, YtmDiscountFunction] = {\n    \"regular\": _v2_,\n    \"annual\": _v2_annual,\n    \"annual_pay_adjust\": _v2_annual_pay_adjust,\n}\n\nV3_FUNCS: dict[str, YtmStubDiscountFunction] = {\n    \"compounding\": _v3_compounded,\n    \"compounding_pay_adjust\": _v3_compounded_pay_adjust,\n    \"simple\": _v3_simple,\n    \"simple_pay_adjust\": _v3_simple_pay_adjust,\n    \"simple_30e360\": _v3_30e360_u_simple,\n}\n\n\ndef _c_from_obj(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    acc_idx: int,\n    p_idx: int,\n    n: int,\n    curve: CurveOption_,\n) -> DualTypes:\n    \"\"\"\n    Return the cashflow as it has been calculated directly on the object according to the\n    native schedule and conventions, for the specified period index.\n    \"\"\"\n    return obj._period_cashflow(obj.leg1._regular_periods[p_idx], curve)  # type: ignore[no-any-return, attr-defined]\n\n\ndef _c_full_coupon(\n    obj: _SupportsFixedFloatLeg1,\n    ytm: DualTypes,\n    f: float,\n    acc_idx: int,\n    p_idx: int,\n    n: int,\n    curve: CurveOption_,\n) -> DualTypes:\n    \"\"\"\n    Ignore the native schedule and conventions and return an amount based on the period\n    notional, the bond coupon, and the bond frequency.\n    \"\"\"\n    return -obj.leg1._regular_periods[p_idx].settlement_params.notional * obj.fixed_rate / (100 * f)  # type: ignore[attr-defined, no-any-return]\n\n\nC_FUNCS: dict[str, CashflowFunction] = {\n    \"cashflow\": _c_from_obj,\n    \"full_coupon\": _c_full_coupon,\n}\n\n\ndef _pay_adj(obj: _SupportsFixedFloatLeg1, period_idx: int) -> float:\n    sch = obj.leg1.schedule\n    pd = (sch.pschedule[period_idx + 1] - sch.aschedule[period_idx + 1]).days\n    PD = (sch.pschedule[period_idx + 1] - sch.pschedule[period_idx]).days\n    return pd / PD\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/fixed_rate_bond.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.bonds.conventions import (\n    BondCalcMode,\n    _get_bond_calc_mode,\n)\nfrom rateslib.instruments.bonds.protocols import _BaseBondInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        Frequency,\n        FXForwards_,\n        RollDay,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass FixedRateBond(_BaseBondInstrument):\n    \"\"\"\n    A *fixed rate bond* composed of a :class:`~rateslib.legs.FixedLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import FixedRateBond, BondCalcMode\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       frb = FixedRateBond(\n           effective=dt(2000, 1, 1),\n           termination=\"2y\",\n           spec=\"us_gb\",\n           fixed_rate=2.0,\n       )\n       frb.cashflows()\n\n    .. rubric:: Pricing\n\n    A *FixedRateBond* requires one *disc curve*. The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = {\"disc_curve\": disc_curve}  # dict form is explicit\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    calc_mode : str or BondCalcMode\n        A calculation mode for dealing with bonds under different conventions. See notes.\n    settle: int\n        The number of days by which to lag 'today' to arrive at standard settlement.\n    metric : str, :green:`optional` (set as 'clean_price')\n        The pricing metric returned by :meth:`~rateslib.instruments.FixedRateBond.rate`.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    Notes\n    ------\n    The ``calc_mode``, which creates a :class:`~rateslib.instruments.BondCalcMode` defines the\n    specifications for YTM and accrued interest calculations. Examples of these values\n    are shown on the :ref:`FixedRateBond defaults <spec-fixed-rate-bonds-modes>` page.\n    One can also create their own mixing-and-matching some presets already designed, e.g.:\n\n    .. ipython:: python\n\n       mode = BondCalcMode(\n           settle_accrual=\"linear_days_long_front_split\",\n           ytm_accrual=\"linear_days_long_front_split\",\n           v1=\"simple_long_stub_compounding\",\n           v2=\"annual\",\n           v3=\"compounding\",\n           c1=\"cashflow\",\n           ci=\"cashflow\",\n           cn=\"cashflow\",\n       )\n\n    All of the arguments allow callables so it is technically possible to re-write any types of\n    calculations that fit into the framework. A cookbook page which demonstrates doing this\n    is :ref:`Understanding and Customising FixedRateBond Conventions <cook-bond_convs>`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg1(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        # amortization: float_ = NoInput(0),\n        # rate parameters\n        fixed_rate: DualTypes_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        calc_mode: BondCalcMode | str_ = NoInput(0),\n        settle: int_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str = \"clean_price\",\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            stub=stub,\n            front_stub=front_stub,\n            back_stub=back_stub,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            ex_div=ex_div,\n            convention=convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            # amortization=amortization,\n            # rate\n            fixed_rate=fixed_rate,\n            # meta\n            curves=self._parse_curves(curves),\n            calc_mode=calc_mode,\n            settle=settle,\n            metric=metric,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            initial_exchange=False,\n            final_exchange=True,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            calc_mode=defaults.calc_mode[type(self).__name__],\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_specific[type(self).__name__],\n            ex_div=defaults.ex_div,\n            settle=defaults.settle,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"calc_mode\", \"settle\", \"metric\", \"vol\"],\n        )\n        self.kwargs.meta[\"calc_mode\"] = _get_bond_calc_mode(self.kwargs.meta[\"calc_mode\"])\n\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            raise ValueError(f\"`fixed_rate` must be provided for {type(self).__name__}.\")\n\n        self._leg1 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._legs = [self.leg1]\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An FRB has one curve requirements: a disc_curve.\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 1:\n                return _Curves(\n                    disc_curve=curves[0],\n                )\n            elif len(curves) == 2:\n                return _Curves(\n                    disc_curve=curves[1],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 1 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n\n        settlement_ = self._maybe_get_settlement(settlement=settlement, disc_curve=disc_curve)\n        npv = self.leg1.local_npv(\n            disc_curve=disc_curve,\n            settlement=settlement_,\n            forward=settlement_,\n        )\n        # scale price to par 100 (npv is already projected forward to settlement)\n        dirty_price = npv * 100 / -self.leg1.settlement_params.notional\n\n        if metric_ == \"dirty_price\":\n            return dirty_price\n        elif metric_ == \"clean_price\":\n            return dirty_price - self.accrued(settlement_)\n        elif metric_ == \"ytm\":\n            return self.ytm(dirty_price, settlement_, True)\n        else:\n            raise ValueError(\"`metric` must be in {'dirty_price', 'clean_price', 'ytm'}.\")\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/float_rate_note.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import FloatFixingMethod\nfrom rateslib.instruments.bonds.conventions import (\n    BondCalcMode,\n    _get_bond_calc_mode,\n)\nfrom rateslib.instruments.bonds.protocols import _BaseBondInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FloatLeg\nfrom rateslib.periods import FloatPeriod\nfrom rateslib.scheduling import Frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurveOption_,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        FloatRateSeries,\n        FXForwards_,\n        LegFixings,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass FloatRateNote(_BaseBondInstrument):\n    \"\"\"\n    A *floating rate note (FRN)* composed of a :class:`~rateslib.legs.FloatLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import FloatRateNote\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       frn = FloatRateNote(\n           effective=dt(2000, 1, 1),\n           termination=\"2y\",\n           frequency=\"A\",\n           currency=\"usd\",\n           fixing_method=\"rfr_observation_shift(5)\",\n           convention=\"Act360\",\n           calendar=\"nyc|fed\",\n           float_spread=25.0,\n       )\n       frn.cashflows()\n\n    .. rubric:: Pricing\n\n    A *FloatRateNote* requires a *disc curve* and a *rate curve*. The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = [rate_curve, disc_curve]  #  a sequence of two curves\n       curves = {  # dict form is explicit\n           \"disc_curve\": disc_curve,\n           \"rate_curve\": rate_curve,\n       }\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    calc_mode : str or BondCalcMode\n        A calculation mode for dealing with bonds under different conventions. See notes.\n    settle: int\n        The number of days by which to lag 'today' to arrive at standard settlement.\n    metric : str, :green:`optional` (set as 'clean_price')\n        The pricing metric returned by :meth:`~rateslib.instruments.FixedRateBond.rate`.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def float_spread(self) -> DualTypes:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        return self.leg1.float_spread\n\n    @float_spread.setter\n    def float_spread(self, value: DualTypes) -> None:\n        self.kwargs.leg1[\"float_spread\"] = value\n        self.leg1.float_spread = value\n\n    @property\n    def leg1(self) -> FloatLeg:\n        \"\"\"The :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: int_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: str | int_ = NoInput(0),\n        eom: bool | NoInput = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        # settlement params\n        currency: str_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        amortization: DualTypes_ = NoInput(0),\n        # rate params\n        float_spread: DualTypes_ = NoInput(0),\n        spread_compound_method: str_ = NoInput(0),\n        rate_fixings: LegFixings = NoInput(0),\n        fixing_method: str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        calc_mode: BondCalcMode | str_ = NoInput(0),\n        settle: int_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str = \"clean_price\",\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            stub=stub,\n            front_stub=front_stub,\n            back_stub=back_stub,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            ex_div=ex_div,\n            convention=convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            amortization=amortization,\n            # rate\n            float_spread=float_spread,\n            spread_compound_method=spread_compound_method,\n            rate_fixings=rate_fixings,\n            fixing_method=fixing_method,\n            fixing_frequency=fixing_frequency,\n            fixing_series=fixing_series,\n            # meta\n            curves=self._parse_curves(curves),\n            calc_mode=calc_mode,\n            settle=settle,\n            metric=metric,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            initial_exchange=False,\n            final_exchange=True,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            calc_mode=defaults.calc_mode[type(self).__name__],\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_specific[type(self).__name__],\n            ex_div=defaults.ex_div,\n            settle=defaults.settle,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"calc_mode\", \"settle\", \"metric\", \"vol\"],\n        )\n        self.kwargs.meta[\"calc_mode\"] = _get_bond_calc_mode(self.kwargs.meta[\"calc_mode\"])\n\n        self._leg1 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        if self._leg1.schedule.frequency_obj == Frequency.Zero():\n            raise ValueError(\"A `FloatRateNote` cannot have a 'zero' frequency.\")\n        self._legs = [self.leg1]\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An FRN has two curve requirements: a leg2_rate_curve and a disc_curve used by both legs.\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                )\n            elif len(curves) == 1:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[0],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                rate_curve=curves,  # type: ignore[arg-type]\n                disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n        rate_curve = _get_curve(\"rate_curve\", True, True, *c)\n\n        metric = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n        if metric in [\"clean_price\", \"dirty_price\", \"spread\", \"ytm\"]:\n            settlement_ = self._maybe_get_settlement(settlement, disc_curve)\n\n            if metric == \"spread\":\n                _: DualTypes = self.leg1.spread(\n                    # target_npv=-(npv + self.leg1.settlement_params.notional),\n                    target_npv=-(self.leg1.settlement_params.notional),\n                    rate_curve=rate_curve,\n                    disc_curve=disc_curve,\n                    settlement=settlement_,\n                    forward=settlement_,\n                )\n                return _\n            else:\n                npv = self.leg1.local_npv(\n                    rate_curve=rate_curve,\n                    disc_curve=disc_curve,\n                    settlement=settlement_,\n                    forward=settlement_,\n                )\n                # scale price to par 100 (npv is already projected forward to settlement)\n                dirty_price = npv * 100 / -self.leg1.settlement_params.notional\n\n                if metric == \"dirty_price\":\n                    return dirty_price\n                elif metric == \"clean_price\":\n                    return dirty_price - self.accrued(settlement_, rate_curve=rate_curve)\n                elif metric == \"ytm\":\n                    return self.ytm(\n                        price=dirty_price, settlement=settlement_, dirty=True, rate_curve=rate_curve\n                    )\n\n        raise ValueError(\"`metric` must be in {'dirty_price', 'clean_price', 'spread', 'ytm'}.\")\n\n    def accrued(\n        self,\n        settlement: datetime,\n        rate_curve: CurveOption_ = NoInput(0),\n    ) -> DualTypes:\n        acc_idx = self.leg1._period_index(settlement)\n        if isinstance(self.leg1.rate_params.fixing_method, FloatFixingMethod.IBOR):\n            frac = self.kwargs.meta[\"calc_mode\"]._settle_accrual(self, settlement, acc_idx)\n            if self.ex_div(settlement):\n                frac = frac - 1  # accrued is negative in ex-div period\n            rate = self.leg1._regular_periods[acc_idx].rate(rate_curve=rate_curve)\n            cashflow = (\n                -self.leg1._regular_periods[acc_idx].settlement_params.notional\n                * self.leg1._regular_periods[acc_idx].period_params.dcf\n                * rate\n                / 100.0\n            )\n            return frac * cashflow / -self.leg1.settlement_params.notional * 100.0  # type: ignore[no-any-return]\n\n        else:  # is \"rfr\"\n            p = FloatPeriod(\n                start=self.leg1.schedule.aschedule[acc_idx],\n                end=settlement,\n                payment=settlement,\n                termination=self.leg1.schedule.aschedule[acc_idx + 1],\n                stub=True,\n                frequency=self.leg1.schedule.frequency_obj,\n                notional=-100,\n                currency=self.leg1.settlement_params.currency,\n                convention=self.leg1._regular_periods[acc_idx].period_params.convention,\n                float_spread=self.float_spread,\n                fixing_method=self.leg1.rate_params.fixing_method,\n                rate_fixings=self.leg1.rate_params.fixing_identifier,\n                spread_compound_method=self.leg1.rate_params.spread_compound_method,\n                fixing_series=self.leg1.rate_params.fixing_series,\n                fixing_frequency=self.leg1.rate_params.fixing_frequency,\n                # roll=self.leg1.schedule.roll,\n                calendar=self.leg1.schedule.calendar,\n                adjuster=self.leg1.schedule.accrual_adjuster,\n            )\n            if p.period_params.start == p.period_params.end and acc_idx == 0:\n                # bond settlement on issue date so there is no accrued\n                return 0.0\n\n            is_ex_div = self.ex_div(settlement)\n            if is_ex_div and settlement == self.leg1._regular_periods[acc_idx].period_params.end:\n                # then settlement is on a coupon date so no accrued\n                return 0.0\n\n            rate_to_settle = p.rate(rate_curve)\n            accrued_to_settle = 100.0 * p.period_params.dcf * rate_to_settle / 100.0\n\n            if is_ex_div:\n                rate_to_end = self.leg1._regular_periods[acc_idx].rate(rate_curve=rate_curve)\n                accrued_to_end = (\n                    100.0\n                    * self.leg1._regular_periods[acc_idx].period_params.dcf\n                    * rate_to_end\n                    / 100.0\n                )\n                return accrued_to_settle - accrued_to_end\n            else:\n                return accrued_to_settle\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/index_fixed_rate_bond.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual import Dual, gradient\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.bonds.conventions import (\n    BondCalcMode,\n    _get_bond_calc_mode,\n)\nfrom rateslib.instruments.bonds.protocols import _BaseBondInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg\nfrom rateslib.periods.parameters import _IndexParams\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurveOption_,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        Frequency,\n        FXForwards_,\n        IndexMethod,\n        LegFixings,\n        Number,\n        RollDay,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseCurve_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass IndexFixedRateBond(_BaseBondInstrument):\n    \"\"\"\n    An *index-linked fixed rate bond* composed of a :class:`~rateslib.legs.FixedLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import IndexFixedRateBond\n       from datetime import datetime as dt\n       from rateslib import fixings\n\n    .. ipython:: python\n\n       fixings.add(\"RPI_series\", Series(index=[dt(2024, 4, 1), dt(2024, 5, 1)], data=[385.0, 386.4]))\n       ifrb = IndexFixedRateBond(\n           effective=dt(2024, 7, 12),\n           termination=\"2y\",\n           fixed_rate=2.25,\n           spec=\"us_gbi\",\n           index_fixings=\"RPI_series\",\n       )\n       ifrb.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"RPI_series\")\n\n    .. rubric:: Pricing\n\n    An *IndexFixedRateBond* requires an *index curve* and a *disc curve*. The following input\n    formats are allowed:\n\n    .. code-block:: python\n\n       curves = [index_curve, disc_curve]   # two curves as a list\n       curves = {\"index_curve\": index_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    The available ``metric`` for the :meth:`~rateslib.instruments.IndexFixedRateBond.rate`\n    are in *{'clean_price', 'dirty_price', 'ytm', 'indexed_ytm', 'indexed_clean_price',\n    'indexed_dirty_price'}*.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n\n        .. note::\n\n           The following parameters define **indexation**.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value applied as the base index value for all *Periods*.\n        If not given and ``index_fixings`` is a string fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The index value for the reference date.\n        Best practice is to supply this value as string identifier relating to the global\n        ``fixings`` object.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    calc_mode : str or BondCalcMode\n        A calculation mode for dealing with bonds under different conventions. See notes.\n    settle: int\n        The number of days by which to lag 'today' to arrive at standard settlement.\n    metric : str, :green:`optional` (set as 'clean_price')\n        The pricing metric returned by :meth:`~rateslib.instruments.IndexFixedRateBond.rate`.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg1(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        # amortization: float_ = NoInput(0),\n        # index params\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: LegFixings = NoInput(0),\n        # rate parameters\n        fixed_rate: DualTypes_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        calc_mode: BondCalcMode | str_ = NoInput(0),\n        settle: int_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str = \"clean_price\",\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            stub=stub,\n            front_stub=front_stub,\n            back_stub=back_stub,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            ex_div=ex_div,\n            convention=convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            # amortization=amortization,\n            # index_params\n            index_base=index_base,\n            index_lag=index_lag,\n            index_method=index_method,\n            index_fixings=index_fixings,\n            # rate\n            fixed_rate=fixed_rate,\n            # meta\n            curves=self._parse_curves(curves),\n            calc_mode=calc_mode,\n            settle=settle,\n            metric=metric,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            initial_exchange=False,\n            final_exchange=True,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            calc_mode=defaults.calc_mode[type(self).__name__],\n            initial_exchange=False,\n            final_exchange=True,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_specific[type(self).__name__],\n            ex_div=defaults.ex_div,\n            settle=defaults.settle,\n            index_lag=defaults.index_lag,\n            index_method=defaults.index_method,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"calc_mode\", \"settle\", \"metric\", \"vol\"],\n        )\n        self.kwargs.meta[\"calc_mode\"] = _get_bond_calc_mode(self.kwargs.meta[\"calc_mode\"])\n\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            raise ValueError(f\"`fixed_rate` must be provided for {type(self).__name__}.\")\n\n        self._leg1 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._legs = [self.leg1]\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An IFRB has two curve requirements: an index_curve and a disc_curve.\n\n        No available index curve can be input as None or NoInput\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                index_curve=curves.get(\"index_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    index_curve=curves[0] if curves[0] is not None else NoInput(0),\n                    disc_curve=curves[1],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:\n            raise ValueError(f\"{type(self).__name__} requires 2 curve types. Got 1.\")\n\n    def index_ratio(self, settlement: datetime, index_curve: _BaseCurve_ = NoInput(0)) -> DualTypes:\n        \"\"\"\n        Return the index ratio assigned to an *IndexFixedRateBond* for a given settlement.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from pandas import Series\n           from datetime import datetime as dt\n           from rateslib import fixings\n           from rateslib.instruments import IndexFixedRateBond\n\n        .. ipython:: python\n\n           fixings.add(\"UK_RPI\", Series(index=[dt(2025, 3, 1), dt(2025, 4, 1), dt(2025, 5, 1)], data=[395.3, 402.2, 402.9]))\n           ukti = IndexFixedRateBond(  # ISIN: GB00BMY62Z61\n               effective=dt(2025, 6, 11),\n               termination=dt(2038, 9, 22),\n               fixed_rate=1.75,\n               spec=\"uk_gbi\",\n               index_fixings=\"UK_RPI\"\n           )\n           ukti.index_ratio(settlement=dt(2025, 7, 29))\n\n        .. ipython:: python\n           :suppress:\n\n           fixings.pop(\"UK_RPI\")\n\n        Parameters\n        ----------\n        settlement: datetime\n            The settlement date of the bond.\n        index_curve: _BaseCurve, optional\n            A curve capable of forecasting index values.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"  # noqa: E501\n\n        left_index = self.leg1._period_index(settlement)\n        period_index_params: _IndexParams = self.leg1._regular_periods[left_index].index_params  # type: ignore[assignment]\n\n        new_index_params = _IndexParams(\n            _index_method=period_index_params.index_method,\n            _index_lag=period_index_params.index_lag,\n            _index_base=period_index_params.index_base.value,\n            _index_base_date=period_index_params.index_base.date,\n            _index_reference_date=settlement,\n            _index_fixings=period_index_params.index_fixing.identifier,\n            _index_only=False,\n        )\n        return new_index_params.index_ratio(index_curve=index_curve)[0]\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Calculate some pricing rate metric for the *Instrument*.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from pandas import Series\n           from datetime import datetime as dt\n           from rateslib import fixings, Curve\n           from rateslib.instruments import IndexFixedRateBond\n\n        .. ipython:: python\n\n           disc_curve = Curve(\n               nodes={dt(2025, 7, 28): 1.0, dt(2045, 7, 25): 1.0},\n               convention=\"act365f\"\n           ).shift(250)  # curve begins at 0% and gets shifted by 250 Act365F O/N basis points\n           index_curve = Curve(\n               nodes={dt(2025, 5, 1): 1.0, dt(2045, 5, 1): 1.0},\n               convention=\"act365f\", index_lag=0, index_base=402.9\n           ).shift(100)  # curves begins at 0% and gets shifted by 100 Ac6t365f O/N basis points\n           fixings.add(\n               \"UK_RPI\",\n               Series(index=[dt(2025, 3, 1), dt(2025, 4, 1), dt(2025, 5, 1)], data=[395.3, 402.2, 402.9]),\n           )\n           ukti = IndexFixedRateBond(  # ISIN: GB00BMY62Z61\n               effective=dt(2025, 6, 11),\n               termination=dt(2038, 9, 22),\n               fixed_rate=1.75,\n               spec=\"uk_gbi\",\n               index_fixings=\"UK_RPI\"\n           )\n           ukti.rate(curves=[index_curve, disc_curve], metric=\"clean_price\")  # settles T+1 i.e. 29th July\n           ukti.rate(curves=[index_curve, disc_curve], metric=\"dirty_price\")\n           ukti.rate(curves=[index_curve, disc_curve], metric=\"indexed_clean_price\")\n           ukti.rate(curves=[index_curve, disc_curve], metric=\"indexed_dirty_price\")\n           ukti.rate(curves=[index_curve, disc_curve], metric=\"ytm\")\n           ukti.rate(curves=[index_curve, disc_curve], metric=\"indexed_ytm\")\n\n        .. ipython:: python\n           :suppress:\n\n           fixings.pop(\"UK_RPI\")\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        local: bool, :green:`optional (set as False)`\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n        metric: str, :green:`optional`\n            The specific calculation to perform and the value to return.\n            See **Pricing** on each *Instrument* for details of allowed inputs.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"  # noqa: E501\n        c = _parse_curves(self, curves, solver)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n        index_curve = _get_curve(\"index_curve\", False, True, *c)\n\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n\n        if isinstance(settlement, NoInput):\n            settlement_ = self.leg1.schedule.calendar.lag_bus_days(\n                disc_curve.nodes.initial,\n                self.kwargs.meta[\"settle\"],\n                True,\n            )\n        else:\n            settlement_ = settlement\n        npv = self.leg1.local_npv(\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            settlement=settlement_,\n            forward=settlement_,\n        )\n        # scale price to par 100 (npv is already projected forward to settlement)\n        index_dirty_price = npv * 100 / -self.leg1.settlement_params.notional\n        index_ratio = self.index_ratio(settlement_, index_curve)\n        dirty_price = index_dirty_price / index_ratio\n\n        if metric_ == \"dirty_price\":\n            return dirty_price\n        elif metric_ == \"clean_price\":\n            return dirty_price - self.accrued(settlement_)\n        elif metric_ == \"ytm\":\n            return self.ytm(dirty_price, settlement_, True)\n        elif metric_ == \"index_dirty_price\" or metric_ == \"indexed_dirty_price\":\n            return index_dirty_price\n        elif metric_ == \"index_clean_price\" or metric_ == \"indexed_clean_price\":\n            return index_dirty_price - self.accrued(settlement_) * index_ratio\n        elif metric_ == \"index_ytm\" or metric_ == \"indexed_ytm\":\n            return self.ytm(\n                price=index_dirty_price,\n                settlement=settlement_,\n                dirty=True,\n                indexed_price=True,\n                indexed_ytm=True,\n                index_curve=index_curve,\n            )\n        else:\n            raise ValueError(\n                \"`metric` must be in {'dirty_price', 'clean_price', 'ytm', \"\n                \"'indexed_dirty_price', 'indexed_clean_price', 'indexed_ytm'}.\",\n            )\n\n    def accrued(\n        self, settlement: datetime, indexed: bool = False, index_curve: _BaseCurve_ = NoInput(0)\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the accrued amount per nominal par value of 100.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from pandas import Series\n           from datetime import datetime as dt\n           from rateslib import fixings\n           from rateslib.instruments import IndexFixedRateBond\n\n        .. ipython:: python\n\n           fixings.add(\"UK_RPI\", Series(index=[dt(2025, 3, 1), dt(2025, 4, 1), dt(2025, 5, 1)], data=[395.3, 402.2, 402.9]))\n           ukti = IndexFixedRateBond(  # ISIN: GB00BMY62Z61\n               effective=dt(2025, 6, 11),\n               termination=dt(2038, 9, 22),\n               fixed_rate=1.75,\n               spec=\"uk_gbi\",\n               index_fixings=\"UK_RPI\"\n           )\n           ukti.accrued(settlement=dt(2025, 7, 29))\n           ukti.accrued(settlement=dt(2025, 7, 29), indexed=True)\n\n        .. ipython:: python\n           :suppress:\n\n           fixings.pop(\"UK_RPI\")\n\n\n        Parameters\n        ----------\n        settlement : datetime\n            The settlement date which to measure accrued interest against.\n        indexed : bool\n            Whether to calculate the accrued amount indexed up according to settlement.\n        index_curve : _BaseCurve, optional\n            The curve used to forecast index values if required.\n\n        Notes\n        -----\n        Calculation depends upon the\n        :class:`~rateslib.instruments.bonds.conventions.BondCalcMode` of the\n        *Instrument*.\n        \"\"\"  # noqa: E501\n        unindexed_accrued = super().accrued(settlement=settlement)\n        if indexed:\n            index_ratio = self.index_ratio(settlement=settlement, index_curve=index_curve)\n            return unindexed_accrued * index_ratio\n        else:\n            return unindexed_accrued\n\n    def fwd_from_repo(\n        self,\n        price: DualTypes,\n        settlement: datetime,\n        forward_settlement: datetime,\n        repo_rate: DualTypes,\n        convention: str_ = NoInput(0),\n        dirty: bool = False,\n        method: str = \"proceeds\",\n        indexed: bool = False,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return a forward price implied by a given repo rate.\n\n        Parameters\n        ----------\n        price : float, Dual, or Dual2\n            The initial price of the security at ``settlement``.\n        settlement : datetime\n            The settlement date of the bond\n        forward_settlement : datetime\n            The forward date for which to calculate the forward price.\n        repo_rate : float, Dual or Dual2\n            The rate which is used to calculate values.\n        convention : str, optional\n            The day count convention applied to the rate. If not given uses default\n            values.\n        dirty : bool, optional\n            Whether the input and output price are specified including accrued interest.\n        method : str in {\"proceeds\", \"compounded\"}, optional\n            The method for determining the forward price.\n        indexed : bool, optional\n            Whether the given price is expressed with indexation.\n        index_curve : _BaseCurve, optional\n            The curve for forecasting index values if required.\n\n        Returns\n        -------\n        float, Dual or Dual2\n\n        Notes\n        -----\n        Any intermediate (non ex-dividend) cashflows between ``settlement`` and\n        ``forward_settlement`` will also be assumed to accrue at ``repo_rate``.\n        \"\"\"\n        match (indexed, dirty):\n            # need to adjust any input to yield an indexed_dirty_price\n            case (True, True):\n                indexed_dirty_price = price\n            case (False, True):\n                indexed_dirty_price = price * self.index_ratio(\n                    settlement=settlement, index_curve=index_curve\n                )\n            case (True, False):\n                indexed_dirty_price = price + self.accrued(\n                    settlement, indexed=True, index_curve=index_curve\n                )\n            case (False, False):\n                indexed_dirty_price = (\n                    price + self.accrued(settlement, indexed=False)\n                ) * self.index_ratio(settlement=settlement, index_curve=index_curve)\n\n        forward_indexed_dirty_price = super().fwd_from_repo(\n            price=indexed_dirty_price,\n            settlement=settlement,\n            forward_settlement=forward_settlement,\n            repo_rate=repo_rate,\n            convention=convention,\n            dirty=True,\n            method=method,\n        )\n\n        match (indexed, dirty):\n            # reverse adjust the forward indexed_dirty_price to suit the input arguments\n            case (True, True):\n                forward_price = forward_indexed_dirty_price\n            case (False, True):\n                forward_price = forward_indexed_dirty_price / self.index_ratio(\n                    forward_settlement, index_curve=index_curve\n                )\n            case (True, False):\n                forward_price = forward_indexed_dirty_price - self.accrued(\n                    forward_settlement, indexed=True, index_curve=index_curve\n                )\n            case (False, False):\n                forward_price = forward_indexed_dirty_price / self.index_ratio(\n                    forward_settlement, index_curve=index_curve\n                ) - self.accrued(forward_settlement, indexed=False)\n\n        return forward_price\n\n    def repo_from_fwd(\n        self,\n        price: DualTypes,\n        settlement: datetime,\n        forward_settlement: datetime,\n        forward_price: DualTypes,\n        convention: str_ = NoInput(0),\n        dirty: bool = False,\n        indexed: bool = False,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return an implied repo rate from a forward price.\n\n        Parameters\n        ----------\n        price : float, Dual, or Dual2\n            The initial price of the security at ``settlement``.\n        settlement : datetime\n            The settlement date of the bond\n        forward_settlement : datetime\n            The forward date for which to calculate the forward price.\n        forward_price : float, Dual or Dual2\n            The forward price which implies the repo rate\n        convention : str, optional\n            The day count convention applied to the rate. If not given uses default\n            values.\n        dirty : bool, optional\n            Whether the input and output price are specified including accrued interest.\n        indexed : bool, optional\n            Whether the given price is expressed with indexation.\n        index_curve : _BaseCurve, optional\n            The curve for forecasting index values if required.\n\n        Returns\n        -------\n        float, Dual or Dual2\n\n        Notes\n        -----\n        Any intermediate (non ex-dividend) cashflows between ``settlement`` and\n        ``forward_settlement`` will also be assumed to accrue at ``repo_rate``.\n        \"\"\"\n        match (indexed, dirty):\n            # must convert input price to indexed_dirty_price equivalents\n            case (True, True):\n                indexed_dirty_price = price\n                forward_indexed_dirty_price = forward_price\n            case (False, True):\n                indexed_dirty_price = price * self.index_ratio(\n                    settlement=settlement, index_curve=index_curve\n                )\n                forward_indexed_dirty_price = forward_price * self.index_ratio(\n                    settlement=forward_settlement, index_curve=index_curve\n                )\n            case (True, False):\n                indexed_dirty_price = price + self.accrued(\n                    settlement, indexed=True, index_curve=index_curve\n                )\n                forward_indexed_dirty_price = forward_price + self.accrued(\n                    forward_settlement, indexed=True, index_curve=index_curve\n                )\n            case (False, False):\n                indexed_dirty_price = (\n                    price + self.accrued(settlement, indexed=False)\n                ) * self.index_ratio(settlement=settlement, index_curve=index_curve)\n                forward_indexed_dirty_price = (\n                    forward_price + self.accrued(forward_settlement, indexed=False)\n                ) * self.index_ratio(settlement=forward_settlement, index_curve=index_curve)\n\n        repo = super().repo_from_fwd(\n            price=indexed_dirty_price,\n            settlement=settlement,\n            forward_settlement=forward_settlement,\n            forward_price=forward_indexed_dirty_price,\n            convention=convention,\n            dirty=True,\n        )\n        return repo\n\n    def duration(\n        self,\n        ytm: DualTypes,\n        settlement: datetime,\n        metric: str = \"risk\",\n        indexed_price: bool = False,\n        indexed_ytm: bool = False,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> float:\n        \"\"\"\n        Return the (negated) derivative of ``price`` w.r.t. ``ytm``.\n\n        Parameters\n        ----------\n        ytm : float\n            The yield-to-maturity for the bond.\n        settlement : datetime\n            The settlement date of the bond.\n        metric : str\n            The specific duration calculation to return. See notes.\n        indexed_price: bool, :green:`optional (set as False)`\n            Indicated whether the returned price should be indexed or not.\n        indexed_ytm: bool, :green:`optional (set as False)`\n            Indicates if the given ``ytm`` is expressed indexed or not.\n        index_curve : _BaseCurve, optional\n            If either the ytm or the price are indicated as indexed then an index curve may be\n            required to forecast index values.\n\n        Returns\n        -------\n        float\n\n        Notes\n        -----\n        For an *IndexFixedRateBond* both the price and the ytm are expressible unindexed or\n        indexed. The below notation :math:`P_i` and :math:`y_j` describes either of these\n        varieties provided they align with the ``indexed_price`` and ``indexed_ytm`` arguments.\n\n        The available metrics are:\n\n        - *\"risk\"*: the derivative of price w.r.t. ytm, scaled to -1bp.\n\n          .. math::\n\n             risk = - \\\\frac{\\\\partial P_i }{\\\\partial y_j}\n\n        - *\"modified\"*: the modified duration which is *risk* divided by dirty price.\n\n          .. math::\n\n             mod \\\\; duration = \\\\frac{risk}{P_i} = - \\\\frac{1}{P_i} \\\\frac{\\\\partial P_i }{\\\\partial y_j}\n\n        - *\"duration\"* (or *\"macaulay\"*): the duration which is modified duration reverse modified.\n\n          .. math::\n\n             duration = mod \\\\; duration \\\\times (1 + y_j / f)\n\n        \"\"\"  # noqa: E501\n        # TODO: this is not AD safe: returns only float\n        ytm_: Dual = Dual(_dual_float(ytm), [\"__y__§\"], [])\n        dirty_price: Dual = self.price(  # type: ignore[assignment]\n            ytm=ytm_,\n            settlement=settlement,\n            dirty=True,\n            indexed_price=indexed_price,\n            indexed_ytm=indexed_ytm,\n            index_curve=index_curve,\n        )\n\n        if metric == \"risk\":\n            ret: float = -gradient(dirty_price, [\"__y__§\"])[0]\n        elif metric == \"modified\":\n            ret = -gradient(dirty_price, [\"__y__§\"])[0] / _dual_float(dirty_price) * 100\n        elif metric == \"duration\" or metric == \"macaulay\":\n            f = self.leg1.schedule.periods_per_annum\n            v = _dual_float(1 + ytm_ / (100 * f))\n            ret = -gradient(dirty_price, [\"__y__§\"])[0] / _dual_float(dirty_price) * v * 100\n        else:\n            raise ValueError(\n                \"`metric` must be one of {'risk', 'modified', 'duration'}.\"\n            )  # pragma: no cover\n        return ret\n\n    def ytm(\n        self,\n        price: DualTypes,\n        settlement: datetime,\n        dirty: bool = False,\n        rate_curve: CurveOption_ = NoInput(0),\n        calc_mode: BondCalcMode | str_ = NoInput(0),\n        indexed_price: bool = False,\n        indexed_ytm: bool = False,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> Number:\n        # overloaded ytm by IndexFixedRateBond\n        \"\"\"\n        Calculate the yield-to-maturity of the security given its price.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import FixedRateBond, dt, Dual, Dual2\n\n        .. ipython:: python\n\n           aapl_bond = FixedRateBond(dt(2013, 5, 4), dt(2043, 5, 4), fixed_rate=3.85, spec=\"us_corp\")\n           aapl_bond.ytm(price=87.24, settlement=dt(2014, 3, 5))\n           aapl_bond.ytm(price=87.24, settlement=dt(2014, 3, 5), calc_mode=\"us_gb_tsy\")\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        price: float, Dual, Dual2, Variable, :red:`required`\n            The price, per 100 nominal, against which to determine the yield. Can be given as\n            either clean or dirty, and either unindexed or indexed.\n        settlement: datetime, :red:`required`\n            The settlement date on which to determine the price.\n        dirty: bool, :green:`optional (set as False)`\n            If `True` will assume the (settlement)\n            :meth:`~rateslib.instruments.FixedRateBond.accrued` is included in the price.\n        rate_curve: _BaseCurve or dict of such, :green:`optional`\n            Used to forecast floating rates if required.\n        calc_mode: str or BondCalcMode, :green:`optional`\n            An alternative calculation mode to use. The ``calc_mode`` is typically set at\n            *Instrument* initialisation and is not required, but is useful as an override to\n            allow comparisons, e.g. of *\"us_gb\"* street convention versus *\"us_gb_tsy\"* treasury\n            convention.\n        indexed_price: bool, :green:`optional (set as False)`\n            Indicates whether the input price is indexed or not.\n        indexed_ytm: bool, :green:`optional (set as False)`\n            Indicates whether the returned ``ytm`` is expressed indexed or not.\n        index_curve: _BaseCurve :green:`optional`\n            If any element is ``indexed`` then a *Curve* may be required to determine\n            index ratio's in order to properly index up cashflows.\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Notes\n        -----\n        If ``price`` is given as :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2` input the result of the yield will be output\n        as the same type with the variables passed through accordingly.\n\n        .. ipython:: python\n\n           aapl_bond.ytm(price=Dual(87.24, [\"price\", \"a\"], [1, -0.75]), settlement=dt(2014, 3, 5))\n           aapl_bond.ytm(price=Dual2(87.24, [\"price\", \"a\"], [1, -0.75], []), settlement=dt(2014, 3, 5))\n\n        \"\"\"  # noqa: E501\n        match (indexed_price, indexed_ytm):\n            case (False, False) | (True, True):\n                # when both price and yield are expressed in the same indexation this will be\n                # handled directly\n                adjusted_price = price\n            case (False, True):\n                # if the ytm is requested indexed but the price is given unindexed then it\n                # must be indexed-up for calculation\n                adjusted_price = price * self.index_ratio(\n                    settlement=settlement, index_curve=index_curve\n                )\n            case (True, False):\n                # if the ytm is requested unindexed but the price is given as indexed then it must\n                # be indexed down for calculation\n                adjusted_price = price / self.index_ratio(\n                    settlement=settlement, index_curve=index_curve\n                )\n            case _:  # pragma: no cover\n                raise ValueError(\n                    \"`indexed_price` and `indexed_ytm` must each be given as a boolean.\"\n                )\n\n        return self._ytm(\n            price=adjusted_price,\n            settlement=settlement,\n            dirty=dirty,\n            rate_curve=rate_curve,\n            calc_mode=calc_mode,\n            indexed=indexed_ytm,\n            index_curve=index_curve,\n        )\n\n    def price(\n        self,\n        ytm: DualTypes,\n        settlement: datetime,\n        dirty: bool = False,\n        indexed_price: bool = False,\n        indexed_ytm: bool = False,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the price of the security per nominal value of 100, given\n        yield-to-maturity.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        ytm : float, :red:`required`\n            The yield-to-maturity against which to determine the price. If ``indexed`` this\n            should be given as a nominal ytm.\n        settlement : datetime, :red:`required`\n            The settlement date on which to determine the price.\n        dirty : bool, optional, :green:`optional (set as False)`\n            If `True` will include the\n            :meth:`rateslib.instruments.FixedRateBond.accrued` in the price.\n        indexed_price: bool, :green:`optional (set as False)`\n            Indicated whether the returned price should be indexed or not.\n        indexed_ytm: bool, :green:`optional (set as False)`\n            Indicates if the given ``ytm`` is expressed indexed or not.\n        index_curve: _BaseCurve, :green:`optional`\n            An inflation curve to forecast index ratios if required.\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Examples\n        --------\n\n        .. ipython:: python\n           :suppress:\n\n           from pandas import Series\n           from datetime import datetime as dt\n           from rateslib import fixings, Curve\n           from rateslib.instruments import IndexFixedRateBond\n\n        .. ipython:: python\n\n           index_curve = Curve(\n               nodes={dt(2025, 5, 1): 1.0, dt(2045, 5, 1): 1.0},\n               convention=\"act365f\", index_lag=0, index_base=402.9\n           ).shift(100)  # curves begins at 0% and gets shifted by 100 Act365f O/N basis points\n           ukti = IndexFixedRateBond(  # ISIN: GB00BMY62Z61\n               effective=dt(2025, 6, 11),\n               termination=dt(2038, 9, 22),\n               fixed_rate=1.75,\n               spec=\"uk_gbi\",\n               index_base=397.6,\n           )\n           ukti.index_ratio(index_curve=index_curve, settlement=dt(2025, 8, 5))\n           ukti.price(ytm=2.5, settlement=dt(2025, 8, 5), indexed_ytm=True, index_curve=index_curve)\n           ukti.price(ytm=1.5, settlement=dt(2025, 8, 5), indexed_ytm=False)\n           ukti.price(ytm=2.5, settlement=dt(2025, 8, 5), dirty=True, indexed_ytm=True, index_curve=index_curve)\n           ukti.price(ytm=1.5, settlement=dt(2025, 8, 5), dirty=True, indexed_ytm=False)\n\n        \"\"\"  # noqa: E501\n        _price = self._price_from_ytm(\n            ytm=ytm,\n            settlement=settlement,\n            calc_mode=NoInput(0),  # will be set to kwargs.meta\n            dirty=dirty,\n            rate_curve=NoInput(0),\n            indexed=indexed_ytm,\n            index_curve=index_curve,\n        )\n\n        match (indexed_price, indexed_ytm):\n            case (True, True) | (False, False):\n                # then both price and ytm has the same indexation expression\n                return _price\n            case (True, False):\n                # then the yield is given unindexed but the returned price must be indexed-up\n                return _price * self.index_ratio(settlement, index_curve)\n            case (False, True):\n                # then the yield is given unindexed but the returned price requires indexing-down\n                return _price / self.index_ratio(settlement, index_curve)\n            case _:  # pragma: no cover\n                raise ValueError(\n                    \"`indexed_price` and `indexed_ytm` must each be given as a boolean.\"\n                )\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/protocols/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.bonds.protocols.accrued import _WithAccrued\nfrom rateslib.instruments.bonds.protocols.cashflows import _WithExDiv\nfrom rateslib.instruments.bonds.protocols.duration import _WithDuration\nfrom rateslib.instruments.bonds.protocols.oaspread import _WithOASpread\nfrom rateslib.instruments.bonds.protocols.repo import _WithRepo\nfrom rateslib.instruments.bonds.protocols.ytm import _WithYTM\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.pricing import (\n    _get_curve,\n    _parse_curves,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        _BaseCurve,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass _BaseBondInstrument(\n    _BaseInstrument,\n    _WithExDiv,\n    _WithDuration,\n    _WithRepo,\n    _WithYTM,\n    _WithOASpread,\n):\n    \"\"\"Abstract base class used in the construction of bond type *Instruments*\"\"\"\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        if isinstance(settlement, NoInput):\n            c = _parse_curves(self, curves, solver)\n            disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n            settlement_ = self.leg1.schedule.calendar.lag_bus_days(\n                disc_curve.nodes.initial,\n                self.kwargs.meta[\"settle\"],\n                True,\n            )\n            forward_ = _drb(disc_curve.nodes.initial, forward)\n        else:\n            settlement_ = settlement\n            forward_ = forward  # if NoInput adopts the usual default settings from 'settlement'\n\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement_,\n            forward=forward_,\n        )\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def analytic_delta(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        leg: int = 1,\n    ) -> DualTypes | dict[str, DualTypes]:\n        c = _parse_curves(self, curves, solver)\n\n        settlement_ = self._maybe_get_settlement(\n            settlement=settlement,\n            disc_curve=_get_curve(\"disc_curve\", False, False, *c),\n        )\n\n        return super().analytic_delta(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement_,\n            forward=forward,\n            leg=leg,\n        )\n\n    def price(self, ytm: DualTypes, settlement: datetime, dirty: bool = False) -> DualTypes:\n        # overloaded by IndexFixedRateBond\n        \"\"\"\n        Calculate the price of the security per nominal value of 100, given\n        yield-to-maturity.\n\n        Parameters\n        ----------\n        ytm : float\n            The yield-to-maturity against which to determine the price.\n        settlement : datetime\n            The settlement date on which to determine the price.\n        dirty : bool, optional\n            If `True` will include the\n            :meth:`rateslib.instruments.FixedRateBond.accrued` in the price.\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Examples\n        --------\n        This example is taken from the UK debt management office website.\n        The result should be `141.070132` and the bond is ex-div.\n\n        .. ipython:: python\n\n           gilt = FixedRateBond(\n               effective=dt(1998, 12, 7),\n               termination=dt(2015, 12, 7),\n               frequency=\"S\",\n               calendar=\"ldn\",\n               currency=\"gbp\",\n               convention=\"ActActICMA\",\n               ex_div=7,\n               fixed_rate=8.0\n           )\n           gilt.ex_div(dt(1999, 5, 27))\n           gilt.price(\n               ytm=4.445,\n               settlement=dt(1999, 5, 27),\n               dirty=True\n           )\n\n        This example is taken from the Swedish national debt office website.\n        The result of accrued should, apparently, be `0.210417` and the clean\n        price should be `99.334778`.\n\n        .. ipython:: python\n\n           bond = FixedRateBond(\n               effective=dt(2017, 5, 12),\n               termination=dt(2028, 5, 12),\n               frequency=\"A\",\n               calendar=\"stk\",\n               currency=\"sek\",\n               convention=\"ActActICMA\",\n               ex_div=5,\n               fixed_rate=0.75\n           )\n           bond.ex_div(dt(2017, 8, 23))\n           bond.accrued(dt(2017, 8, 23))\n           bond.price(\n               ytm=0.815,\n               settlement=dt(2017, 8, 23),\n               dirty=False\n           )\n\n        \"\"\"\n        return self._price_from_ytm(\n            ytm=ytm,\n            settlement=settlement,\n            calc_mode=NoInput(0),  # will be set to kwargs.meta\n            dirty=dirty,\n            rate_curve=NoInput(0),\n            indexed=False,\n            index_curve=NoInput(0),\n        )\n\n    def _maybe_get_settlement(\n        self,\n        settlement: datetime_,\n        disc_curve: _BaseCurve,\n    ) -> datetime:\n        if isinstance(settlement, NoInput):\n            return self.leg1.schedule.calendar.lag_bus_days(\n                disc_curve.nodes.initial,\n                self.kwargs.meta[\"settle\"],\n                True,\n            )\n        else:\n            return settlement\n\n\n__all__ = [\n    \"_WithYTM\",\n    \"_WithExDiv\",\n    \"_WithAccrued\",\n    \"_WithDuration\",\n    \"_WithRepo\",\n    \"_WithOASpread\",\n]\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/protocols/accrued.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.enums.generics import NoInput\n\nif TYPE_CHECKING:\n    from rateslib.instruments.bonds.conventions.accrued import AccrualFunction  # pragma: no cover\n    from rateslib.local_types import (  # pragma: no cover\n        Cashflow,\n        DualTypes,\n        FixedLeg,\n        FixedPeriod,\n        FloatLeg,\n        FloatPeriod,\n        ZeroFloatPeriod,\n        _BaseCurveOrDict_,\n        _KWArgs,\n        datetime,\n    )\n\n\nclass _WithAccrued(Protocol):\n    \"\"\"\n    Protocol to determine the *yield-to-maturity* of a bond type *Instrument*.\n    \"\"\"\n\n    def _period_cashflow(\n        self,\n        period: Cashflow | FixedPeriod | FloatPeriod | ZeroFloatPeriod,\n        rate_curve: _BaseCurveOrDict_,\n    ) -> DualTypes: ...\n\n    @property\n    def leg1(self) -> FixedLeg | FloatLeg: ...\n\n    @property\n    def kwargs(self) -> _KWArgs: ...\n\n    def _accrued(self, settlement: datetime, func: AccrualFunction) -> DualTypes:\n        \"\"\"func is the specific accrued function associated with the bond ``calc_mode``\"\"\"\n        acc_idx = self.leg1._period_index(settlement)\n        frac = func(self, settlement, acc_idx)\n        if self.leg1.ex_div(settlement):\n            frac = frac - 1  # accrued is negative in ex-div period\n        _: DualTypes = self._period_cashflow(self.leg1._regular_periods[acc_idx], NoInput(0))\n        return frac * _ / -self.leg1._regular_periods[acc_idx].settlement_params.notional * 100\n\n    def accrued(self, settlement: datetime) -> DualTypes:\n        \"\"\"\n        Calculate the accrued amount per nominal par value of 100.\n\n        Parameters\n        ----------\n        settlement : datetime\n            The settlement date which to measure accrued interest against.\n\n        Notes\n        -----\n        The amount of accrued interest is calculated using the following formula:\n\n        .. math::\n\n           &AI = \\\\xi c_i \\\\qquad \\\\text{if not ex-dividend} \\\\\\\\\n           &AI = (\\\\xi - 1) c_i \\\\qquad \\\\text{if ex-dividend} \\\\\\\\\n\n        where :math:`c_i` is the physical ``cashflow`` related to the period in which ``settlement``\n        falls, and :math:`\\\\xi` is a fraction of that amount determined according to the\n        calculation mode specific to the :class:`~rateslib.instruments.BondCalcMode`.\n\n        \"\"\"  # noqa: E501\n        value = self._accrued(settlement, self.kwargs.meta[\"calc_mode\"]._settle_accrual)\n        return value\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/protocols/cashflows.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        FixedLeg,\n        FloatLeg,\n        datetime,\n    )\n\n\nclass _WithExDiv(Protocol):\n    \"\"\"\n    Protocol to determine the *yield-to-maturity* of a bond type *Instrument*.\n    \"\"\"\n\n    @property\n    def leg1(self) -> FixedLeg | FloatLeg: ...\n\n    def ex_div(self, settlement: datetime) -> bool:\n        \"\"\"\n        Return a boolean whether the security is ex-div at the given settlement.\n\n        Parameters\n        ----------\n        settlement : datetime\n            The settlement date to test.\n\n        Returns\n        -------\n        bool\n\n        Notes\n        -----\n        Uses the UK DMO convention of returning *False* if ``settlement``\n        **is on or before** the ex-div date for a regular coupon period.\n\n        This is evaluated by analysing the attribute ``pschedule3`` of the associated\n        :class:`~rateslib.scheduling.Schedule` object of the *Leg*.\n        \"\"\"\n        return self.leg1.ex_div(settlement)\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/protocols/duration.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.dual import Dual, Dual2, gradient\nfrom rateslib.dual.utils import _dual_float\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        DualTypes,\n        FixedLeg,\n        FloatLeg,\n        datetime,\n    )\n\n\nclass _WithDuration(Protocol):\n    \"\"\"\n    Protocol to determine the *yield-to-maturity* of a bond type *Instrument*.\n    \"\"\"\n\n    def price(self, *args: Any, **kwargs: Any) -> DualTypes: ...\n\n    @property\n    def leg1(self) -> FixedLeg | FloatLeg: ...\n\n    def duration(self, ytm: DualTypes, settlement: datetime, metric: str = \"risk\") -> float:\n        \"\"\"\n        Return the (negated) derivative of ``price`` w.r.t. ``ytm``.\n\n        Parameters\n        ----------\n        ytm : float\n            The yield-to-maturity for the bond.\n        settlement : datetime\n            The settlement date of the bond.\n        metric : str\n            The specific duration calculation to return. See notes.\n\n        Returns\n        -------\n        float\n\n        Notes\n        -----\n        The available metrics are:\n\n        - *\"risk\"*: the derivative of price w.r.t. ytm, scaled to -1bp.\n\n          .. math::\n\n             risk = - \\\\frac{\\\\partial P }{\\\\partial y}\n\n        - *\"modified\"*: the modified duration which is *risk* divided by dirty price.\n\n          .. math::\n\n             mod \\\\; duration = \\\\frac{risk}{P} = - \\\\frac{1}{P} \\\\frac{\\\\partial P }{\\\\partial y}\n\n        - *\"duration\"* (or *\"macaulay\"*): the duration which is modified duration reverse modified.\n\n          .. math::\n\n             duration = mod \\\\; duration \\\\times (1 + y / f)\n\n        Examples\n        --------\n        .. ipython:: python\n\n           gilt = FixedRateBond(\n               effective=dt(1998, 12, 7),\n               termination=dt(2015, 12, 7),\n               frequency=\"S\",\n               calendar=\"ldn\",\n               currency=\"gbp\",\n               convention=\"ActActICMA\",\n               ex_div=7,\n               fixed_rate=8.0\n           )\n           gilt.duration(4.445, dt(1999, 5, 27), \"risk\")\n           gilt.duration(4.445, dt(1999, 5, 27), \"modified\")\n           gilt.duration(4.445, dt(1999, 5, 27), \"duration\")\n\n        This result is interpreted as cents. If the yield is increased by 1bp the price\n        will fall by 14.65 cents.\n\n        .. ipython:: python\n\n           gilt.price(4.445, dt(1999, 5, 27))\n           gilt.price(4.455, dt(1999, 5, 27))\n        \"\"\"\n        # TODO: this is not AD safe: returns only float\n        ytm_: float = _dual_float(ytm)\n        if metric == \"risk\":\n            price_dual: Dual = self.price(Dual(ytm_, [\"__y__§\"], []), settlement)  # type: ignore[assignment]\n            _: float = -gradient(price_dual, [\"__y__§\"])[0]\n        elif metric == \"modified\":\n            price_dual = -self.price(Dual(ytm_, [\"__y__§\"], []), settlement, dirty=True)  # type: ignore[assignment]\n            _ = -gradient(price_dual, [\"__y__§\"])[0] / float(price_dual) * 100\n        elif metric == \"duration\" or metric == \"macaulay\":\n            price_dual = self.price(Dual(ytm_, [\"__y__§\"], []), settlement, dirty=True)  # type: ignore[assignment]\n            f = self.leg1.schedule.periods_per_annum\n            v = 1 + ytm_ / (100 * f)\n            _ = -gradient(price_dual, [\"__y__§\"])[0] / float(price_dual) * v * 100\n        return _\n\n    def convexity(self, ytm: DualTypes, settlement: datetime, metric: str = \"risk\") -> float:\n        \"\"\"\n        Return the second derivative of ``price`` w.r.t. ``ytm``.\n\n        Parameters\n        ----------\n        ytm : float\n            The yield-to-maturity for the bond.\n        settlement : datetime\n            The settlement date of the bond.\n        metric: str, optional\n\n        Returns\n        -------\n        float\n\n        Notes\n        ------\n        The default metric is similar to the :meth:`duration` method and is *'risk'* based,\n        but the traditional calculation is available.\n\n        - *\"risk\"*: the second derivative of price w.r.t. ytm, scaled to -1bp.\n\n          .. math::\n\n             risk = \\\\frac{\\\\partial^2 P }{\\\\partial y^2}\n\n        - *\"convexity\"*: the standard formula for convexity which is the above scaled by price.\n\n          .. math::\n\n             convexity = \\\\frac{1}{P} \\\\frac{\\\\partial P^2 }{\\\\partial y^2}\n\n        Examples\n        --------\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import FixedRateBond\n\n        .. ipython:: python\n\n           gilt = FixedRateBond(\n               effective=dt(1998, 12, 7),\n               termination=dt(2015, 12, 7),\n               frequency=\"S\",\n               calendar=\"ldn\",\n               currency=\"gbp\",\n               convention=\"ActActICMA\",\n               ex_div=7,\n               fixed_rate=8.0\n           )\n           gilt.convexity(4.445, dt(1999, 5, 27))\n\n        This number is interpreted as hundredths of a cent. For a 1bp increase in\n        yield the duration will decrease by 2 hundredths of a cent.\n\n        .. ipython:: python\n\n           gilt.duration(4.445, dt(1999, 5, 27))\n           gilt.duration(4.455, dt(1999, 5, 27))\n        \"\"\"\n        # TODO: method is not AD safe: returns float\n        ytm_: float = _dual_float(ytm)\n        d = self.price(Dual2(ytm_, [\"_ytm__§\"], [], []), settlement, dirty=True)\n        ret: float = gradient(d, [\"_ytm__§\"], 2)[0][0]\n\n        if metric == \"risk\":\n            return ret\n        elif metric == \"convexity\":\n            return ret * 100.0 / _dual_float(d)\n        return ret\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/protocols/oaspread.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import partial\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib import defaults\nfrom rateslib.curves._parsers import (\n    _maybe_set_ad_order,\n)\nfrom rateslib.dual import ift_1dim\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.bonds.protocols import _WithAccrued\nfrom rateslib.instruments.protocols.pricing import (\n    _get_curve,\n    _parse_curves,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        _BaseCurve,\n        _BaseCurveOrDict_,\n        _Curves,\n        datetime_,\n        float_,\n        str_,\n    )\n\n\nclass _WithOASpread(_WithAccrued, Protocol):\n    \"\"\"\n    Protocol to determine the *yield-to-maturity* of a bond type *Instrument*.\n    \"\"\"\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves: ...\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes: ...\n\n    def oaspread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        price: DualTypes_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        func_tol: float_ = NoInput(0),\n        conv_tol: float_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        The option adjusted spread added to the discounting *Curve* to value the security\n        at ``price``.\n\n        Parameters\n        ----------\n        curves : Curve, str or list of such\n            A single :class:`Curve` or id or a list of such. A list defines the\n            following curves in the order:\n\n              - Forecasting :class:`Curve` for ``leg1``.\n              - Discounting :class:`Curve` for ``leg1``.\n        solver : Solver, optional\n            The numerical :class:`Solver` that constructs ``Curves`` from calibrating\n            instruments.\n        fx : float, FXRates, FXForwards, optional\n            The immediate settlement FX rate that will be used to convert values\n            into another currency. A given `float` is used directly. If giving a\n            ``FXRates`` or ``FXForwards`` object, converts from local currency\n            into ``base``.\n        base : str, optional\n            The base currency to convert cashflows into (3-digit code), set by default.\n            Only used if ``fx`` is an ``FXRates`` or ``FXForwards`` object.\n        price : float, Dual, Dual2\n            The price of the bond to match.\n        metric : str, optional\n            The metric to use when evaluating the price/rate of the instrument. If not\n            given uses the instrument's :meth:`~rateslib.instruments.FixedRateBond.rate` method\n            default.\n        func_tol: float, optional\n            The tolerance for the objective function value when iteratively solving. If not given\n            uses `defaults.oaspread_func_tol`.\n        conv_tol: float, optional\n            The tolerance used for stopping criteria of successive iteration values. If not\n            given uses `defaults.oaspread_conv_tol`.\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Notes\n        ------\n        The discount curve must be of type :class:`~rateslib.curves._BaseCurve` with a\n        provided :meth:`~rateslib.curves._BaseCurve.shift` method available.\n\n        .. warning::\n           The sensitivity of variables is preserved for the input argument ``price``, but this\n           function does **not** preserve AD towards variables associated with the ``curves`` or\n           ``solver``.\n\n        Examples\n        --------\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import Variable\n\n        .. ipython:: python\n\n           bond = FixedRateBond(dt(2000, 1, 1), \"3Y\", fixed_rate=2.5, spec=\"us_gb\")\n           curve = Curve({dt(2000, 7, 1): 1.0, dt(2005, 7, 1): 0.80})\n           # Add AD variables to the curve without a Solver\n           curve._set_ad_order(1)\n\n           bond.oaspread(curves=curve, price=Variable(95.0, [\"price\"], []))\n\n        This result excludes curve sensitivities but includes sensitivity to the\n        constructed *'price'* variable. Accuracy can be observed through numerical simulation.\n\n        .. ipython:: python\n\n           bond.oaspread(curves=curve, price=96.0)\n           bond.oaspread(curves=curve, price=94.0)\n\n        \"\"\"\n\n        if isinstance(price, NoInput):\n            raise ValueError(\"`price` must be supplied in order to derive the `oaspread`.\")\n\n        c = _parse_curves(self, curves, solver)  # type: ignore[arg-type]\n        disc_curve_ = _get_curve(\"disc_curve\", False, False, *c)\n        rate_curve_ = _get_curve(\"rate_curve\", True, True, *c)\n\n        _ad_disc = _maybe_set_ad_order(disc_curve_, 0)\n        _ad_fore = _maybe_set_ad_order(rate_curve_, 0)\n\n        def s_with_args(\n            g: DualTypes, curve: _BaseCurveOrDict_, disc_curve: _BaseCurve, metric: str_\n        ) -> DualTypes:\n            \"\"\"\n            Return the price of a bond given an OASpread.\n\n            Parameters\n            ----------\n            g: DualTypes\n                The OASpread value in basis points.\n            curve:\n                The forecasting curve.\n            disc_curve:\n                The discount curve.\n\n            Returns\n            -------\n            DualTypes\n            \"\"\"\n            _shifted_discount_curve = disc_curve.shift(g)\n            return self.rate(curves=[curve, _shifted_discount_curve], metric=metric)  # type: ignore[list-item]\n\n        s = partial(\n            s_with_args,\n            curve=rate_curve_,\n            disc_curve=disc_curve_,\n            metric=metric,\n        )\n\n        result = ift_1dim(\n            s,\n            price,\n            \"ytm_quadratic\",\n            (-300, 200, 1200),\n            func_tol=_drb(defaults.oaspread_func_tol, func_tol),\n            conv_tol=_drb(defaults.oaspread_conv_tol, conv_tol),\n        )\n\n        _maybe_set_ad_order(disc_curve_, _ad_disc)\n        _maybe_set_ad_order(rate_curve_, _ad_fore)\n        ret: DualTypes = result[\"g\"]\n        return ret\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/protocols/repo.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib import defaults\nfrom rateslib.curves import index_left\nfrom rateslib.curves.utils import average_rate\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.bonds.protocols import _WithAccrued\nfrom rateslib.legs.amortization import _AmortizationType\nfrom rateslib.scheduling import dcf\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        DualTypes,\n        datetime,\n        str_,\n    )\n\n\nclass _WithRepo(_WithAccrued, Protocol):\n    \"\"\"\n    Protocol to determine the *yield-to-maturity* of a bond type *Instrument*.\n    \"\"\"\n\n    def fwd_from_repo(\n        self,\n        price: DualTypes,\n        settlement: datetime,\n        forward_settlement: datetime,\n        repo_rate: DualTypes,\n        convention: str_ = NoInput(0),\n        dirty: bool = False,\n        method: str = \"proceeds\",\n    ) -> DualTypes:\n        \"\"\"\n        Return a forward price implied by a given repo rate.\n\n        Parameters\n        ----------\n        price : float, Dual, or Dual2\n            The initial price of the security at ``settlement``.\n        settlement : datetime\n            The settlement date of the bond\n        forward_settlement : datetime\n            The forward date for which to calculate the forward price.\n        repo_rate : float, Dual or Dual2\n            The rate which is used to calculate values.\n        convention : str, optional\n            The day count convention applied to the rate. If not given uses default\n            values.\n        dirty : bool, optional\n            Whether the input and output price are specified including accrued interest.\n        method : str in {\"proceeds\", \"compounded\"}, optional\n            The method for determining the forward price.\n\n        Returns\n        -------\n        float, Dual or Dual2\n\n        Notes\n        -----\n        Any intermediate (non ex-dividend) cashflows between ``settlement`` and\n        ``forward_settlement`` will also be assumed to accrue at ``repo_rate``.\n        \"\"\"\n        convention_ = _drb(defaults.convention, convention)\n        dcf_ = dcf(settlement, forward_settlement, convention_)\n        if not dirty:\n            d_price = price + self._accrued(\n                settlement=settlement, func=self.kwargs.meta[\"calc_mode\"]._settle_accrual\n            )\n        else:\n            d_price = price\n        if self.leg1.amortization._type != _AmortizationType.NoAmortization:\n            raise NotImplementedError(\n                \"method for forward price not available with amortization\",\n            )  # pragma: no cover\n        total_rtn = (\n            d_price * (1 + repo_rate * dcf_ / 100) * -self.leg1.settlement_params.notional / 100\n        )\n\n        # now systematically deduct coupons paid between settle and forward settle\n        settlement_idx = index_left(\n            self.leg1.schedule.aschedule,\n            self.leg1.schedule.n_periods + 1,\n            settlement,\n        )\n        fwd_settlement_idx = index_left(\n            self.leg1.schedule.aschedule,\n            self.leg1.schedule.n_periods + 1,\n            forward_settlement,\n        )\n\n        # do not accrue a coupon not received\n        settlement_idx += 1 if self.leg1.ex_div(settlement) else 0\n        # deduct final coupon if received within period\n        fwd_settlement_idx += 1 if self.leg1.ex_div(forward_settlement) else 0\n\n        for p_idx in range(settlement_idx, fwd_settlement_idx):\n            # deduct accrued coupon from dirty price\n            c_period = self.leg1._regular_periods[p_idx]\n            c_cashflow: DualTypes = c_period.cashflow()\n            # TODO handle FloatPeriod cashflow fetch if need a curve.\n            if method.lower() == \"proceeds\":\n                dcf_ = dcf(c_period.settlement_params.payment, forward_settlement, convention_)\n                accrued_coup = c_cashflow * (1 + dcf_ * repo_rate / 100)\n                total_rtn -= accrued_coup\n            elif method.lower() == \"compounded\":\n                r_bar, d, _ = average_rate(\n                    settlement, forward_settlement, convention_, repo_rate, dcf_\n                )\n                n = (forward_settlement - c_period.settlement_params.payment).days\n                accrued_coup = c_cashflow * (1 + d * r_bar / 100) ** n\n                total_rtn -= accrued_coup\n            else:\n                raise ValueError(\"`method` must be in {'proceeds', 'compounded'}.\")\n\n        forward_price: DualTypes = total_rtn / -self.leg1.settlement_params.notional * 100\n        if dirty:\n            return forward_price\n        else:\n            return forward_price - self._accrued(\n                settlement=forward_settlement, func=self.kwargs.meta[\"calc_mode\"]._settle_accrual\n            )\n\n    def repo_from_fwd(\n        self,\n        price: DualTypes,\n        settlement: datetime,\n        forward_settlement: datetime,\n        forward_price: DualTypes,\n        convention: str_ = NoInput(0),\n        dirty: bool = False,\n    ) -> DualTypes:\n        \"\"\"\n        Return an implied repo rate from a forward price.\n\n        Parameters\n        ----------\n        price : float, Dual, or Dual2\n            The initial price of the security at ``settlement``.\n        settlement : datetime\n            The settlement date of the bond\n        forward_settlement : datetime\n            The forward date for which to calculate the forward price.\n        forward_price : float, Dual or Dual2\n            The forward price which iplies the repo rate\n        convention : str, optional\n            The day count convention applied to the rate. If not given uses default\n            values.\n        dirty : bool, optional\n            Whether the input and output price are specified including accrued interest.\n\n        Returns\n        -------\n        float, Dual or Dual2\n\n        Notes\n        -----\n        Any intermediate (non ex-dividend) cashflows between ``settlement`` and\n        ``forward_settlement`` will also be assumed to accrue at ``repo_rate``.\n        \"\"\"\n        convention_ = _drb(defaults.convention, convention)\n        # forward price from repo is linear in repo_rate so reverse calculate with AD\n        if not dirty:\n            p_t = forward_price + self._accrued(\n                settlement=forward_settlement, func=self.kwargs.meta[\"calc_mode\"]._settle_accrual\n            )\n            p_0 = price + self._accrued(\n                settlement=settlement, func=self.kwargs.meta[\"calc_mode\"]._settle_accrual\n            )\n        else:\n            p_t, p_0 = forward_price, price\n\n        dcf_ = dcf(settlement, forward_settlement, convention_)\n        numerator = p_t - p_0\n        denominator = p_0 * dcf_\n\n        # now systematically deduct coupons paid between settle and forward settle\n        settlement_idx = index_left(\n            self.leg1.schedule.aschedule,\n            self.leg1.schedule.n_periods + 1,\n            settlement,\n        )\n        fwd_settlement_idx = index_left(\n            self.leg1.schedule.aschedule,\n            self.leg1.schedule.n_periods + 1,\n            forward_settlement,\n        )\n\n        # do not accrue a coupon not received\n        settlement_idx += 1 if self.leg1.ex_div(settlement) else 0\n        # deduct final coupon if received within period\n        fwd_settlement_idx += 1 if self.leg1.ex_div(forward_settlement) else 0\n\n        for p_idx in range(settlement_idx, fwd_settlement_idx):\n            # deduct accrued coupon from dirty price\n            c_period = self.leg1._regular_periods[p_idx]\n            c_cashflow: DualTypes = c_period.cashflow()\n            # TODO handle FloatPeriod if it needs a Curve to forecast cashflow\n            dcf_ = dcf(\n                start=c_period.settlement_params.payment,\n                end=forward_settlement,\n                convention=convention_,\n            )\n            numerator += 100 * c_cashflow / -self.leg1.settlement_params.notional\n            denominator -= 100 * dcf_ * c_cashflow / -self.leg1.settlement_params.notional\n\n        return numerator / denominator * 100\n"
  },
  {
    "path": "python/rateslib/instruments/bonds/protocols/ytm.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.dual import ift_1dim\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.bonds.conventions import BOND_MODE_MAP\nfrom rateslib.instruments.bonds.protocols.accrued import _WithAccrued\n\nif TYPE_CHECKING:\n    from rateslib.instruments.bonds.conventions import (  # pragma: no cover\n        BondCalcMode,\n    )\n    from rateslib.instruments.bonds.conventions.accrued import (  # pragma: no cover\n        AccrualFunction,\n    )\n    from rateslib.instruments.bonds.conventions.discounting import (  # pragma: no cover\n        CashflowFunction,\n        YtmDiscountFunction,\n    )\n    from rateslib.local_types import (  # pragma: no cover\n        Cashflow,\n        CurveOption_,\n        DualTypes,\n        FixedLeg,\n        FixedPeriod,\n        FloatLeg,\n        FloatPeriod,\n        Number,\n        ZeroFloatPeriod,\n        _BaseCurve_,\n        _KWArgs,\n        datetime,\n        str_,\n    )\n\n\nclass _WithYTM(_WithAccrued, Protocol):\n    \"\"\"\n    Protocol to determine the *yield-to-maturity* of a bond type *Instrument*.\n    \"\"\"\n\n    @property\n    def kwargs(self) -> _KWArgs: ...\n\n    @property\n    def leg1(self) -> FixedLeg | FloatLeg: ...\n\n    def _price_from_ytm(\n        self,\n        ytm: DualTypes,\n        settlement: datetime,\n        calc_mode: BondCalcMode | str_,\n        dirty: bool,\n        rate_curve: CurveOption_,\n        index_curve: _BaseCurve_,\n        indexed: bool,\n    ) -> DualTypes:\n        \"\"\"\n        Loop through all future cashflows and discount them with ``ytm`` to achieve\n        correct price.\n        \"\"\"\n        calc_mode_ = _drb(self.kwargs.meta[\"calc_mode\"], calc_mode)\n        if isinstance(calc_mode_, str):\n            calc_mode_ = BOND_MODE_MAP[calc_mode_]\n        try:\n            if indexed:\n                q = self._generic_price_from_ytm_indexed(\n                    ytm=ytm,\n                    settlement=settlement,\n                    f1=calc_mode_._v1,\n                    f2=calc_mode_._v2,\n                    f3=calc_mode_._v3,\n                    c1=calc_mode_._c1,\n                    ci=calc_mode_._ci,\n                    cn=calc_mode_._cn,\n                    accrual=calc_mode_._ytm_accrual,\n                    rate_curve=rate_curve,\n                    index_curve=index_curve,\n                )\n                if dirty:\n                    return q + self.accrued(settlement, indexed=True, index_curve=index_curve)  # type: ignore[call-arg]\n                else:\n                    return q\n            else:\n                q = self._generic_price_from_ytm(\n                    ytm=ytm,\n                    settlement=settlement,\n                    f1=calc_mode_._v1,\n                    f2=calc_mode_._v2,\n                    f3=calc_mode_._v3,\n                    c1=calc_mode_._c1,\n                    ci=calc_mode_._ci,\n                    cn=calc_mode_._cn,\n                    accrual=calc_mode_._ytm_accrual,\n                    rate_curve=rate_curve,\n                )\n                if dirty:\n                    return q + self._accrued(settlement, calc_mode_._settle_accrual)\n                else:\n                    return q\n        except KeyError:\n            raise ValueError(f\"Cannot calculate with `calc_mode`: {calc_mode}\")\n\n    def _generic_price_from_ytm(\n        self,\n        ytm: DualTypes,\n        settlement: datetime,\n        f1: YtmDiscountFunction,\n        f2: YtmDiscountFunction,\n        f3: YtmDiscountFunction,\n        c1: CashflowFunction,\n        ci: CashflowFunction,\n        cn: CashflowFunction,\n        accrual: AccrualFunction,\n        rate_curve: CurveOption_,\n    ) -> DualTypes:\n        \"\"\"\n        Refer to supplementary material.\n\n        Note: `curve` is only needed for FloatRate Periods on `_period_cashflow`\n        \"\"\"\n        f: float = self.leg1.schedule.frequency_obj.periods_per_annum()\n        acc_idx: int = self.leg1._period_index(settlement)\n        _is_ex_div: bool = self.leg1.ex_div(settlement)\n        if settlement == self.leg1.schedule.aschedule[acc_idx + 1]:\n            # then settlement aligns with a cashflow: manually adjust to next period\n            _is_ex_div = False\n            acc_idx += 1\n\n        v2 = f2(self, ytm, f, settlement, acc_idx, None, accrual, -100000)\n        v1 = f1(self, ytm, f, settlement, acc_idx, v2, accrual, acc_idx)\n        v3 = f3(\n            self,\n            ytm,\n            f,\n            settlement,\n            self.leg1.schedule.n_periods - 1,\n            v2,\n            accrual,\n            self.leg1.schedule.n_periods - 1,\n        )\n\n        # Sum up the coupon cashflows discounted by the calculated factors\n        d: DualTypes = 0.0\n        n = self.leg1.schedule.n_periods\n        for i, p_idx in enumerate(range(acc_idx, n)):\n            if i == 0 and _is_ex_div:\n                # no coupon cashflow is received so no addition to the sum\n                continue\n            elif i == 0:\n                # then this is the first period: c1 and v1 are used\n                cf1 = c1(self, ytm, f, acc_idx, p_idx, n, rate_curve)\n                d += cf1 * v1\n            elif p_idx == (self.leg1.schedule.n_periods - 1):\n                # then this is last period, but it is not the first (i>0).\n                # cn and v3 are relevant, but v1 is also used, and if i > 1 then v2 is also used.\n                cfn = cn(self, ytm, f, acc_idx, p_idx, n, rate_curve)\n                d += cfn * v2 ** (i - 1) * v3 * v1\n            else:\n                # this is not the first and not the last period.\n                # ci and v2i are relevant, but v1 is also required and v2 may also be used if i > 1.\n                # v2i allows for a per-period adjustment to the v2 discount factor, e.g. BTPs.\n                cfi = ci(self, ytm, f, acc_idx, p_idx, n, rate_curve)\n                v2i = f2(self, ytm, f, settlement, acc_idx, v2, accrual, p_idx)\n                d += cfi * v2 ** (i - 1) * v2i * v1\n\n        # Add the redemption payment discounted by relevant factors\n        redemption: Cashflow = self.leg1._exchange_periods[1]  # type: ignore[assignment]\n        if i == 0:  # only looped 1 period, only use the last discount\n            d += self._period_cashflow(redemption, rate_curve) * v1\n        elif i == 1:  # only looped 2 periods, no need for v2\n            d += self._period_cashflow(redemption, rate_curve) * v3 * v1\n        else:  # looped more than 2 periods, regular formula applied\n            d += self._period_cashflow(redemption, rate_curve) * v2 ** (i - 1) * v3 * v1\n\n        # discount all by the first period factor and scaled to price\n        p = d / -self.leg1.settlement_params.notional * 100\n\n        return p - self._accrued(settlement, accrual)  # always return the clean price due to\n        # the possibility of different accrual functions for physical settlement vs YTM calc.\n\n    def _generic_price_from_ytm_indexed(\n        self,\n        ytm: DualTypes,\n        settlement: datetime,\n        f1: YtmDiscountFunction,\n        f2: YtmDiscountFunction,\n        f3: YtmDiscountFunction,\n        c1: CashflowFunction,\n        ci: CashflowFunction,\n        cn: CashflowFunction,\n        accrual: AccrualFunction,\n        rate_curve: CurveOption_,\n        index_curve: _BaseCurve_,\n    ) -> DualTypes:\n        \"\"\"\n        Very similar to `_generic_price_from_ytm` except every cashflow is indexed by the\n        index ratio.\n        \"\"\"\n        assert hasattr(self, \"index_ratio\")  # noqa: S101 # i.e. object is an IndexFixedRatedBond\n\n        f: float = self.leg1.schedule.frequency_obj.periods_per_annum()\n        acc_idx: int = self.leg1._period_index(settlement)\n        _is_ex_div: bool = self.leg1.ex_div(settlement)\n        if settlement == self.leg1.schedule.aschedule[acc_idx + 1]:\n            # then settlement aligns with a cashflow: manually adjust to next period\n            _is_ex_div = False\n            acc_idx += 1\n\n        v2 = f2(self, ytm, f, settlement, acc_idx, None, accrual, -100000)\n        v1 = f1(self, ytm, f, settlement, acc_idx, v2, accrual, acc_idx)\n        v3 = f3(\n            self,\n            ytm,\n            f,\n            settlement,\n            self.leg1.schedule.n_periods - 1,\n            v2,\n            accrual,\n            self.leg1.schedule.n_periods - 1,\n        )\n\n        # Sum up the coupon cashflows discounted by the calculated factors\n        d: DualTypes = 0.0\n        n = self.leg1.schedule.n_periods\n        for i, p_idx in enumerate(range(acc_idx, n)):\n            irn = self.index_ratio(self.leg1.schedule.aschedule[p_idx + 1], index_curve=index_curve)\n            if i == 0 and _is_ex_div:\n                # no coupon cashflow is received so no addition to the sum\n                continue\n            elif i == 0:\n                # then this is the first period: c1 and v1 are used\n                cf1 = c1(self, ytm, f, acc_idx, p_idx, n, rate_curve)\n                d += cf1 * v1 * irn\n            elif p_idx == (self.leg1.schedule.n_periods - 1):\n                # then this is last period, but it is not the first (i>0).\n                # cn and v3 are relevant, but v1 is also used, and if i > 1 then v2 is also used.\n                cfn = cn(self, ytm, f, acc_idx, p_idx, n, rate_curve)\n                d += cfn * v2 ** (i - 1) * v3 * v1 * irn\n            else:\n                # this is not the first and not the last period.\n                # ci and v2i are relevant, but v1 is also required and v2 may also be used if i > 1.\n                # v2i allows for a per-period adjustment to the v2 discount factor, e.g. BTPs.\n                cfi = ci(self, ytm, f, acc_idx, p_idx, n, rate_curve)\n                v2i = f2(self, ytm, f, settlement, acc_idx, v2, accrual, p_idx)\n                d += cfi * v2 ** (i - 1) * v2i * v1 * irn\n\n        # Add the redemption payment discounted by relevant factors\n        redemption: Cashflow = self.leg1._exchange_periods[1]  # type: ignore[assignment]\n        if i == 0:  # only looped 1 period, only use the last discount\n            d += self._period_cashflow(redemption, rate_curve) * v1 * irn\n        elif i == 1:  # only looped 2 periods, no need for v2\n            d += self._period_cashflow(redemption, rate_curve) * v3 * v1 * irn\n        else:  # looped more than 2 periods, regular formula applied\n            d += self._period_cashflow(redemption, rate_curve) * v2 ** (i - 1) * v3 * v1 * irn\n\n        # discount all by the first period factor and scaled to price\n        p = d / -self.leg1.settlement_params.notional * 100\n\n        settle_ir: DualTypes = self.index_ratio(settlement=settlement, index_curve=index_curve)\n        return p - self._accrued(settlement, accrual) * settle_ir  # return the clean indexed price\n\n    def _ytm(\n        self,\n        price: DualTypes,\n        settlement: datetime,\n        rate_curve: CurveOption_,\n        dirty: bool,\n        indexed: bool,\n        calc_mode: BondCalcMode | str_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> Number:\n        \"\"\"\n        Calculate the yield-to-maturity of the security given its price.\n\n        Parameters\n        ----------\n        price : float, Dual, Dual2\n            The price, per 100 nominal, against which to determine the yield.\n        settlement : datetime\n            The settlement date on which to determine the price.\n        dirty : bool, optional\n            If `True` will assume the\n            :meth:`~rateslib.instruments.FixedRateBond.accrued` is included in the price.\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Notes\n        -----\n        If ``price`` is given as :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2` input the result of the yield will be output\n        as the same type with the variables passed through accordingly.\n\n        \"\"\"  # noqa: E501\n\n        def s(g: DualTypes) -> DualTypes:\n            return self._price_from_ytm(\n                ytm=g,\n                settlement=settlement,\n                calc_mode=calc_mode,\n                dirty=dirty,\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                indexed=indexed,\n            )\n\n        result = ift_1dim(\n            s,\n            s_tgt=price,\n            h=\"ytm_quadratic\",\n            ini_h_args=(-3.0, 2.0, 12.0),\n            func_tol=1e-9,\n            conv_tol=1e-9,\n            raise_on_fail=True,\n        )\n        return result[\"g\"]  # type: ignore[no-any-return]\n\n    def ytm(\n        self,\n        price: DualTypes,\n        settlement: datetime,\n        dirty: bool = False,\n        rate_curve: CurveOption_ = NoInput(0),\n        calc_mode: BondCalcMode | str_ = NoInput(0),\n    ) -> Number:\n        # overloaded ytm by IndexFixedRateBond\n        \"\"\"\n        Calculate the yield-to-maturity of the security given its price.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import FixedRateBond, dt, Dual, Dual2\n\n        .. ipython:: python\n\n           aapl_bond = FixedRateBond(dt(2013, 5, 4), dt(2043, 5, 4), fixed_rate=3.85, spec=\"us_corp\")\n           aapl_bond.ytm(price=87.24, settlement=dt(2014, 3, 5))\n           aapl_bond.ytm(price=87.24, settlement=dt(2014, 3, 5), calc_mode=\"us_gb_tsy\")\n\n        .. image:: https://ebrary.net/imag/econom/smith_bondm/image232.jpg\n           :align: center\n           :alt: Image from ebrary.net\n           :height: 310\n           :width: 433\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        price: float, Dual, Dual2, Variable, :red:`required`\n            The price, per 100 nominal, against which to determine the yield.\n        settlement: datetime, :red:`required`\n            The settlement date on which to determine the price.\n        dirty: bool, :green:`optional (set as False)`\n            If `True` will assume the (settlement)\n            :meth:`~rateslib.instruments.FixedRateBond.accrued` is included in the price.\n        rate_curve: _BaseCurve or dict of such, :green:`optional`\n            Used to forecast floating rates if required.\n        calc_mode: str or BondCalcMode, :green:`optional`\n            An alternative calculation mode to use. The ``calc_mode`` is typically set at\n            *Instrument* initialisation and is not required, but is useful as an override to\n            allow comparisons, e.g. of *\"us_gb\"* street convention versus *\"us_gb_tsy\"* treasury\n            convention.\n\n        Returns\n        -------\n        float, Dual, Dual2\n\n        Notes\n        -----\n        If ``price`` is given as :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2` input the result of the yield will be output\n        as the same type with the variables passed through accordingly.\n\n        .. ipython:: python\n\n           aapl_bond.ytm(price=Dual(87.24, [\"price\", \"a\"], [1, -0.75]), settlement=dt(2014, 3, 5))\n           aapl_bond.ytm(price=Dual2(87.24, [\"price\", \"a\"], [1, -0.75], []), settlement=dt(2014, 3, 5))\n\n        \"\"\"  # noqa: E501\n        return self._ytm(\n            price=price,\n            settlement=settlement,\n            dirty=dirty,\n            rate_curve=rate_curve,\n            calc_mode=calc_mode,\n            indexed=False,\n        )\n\n    def _period_cashflow(\n        self,\n        period: Cashflow | FixedPeriod | FloatPeriod | ZeroFloatPeriod,\n        rate_curve: CurveOption_,\n    ) -> DualTypes:\n        \"\"\"Nominal fixed rate bonds use the known \"cashflow\" attribute on the *Period*.\"\"\"\n        return period.unindexed_cashflow(rate_curve=rate_curve)\n"
  },
  {
    "path": "python/rateslib/instruments/cds.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _get_fx_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import CreditPremiumLeg, CreditProtectionLeg\nfrom rateslib.scheduling import Frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        RollDay,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass CDS(_BaseInstrument):\n    \"\"\"\n    A *credit default swap (CDS)* composing a :class:`~rateslib.legs.CreditPremiumLeg`\n    and a :class:`~rateslib.legs.CreditProtectionLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import CDS\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       irs = CDS(\n           effective=dt(2001, 12, 20),\n           termination=\"2y\",\n           spec=\"us_ig_cds\",\n       )\n       irs.cashflows()\n\n    .. rubric:: Pricing\n\n    A *CDS* requires a hazard *rate curve*  and a *disc curve* on both legs\n    (which should be the same). The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = [rate_curve, disc_curve, rate_curve, disc_curve]  # four curves applied to each leg\n       curves = {\"rate_curve\": rate_curve, \"disc_curve\": disc_curve}\n       curves = {  # dict form is explicit\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve\n           \"leg2_rate_curve\": rate_curve,\n           \"leg2_disc_curve\": rate_curve,\n       }\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n    leg2_amortization : float, Dual, Dual2, Variable, str, Amortization, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n\n        .. note::\n\n           The following parameters define **credit specific** elements.\n\n    premium_accrued: bool, :green:`optional (set by 'defaults')`\n        Whether an accrued premium is paid on the event of mid-period credit default.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg1(self) -> CreditPremiumLeg:\n        \"\"\"The :class:`~rateslib.legs.CreditPremiumLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> CreditProtectionLeg:\n        \"\"\"The :class:`~rateslib.legs.CreditProtectionLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        leg2_frequency: Frequency | str_ = NoInput(0),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: int_ = NoInput(1),\n        leg2_payment_lag_exchange: int_ = NoInput(1),\n        # leg2_convention: str_ = NoInput(1),\n        leg2_ex_div: int_ = NoInput(1),\n        # settlement\n        notional: float_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        amortization: float_ = NoInput(0),\n        leg2_notional: float_ = NoInput(-1),\n        leg2_amortization: float_ = NoInput(-1),\n        # rate and credit params\n        premium_accrued: bool_ = NoInput(0),\n        fixed_rate: DualTypes_ = NoInput(0),\n        # meta params\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            stub=stub,\n            front_stub=front_stub,\n            back_stub=back_stub,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            ex_div=ex_div,\n            notional=notional,\n            currency=currency,\n            amortization=amortization,\n            convention=convention,\n            leg2_effective=leg2_effective,\n            leg2_termination=leg2_termination,\n            leg2_frequency=leg2_frequency,\n            leg2_stub=leg2_stub,\n            leg2_front_stub=leg2_front_stub,\n            leg2_back_stub=leg2_back_stub,\n            leg2_roll=leg2_roll,\n            leg2_eom=leg2_eom,\n            leg2_modifier=leg2_modifier,\n            leg2_calendar=leg2_calendar,\n            leg2_payment_lag=leg2_payment_lag,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            leg2_ex_div=leg2_ex_div,\n            leg2_notional=leg2_notional,\n            leg2_amortization=leg2_amortization,\n            # leg2_convention=leg2_convention,\n            # rate and credit\n            premium_accrued=premium_accrued,\n            fixed_rate=fixed_rate,\n            # meta\n            curves=self._parse_curves(curves),\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            leg2_currency=NoInput(1),\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n            premium_accrued=defaults.cds_premium_accrued,\n            leg2_frequency=Frequency.Zero(),\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"vol\"],\n        )\n\n        self._leg1 = CreditPremiumLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = CreditProtectionLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self._leg1, self._leg2]\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        leg2_npv: DualTypes = self.leg2.local_npv(\n            rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c),\n            disc_curve=_get_curve(\"leg2_disc_curve\", False, True, *c),\n            index_curve=NoInput(0),\n            settlement=settlement,\n            forward=forward,\n        )\n        return (\n            self.leg1.spread(\n                target_npv=-leg2_npv,\n                rate_curve=_get_curve(\"rate_curve\", True, True, *c),\n                disc_curve=_get_curve(\"disc_curve\", False, True, *c),\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n            / 100\n        )\n\n    def accrued(self, settlement: datetime) -> DualTypes:\n        \"\"\"\n        Calculate the amount of premium accrued until a specific date within the relevant *Period*.\n\n        Parameters\n        ----------\n        settlement: datetime\n            The date against which accrued is measured.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n\n        Notes\n        ------\n        Will raise an exception if there is no set ``fixed_rate``.\n        \"\"\"\n        return self.leg1.accrued(settlement=settlement)\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        return (\n            self.rate(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                vol=vol,\n                base=base,\n                settlement=settlement,\n                forward=forward,\n            )\n            * 100.0\n        )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IRS is that its fixed rate is not set.\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            # set a fixed rate for the purpose of generic methods NPV will be zero.\n            mid_market_rate = self.rate(\n                curves=curves,\n                solver=solver,\n                settlement=settlement,\n                forward=forward,\n            )\n            self.leg1.fixed_rate = _dual_float(mid_market_rate)\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        A CDS has two curve requirements: a hazard_curve and a disc_curve used by both legs.\n\n        When given as anything other than two curves will raise an Exception.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_rate_curve=_drb(\n                    curves.get(\"rate_curve\", NoInput(0)),\n                    curves.get(\"leg2_rate_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    rate_curve=curves[0],\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    rate_curve=curves[0],\n                    leg2_rate_curve=curves[2],\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(f\"{type(self).__name__} requires 2 `curves`. Got {len(curves)}.\")\n\n        else:  # `curves` is just a single input\n            raise ValueError(f\"{type(self).__name__} requires 2 `curves`. Got 1.\")\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def analytic_rec_risk(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        return self.leg2.analytic_rec_risk(\n            rate_curve=_get_curve(\"leg2_rate_curve\", False, True, *c),\n            disc_curve=_get_curve(\"leg2_disc_curve\", False, True, *c),\n            fx=_get_fx_maybe_from_solver(solver=solver, fx=fx),\n            base=base,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/fee.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime as dt\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _Vol,\n)\nfrom rateslib.legs import CustomLeg\nfrom rateslib.periods import Cashflow\nfrom rateslib.scheduling import Frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Adjuster,\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        IndexMethod,\n        PeriodFixings,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass Fee(_BaseInstrument):\n    \"\"\"\n    A single :class:`~rateslib.periods.Cashflow` payable on a payment date.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import Fee\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fee = Fee(dt(2022, 1, 4), notional=2e6, calendar=\"nyc\", payment_lag=0)\n       fee.cashflows()\n\n    .. rubric:: Pricing\n\n    A *Fee* requires just one *Curve* for discounting, unless it is also indexed, in which\n    case it may also require an additional index *Curve*\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = [index_curve, disc_curve]  #  two curves given the specific order\n       curves = {  # dict form is explicit\n           \"disc_curve\": disc_curve,\n           \"index_curve\": index_curve,\n       }\n\n    The concept of *rate* is alien to a *Fee*, and these are not *Instruments* that would\n    typically be expected to form part of a *Solver* framework. However, for flexibility,\n    two *rate* ``metric`` that are available are:\n\n    - *'npv'*: returns the result of the :meth:`~rateslib.instruments.Fee.npv` method.\n    - *'payment'*: returns the physical settlement amount.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    effective : datetime, :red:`required`\n        The datetime index for which the `rate`, which is just the curve value, is\n        returned.\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n        calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use for date manipulation. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used to modify the ``effective`` payment date\n        according to a given ``calendar``.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map the adjusted payment date into\n        an additional date acting an ex-dividend indicator. If given as integer\n        will define the number of business days to lag dates by.\n\n    .. note::\n\n           The following define **non-deliverability** parameters. If the fee is\n           directly deliverable do not use these parameters.\n\n    pair: FXIndex, str, :green:`optional`\n        The currency pair for :class:`~rateslib.data.fixings.FXFixing` that determines *Period*\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` according\n        to non-deliverability.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value set of the base index value.\n        If not given and ``index_fixings`` is a str fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The index value for the reference date.\n        If a scalar value this is used directly. If a string identifier will link to the\n        central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n    index_base_date: datetime, :green:`optional`\n        The reference date for determining the base index value. Not required if ``index_base``\n        value is given directly, but required for indexation in all other cases.\n    index_reference_date: datetime, :green:`optional (set as 'payment')`\n        The reference date for determining the index value. Not required if ``_index_fixings``\n        is given as a scalar value.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which determines non-payment of notional on supported *Periods*.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    metric : str, :green:`optional` (set as 'curve_value')\n        The pricing metric returned by :meth:`~rateslib.instruments.Value.rate`. See\n        **Pricing**.\n\n    \"\"\"\n\n    _rate_scalar = 1.0\n\n    @property\n    def leg1(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs  # type: ignore[return-value]\n\n    def __init__(\n        self,\n        # settlement\n        effective: datetime,\n        notional: float_ = NoInput(0),\n        *,\n        currency: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: Adjuster | str | int_ = NoInput(0),\n        ex_div: Adjuster | str | int_ = NoInput(0),\n        # non-deliverability\n        pair: str_ = NoInput(0),\n        fx_fixings: PeriodFixings = NoInput(0),\n        # index-args:\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: PeriodFixings = NoInput(0),\n        index_only: bool_ = NoInput(0),\n        index_base_date: datetime_ = NoInput(0),\n        index_reference_date: datetime_ = NoInput(0),\n        # meta\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            effective=effective,\n            notional=notional,\n            ex_div=ex_div,\n            currency=currency,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            # non-deliverable\n            pair=pair,\n            fx_fixings=fx_fixings,\n            # indexation\n            index_base=index_base,\n            index_lag=index_lag,\n            index_method=index_method,\n            index_fixings=index_fixings,\n            index_only=index_only,\n            index_base_date=index_base_date,\n            index_reference_date=index_reference_date,\n            # meta\n            curves=self._parse_curves(curves),\n            metric=metric,\n            vol=_Vol(),\n        )\n        default_args = dict(\n            metric=\"npv\",\n            notional=defaults.notional,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            calendar=\"all\",\n        )\n        self._kwargs = _KWArgs(\n            spec=NoInput(0),\n            user_args=user_args,\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\", \"vol\"],\n        )\n\n        _ = _convert_to_schedule_kwargs(\n            dict(\n                effective=dt(1600, 1, 1),\n                termination=effective,\n                frequency=Frequency.Zero(),\n                payment_lag=self.kwargs.leg1[\"payment_lag\"],\n                calendar=self.kwargs.leg1[\"calendar\"],\n                ex_div=self.kwargs.leg1[\"ex_div\"],\n            ),\n            1,\n        )[\"schedule\"]\n\n        self._leg1 = CustomLeg(\n            periods=[\n                Cashflow(\n                    payment=_.pschedule[-1],\n                    notional=self.kwargs.leg1[\"notional\"],\n                    currency=self.kwargs.leg1[\"currency\"],\n                    ex_dividend=_.pschedule3[-1],\n                    # non-deliverable\n                    pair=self.kwargs.leg1[\"pair\"],\n                    fx_fixings=self.kwargs.leg1[\"fx_fixings\"],\n                    delivery=NoInput(0),  # set as payment\n                    # indexation\n                    index_base=self.kwargs.leg1[\"index_base\"],\n                    index_lag=self.kwargs.leg1[\"index_lag\"],\n                    index_method=self.kwargs.leg1[\"index_method\"],\n                    index_fixings=self.kwargs.leg1[\"index_fixings\"],\n                    index_only=self.kwargs.leg1[\"index_only\"],\n                    index_base_date=self.kwargs.leg1[\"index_base_date\"],\n                    index_reference_date=self.kwargs.leg1[\"index_reference_date\"],\n                )\n            ]\n        )\n        self._legs = [self._leg1]\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    @classmethod\n    def _parse_curves(cls, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        A Value requires only one 1 curve, if not indexed, which is set as all element values.\n\n        If the fee is indexed then an `index_curve` may be required.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                index_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"index_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 1:\n                return _Curves(\n                    disc_curve=curves[0],\n                    index_curve=curves[0],\n                )\n            elif len(curves) == 2:\n                return _Curves(\n                    disc_curve=curves[1],\n                    index_curve=curves[0],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(cls).__name__} requires upto 2 curve type. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input\n            return _Curves(\n                disc_curve=curves,  # type: ignore[arg-type]\n                index_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n\n        if metric_ == \"npv\":\n            return self.npv(  # type: ignore[return-value]\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                vol=vol,\n                base=base,\n                settlement=settlement,\n                forward=forward,\n                local=False,\n            )\n        elif metric_ == \"payment\":\n            return -1 * self.settlement_params.notional\n        else:\n            raise ValueError(\"`metric`must be in {'npv', 'cashflow'}.\")\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def analytic_delta(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        leg: int = 1,\n    ) -> DualTypes | dict[str, DualTypes]:\n        return super().analytic_delta(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n            leg=leg,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/fly.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\nfrom typing import TYPE_CHECKING, NoReturn\n\nfrom pandas import DataFrame, DatetimeIndex\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.pricing import (\n    _get_fx_maybe_from_solver,\n)\nfrom rateslib.periods.utils import _maybe_fx_converted\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        datetime_,\n        str_,\n    )\n\n\ndef _composit_fixings_table(df_result: DataFrame, df: DataFrame) -> DataFrame:\n    \"\"\"\n    Add a DataFrame to an existing fixings table by extending or adding to relevant columns.\n\n    Parameters\n    ----------\n    df_result: The main DataFrame that will be updated\n    df: The incoming DataFrame with new data to merge\n\n    Returns\n    -------\n    DataFrame\n    \"\"\"\n    # reindex the result DataFrame\n    if df_result.empty:\n        return df\n    else:\n        df_result = df_result.reindex(index=df_result.index.union(df.index))\n\n    # # update existing columns with missing data from the new available data\n    # for c in [c for c in df.columns if c in df_result.columns and c[1] in [\"dcf\", \"rates\"]]:\n    #     df_result[c] = df_result[c].combine_first(df[c])\n\n    # merge by addition existing values with missing filled to zero\n    m = [c for c in df.columns if c in df_result.columns]\n    if len(m) > 0:\n        df_result[m] = df_result[m].add(df[m], fill_value=0.0)\n\n    # append new columns without additional calculation\n    a = [c for c in df.columns if c not in df_result.columns]\n    if len(a) > 0:\n        df_result[a] = df[a]\n\n    # df_result.columns = MultiIndex.from_tuples(df_result.columns)\n    return df_result\n\n\nclass Fly(_BaseInstrument):\n    \"\"\"\n    A *Butterfly* of :class:`~rateslib.instruments.protocols._BaseInstrument`.\n\n    .. rubric:: Examples\n\n    The following initialises a *Butterfly* of *IRSs*.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import Fly, IRS\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fly = Fly(\n           instrument1=IRS(dt(2000, 1, 1), \"1y\", notional=10e6, spec=\"eur_irs\", curves=[\"estr\"]),\n           instrument2=IRS(dt(2000, 1, 1), \"2y\", notional=-5e6, spec=\"eur_irs\", curves=[\"estr\"]),\n           instrument3=IRS(dt(2000, 1, 1), \"3y\", notional=1.75e6, spec=\"eur_irs\", curves=[\"estr\"]),\n       )\n       fly.cashflows()\n\n    .. rubric:: Pricing\n\n    Each :class:`~rateslib.instruments.protocols._BaseInstrument` should have\n    its own ``curves`` and ``vol`` objects set at its initialisation, according to the\n    documentation for that *Instrument*. For the pricing methods ``curves`` and ``vol`` objects,\n    these can be universally passed to each *Instrument* but in many cases that would be\n    technically impossible since each *Instrument* might require difference pricing objects, e.g.\n    if the *Instruments* have difference currencies. For a *Fly*\n    of three *IRS* in the same currency this would be possible, however.\n\n    Parameters\n    ----------\n    instrument1 : _BaseInstrument\n        The *Instrument* with the shortest maturity.\n    instrument2 : _BaseInstrument\n        The *Instrument* with the intermediate maturity.\n    instrument3 : _BaseInstrument\n        The *Instrument* with the longest maturity.\n\n    Notes\n    -----\n    A *Fly* is just a container for three\n    :class:`~rateslib.instruments.protocols._BaseInstrument`, with an overload\n    for the :meth:`~rateslib.instruments.Spread.rate` method to calculate twice the\n    belly rate minus the wings (whatever metric is in use for each *Instrument*), which allows\n    it to offer a lot of flexibility in *pseudo Instrument* creation.\n\n    \"\"\"\n\n    _instruments: Sequence[_BaseInstrument]\n\n    @property\n    def instruments(self) -> Sequence[_BaseInstrument]:\n        \"\"\"The *Instruments* contained within the *Portfolio*.\"\"\"\n        return self._instruments\n\n    def __init__(\n        self,\n        instrument1: _BaseInstrument,\n        instrument2: _BaseInstrument,\n        instrument3: _BaseInstrument,\n    ) -> None:\n        self._instruments = [instrument1, instrument2, instrument3]\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Return the NPV of the *Portfolio* by summing individual *Instrument* NPVs.\n        \"\"\"\n        local_npv = self._npv_single_core(curves=curves, solver=solver, fx=fx, vol=vol, base=base)\n        if not local:\n            single_value: DualTypes = 0.0\n            for k, v in local_npv.items():\n                single_value += _maybe_fx_converted(\n                    value=v,\n                    currency=k,\n                    fx=_get_fx_maybe_from_solver(fx=fx, solver=solver),\n                    base=base,\n                    forward=forward,\n                )\n            return single_value\n        else:\n            return local_npv\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        TBD\n        \"\"\"\n        df_result = DataFrame(index=DatetimeIndex([], name=\"obs_dates\"))\n        for inst in self.instruments:\n            try:\n                df = inst.local_analytic_rate_fixings(\n                    curves=curves,\n                    solver=solver,\n                    fx=fx,\n                    vol=vol,\n                    forward=forward,\n                    settlement=settlement,\n                )\n            except AttributeError:\n                continue\n            df_result = _composit_fixings_table(df_result, df)\n        return df_result\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._cashflows_from_instruments(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n            base=base,\n        )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        rates: list[DualTypes] = []\n        for inst in self.instruments:\n            rates.append(\n                inst.rate(\n                    curves=curves,\n                    solver=solver,\n                    fx=fx,\n                    vol=vol,\n                    base=base,\n                    settlement=settlement,\n                    forward=forward,\n                    metric=metric,\n                )\n            )\n        return (-rates[0] + 2 * rates[1] - rates[2]) * 100.0\n\n    def analytic_delta(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`analytic_delta` is not defined for Portfolio.\")\n"
  },
  {
    "path": "python/rateslib/instruments/fra.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, Ok, Result, _drb\nfrom rateslib.enums.parameters import FloatFixingMethod, SpreadCompoundMethod\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg, FloatLeg\nfrom rateslib.scheduling import Adjuster\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FixingsRates_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        RollDay,\n        Solver_,\n        VolT_,\n        _BaseCurveOrDict_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass FRA(_BaseInstrument):\n    \"\"\"\n    A *forward rate agreement (FRA)* compositing a\n    :class:`~rateslib.legs.FixedLeg` and :class:`~rateslib.legs.FloatLeg`.\n\n    These *Legs* have *Instrument* level overloads in order to satisfy the cashflow determination\n    conventions of a *FRA* instruments.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import FRA\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fra = FRA(\n           effective=dt(2000, 1, 1),\n           termination=\"6m\",\n           spec=\"eur_fra6\",\n           fixed_rate=2.0,\n       )\n       fra.cashflows()\n\n    .. rubric:: Pricing\n\n    An *FRA* requires a *disc curve* on both legs (which should be the same *Curve*) and a\n    *leg2 rate curve* to forecast the IBOR type rate on the *FloatLeg*. The following input\n    formats are allowed:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = [None, disc_curve, rate_curve, disc_curve]     # four curves applied to each leg\n       curves = {\"leg2_rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    The only ``metric`` is *'rate'*.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: int, :green:`optional (set as 0)`\n        A number of business days by which to lag a traditional *FRA* payment date.\n\n        .. warning::\n\n           *FRAs* are defined by a payment structure that has a cashflow at the accrual start\n           date and an amount adjusted by the rate fixing. An input to this parameter, say 5,\n           will apply an adjuster: `Adjuster.BusDaysLagSettleInAdvance(5)`.\n\n    ex_div: int, :green:`optional (set as 0)`\n        Applied in the same manner as the ``payment_lag``, except negated. An input of 1 will\n        apply an adjuster: `Adjuster.BusDaysLagSettleInAdvance(-1)`.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n    leg2_fixing_method: int, :green:`optional (set by 'defaults')`\n        The ``fixing_method`` used by the *Instrument*. This will be IBOR with a defined\n        publication lag. The default is \"IBOR(2)\" with a two-day lag.\n    leg2_fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule.\n    leg2_fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n    metric : str, :green:`optional` (set as 'rate')`\n        The pricing metric returned by :meth:`~rateslib.instruments.FRA.rate`.\n\n    Notes\n    -----\n\n    A *FRA* is modelled as a single period *IRS* whose payment date is overloaded to be\n    based on the 'accrual' effective date, and whose cashflow values are adjusted by a scaling\n    factor related to the floating rate, i.e. :math:`\\\\frac{1}{1 + d r}`, thus replicating the\n    payoff calculation for a traditional *FRA*.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg1(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FloatLeg:\n        \"\"\"The :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An STIRFuture has two curve requirements: a leg2_rate_curve and a disc_curve used by\n        both legs.\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_rate_curve=_drb(\n                    curves.get(\"rate_curve\", NoInput(0)),\n                    curves.get(\"leg2_rate_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 1:\n                return _Curves(\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[0],\n                    leg2_disc_curve=curves[0],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_rate_curve=curves[2],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                leg2_rate_curve=curves,  # type: ignore[arg-type]\n                disc_curve=curves,  # type: ignore[arg-type]\n                leg2_disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int = 0,\n        ex_div: int = 0,\n        convention: str_ = NoInput(0),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        # rate parameters\n        fixed_rate: DualTypes_ = NoInput(0),\n        leg2_rate_fixings: FixingsRates_ = NoInput(0),\n        leg2_fixing_method: FloatFixingMethod | str_ = NoInput(0),\n        leg2_fixing_frequency: Frequency | str_ = NoInput(0),\n        leg2_fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            convention=convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            # rate\n            fixed_rate=fixed_rate,\n            leg2_rate_fixings=leg2_rate_fixings,\n            leg2_fixing_series=leg2_fixing_series,\n            leg2_fixing_frequency=leg2_fixing_frequency,\n            leg2_fixing_method=leg2_fixing_method,\n            # meta\n            curves=self._parse_curves(curves),\n            metric=metric,\n        )\n        instrument_args = dict(\n            leg2_effective=NoInput.inherit,\n            leg2_termination=NoInput.inherit,\n            leg2_frequency=NoInput.inherit,\n            leg2_roll=NoInput.inherit,\n            leg2_eom=NoInput.inherit,\n            leg2_modifier=NoInput.inherit,\n            leg2_calendar=NoInput.inherit,\n            leg2_payment_lag=NoInput.inherit,\n            leg2_ex_div=NoInput.inherit,\n            leg2_convention=NoInput.inherit,\n            leg2_float_spread=0.0,\n            leg2_spread_compound_method=SpreadCompoundMethod.NoneSimple,\n            leg2_notional=NoInput.negate,\n            leg2_currency=NoInput.inherit,\n            payment_lag=Adjuster.BusDaysLagSettleInAdvance(payment_lag),\n            ex_div=Adjuster.BusDaysLagSettleInAdvance(-ex_div),\n            initial_exchange=False,\n            final_exchange=False,\n            leg2_initial_exchange=False,\n            leg2_final_exchange=False,\n            vol=_Vol(),\n        )\n        default_args = dict(\n            notional=defaults.notional,\n            leg2_fixing_method=FloatFixingMethod.IBOR(2),\n            metric=\"rate\",\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\", \"vol\"],\n        )\n\n        self._leg1 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self.leg1, self.leg2]\n\n        if self._leg1.schedule.n_periods != 1:\n            raise ValueError(\n                \"The scheduling parameters of the STIRFuture must define exactly \"\n                f\"one regular period. Got '{self.leg1.schedule.n_periods}'.\"\n            )\n\n    def _fra_rate_scalar(self, leg2_rate_curve: _BaseCurveOrDict_) -> DualTypes:\n        r = self.leg2._regular_periods[0].rate(rate_curve=leg2_rate_curve)\n        return 1 / (1 + self.leg2._regular_periods[0].period_params.dcf * r / 100.0)\n\n    def _try_fra_rate_scalar(self, leg2_rate_curve: _BaseCurveOrDict_) -> Result[DualTypes]:\n        r = self.leg2._regular_periods[0].try_rate(rate_curve=leg2_rate_curve)\n        if r.is_err:\n            return r\n        else:\n            return Ok(\n                1 / (1 + self.leg2._regular_periods[0].period_params.dcf * r.unwrap() / 100.0)\n            )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n        )\n\n        c = _parse_curves(self, curves, solver)\n\n        fra_scalar = self._fra_rate_scalar(\n            leg2_rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c)\n        )\n\n        npv = super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n        if isinstance(npv, dict):\n            return {k: v * fra_scalar for k, v in npv.items()}\n        else:\n            return npv * fra_scalar\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IRS is that its fixed rate is not set.\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            # set a fixed rate for the purpose of generic methods NPV will be zero.\n            mid_market_rate = self.rate(\n                curves=curves,\n                solver=solver,\n                settlement=settlement,\n                forward=forward,\n            )\n            self.leg1.fixed_rate = _dual_float(mid_market_rate)\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n\n        leg2_npv: DualTypes = self.leg2.local_npv(\n            rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c),\n            disc_curve=_get_curve(\"leg2_disc_curve\", False, True, *c),\n            settlement=settlement,\n            forward=forward,\n        )\n        rate = (\n            self.leg1.spread(\n                target_npv=-leg2_npv,\n                rate_curve=NoInput(0),\n                disc_curve=_get_curve(\"disc_curve\", False, True, *c),\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n            / 100\n        )\n        if metric_ == \"rate\":\n            return rate\n        else:\n            raise ValueError(\"`metric` must be in {'rate'}.\")\n\n    def analytic_delta(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        leg: int = 1,\n    ) -> DualTypes | dict[str, DualTypes]:\n        c = _parse_curves(self, curves, solver)\n\n        fra_scalar = self._fra_rate_scalar(\n            leg2_rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c)\n        )\n        a_delta = super().analytic_delta(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n            leg=leg,\n        )\n        if isinstance(a_delta, dict):\n            return {k: v * fra_scalar for k, v in a_delta.items()}\n        else:\n            return a_delta * fra_scalar\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        df = self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n        c = _parse_curves(self, curves, solver)\n        return df * self._fra_rate_scalar(\n            leg2_rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c)\n        )\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        df = super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n        c = _parse_curves(self, curves, solver)\n        scalar = self._try_fra_rate_scalar(\n            leg2_rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c)\n        )\n\n        headers = [\n            defaults.headers[\"cashflow\"],\n            defaults.headers[\"npv\"],\n            defaults.headers[\"npv_fx\"],\n        ]\n        for header in headers:\n            if scalar.is_err:\n                df[header] = None\n            else:\n                df[header] = df[header] * _dual_float(scalar.unwrap())\n        return df\n"
  },
  {
    "path": "python/rateslib/instruments/fx_forward.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.data.fixings import _fx_index_set_cross, _get_fx_index\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.fx import FXForwards, FXRates, forward_fx\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _get_fx_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import CustomLeg\nfrom rateslib.periods import Cashflow\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass FXForward(_BaseInstrument):\n    \"\"\"\n    A dated *FX exchange* composing two\n    :class:`~rateslib.legs.CustomLeg`\n    of individual :class:`~rateslib.periods.Cashflow` of different currencies.\n\n    .. rubric:: Examples\n\n    A sold EURUSD *FX forward* at 1.165 expressed in $10mm.\n\n    .. ipython:: python\n       :suppress:\n\n       from datetime import datetime as dt\n       from rateslib.instruments import FXForward\n\n    .. ipython:: python\n\n       fxfwd = FXForward(\n           settlement=dt(2022, 2, 24),\n           pair=\"eurusd\",\n           leg2_notional=10e6,\n           fx_rate=1.165\n       )\n       fxfwd.cashflows()\n\n    .. rubric:: Pricing\n\n    An *FX Forward* requires a *disc curve* and a *leg2 disc curve* to discount the cashflows\n    of the respective currencies (typically with the same collateral definition).\n    The following input formats are allowed:\n\n    .. code-block:: python\n\n       curves = [disc_curve, leg2_disc_curve]  #  two curves are applied in the given order\n       curves = [None, disc_curve, None, leg2_disc_curve]  # four curves applied to each leg\n       curves = {\"disc_curve\": disc_curve, \"leg2_disc_curve\": leg2_disc_curve}  # dict form is explicit\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    settlement : datetime, :red:`required`\n        The date of the currency exchange.\n    pair: FXIndex, str, :red:`required`\n        The currency pair of the exchange, e.g. \"eurusd\", using 3-digit iso codes.\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        To define the notional of the trade in units of LHS pair use ``notional``.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n        To define the notional of the trade in units of RHS pair use ``leg2_notional``.\n        Only one of ``notional`` or ``leg2_notional`` can be specified.\n    fx_rate : float, :green:`optional`\n        The FX rate of ``pair`` defining the transaction price. If not given, set at pricing.\n    curves : Curve, LineCurve, str or list of such, :green:`optional`\n        For *FXExchange* only discounting curves are required in each currency and not rate\n        forecasting curves.\n        The signature should be: `[None, eur_curve, None, usd_curve]` for a \"eurusd\" pair.\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def leg1(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An FXExchange requires 2 curves; a disc_curve and leg2_disc_curve.\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    disc_curve=curves[0],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            raise ValueError(f\"{type(self).__name__} requires 2 curve types. Got 1.\")\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def __init__(\n        self,\n        settlement: datetime,\n        pair: str,\n        fx_rate: DualTypes_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        leg2_notional: DualTypes_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n    ):\n        # FXForwards are physically settled so do not allow WMR cross methodology to impact\n        # forecast rates for FXFixings.\n        pair_ = _fx_index_set_cross(_get_fx_index(pair), allow_cross=False)\n\n        if isinstance(notional, NoInput) and isinstance(leg2_notional, NoInput):\n            notional = defaults.notional\n        elif not isinstance(notional, NoInput) and not isinstance(leg2_notional, NoInput):\n            raise ValueError(\"Only one of `notional` and `leg2_notional` can be given.\")\n\n        user_args = dict(\n            settlement=settlement,\n            currency=pair_.pair[:3],\n            leg2_currency=pair_.pair[3:6],\n            notional=notional,\n            leg2_notional=leg2_notional,\n            curves=self._parse_curves(curves),\n        )\n        instrument_args = dict(\n            leg2_settlement=NoInput.inherit,\n            pair=NoInput(0),\n            leg2_pair=NoInput(0),\n            fx_fixings=NoInput(0),\n            leg2_fx_fixings=NoInput(0),\n            vol=_Vol(),\n        )  # these are hard coded arguments specific to this instrument\n        default_args = dict(\n            notional=defaults.notional,\n        )\n        self._kwargs = _KWArgs(\n            spec=NoInput(0),\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"vol\"],\n        )\n\n        # allocate arguments to correct legs for non-deliverability\n        if isinstance(notional, NoInput):\n            # both notionals cannot be NoInput so leg2_notional is assumed given\n            self.kwargs.leg1[\"notional\"] = -1.0 * self.kwargs.leg2[\"notional\"]\n            self.kwargs.leg1[\"pair\"] = pair_\n            self.kwargs.leg1[\"fx_fixings\"] = fx_rate\n        else:  # notional set on leg1\n            self.kwargs.leg2[\"notional\"] = -1.0 * self.kwargs.leg1[\"notional\"]\n            self.kwargs.leg2[\"pair\"] = pair_\n            self.kwargs.leg2[\"fx_fixings\"] = fx_rate\n\n        self._leg1 = CustomLeg(\n            periods=[\n                Cashflow(\n                    currency=self.kwargs.leg1[\"currency\"],\n                    notional=-1.0 * self.kwargs.leg1[\"notional\"],\n                    payment=self.kwargs.leg1[\"settlement\"],\n                    pair=self.kwargs.leg1[\"pair\"],\n                    fx_fixings=self.kwargs.leg1[\"fx_fixings\"],\n                ),\n            ]\n        )\n        self._leg2 = CustomLeg(\n            periods=[\n                Cashflow(\n                    currency=self.kwargs.leg2[\"currency\"],\n                    notional=-1.0 * self.kwargs.leg2[\"notional\"],\n                    payment=self.kwargs.leg2[\"settlement\"],\n                    pair=self.kwargs.leg2[\"pair\"],\n                    fx_fixings=self.kwargs.leg2[\"fx_fixings\"],\n                )\n            ]\n        )\n        self._legs = [self._leg1, self._leg2]\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        fx_ = _get_fx_maybe_from_solver(solver=solver, fx=fx)\n        if isinstance(fx_, FXForwards | FXRates):\n            imm_fx: DualTypes = fx_.rate(self.kwargs.leg2[\"pair\"])\n        elif isinstance(fx_, NoInput):\n            raise ValueError(\n                \"`fx` must be supplied to price FXExchange object.\\n\"\n                \"Note: it can be attached to, and then fetched from, a Solver.\",\n            )\n        else:\n            # this is a mypy error since FXForwards is a case above\n            imm_fx = fx_  # type: ignore[assignment]\n\n        _: DualTypes = forward_fx(\n            date=self.kwargs.leg1[\"settlement\"],\n            curve_domestic=_get_curve(\"disc_curve\", False, False, *c),\n            curve_foreign=_get_curve(\"leg2_disc_curve\", False, False, *c),\n            fx_rate=imm_fx,\n        )\n        return _\n"
  },
  {
    "path": "python/rateslib/instruments/fx_options/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.instruments.fx_options.brokerfly import FXBrokerFly\nfrom rateslib.instruments.fx_options.call_put import FXCall, FXPut, _BaseFXOption\nfrom rateslib.instruments.fx_options.risk_reversal import FXRiskReversal, _BaseFXOptionStrat\nfrom rateslib.instruments.fx_options.straddle import FXStraddle\nfrom rateslib.instruments.fx_options.strangle import FXStrangle\nfrom rateslib.instruments.fx_options.vol_value import FXVolValue\n\n__all__ = [\n    \"FXCall\",\n    \"FXPut\",\n    \"FXRiskReversal\",\n    \"FXStraddle\",\n    \"FXStrangle\",\n    \"FXBrokerFly\",\n    \"FXVolValue\",\n    \"_BaseFXOption\",\n    \"_BaseFXOptionStrat\",\n]\n"
  },
  {
    "path": "python/rateslib/instruments/fx_options/brokerfly.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.fx_options.risk_reversal import _BaseFXOptionStrat\nfrom rateslib.instruments.fx_options.straddle import FXStraddle\nfrom rateslib.instruments.fx_options.strangle import FXStrangle\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalInput,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        Sequence,\n        Solver_,\n        VolStrat_,\n        VolT_,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass FXBrokerFly(_BaseFXOptionStrat):\n    \"\"\"\n    An *FX BrokerFly* :class:`~rateslib.instruments._BaseFXOptionStrat`.\n\n    A *BrokerFly* is composed of a :class:`~rateslib.instruments.FXStrangle`\n    and a :class:`~rateslib.instruments.FXStraddle`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import FXBrokerFly, Curve, FXForwards, FXDeltaVolSmile, FXRates, dt\n\n    .. ipython:: python\n\n       fxbf = FXBrokerFly(\n           expiry=\"3m\",\n           strike=[[\"-10d\", \"10d\"], \"atm_delta\"],\n           eval_date=dt(2020, 1, 1),\n           spec=\"eurusd_call\",\n           notional=[1000000.0, None],  # <- straddle notional is derived from vega neutral\n       )\n       fxbf.cashflows()\n\n    .. rubric:: Pricing\n\n    The pricing mirrors that for an :class:`~rateslib.instruments.FXCall`.\n    All options use the same ``curves``. Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = {\"rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    Any *FXOption* also requires an :class:`~rateslib.fx.FXForwards` as input to the ``fx``\n    argument.\n\n    A ``vol`` argument must be provided to each *Instrument*. This can either be a single\n    value universally used for all, or an individual item as part of a sequence. Allowed\n    inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0 | vol_obj  # a single item universally applied\n       vol = [[13.1, 13.4], 12.0]  # values for Strangle and Straddle respectively\n\n    *BrokerFlys* inherit the peculiarities of an :class:`~rateslib.instruments.FXStrangle`.\n    If the notional is not set on the *FXStraddle* then a calculation will be performed to derive a\n    notional that yields a vega neutral strategy.\n    The following pricing ``metric`` are available, with examples:\n\n    .. ipython:: python\n\n       eur = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.98})\n       usd = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.96})\n       fxf = FXForwards(\n           fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2020, 1, 3)),\n           fx_curves={\"eureur\": eur, \"eurusd\": eur, \"usdusd\": usd},\n       )\n       fxvs = FXDeltaVolSmile(\n           nodes={0.25: 11.0, 0.5: 9.8, 0.75: 10.7},\n           expiry=dt(2020, 4, 1),\n           eval_date=dt(2020, 1, 1),\n           delta_type=\"forward\",\n       )\n\n    - **'single_vol'**: this is the *'single_vol'* price of the *FXStrangle* minus the *'single_vol'*\n      price of the *FXStraddle*. **'vol'** is an alias for single vol and returns the same value.\n\n      .. ipython:: python\n\n         fxbf.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"single_vol\")\n         fxbf.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"vol\")\n\n    - **'premium'**: the summed cash premium amount, of both options, applicable to the 'payment'\n      date. If *FXStrangle* strikes are given as delta percentages then they are first determined\n      using the *'single_vol'*.\n\n      .. ipython:: python\n\n         fxbf.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n\n    - **'pips_or_%'**: if the premium currency is LHS of ``pair`` this is a % of notional, whilst if\n      the premium currency is RHS this gives a number of pips of the FX rate. Summed over both\n      options. For *FXStrangle* strikes set with delta percentages these are first determined using the\n      'single_vol'.\n\n      .. ipython:: python\n\n         fxbf.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **fx option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    strike: 2-tuple of float, Variable, str, :red:`required`\n        The strikes of the *FXStrangle* and the *FXStraddle* in order.\n    pair: str, :red:`required`\n        The currency pair for the FX rate which settles the option, in 3-digit codes, e.g. \"eurusd\".\n        May be included as part of ``spec``.\n    notional: 2-tuple of float or None, :green:`optional (set by 'defaults')`\n        The notional amount of each option strategy expressed in units of LHS of ``pair``.\n        If the straddle notional is given as None then it will be determined from the strangle\n        notional under a vega neutral approach.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot. Spot is derived\n        from ``delivery_lag`` and ``calendar``.\n    modifier : str, :green:`optional (set by 'defaults')`\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"} for date evaluation.\n    eom: bool, :green:`optional (set by 'defaults')`\n        Whether to use end-of-month rolls when expiry is given as a month or year tenor.\n    calendar : calendar or str, :green:`optional`\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data.\n    delivery_lag: int, :green:`optional (set by 'defaults')`\n        The number of business days after expiry that the physical settlement of the FX\n        exchange occurs.\n    payment_lag: int or datetime, :green:`optional (set by 'defaults')`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    premium_ccy: str, :green:`optional (set as RHS of 'pair')`\n        The currency in which the premium is paid. Can *only* be one of the two currencies\n        in `pair`.\n    delta_type: FXDeltaMethod, str, :green:`optional (set by 'defaults')`\n        When deriving strike from delta use the equation associated with *'spot'* or *'forward'*\n        delta. If premium currency is LHS of ``pair`` then this will produce\n        **premium adjusted** delta values. If the `premium_ccy` is RHS of ``pair`` then delta values\n        are **unadjusted**.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: 2-tuple of 2-tuple float, :green:`optional`\n        The amount paid for each option in each strategy in order. If not given assumes unpriced\n        *Options* and sets this as mid-market premium during pricing.\n    option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of each option's :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric : str, :green:`optional (set as \"pips_or_%\")`\n        The pricing metric returned by the ``rate`` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable, Sequence\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    Notes\n    -----\n    Buying a *Straddle* equates to buying a :class:`~rateslib.instruments.FXPut`\n    and buying a :class:`~rateslib.instruments.FXCall` with the same strike. The ``notional`` of\n    each are the same, and should be entered as a single value.\n\n    When supplying ``strike`` as a string delta the strike will be determined at price time from\n    the provided volatility.\n\n    This class is an alias constructor for an\n    :class:`~rateslib.instruments._FXOptionStrat` where the number\n    of options and their definitions and nominals have been specifically overloaded for\n    convenience.\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 100.0\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        strike: tuple[tuple[DualTypes | str, DualTypes | str], DualTypes | str],\n        pair: str_ = NoInput(0),\n        *,\n        notional: tuple[DualTypes_, DualTypes_] | NoInput = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        modifier: str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        delivery_lag: int_ = NoInput(0),\n        premium: tuple[tuple[DualTypes_, DualTypes_], tuple[DualTypes_, DualTypes_]] = (\n            (NoInput(0), NoInput(0)),\n            (NoInput(0), NoInput(0)),\n        ),\n        premium_ccy: str_ = NoInput(0),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        delta_type: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        vol_ = self._parse_vol(vol)\n        if isinstance(notional, NoInput):\n            notional_: tuple[DualTypes_, DualTypes_] = (defaults.notional, NoInput(0))\n        elif isinstance(notional, tuple | list):\n            notional_ = notional\n            notional_[1] = NoInput(0) if notional_[1] is None else notional_[1]  # type: ignore[index]\n        else:\n            raise ValueError(\"FXBrokerFly `notional` must be a 2 element sequence if given.\")\n        strategies = [\n            FXStrangle(\n                pair=pair,\n                expiry=expiry,\n                delivery_lag=delivery_lag,\n                payment_lag=payment_lag,\n                calendar=calendar,\n                modifier=modifier,\n                eom=eom,\n                eval_date=eval_date,\n                strike=strike[0],\n                notional=notional_[0],\n                option_fixings=option_fixings[0]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                delta_type=delta_type,\n                premium=premium[0],\n                premium_ccy=premium_ccy,\n                curves=curves,\n                vol=vol_[0],  # type: ignore[arg-type]\n                metric=NoInput(0),\n                spec=spec,\n            ),\n            FXStraddle(\n                pair=pair,\n                expiry=expiry,\n                delivery_lag=delivery_lag,\n                payment_lag=payment_lag,\n                calendar=calendar,\n                modifier=modifier,\n                eom=eom,\n                eval_date=eval_date,\n                strike=strike[1],\n                notional=notional_[1],\n                option_fixings=option_fixings[1]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                delta_type=delta_type,\n                premium=premium[1],\n                premium_ccy=premium_ccy,\n                curves=curves,\n                vol=vol_[1],  # type: ignore[arg-type]\n                metric=NoInput(0),\n                spec=spec,\n            ),\n        ]\n        super().__init__(\n            options=strategies,\n            rate_weight=[1.0, 1.0],\n            rate_weight_vol=[1.0, -1.0],\n            metric=_drb(\"single_vol\", metric),\n            curves=curves,\n            vol=vol_,\n        )\n        self.kwargs.leg1[\"notional\"] = notional_\n\n    @property\n    def instruments(self) -> tuple[FXStrangle, FXStraddle]:\n        \"\"\"A tuple containing the :class:`~rateslib.instruments.FXStrangle` and\n        :class:`~rateslib.instruments.FXStraddle` of the *Fly*.\"\"\"\n        return self.kwargs.meta[\"instruments\"]  # type: ignore[no-any-return]\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> tuple[VolStrat_, VolStrat_]:  # type: ignore[override]\n        if not isinstance(vol, list | tuple):\n            vol = (vol, vol)\n        return (FXStrangle._parse_vol(vol[0]), FXStrangle._parse_vol(vol[1]))\n\n    def _maybe_set_vega_neutral_notional(\n        self,\n        curves: CurvesT_,\n        solver: Solver_,\n        fx: FXForwards_,\n        vol: tuple[VolStrat_, VolStrat_],\n        metric: str_,\n    ) -> None:\n        \"\"\"\n        Calculate the vega of the strangle and then set the notional on the straddle\n        to yield a vega neutral strategy.\n\n        Notional is set as a fixed quantity, collapsing any AD sensitivities in accordance\n        with the general principle for determining risk sensitivities of unpriced instruments.\n\n        This is only applied if ``metric`` is a cash based quantity, {\"pips_or_%\", \"premium\"}\n        \"\"\"\n        if isinstance(self.kwargs.leg1[\"notional\"][1], NoInput) and metric in [\n            \"pips_or_%\",\n            \"premium\",\n        ]:\n            self.instruments[0]._rate(\n                curves,\n                solver,\n                fx,\n                base=NoInput(0),\n                vol=vol[0],\n                metric=\"single_vol\",\n                record_greeks=True,\n                forward=NoInput(0),\n                settlement=NoInput(0),\n            )\n            self._greeks[\"straddle\"] = self.instruments[1].analytic_greeks(\n                curves,\n                solver,\n                fx,\n                vol=vol[1],\n            )\n            strangle_vega = self._greeks[\"strangle\"][\"market_vol\"][\"FXPut\"][\"vega\"]\n            strangle_vega += self._greeks[\"strangle\"][\"market_vol\"][\"FXCall\"][\"vega\"]\n            straddle_vega = self._greeks[\"straddle\"][\"vega\"]\n            scalar = strangle_vega / straddle_vega\n            self.instruments[1].kwargs.leg1[\"notional\"] = _dual_float(\n                self.instruments[0].kwargs.leg1[\"notional\"] * -scalar,\n            )\n            self.instruments[1]._set_notionals(self.instruments[1].kwargs.leg1[\"notional\"])\n            # BrokerFly -> Strangle -> FXPut -> FXPutPeriod\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        # Get curves and vol\n        vol_ = tuple(\n            [\n                _drb(d, b)\n                for (d, b) in zip(self.kwargs.meta[\"vol\"], self._parse_vol(vol), strict=True)\n            ]\n        )\n        _curves = self._parse_curves(curves)\n\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n        self._maybe_set_vega_neutral_notional(_curves, solver, fx, vol_, metric_)\n\n        if metric_ == \"pips_or_%\":\n            straddle_scalar = (\n                self.instruments[1].instruments[0]._option.settlement_params.notional\n                / self.instruments[0].instruments[0]._option.settlement_params.notional\n            )\n            weights: Sequence[DualTypes] = [1.0, straddle_scalar]\n        elif metric_ == \"premium\":\n            weights = self.kwargs.meta[\"rate_weight\"]\n        else:\n            weights = self.kwargs.meta[\"rate_weight_vol\"]\n        _: DualTypes = 0.0\n        for option_strat, vol__, weight in zip(self.instruments, vol_, weights, strict=False):\n            _ += (\n                option_strat.rate(\n                    curves=_curves,\n                    solver=solver,\n                    fx=fx,\n                    base=base,\n                    vol=vol__,\n                    metric=metric_,\n                    forward=forward,\n                    settlement=settlement,\n                )\n                * weight\n            )\n        return _\n\n    def analytic_greeks(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n    ) -> dict[str, Any]:\n        # implicitly call set_pricing_mid for unpriced parameters\n        self.rate(curves=curves, solver=solver, fx=fx, base=NoInput(0), vol=vol, metric=\"pips_or_%\")\n\n        vol_ = self._parse_vol(vol)\n\n        # TODO: this meth can be optimised because it calculates greeks at multiple times in frames\n        g_grks = self.instruments[0].analytic_greeks(curves, solver, fx, vol_[0])\n        d_grks = self.instruments[1].analytic_greeks(curves, solver, fx, vol_[1])\n        sclr = abs(\n            self.instruments[1].instruments[0]._option.settlement_params.notional\n            / self.instruments[0].instruments[0]._option.settlement_params.notional,\n        )\n\n        _unit_attrs = [\"delta\", \"gamma\", \"vega\", \"vomma\", \"vanna\", \"_kega\", \"_kappa\", \"__bs76\"]\n        _: dict[str, Any] = {}\n        for attr in _unit_attrs:\n            _[attr] = g_grks[attr] - sclr * d_grks[attr]\n\n        _notional_attrs = [\n            f\"delta_{self.kwargs.leg1['pair'].pair[:3]}\",\n            f\"gamma_{self.kwargs.leg1['pair'].pair[:3]}_1%\",\n            f\"vega_{self.kwargs.leg1['pair'].pair[3:]}\",\n        ]\n        for attr in _notional_attrs:\n            _[attr] = g_grks[attr] - d_grks[attr]\n\n        _.update(\n            {\n                \"__class\": \"_FXOptionStrat\",\n                \"__strategies\": {\"FXStrangle\": g_grks, \"FXStraddle\": d_grks},\n                \"__delta_type\": g_grks[\"__delta_type\"],\n                \"__notional\": self.kwargs.leg1[\"notional\"],\n            },\n        )\n        return _\n\n    def _plot_payoff(\n        self,\n        window: tuple[float, float] | NoInput = NoInput(0),  # noqa: A002\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n    ) -> tuple[Any, Any]:\n        vol_ = self._parse_vol(vol)\n        self._maybe_set_vega_neutral_notional(curves, solver, fx, vol_, metric=\"pips_or_%\")\n        return super()._plot_payoff(window, curves, solver, fx, vol_)\n"
  },
  {
    "path": "python/rateslib/instruments/fx_options/call_put.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom abc import ABCMeta\nfrom dataclasses import dataclass\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Any\n\nfrom pandas import DataFrame\n\nfrom rateslib import defaults\nfrom rateslib.curves._parsers import _validate_obj_not_no_input\nfrom rateslib.data.fixings import _fx_index_set_cross, _get_fx_index\nfrom rateslib.default import PlotOutput, plot\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import FXOptionMetric, _get_fx_delta_type\nfrom rateslib.instruments.protocols import _BaseInstrument, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _get_fx_forwards_maybe_from_solver,\n    _get_fx_vol,\n    _parse_curves,\n    _parse_vol,\n    _Vol,\n)\nfrom rateslib.legs import CustomLeg\nfrom rateslib.periods import Cashflow, FXCallPeriod, FXPutPeriod\nfrom rateslib.periods.utils import _validate_fx_as_forwards\nfrom rateslib.scheduling.frequency import _get_fx_expiry_and_delivery_and_payment\nfrom rateslib.volatility import FXDeltaVolSmile, FXDeltaVolSurface, FXSabrSmile, FXSabrSurface\nfrom rateslib.volatility.ir import _BaseIRCube, _BaseIRSmile\n\nif TYPE_CHECKING:\n    from typing import NoReturn  # pragma: no cover\n\n    import numpy as np  # pragma: no cover\n\n    from rateslib.local_types import (  # pragma: no cover\n        FX_,\n        Any,\n        CalInput,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        FXForwards,\n        FXForwards_,\n        FXIndex,\n        FXVol_,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseCurve,\n        _BaseCurve_,\n        _BaseFXOptionPeriod,\n        _BaseLeg,\n        _FXVolOption_,\n        bool_,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\n@dataclass\nclass _PricingMetrics:\n    \"\"\"None elements are used as flags to indicate an element is not yet set.\"\"\"\n\n    vol: _FXVolOption_ | None\n    k: DualTypes | None\n    delta_index: DualTypes | None\n    spot: datetime\n    t_e: DualTypes | None\n    f_d: DualTypes\n\n\nclass _BaseFXOption(_BaseInstrument, metaclass=ABCMeta):\n    \"\"\"\n    Abstract base class for implementing *FXOptions*.\n\n    See :class:`~rateslib.instruments.FXCall` and :class:`~rateslib.instruments.FXPut`.\n    \"\"\"\n\n    _rate_scalar: float = 1.0\n    _pricing: _PricingMetrics\n\n    @property\n    def leg1(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument* containing the\n        :class:`~rateslib.periods.FXOptionPeriod`.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument* containing the\n        premium :class:`~rateslib.periods.Cashflow`.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    @property\n    def _option(self) -> _BaseFXOptionPeriod:\n        return self.leg1.periods[0]  # type: ignore[return-value]\n\n    @property\n    def _premium(self) -> Cashflow:\n        return self.leg2.periods[0]  # type: ignore[return-value]\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An FXOption has two curve requirements:\n\n        The *rate curve* is the curve for the LHS of ``pair`` which is the curve typically\n        used to convert between spot and forward delta types. However, if the premium currency is\n        in the LHS side currency this cure will also be used as a discount curve for that\n        payment.\n\n        The *disc curve* is the curve for the RHS side of ``pair``.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            rate_curve = curves.get(\"rate_curve\", NoInput(0))\n            disc_curve = curves.get(\"disc_curve\", NoInput(0))\n            if self._premium.settlement_params.currency == self.kwargs.leg1[\"pair\"][:3]:\n                leg2_disc_curve = rate_curve\n            else:\n                leg2_disc_curve = disc_curve\n            return _Curves(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                leg2_disc_curve=leg2_disc_curve,\n            )\n        elif isinstance(curves, list | tuple) and len(curves) == 2:\n            rate_curve = curves[0]  # type: ignore[assignment]\n            disc_curve = curves[1]  # type: ignore[assignment]\n            if self.kwargs.leg2[\"premium_ccy\"] == self.kwargs.leg1[\"pair\"].pair[:3]:\n                leg2_disc_curve = rate_curve\n            else:\n                leg2_disc_curve = disc_curve\n            return _Curves(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                leg2_disc_curve=leg2_disc_curve,\n            )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:\n            raise ValueError(f\"{type(self).__name__} requires 2 curve types.\")\n\n    @classmethod\n    def _parse_vol(cls, vol: VolT_) -> _Vol:\n        \"\"\"\n        FXoptions requires only a single FXVolObj or a scalar.\n        \"\"\"\n        if isinstance(vol, _Vol):\n            return vol\n        elif isinstance(vol, _BaseIRSmile | _BaseIRCube):\n            raise TypeError(\"`vol` cannot be an IR type vol object and must be FX type vol object.\")\n        else:\n            return _Vol(fx_vol=vol)\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        strike: DualTypes | str,\n        pair: FXIndex | str_ = NoInput(0),\n        *,\n        notional: DualTypes_ = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        modifier: str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        delivery_lag: int_ = NoInput(0),\n        premium: DualTypes_ = NoInput(0),\n        premium_ccy: str_ = NoInput(0),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        delta_type: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        call: bool = True,\n    ):\n        user_args = dict(\n            pair=pair,\n            expiry=expiry,\n            notional=notional,\n            strike=strike,\n            calendar=calendar,\n            eom=eom,\n            modifier=modifier,\n            delta_type=delta_type,\n            option_fixings=option_fixings,\n            delivery_lag=delivery_lag,\n            leg2_payment_lag=payment_lag,\n            leg2_premium=premium,\n            leg2_premium_ccy=premium_ccy,\n            metric=metric,\n            curves=curves,\n            vol=self._parse_vol(vol),\n        )\n        # instrument_args: dict[str, Any] = dict()\n        default_args = dict(\n            delta_type=defaults.fx_delta_type,\n            notional=defaults.notional,\n            modifier=defaults.modifier,\n            metric=\"pips_or_%\",\n            delivery_lag=defaults.fx_delivery_lag,\n            leg2_payment_lag=defaults.payment_lag,\n            eom=defaults.eom_fx,\n        )\n        self._kwargs = _KWArgs(\n            user_args=user_args,\n            default_args=default_args,\n            spec=spec,\n            meta_args=[\"curves\", \"vol\", \"metric\"],\n        )\n\n        # This configuration here assumes that the options are physically settled, so do not\n        # allow WMR cross methodology to impact forecast rates for FXFixings.\n        self.kwargs.leg1[\"pair\"] = _fx_index_set_cross(\n            _get_fx_index(self.kwargs.leg1[\"pair\"]),\n            allow_cross=False,\n        )\n\n        # apply the parse knowing the premium currency\n        self._kwargs.leg2[\"premium_ccy\"] = _drb(\n            self.kwargs.leg1[\"pair\"].pair[3:], self.kwargs.leg2[\"premium_ccy\"]\n        )\n        self._kwargs.meta[\"curves\"] = self._parse_curves(self._kwargs.meta[\"curves\"])\n\n        # determine the `expiry` and `delivery` as datetimes if derived from other combinations\n        (self.kwargs.leg1[\"expiry\"], self.kwargs.leg1[\"delivery\"], self.kwargs.leg2[\"payment\"]) = (\n            _get_fx_expiry_and_delivery_and_payment(\n                eval_date=eval_date,\n                expiry=self.kwargs.leg1[\"expiry\"],\n                delivery_lag=self.kwargs.leg1[\"delivery_lag\"],\n                calendar=self.kwargs.leg1[\"calendar\"],\n                modifier=self.kwargs.leg1[\"modifier\"],\n                eom=self.kwargs.leg1[\"eom\"],\n                payment_lag=self.kwargs.leg2[\"payment_lag\"],\n            )\n        )\n\n        if self.kwargs.leg2[\"premium_ccy\"] not in [\n            self.kwargs.leg1[\"pair\"].pair[:3],\n            self.kwargs.leg1[\"pair\"].pair[3:],\n        ]:\n            raise ValueError(\n                f\"`premium_ccy`: '{self.kwargs.leg2['premium_ccy']}' must be one of option \"\n                f\"currency pair: '{self.kwargs.leg1['pair'].pair}'.\",\n            )\n        elif self.kwargs.leg2[\"premium_ccy\"] == self.kwargs.leg1[\"pair\"].pair[3:]:\n            self.kwargs.meta[\"metric_period\"] = \"pips\"\n            self.kwargs.meta[\"delta_method\"] = _get_fx_delta_type(self.kwargs.leg1[\"delta_type\"])\n        else:\n            self.kwargs.meta[\"metric_period\"] = \"percent\"\n            self.kwargs.meta[\"delta_method\"] = _get_fx_delta_type(\n                self.kwargs.leg1[\"delta_type\"] + \"_pa\"\n            )\n\n        self._validate_strike_and_premiums()\n\n        self._leg1 = CustomLeg(\n            [\n                FXCallPeriod(  # type: ignore[abstract]\n                    pair=self.kwargs.leg1[\"pair\"],\n                    expiry=self.kwargs.leg1[\"expiry\"],\n                    delivery=self.kwargs.leg1[\"delivery\"],\n                    strike=(\n                        NoInput(0)\n                        if isinstance(self.kwargs.leg1[\"strike\"], str)\n                        else self.kwargs.leg1[\"strike\"]\n                    ),\n                    notional=self.kwargs.leg1[\"notional\"],\n                    option_fixings=self.kwargs.leg1[\"option_fixings\"],\n                    delta_type=self.kwargs.meta[\"delta_method\"],\n                    metric=self.kwargs.meta[\"metric_period\"],\n                )\n                if call\n                else FXPutPeriod(  # type: ignore[abstract]\n                    pair=self.kwargs.leg1[\"pair\"],\n                    expiry=self.kwargs.leg1[\"expiry\"],\n                    delivery=self.kwargs.leg1[\"delivery\"],\n                    strike=(\n                        NoInput(0)\n                        if isinstance(self.kwargs.leg1[\"strike\"], str)\n                        else self.kwargs.leg1[\"strike\"]\n                    ),\n                    notional=self.kwargs.leg1[\"notional\"],\n                    option_fixings=self.kwargs.leg1[\"option_fixings\"],\n                    delta_type=self.kwargs.meta[\"delta_method\"],\n                    metric=self.kwargs.meta[\"metric_period\"],\n                )\n            ]\n        )\n        self._leg2 = CustomLeg(\n            [\n                Cashflow(\n                    notional=_drb(0.0, self.kwargs.leg2[\"premium\"]),\n                    payment=self.kwargs.leg2[\"payment\"],\n                    currency=self.kwargs.leg2[\"premium_ccy\"],\n                ),\n            ]\n        )\n        self._legs = [self._leg1, self._leg2]\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    def _validate_strike_and_premiums(self) -> None:\n        if isinstance(self.kwargs.leg1[\"strike\"], str) and not isinstance(\n            self.kwargs.leg2[\"premium\"], NoInput\n        ):\n            raise ValueError(\n                \"FXOption with string delta as `strike` cannot be initialised with a known \"\n                \"`premium`.\\n\"\n                \"Either set `strike` as a defined numeric value, or remove the `premium`.\",\n            )\n\n    def _set_strike_and_vol(\n        self,\n        rate_curve: _BaseCurve_,\n        disc_curve: _BaseCurve_,\n        fx: FX_,\n        vol: _FXVolOption_,\n    ) -> None:\n        \"\"\"\n        Set the strike, if necessary, and determine pricing metrics from the volatility objects.\n\n        The strike for the *OptionPeriod* is either; string or numeric.\n\n        If it is string, then a numeric strike must be determined with an associated vol.\n\n        If it is numeric then the volatility must be determined for the given strike.\n\n        Pricing elements are captured and cached so they can be used later by subsequent methods.\n        \"\"\"\n        fx_ = _validate_fx_as_forwards(fx)\n        _pricing = _PricingMetrics(\n            vol=None,\n            k=None,\n            delta_index=None,\n            spot=fx_.pairs_settlement[self.kwargs.leg1[\"pair\"].pair],\n            t_e=None,\n            f_d=fx_.rate(self.kwargs.leg1[\"pair\"], self.kwargs.leg1[\"delivery\"]),\n        )\n\n        if isinstance(vol, FXDeltaVolSmile | FXDeltaVolSurface | FXSabrSmile | FXSabrSurface):\n            eval_date = vol.meta.eval_date\n        else:\n            _ = _validate_obj_not_no_input(disc_curve, \"disc_curve\")\n            eval_date = _.nodes.initial\n            _pricing.vol = vol  # Not a vol model so set directly\n        _pricing.t_e = self._option.fx_option_params.time_to_expiry(eval_date)\n        self._update_pricing_for_strike(\n            strike=self.kwargs.leg1[\"strike\"],\n            fx=fx_,\n            pricing=_pricing,\n            vol=vol,\n            rate_curve=rate_curve,\n        )\n\n        # _PricingMetrics.k is completely specified\n        assert _pricing.k is not None  # noqa: S101\n        # Review section in book regarding Hyper-parameters and Solver interaction\n        self._option.fx_option_params.strike = _pricing.k\n        self._pricing = _pricing\n        # self._option_periods[0].strike = _dual_float(self._pricing.k)\n\n    def _update_pricing_for_strike(\n        self,\n        strike: str | DualTypes,\n        fx: FXForwards,\n        pricing: _PricingMetrics,\n        vol: _FXVolOption_,\n        rate_curve: _BaseCurve_,\n    ) -> None:\n        \"\"\"Update the _PricingMetrics object to populate values.\"\"\"\n        if not isinstance(strike, str):\n            # then strike is a numeric quantity\n            pricing.k = strike\n        else:\n            # then strike is a string which must be converted to a numeric value\n            strike = strike.lower()\n            if strike == \"atm_forward\":\n                pricing.k = fx.rate(self.kwargs.leg1[\"pair\"], self.kwargs.leg1[\"delivery\"])\n            elif strike == \"atm_spot\":\n                pricing.k = fx.rate(self.kwargs.leg1[\"pair\"], pricing.spot)\n            elif strike == \"atm_delta\":\n                rc = _validate_obj_not_no_input(rate_curve, \"rate_curve\")\n                pricing.delta_index, pricing.vol, pricing.k = (\n                    self._option._index_vol_and_strike_from_atm(\n                        delta_type=self._option.fx_option_params.delta_type,\n                        vol=_validate_obj_not_no_input(vol, \"vol\"),  # type: ignore[arg-type]\n                        w_deli=rc[self.kwargs.leg1[\"delivery\"]],\n                        w_spot=rc[pricing.spot],\n                        f=fx if isinstance(vol, FXSabrSurface) else pricing.f_d,\n                        t_e=pricing.t_e,  # type: ignore[arg-type]\n                    )\n                )\n                return None\n            elif strike[-1] == \"d\":  # representing a delta percentage\n                rc = _validate_obj_not_no_input(rate_curve, \"rate_curve\")\n                pricing.delta_index, pricing.vol, pricing.k = (\n                    self._option._index_vol_and_strike_from_delta(\n                        delta=float(strike[:-1]) / 100.0,\n                        delta_type=self.kwargs.meta[\"delta_method\"],\n                        vol=_validate_obj_not_no_input(vol, \"vol\"),  # type: ignore[arg-type]\n                        w_deli=rc[self.kwargs.leg1[\"delivery\"]],\n                        w_spot=rc[pricing.spot],\n                        f=fx if isinstance(vol, FXSabrSurface) else pricing.f_d,\n                        t_e=pricing.t_e,  # type: ignore[arg-type]\n                    )\n                )\n                return None\n\n        if pricing.vol is None:\n            # vol is only None if vol_ is a VolObj so can be safely type ignored.\n            # a numeric vol has already been set on the 'pricing' object.\n            # then an explicit strike is set so determine the vol from strike, set and return.\n            rc = _validate_obj_not_no_input(rate_curve, \"rate_curve\")\n            pricing.delta_index, pricing.vol, _ = vol.get_from_strike(  # type: ignore[union-attr]\n                k=pricing.k,  # type: ignore[arg-type]\n                f=pricing.f_d if not isinstance(vol, FXSabrSurface) else fx,  # type: ignore[arg-type]\n                expiry=self.kwargs.leg1[\"expiry\"],\n                z_w=rc[self.kwargs.leg1[\"delivery\"]] / rc[pricing.spot],\n            )\n        return None\n\n    def _set_premium(\n        self,\n        rate_curve: _BaseCurve_,\n        disc_curve: _BaseCurve_,\n        fx: FXForwards_,\n        pricing: _PricingMetrics,\n    ) -> None:\n        \"\"\"\n        Set an unspecified premium on the Option to be equal to the mid-market premium.\n        \"\"\"\n        if isinstance(self.kwargs.leg2[\"premium\"], NoInput):\n            # then set the CashFlow to mid-market\n            disc_curve_: _BaseCurve = _validate_obj_not_no_input(disc_curve, \"disc curve\")\n            rate_curve_: _BaseCurve = _validate_obj_not_no_input(rate_curve, \"rate curve\")\n            try:\n                npv: DualTypes = self._option.npv(  # type: ignore[assignment]\n                    rate_curve=rate_curve_,\n                    disc_curve=disc_curve_,\n                    fx=fx,\n                    fx_vol=pricing.vol,  # type: ignore[arg-type]\n                    local=False,\n                    forward=self.kwargs.leg2[\"payment\"],\n                    base=self.kwargs.leg2[\"premium_ccy\"],\n                )\n            except AttributeError:\n                raise ValueError(\n                    \"`premium` has not been configured for the specified FXOption.\\nThis is \"\n                    \"normally determined at mid-market from the given `curves` and `vol` but \"\n                    \"in this case these values do not provide a valid calculation. \"\n                    \"If not required, initialise the \"\n                    \"FXOption with a `premium` of 0.0, and this will be avoided.\",\n                )\n            self._premium.settlement_params._notional = _dual_float(npv)\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", False, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        v = _parse_vol(self, vol, solver, False)\n        fx_vol = _get_fx_vol(True, True, *v)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n        self._set_strike_and_vol(rate_curve=rate_curve, disc_curve=disc_curve, fx=fx_, vol=fx_vol)\n\n        # Premium is not required for rate and also sets as float\n        # Review section: \"Hyper-parameters and Solver interaction\" before enabling.\n        # self._set_premium(curves, fx)\n\n        metric = _drb(self.kwargs.meta[\"metric\"], metric)\n        if metric in [\"vol\", \"single_vol\"]:\n            return _validate_obj_not_no_input(self._pricing.vol, \"vol\")  # type: ignore[return-value]\n\n        _: DualTypes = self._option.rate(\n            rate_curve=_validate_obj_not_no_input(rate_curve, \"curve\"),\n            disc_curve=_validate_obj_not_no_input(disc_curve, \"curve\"),\n            fx=fx_,\n            fx_vol=self._pricing.vol,  # type: ignore[arg-type]\n            forward=self.kwargs.leg2[\"payment\"],\n        )\n        if metric == \"premium\":\n            if self._option.fx_option_params.metric == FXOptionMetric.Pips:\n                # is expressed in RHS currency\n                _ *= self._option.settlement_params.notional / 10000\n            else:  # == \"percent\"\n                # is expressed in LHS currency\n                _ *= self._option.settlement_params.notional / 100\n        return _\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", False, False, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, False, *c)\n\n        v = _parse_vol(self, vol, solver, False)\n        fx_vol = _get_fx_vol(True, True, *v)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n        self._set_strike_and_vol(rate_curve=rate_curve, disc_curve=disc_curve, fx=fx_, vol=fx_vol)\n\n        self._set_premium(\n            rate_curve=rate_curve, disc_curve=disc_curve, fx=fx_, pricing=self._pricing\n        )\n\n        if not local:\n            base_ = _drb(self.legs[0].settlement_params.currency, base)\n        else:\n            base_ = base\n\n        opt_npv = self._option.npv(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            fx=fx_,\n            base=base_,\n            local=local,\n            fx_vol=self._pricing.vol,  # type: ignore[arg-type]\n            settlement=settlement,\n            forward=forward,\n        )\n        prem_npv = self._premium.npv(\n            disc_curve=leg2_disc_curve,\n            fx=fx_,\n            base=base_,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n        if local:\n            return {k: opt_npv.get(k, 0) + prem_npv.get(k, 0) for k in set(opt_npv) | set(prem_npv)}  # type:ignore[union-attr, arg-type]\n        else:\n            return opt_npv + prem_npv  # type: ignore[operator]\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        c = _parse_curves(self, curves, solver)\n        v = _parse_vol(self, vol, solver, False)\n\n        try:\n            rate_curve = _get_curve(\"rate_curve\", False, True, *c)\n            disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n            fx_vol = _get_fx_vol(True, True, *v)\n            fx_ = _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n            self._set_strike_and_vol(\n                rate_curve=rate_curve, disc_curve=disc_curve, fx=fx_, vol=fx_vol\n            )\n            self._set_premium(\n                rate_curve=rate_curve, disc_curve=disc_curve, fx=fx_, pricing=self._pricing\n            )\n        except Exception:  # noqa: S110\n            pass  # `cashflows` proceed without pricing determined values\n\n        return self._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n            vol=vol,\n        )\n\n    def analytic_greeks(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: FXVol_ = NoInput(0),\n    ) -> dict[str, Any]:\n        \"\"\"\n        Return various pricing metrics of the *FX Option*.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import Curve, FXCall, dt, FXForwards, FXRates, FXDeltaVolSmile\n\n        .. ipython:: python\n\n           eur = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.98})\n           usd = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.96})\n           fxf = FXForwards(\n               fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2020, 1, 3)),\n               fx_curves={\"eureur\": eur, \"eurusd\": eur, \"usdusd\": usd},\n           )\n           fxvs = FXDeltaVolSmile(\n               nodes={0.25: 11.0, 0.5: 9.8, 0.75: 10.7},\n               delta_type=\"forward\",\n               eval_date=dt(2020, 1, 1),\n               expiry=dt(2020, 4, 1)\n           )\n           fxc = FXCall(\n               expiry=\"3m\",\n               strike=1.10,\n               eval_date=dt(2020, 1, 1),\n               spec=\"eurusd_call\",\n           )\n           fxc.analytic_greeks(fx=fxf, curves=[eur, usd], vol=fxvs)\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n\n        Returns\n        -------\n        dict\n        \"\"\"\n        return self._analytic_greeks_set_metrics(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            set_metrics=True,\n        )\n\n    def _analytic_greeks_set_metrics(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: FXVol_ = NoInput(0),\n        set_metrics: bool_ = True,\n    ) -> dict[str, Any]:\n        \"\"\"\n        Return various pricing metrics of the *FX Option*.\n\n        Returns\n        -------\n        float, Dual, Dual2\n        \"\"\"\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", False, False, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n\n        v = _parse_vol(self, vol, solver, False)\n        fx_vol = _get_fx_vol(True, True, *v)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n\n        if set_metrics:\n            self._set_strike_and_vol(\n                rate_curve=rate_curve, disc_curve=disc_curve, fx=fx_, vol=fx_vol\n            )\n            # self._set_premium(curves, fx)\n\n        return self._option.analytic_greeks(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            fx=_validate_fx_as_forwards(fx_),\n            fx_vol=fx_vol,\n            premium=NoInput(0),\n            premium_payment=self.kwargs.leg2[\"payment\"],\n        )\n\n    def _analytic_greeks_reduced(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        base: str_ = NoInput(0),\n        vol: FXVol_ = NoInput(0),\n        set_metrics: bool_ = True,\n    ) -> dict[str, Any]:\n        \"\"\"\n        Return various pricing metrics of the *FX Option*.\n        \"\"\"\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", False, False, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n\n        v = _parse_vol(self, vol, solver, False)\n        fx_vol = _get_fx_vol(True, True, *v)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n\n        if set_metrics:\n            self._set_strike_and_vol(\n                rate_curve=rate_curve, disc_curve=disc_curve, fx=fx_, vol=fx_vol\n            )\n            # self._set_premium(curves, fx)\n\n        return self._option._base_analytic_greeks(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            fx=_validate_fx_as_forwards(fx_),\n            fx_vol=self._pricing.vol,  # type: ignore[arg-type]  # vol is set and != None\n            premium=NoInput(0),\n            _reduced=True,\n        )  # none of the reduced greeks need a VolObj - faster to reuse from _pricing.vol\n\n    def analytic_delta(self, *args: Any, leg: int = 1, **kwargs: Any) -> NoReturn:\n        \"\"\"Not implemented for Option types.\n        Use :meth:`~rateslib.instruments._BaseFXOption.analytic_greeks`.\n        \"\"\"\n        raise NotImplementedError(\"For Option types use `analytic_greeks`.\")\n\n    def _plot_payoff(\n        self,\n        window: tuple[float, float] | NoInput = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: FXVol_ = NoInput(0),\n    ) -> tuple[\n        np.ndarray[tuple[int], np.dtype[np.float64]], np.ndarray[tuple[int], np.dtype[np.float64]]\n    ]:\n        \"\"\"\n        Mechanics to determine (x,y) coordinates for payoff at expiry plot.\n        \"\"\"\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", False, False, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n\n        v = _parse_vol(self, vol, solver, False)\n        fx_vol = _get_fx_vol(True, True, *v)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n        self._set_strike_and_vol(rate_curve=rate_curve, disc_curve=disc_curve, fx=fx_, vol=fx_vol)\n        # self._set_premium(curves, fx)\n\n        x, y = self._option._payoff_at_expiry(window)\n        return x, y\n\n    def plot_payoff(\n        self,\n        range: tuple[float, float] | NoInput = NoInput(0),  # noqa: A002\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        base: str_ = NoInput(0),\n        vol: float_ = NoInput(0),\n    ) -> PlotOutput:\n        \"\"\"\n        Return a plot of the payoff at expiry, indexed by the *FXFixing* value.\n\n        Parameters\n        ----------\n        range: list of float, :green:`optional`\n            A range of values for the *FXFixing* value at expiry to use as the x-axis.\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n\n        Returns\n        -------\n        (Figure, Axes, list[Lines2D])\n        \"\"\"\n\n        x, y = self._plot_payoff(window=range, curves=curves, solver=solver, fx=fx, vol=vol)\n        return plot([x], [y])  # type: ignore\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return DataFrame()\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Not implemented for Option types. Use :meth:`~rateslib.instruments._BaseFXOption.rate`.\n        \"\"\"\n        raise NotImplementedError(f\"`spread` is not implemented for type: {type(self).__name__}\")\n\n\nclass FXCall(_BaseFXOption):\n    \"\"\"\n    An *FX Call* option.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import dt, FXCall, FXForwards, FXRates, FXDeltaVolSmile, Curve\n\n    .. ipython:: python\n\n       fxc = FXCall(\n           expiry=\"3m\",\n           strike=1.10,\n           eval_date=dt(2020, 1, 1),\n           spec=\"eurusd_call\",\n       )\n       fxc.cashflows()\n\n    .. rubric:: Pricing\n\n    An *FXOption* requires two discount curves; a curve to discount the cashflow of the LHS\n    currency of ``pair``. This is labelled as the *rate curve* and is used to derive the\n    difference between spot and forward deltas. The curve labelled as *disc curve* discounts\n    cashflows of the RHS of ``pair``. For the premium, depending upon whether it is paid in LHS\n    or RHS currency the appropriate curve from *Leg1* will be used and labelled as\n    *leg2 disc curve*. Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = {\"rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    An *FXOption* also requires an :class:`~rateslib.fx.FXForwards` as input to the ``fx``\n    argument, and an *FXVolatility* object or numeric value for the ``vol`` argument. Allowed\n    inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0  #  a specific calendar-day annualized %-volatility until expiry\n       vol = vol_obj  # an explicit volatility object, e.g. FXDeltaVolSurface\n\n    The following pricing ``metric`` are available, with examples:\n\n    .. ipython:: python\n\n       eur = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.98})\n       usd = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.96})\n       fxf = FXForwards(\n           fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2020, 1, 3)),\n           fx_curves={\"eureur\": eur, \"eurusd\": eur, \"usdusd\": usd},\n       )\n       fxvs = FXDeltaVolSmile(\n           nodes={0.25: 11.0, 0.5: 9.8, 0.75: 10.7},\n           expiry=dt(2020, 4, 1),\n           eval_date=dt(2020, 1, 1),\n           delta_type=\"forward\",\n       )\n\n    - **'vol'**: the implied volatility value of the option from a volatility object.\n\n      .. ipython:: python\n\n         fxc.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"vol\")\n\n    - **'premium'**: the cash premium amount applicable to the 'payment' date, expressed in the\n      premium currency.\n\n      .. ipython:: python\n\n         fxc.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n\n    - **'pips_or_%'**: if the premium currency is LHS of ``pair`` this is a % of notional, whilst if\n      the premium currency is RHS this gives a number of pips of the FX rate.\n\n      .. ipython:: python\n\n         fxc.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **fx option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    strike: float, Variable, str, :red:`required`\n        The strike value of the option.\n        If str, there are four possibilities; {\"atm_forward\", \"atm_spot\", \"atm_delta\", \"%d\"}.\n        Call % deltas can be given, as \"25d\".\n    pair: str, :red:`required`\n        The currency pair for the FX rate which settles the option, in 3-digit codes, e.g. \"eurusd\".\n        May be included as part of ``spec``.\n    notional: float, :green:`optional (set by 'defaults')`\n        The notional amount expressed in units of LHS of ``pair``.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot. Spot is derived\n        from ``delivery_lag`` and ``calendar``.\n    modifier : str, :green:`optional (set by 'defaults')`\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"} for date evaluation.\n    eom: bool, :green:`optional (set by 'defaults')`\n        Whether to use end-of-month rolls when expiry is given as a month or year tenor.\n    calendar : calendar or str, :green:`optional`\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data.\n    delivery_lag: int, :green:`optional (set by 'defaults')`\n        The number of business days after expiry that the physical settlement of the FX\n        exchange occurs.\n    payment_lag: int or datetime, :green:`optional (set by 'defaults')`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    premium_ccy: str, :green:`optional (set as RHS of 'pair')`\n        The currency in which the premium is paid. Can *only* be one of the two currencies\n        in `pair`.\n    delta_type: FXDeltaMethod, str, :green:`optional (set by 'defaults')`\n        When deriving strike from delta use the equation associated with *'spot'* or *'forward'*\n        delta. If premium currency is LHS of ``pair`` then this will produce\n        **premium adjusted** delta values. If the `premium_ccy` is RHS of ``pair`` then delta values\n        are **unadjusted**.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: float, :green:`optional`\n        The amount paid for the option. If not given assumes an unpriced *Option* and sets this as\n        mid-market premium during pricing.\n    option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the option :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric : str, :green:`optional (set as \"pips_or_%\")`\n        The pricing metric returned by the ``rate`` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    Notes\n    ------\n\n    Date calculations for *FXOption* products are very specific. See *'Expiry and Delivery Rules'*\n    in *FX Option Pricing* by I. Clark. *Rateslib* uses calendars with associated settlement\n    calendars and the recognised market convention rules to derive dates.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import dt\n       from rateslib.instruments import FXCall\n\n    .. ipython:: python\n\n       fxc = FXCall(\n           pair=\"eursek\",\n           expiry=\"2M\",\n           eval_date=dt(2024, 6, 19),  # <- Wednesday\n           strike=11.0,\n           modifier=\"mf\",\n           calendar=\"tgt,stk|fed\",\n           delivery_lag=2,\n           payment_lag=2,\n       )\n       fxc.kwargs.leg1[\"delivery\"]  # <- '2M' out of spot: Monday 24 Jun 2024: FX delivery\n       fxc.kwargs.leg1[\"expiry\"]    # <- '2b' before 'delivery': Option expiry\n       fxc.kwargs.leg2[\"payment\"]  # <- '2b' after 'expiry': Premium payment date\n\n    \"\"\"\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, call=True, **kwargs)\n\n\nclass FXPut(_BaseFXOption):\n    \"\"\"\n    An *FX Put* option.\n\n    For parameters and examples see :class:`~rateslib.instruments.FXCall`.\n    \"\"\"\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, call=False, **kwargs)\n"
  },
  {
    "path": "python/rateslib/instruments/fx_options/risk_reversal.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom pandas import DataFrame\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.fx_options.call_put import FXCall, FXPut, _BaseFXOption\nfrom rateslib.instruments.protocols import _KWArgs\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalInput,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        Sequence,\n        Solver_,\n        VolStrat_,\n        VolT_,\n        _Vol,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass _BaseFXOptionStrat(_BaseFXOption):\n    \"\"\"\n    A custom option strategy composed of a list of :class:`~rateslib.instruments._BaseFXOption`,\n    or other :class:`~rateslib.instruments._BaseFXOptionStrat` objects, of the same\n    currency ``pair``.\n\n    Parameters\n    ----------\n    options: list\n        The *FXOptions* or *FXOptionStrats* which make up the strategy.\n    rate_weight: list\n        The multiplier for the *'pips_or_%'* metric that sums the options to a final *rate*.\n        E.g. A *RiskReversal* uses [-1.0, 1.0] for a sale and a purchase.\n        E.g. A *Straddle* uses [1.0, 1.0] for summing two premium purchases.\n    rate_weight_vol: list\n        The multiplier for the *'vol'* metric that sums the options to a final *rate*.\n        E.g. A *RiskReversal* uses [-1.0, 1.0] to obtain the vol difference between two options.\n        E.g. A *Straddle* uses [0.5, 0.5] to obtain the volatility at the strike of each option.\n    \"\"\"\n\n    _greeks: dict[str, Any] = {}\n    _strat_elements: tuple[_BaseFXOption | _BaseFXOptionStrat, ...]\n\n    @property\n    def kwargs(self) -> _KWArgs:\n        \"\"\"The :class:`~rateslib.instruments.protocols._KWArgs` of the *Instrument*.\"\"\"\n        return self._kwargs\n\n    def __init__(\n        self,\n        options: Sequence[_BaseFXOption | _BaseFXOptionStrat],\n        rate_weight: list[float],\n        rate_weight_vol: list[float],\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n    ):\n        self._n = len(options)\n        if self._n != len(rate_weight) or self._n != len(rate_weight_vol):\n            raise ValueError(\n                \"`rate_weight` and `rate_weight_vol` must have same length as `options`.\",\n            )\n        self._kwargs = _KWArgs(\n            spec=NoInput(0),\n            user_args=dict(\n                rate_weight=rate_weight,\n                rate_weight_vol=rate_weight_vol,\n                instruments=tuple(options),\n                metric=metric,\n                pair=options[0].kwargs.leg1[\"pair\"],\n                curves=NoInput(0),\n                vol=vol,\n            ),\n            default_args=dict(\n                metric=\"vol\",\n            ),\n            meta_args=[\"metric\", \"vol\", \"curves\", \"instruments\", \"rate_weight\", \"rate_weight_vol\"],\n        )\n        self.kwargs.leg2[\"premium_ccy\"] = self.instruments[0].kwargs.leg2[\"premium_ccy\"]\n        self.kwargs.meta[\"curves\"] = self._parse_curves(curves)\n\n    # @property\n    # def _vol_agg(self) -> FXVolStrat_:\n    #     \"\"\"Aggregate the `vol` metric on contained options into a container\"\"\"\n    #\n    #     def vol_attr(obj: FXOption | FXOptionStrat) -> FXVolStrat_:\n    #         if isinstance(obj, FXOption):\n    #             return obj.vol\n    #         else:\n    #             return obj._vol_agg\n    #\n    #     return [vol_attr(obj) for obj in self._strat_elements]\n    #\n    # def _parse_vol_sequence(self, vol: FXVolStrat_) -> ListFXVol_:\n    #     \"\"\"\n    #     This function exists to determine a recursive list\n    #\n    #     This function must exist to parse an input sequence of given vol values for each\n    #     *Instrument* in the strategy to a list that will be applied sequentially to value\n    #     each of those *Instruments*.\n    #\n    #     If a sub-sequence, e.g BrokerFly is a strategy of strategies then this function will\n    #     be repeatedly called within each strategy.\n    #     \"\"\"\n    #     ret: ListFXVol_ = []\n    #     if isinstance(\n    #         vol,\n    #         str\n    #         | float\n    #         | Dual\n    #         | Dual2\n    #         | Variable\n    #         | FXDeltaVolSurface\n    #         | FXDeltaVolSmile\n    #         | FXSabrSmile\n    #         | FXSabrSurface\n    #         | NoInput,\n    #     ):\n    #         for obj in self.periods:\n    #             if isinstance(obj, FXOptionStrat):\n    #                 ret.append(obj._parse_vol_sequence(vol))\n    #             else:\n    #                 ret.append(vol)\n    #\n    #     elif isinstance(vol, Sequence):\n    #         if len(vol) != len(self.periods):\n    #             raise ValueError(\n    #                 \"`vol` as sequence must have same length as its contained \"\n    #                 f\"strategy elements: {len(self.periods)}\"\n    #             )\n    #         else:\n    #             for obj, vol_ in zip(self.periods, vol, strict=True):\n    #                 if isinstance(obj, FXOptionStrat):\n    #                     ret.append(obj._parse_vol_sequence(vol_))\n    #                 else:\n    #                     assert isinstance(vol_, str) or not isinstance(vol_, Sequence)\n    #                     ret.append(vol_)\n    #     return ret\n    #\n    # def _get_fxvol_maybe_from_solver_recursive(\n    #     self, vol: FXVolStrat_, solver: Solver_\n    # ) -> ListFXVol_:\n    #     \"\"\"\n    #     Function must parse a ``vol`` input in combination with ``vol_agg`` attribute to yield\n    #     a Sequence of vols applied to the various levels of associated *Options* or *OptionStrats*\n    #     \"\"\"\n    #     vol_ = self._parse_vol_sequence(vol)  # vol_ is properly nested for one vol per option\n    #     ret: ListFXVol_ = []\n    #     for obj, vol__ in zip(self.periods, vol_, strict=False):\n    #         if isinstance(obj, FXOptionStrat):\n    #             ret.append(obj._get_fxvol_maybe_from_solver_recursive(vol__, solver))\n    #         else:\n    #             assert isinstance(vol__, str) or not isinstance(vol__, Sequence)  # noqa: S101\n    #             ret.append(\n    #             _get_fxvol_maybe_from_solver(vol_attr=obj.vol, vol=vol__, solver=solver)\n    #             )\n    #     return ret\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> tuple[_Vol, _Vol]:  # type: ignore[override]\n        raise NotImplementedError(f\"{type(cls).__name__} must implement `_parse_vol`.\")\n\n    @property\n    def instruments(self) -> tuple[_BaseFXOption | _BaseFXOptionStrat, ...]:\n        return self.kwargs.meta[\"instruments\"]  # type: ignore[no-any-return]\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        vol_: VolStrat_ = self._parse_vol(vol)\n        metric_: str = _drb(self.kwargs.meta[\"metric\"], metric)\n        map_ = {\n            \"pips_or_%\": self.kwargs.meta[\"rate_weight\"],\n            \"vol\": self.kwargs.meta[\"rate_weight_vol\"],\n            \"premium\": [1.0] * len(self.instruments),\n            \"single_vol\": self.kwargs.meta[\"rate_weight_vol\"],\n        }\n        weights = map_[metric_]\n\n        _: DualTypes = 0.0\n        for option, vol__, weight in zip(self.instruments, vol_, weights, strict=True):  # type: ignore[misc, arg-type]\n            _ += (\n                option.rate(\n                    curves=curves,\n                    solver=solver,\n                    fx=fx,\n                    base=base,\n                    vol=vol__,  # type: ignore[arg-type]\n                    metric=metric_,\n                    settlement=settlement,\n                    forward=forward,\n                )\n                * weight\n            )\n        return _\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        vol_ = self._parse_vol(vol)\n\n        results = [\n            option.npv(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                base=base,\n                local=local,\n                vol=vol__,\n                forward=forward,\n                settlement=settlement,\n            )\n            for (option, vol__) in zip(self.instruments, vol_, strict=True)\n        ]\n\n        if local:\n            df = DataFrame(results).fillna(0.0)\n            df_sum = df.sum()\n            _: DualTypes | dict[str, DualTypes] = df_sum.to_dict()  # type: ignore[assignment]\n        else:\n            _ = sum(results)  # type: ignore[arg-type]\n        return _\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._cashflows_from_instruments(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n            base=base,\n        )\n\n    def _plot_payoff(\n        self,\n        window: tuple[float, float] | NoInput = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n    ) -> tuple[Any, Any]:\n        vol_: VolStrat_ = self._parse_vol(vol)\n\n        y = None\n        for inst, vol__ in zip(self.instruments, vol_, strict=True):  # type: ignore[misc, arg-type]\n            x, y_ = inst._plot_payoff(\n                window=window,\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                vol=vol__,  # type: ignore[arg-type]\n            )\n            if y is None:\n                y = y_\n            else:\n                y += y_\n\n        return x, y\n\n    def analytic_greeks(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n    ) -> dict[str, Any]:\n        # implicitly call set_pricing_mid for unpriced parameters\n        # this is important for Strategies whose options are\n        # dependent upon each other, e.g. Strangle. (RR and Straddle do not have\n        # interdependent options)\n        self.rate(curves=curves, solver=solver, fx=fx, vol=vol)\n\n        vol_: VolStrat_ = self._parse_vol(vol=vol)\n        gks = []\n        for inst, vol_i in zip(self.instruments, vol_, strict=True):  # type: ignore[misc, arg-type]\n            if isinstance(inst, _BaseFXOptionStrat):\n                gks.append(\n                    inst.analytic_greeks(\n                        curves=curves,\n                        solver=solver,\n                        fx=fx,\n                        vol=vol_i,\n                    )\n                )\n            else:  # option is FXOption\n                # by calling on the OptionPeriod directly the strike is maintained from rate call.\n                gks.append(\n                    inst._analytic_greeks_set_metrics(\n                        curves=curves,\n                        solver=solver,\n                        fx=fx,\n                        vol=vol_i,  # type: ignore[arg-type]\n                        set_metrics=False,  # already done in the rate call above\n                    )\n                )\n\n        _unit_attrs = [\"delta\", \"gamma\", \"vega\", \"vomma\", \"vanna\", \"_kega\", \"_kappa\", \"__bs76\"]\n        _: dict[str, Any] = {}\n        for attr in _unit_attrs:\n            _[attr] = sum(gk[attr] * self.kwargs.meta[\"rate_weight\"][i] for i, gk in enumerate(gks))\n\n        _notional_attrs = [\n            f\"delta_{self.kwargs.leg1['pair'].pair[:3]}\",\n            f\"gamma_{self.kwargs.leg1['pair'].pair[:3]}_1%\",\n            f\"vega_{self.kwargs.leg1['pair'].pair[3:]}\",\n        ]\n        for attr in _notional_attrs:\n            _[attr] = sum(gk[attr] * self.kwargs.meta[\"rate_weight\"][i] for i, gk in enumerate(gks))\n\n        _.update(\n            {\n                \"__class\": \"FXOptionStrat\",\n                \"__options\": gks,\n                \"__delta_type\": gks[0][\"__delta_type\"],\n                \"__notional\": self.kwargs.leg1[\"notional\"],\n            },\n        )\n        return _\n\n\nclass FXRiskReversal(_BaseFXOptionStrat):\n    \"\"\"\n    An *FX Risk Reversal* :class:`~rateslib.instruments._BaseFXOptionStrat`.\n\n    A *RiskReversal* is composed of a lower strike :class:`~rateslib.instruments.FXPut`\n    and a higher strike :class:`~rateslib.instruments.FXCall`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import FXRiskReversal, Curve, FXForwards, FXRates, FXDeltaVolSmile, dt\n\n    .. ipython:: python\n\n       fxrr = FXRiskReversal(\n           expiry=\"3m\",\n           strike=[\"-25d\", \"25d\"],\n           eval_date=dt(2020, 1, 1),\n           spec=\"eurusd_call\",\n           notional=1000000,\n       )\n       fxrr.cashflows()\n\n    .. rubric:: Pricing\n\n    The pricing mirrors that for an :class:`~rateslib.instruments.FXCall`. All options use the\n    same ``curves``.\n    Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = {\"rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    Any *FXOption* also requires an :class:`~rateslib.fx.FXForwards` as input to the ``fx``\n    argument.\n\n    A ``vol`` argument must be provided to each *Instrument*. This can either be a single\n    value universally used for all, or an individual item as part of a sequence. Allowed\n    inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0 | vol_obj  # a single item universally applied\n       vol = [12.0, 13.0]  # values for the Put and Call respectively\n\n    The following pricing ``metric`` are available, with examples:\n\n    .. ipython:: python\n\n       eur = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.98})\n       usd = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.96})\n       fxf = FXForwards(\n           fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2020, 1, 3)),\n           fx_curves={\"eureur\": eur, \"eurusd\": eur, \"usdusd\": usd},\n       )\n       fxvs = FXDeltaVolSmile(\n           nodes={0.25: 11.0, 0.5: 9.8, 0.75: 10.7},\n           expiry=dt(2020, 4, 1),\n           eval_date=dt(2020, 1, 1),\n           delta_type=\"forward\",\n       )\n\n    - **'vol'**: the implied volatility value of the *FXCall* minus the volatility of the *FXPut*.\n      **'single_vol'** is also an alias for this.\n\n      .. ipython:: python\n\n         fxrr.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"vol\")\n         fxrr.instruments[0].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"vol\")\n         fxrr.instruments[1].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"vol\")\n\n    - **'premium'**: the summed cash premium amount, of both options, applicable to the 'payment'\n      date.\n\n      .. ipython:: python\n\n         fxrr.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n         fxrr.instruments[0].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n         fxrr.instruments[1].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n\n    - **'pips_or_%'**: if the premium currency is LHS of ``pair`` this is a % of notional, whilst if\n      the premium currency is RHS this gives a number of pips of the FX rate. Summed over both\n      options.\n\n      .. ipython:: python\n\n         fxrr.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n         fxrr.instruments[0].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n         fxrr.instruments[1].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **fx option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    strike: 2-tuple of float, Variable, str, :red:`required`\n        The strike of the put and the call in order.\n    pair: str, :red:`required`\n        The currency pair for the FX rate which settles the option, in 3-digit codes, e.g. \"eurusd\".\n        May be included as part of ``spec``.\n    notional: float, :green:`optional (set by 'defaults')`\n        The notional amount of each option expressed in units of LHS of ``pair``.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot. Spot is derived\n        from ``delivery_lag`` and ``calendar``.\n    modifier : str, :green:`optional (set by 'defaults')`\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"} for date evaluation.\n    eom: bool, :green:`optional (set by 'defaults')`\n        Whether to use end-of-month rolls when expiry is given as a month or year tenor.\n    calendar : calendar or str, :green:`optional`\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data.\n    delivery_lag: int, :green:`optional (set by 'defaults')`\n        The number of business days after expiry that the physical settlement of the FX\n        exchange occurs.\n    payment_lag: int or datetime, :green:`optional (set by 'defaults')`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    premium_ccy: str, :green:`optional (set as RHS of 'pair')`\n        The currency in which the premium is paid. Can *only* be one of the two currencies\n        in `pair`.\n    delta_type: FXDeltaMethod, str, :green:`optional (set by 'defaults')`\n        When deriving strike from delta use the equation associated with *'spot'* or *'forward'*\n        delta. If premium currency is LHS of ``pair`` then this will produce\n        **premium adjusted** delta values. If the `premium_ccy` is RHS of ``pair`` then delta values\n        are **unadjusted**.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: 2-tuple of float, :green:`optional`\n        The amount paid for the put and call in order. If not given assumes unpriced\n        *Options* and sets this as mid-market premium during pricing.\n    option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of each option's :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric : str, :green:`optional (set as \"pips_or_%\")`\n        The pricing metric returned by the ``rate`` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable, Sequence\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    \"\"\"\n\n    _rate_scalar = 100.0\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        strike: tuple[DualTypes | str, DualTypes | str],\n        pair: str_ = NoInput(0),\n        *,\n        notional: DualTypes_ = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        modifier: str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        delivery_lag: int_ = NoInput(0),\n        premium: tuple[DualTypes_, DualTypes_] = (NoInput(0), NoInput(0)),\n        premium_ccy: str_ = NoInput(0),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        delta_type: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        self._n = 2\n        vol_ = self._parse_vol(vol)\n        notional_ = _drb(defaults.notional, notional)\n        options = [\n            FXPut(\n                pair=pair,\n                expiry=expiry,\n                delivery_lag=delivery_lag,\n                payment_lag=payment_lag,\n                calendar=calendar,\n                modifier=modifier,\n                eom=eom,\n                eval_date=eval_date,\n                strike=strike[0],\n                notional=-notional_,\n                option_fixings=option_fixings[0]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                delta_type=delta_type,\n                premium=premium[0],\n                premium_ccy=premium_ccy,\n                curves=curves,\n                vol=vol_[0],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n            FXCall(\n                pair=pair,\n                expiry=expiry,\n                delivery_lag=delivery_lag,\n                payment_lag=payment_lag,\n                calendar=calendar,\n                modifier=modifier,\n                eom=eom,\n                eval_date=eval_date,\n                strike=strike[1],\n                notional=notional_,\n                option_fixings=option_fixings[1]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                delta_type=delta_type,\n                premium=premium[1],\n                premium_ccy=premium_ccy,\n                curves=curves,\n                vol=vol_[1],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n        ]\n        super().__init__(\n            options=options,\n            rate_weight=[-1.0, 1.0],\n            rate_weight_vol=[-1.0, 1.0],\n            metric=metric,\n            curves=curves,\n            vol=vol_,\n        )\n        self.kwargs.leg1[\"notional\"] = notional_\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> tuple[_Vol, _Vol]:  # type: ignore[override]\n        if not isinstance(vol, list | tuple):\n            vol = (vol,) * 2\n        return FXPut._parse_vol(vol[0]), FXCall._parse_vol(vol[1])\n"
  },
  {
    "path": "python/rateslib/instruments/fx_options/straddle.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.fx_options.call_put import FXCall, FXPut\nfrom rateslib.instruments.fx_options.risk_reversal import _BaseFXOptionStrat\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        VolStrat_,\n        VolT_,\n        _Vol,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass FXStraddle(_BaseFXOptionStrat):\n    \"\"\"\n    An *FX Straddle* :class:`~rateslib.instruments._BaseFXOptionStrat`.\n\n    A *Straddle* is composed of a :class:`~rateslib.instruments.FXPut`\n    and :class:`~rateslib.instruments.FXCall` with the same strike.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import FXStraddle, FXForwards, FXRates, FXDeltaVolSmile, Curve, dt\n\n    .. ipython:: python\n\n       fxs = FXStraddle(\n           expiry=\"3m\",\n           strike=1.10,  # <- \"atm_delta\" is also a common input\n           eval_date=dt(2020, 1, 1),\n           spec=\"eurusd_call\",\n           notional=1000000,\n       )\n       fxs.cashflows()\n\n    .. rubric:: Pricing\n\n    The pricing mirrors that for an :class:`~rateslib.instruments.FXCall`. All options use the\n    same ``curves``. Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = {\"rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    Any *FXOption* also requires an :class:`~rateslib.fx.FXForwards` as input to the ``fx``\n    argument.\n\n    A ``vol`` argument must be provided to each *Instrument*. This can either be a single\n    value universally used for all, or an individual item as part of a sequence. Allowed\n    inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0 | vol_obj  # a single item universally applied\n       vol = [12.0, 12.0]  # values for the Put and Call respectively\n\n    The following pricing ``metric`` are available, with examples:\n\n    .. ipython:: python\n\n       eur = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.98})\n       usd = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.96})\n       fxf = FXForwards(\n           fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2020, 1, 3)),\n           fx_curves={\"eureur\": eur, \"eurusd\": eur, \"usdusd\": usd},\n       )\n       fxvs = FXDeltaVolSmile(\n           nodes={0.25: 11.0, 0.5: 9.8, 0.75: 10.7},\n           expiry=dt(2020, 4, 1),\n           eval_date=dt(2020, 1, 1),\n           delta_type=\"forward\",\n       )\n\n    - **'vol'**: the implied volatility value of the straddle from a volatility object.\n      **'single_vol'** is also an alias for this, since both options assume the same volatility.\n\n      .. ipython:: python\n\n         fxs.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"vol\")\n\n    - **'premium'**: the summed cash premium amount, of both options, applicable to the 'payment'\n      date.\n\n      .. ipython:: python\n\n         fxs.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n         fxs.instruments[0].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n         fxs.instruments[1].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n\n    - **'pips_or_%'**: if the premium currency is LHS of ``pair`` this is a % of notional, whilst if\n      the premium currency is RHS this gives a number of pips of the FX rate. Summed over both\n      options.\n\n      .. ipython:: python\n\n         fxs.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n         fxs.instruments[0].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n         fxs.instruments[1].rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **fx option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    strike: float, Variable, str, :red:`required`\n        The strike of the put and the call.\n    pair: str, :red:`required`\n        The currency pair for the FX rate which settles the option, in 3-digit codes, e.g. \"eurusd\".\n        May be included as part of ``spec``.\n    notional: float, :green:`optional (set by 'defaults')`\n        The notional amount of each option expressed in units of LHS of ``pair``.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot. Spot is derived\n        from ``delivery_lag`` and ``calendar``.\n    modifier : str, :green:`optional (set by 'defaults')`\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"} for date evaluation.\n    eom: bool, :green:`optional (set by 'defaults')`\n        Whether to use end-of-month rolls when expiry is given as a month or year tenor.\n    calendar : calendar or str, :green:`optional`\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data.\n    delivery_lag: int, :green:`optional (set by 'defaults')`\n        The number of business days after expiry that the physical settlement of the FX\n        exchange occurs.\n    payment_lag: int or datetime, :green:`optional (set by 'defaults')`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    premium_ccy: str, :green:`optional (set as RHS of 'pair')`\n        The currency in which the premium is paid. Can *only* be one of the two currencies\n        in `pair`.\n    delta_type: FXDeltaMethod, str, :green:`optional (set by 'defaults')`\n        When deriving strike from delta use the equation associated with *'spot'* or *'forward'*\n        delta. If premium currency is LHS of ``pair`` then this will produce\n        **premium adjusted** delta values. If the `premium_ccy` is RHS of ``pair`` then delta values\n        are **unadjusted**.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: 2-tuple of float, :green:`optional`\n        The amount paid for the put and call in order. If not given assumes unpriced\n        *Options* and sets this as mid-market premium during pricing.\n    option_fixings: 2-tuple of float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of each option's :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric : str, :green:`optional (set as \"pips_or_%\")`\n        The pricing metric returned by the ``rate`` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable, Sequence\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    \"\"\"\n\n    _rate_scalar = 100.0\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        strike: DualTypes | str,\n        pair: str_ = NoInput(0),\n        *,\n        notional: DualTypes_ = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        modifier: str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        delivery_lag: int_ = NoInput(0),\n        premium: tuple[DualTypes_, DualTypes_] = (NoInput(0), NoInput(0)),\n        premium_ccy: str_ = NoInput(0),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        delta_type: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        vol_ = self._parse_vol(vol)\n        notional_ = _drb(defaults.notional, notional)\n        options = [\n            FXPut(\n                pair=pair,\n                expiry=expiry,\n                delivery_lag=delivery_lag,\n                payment_lag=payment_lag,\n                calendar=calendar,\n                modifier=modifier,\n                eom=eom,\n                eval_date=eval_date,\n                strike=strike,\n                notional=notional_,\n                option_fixings=option_fixings[0]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                delta_type=delta_type,\n                premium=premium[0],\n                premium_ccy=premium_ccy,\n                curves=curves,\n                vol=vol_[0],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n            FXCall(\n                pair=pair,\n                expiry=expiry,\n                delivery_lag=delivery_lag,\n                payment_lag=payment_lag,\n                calendar=calendar,\n                modifier=modifier,\n                eom=eom,\n                eval_date=eval_date,\n                strike=strike,\n                notional=notional_,\n                option_fixings=option_fixings[1]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                delta_type=delta_type,\n                premium=premium[1],\n                premium_ccy=premium_ccy,\n                curves=curves,\n                vol=vol_[1],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n        ]\n        super().__init__(\n            options=options,\n            rate_weight=[1.0, 1.0],\n            rate_weight_vol=[0.5, 0.5],\n            metric=metric,\n            curves=curves,\n            vol=vol_,\n        )\n        self.kwargs.leg1[\"notional\"] = notional_\n        self.kwargs.leg2[\"premium_ccy\"] = self.instruments[0].kwargs.leg2[\"premium_ccy\"]\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> tuple[_Vol, _Vol]:  # type: ignore[override]\n        if not isinstance(vol, list | tuple):\n            vol = (vol,) * 2\n        return FXPut._parse_vol(vol[0]), FXCall._parse_vol(vol[1])\n\n    def _set_notionals(self, notional: DualTypes) -> None:\n        \"\"\"\n        Set the notionals on each option period. Mainly used by Brokerfly for vega neutral\n        strangle and straddle.\n        \"\"\"\n        for option in self.instruments:\n            option.kwargs.leg1[\"notional\"] = notional\n            option._option.settlement_params._notional = notional\n"
  },
  {
    "path": "python/rateslib/instruments/fx_options/strangle.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual import dual_log, newton_1dim\nfrom rateslib.dual.utils import _set_ad_order_objects\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import FXDeltaMethod\nfrom rateslib.instruments.fx_options.call_put import FXCall, FXPut\nfrom rateslib.instruments.fx_options.risk_reversal import _BaseFXOptionStrat\nfrom rateslib.instruments.protocols.pricing import (\n    _get_curve,\n    _get_fx_forwards_maybe_from_solver,\n    _get_fx_vol,\n    _parse_curves,\n    _parse_vol,\n    _Vol,\n)\nfrom rateslib.periods.utils import _validate_fx_as_forwards\nfrom rateslib.splines import evaluate\nfrom rateslib.volatility import FXDeltaVolSmile, FXDeltaVolSurface, FXSabrSmile, FXSabrSurface\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalInput,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        FXForwards,\n        FXForwards_,\n        Solver,\n        Solver_,\n        VolStrat_,\n        VolT_,\n        _BaseFXOptionPeriod,\n        _FXVolOption,\n        _Vol,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass FXStrangle(_BaseFXOptionStrat):\n    \"\"\"\n    An *FX Strangle* :class:`~rateslib.instruments._BaseFXOptionStrat`.\n\n    A *Strangle* is composed of a lower strike :class:`~rateslib.instruments.FXPut`\n    and a higher strike :class:`~rateslib.instruments.FXCall`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import FXStrangle, Curve, FXForwards, FXRates, FXDeltaVolSmile, dt\n\n    .. ipython:: python\n\n       fxs = FXStrangle(\n           expiry=\"3m\",\n           strike=[\"-10d\", \"10d\"],\n           eval_date=dt(2020, 1, 1),\n           spec=\"eurusd_call\",\n           notional=1000000,\n       )\n       fxs.cashflows()\n\n    .. rubric:: Pricing\n\n    The pricing mirrors that for an :class:`~rateslib.instruments.FXCall`. All options use the\n    same ``curves``.\n    Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = {\"rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    Any *FXOption* also requires an :class:`~rateslib.fx.FXForwards` as input to the ``fx``\n    argument.\n\n    A ``vol`` argument must be provided to each *Instrument*. This can either be a single\n    value universally used for all, or an individual item as part of a sequence. Allowed\n    inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0 | vol_obj  # a single item universally applied\n       vol = [12.0, 12.0]  # values for the Put and Call respectively\n\n    *FXStrangles* have peculiar market conventions. If the strikes are given as delta percentages\n    then numeric values will first be derived using the *'single_vol'* approach. Any *'premium'*\n    or *'pips_or_%'* values can then be calculated using those strikes and this volatility.\n    The following pricing ``metric`` are available, with examples:\n\n    .. ipython:: python\n\n       eur = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.98})\n       usd = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.96})\n       fxf = FXForwards(\n           fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2020, 1, 3)),\n           fx_curves={\"eureur\": eur, \"eurusd\": eur, \"usdusd\": usd},\n       )\n       fxvs = FXDeltaVolSmile(\n           nodes={0.25: 11.0, 0.5: 9.8, 0.75: 10.7},\n           expiry=dt(2020, 4, 1),\n           eval_date=dt(2020, 1, 1),\n           delta_type=\"forward\",\n       )\n\n    - **'single_vol'**: the singular volatility value that when applied to each option separately\n      yields a summed premium amount equal to the summed premium when each option is valued with\n      the appropriate volatility from an object (with the strikes determined by the single vol).\n      **'vol'** is an alias for single vol and returns the same value.\n\n      .. ipython:: python\n\n         fxs.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"single_vol\")\n         fxs.rate(vol=[12.163490, 12.163490], curves=[eur, usd], fx=fxf, metric=\"premium\")\n\n      This requires an iterative calculation for which the tolerance is set to 1e-6 with a\n      maximum allowed number of iterations of 10.\n\n    - **'premium'**: the summed cash premium amount, of both options, applicable to the 'payment'\n      date. If strikes are given as delta percentages then they are first determined using the\n      *'single_vol'*.\n\n      .. ipython:: python\n\n         fxs.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"premium\")\n\n    - **'pips_or_%'**: if the premium currency is LHS of ``pair`` this is a % of notional, whilst if\n      the premium currency is RHS this gives a number of pips of the FX rate. Summed over both\n      options. For strikes set with delta percentages these are first determined using the\n      'single_vol'.\n\n      .. ipython:: python\n\n         fxs.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric=\"pips_or_%\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **fx option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    strike: 2-tuple of float, Variable, str, :red:`required`\n        The strikes of the put and the call in order.\n    pair: str, :red:`required`\n        The currency pair for the FX rate which settles the option, in 3-digit codes, e.g. \"eurusd\".\n        May be included as part of ``spec``.\n    notional: float, :green:`optional (set by 'defaults')`\n        The notional amount of each option expressed in units of LHS of ``pair``.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot. Spot is derived\n        from ``delivery_lag`` and ``calendar``.\n    modifier : str, :green:`optional (set by 'defaults')`\n        The modification rule, in {\"F\", \"MF\", \"P\", \"MP\"} for date evaluation.\n    eom: bool, :green:`optional (set by 'defaults')`\n        Whether to use end-of-month rolls when expiry is given as a month or year tenor.\n    calendar : calendar or str, :green:`optional`\n        The holiday calendar object to use. If str, looks up named calendar from\n        static data.\n    delivery_lag: int, :green:`optional (set by 'defaults')`\n        The number of business days after expiry that the physical settlement of the FX\n        exchange occurs.\n    payment_lag: int or datetime, :green:`optional (set by 'defaults')`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    premium_ccy: str, :green:`optional (set as RHS of 'pair')`\n        The currency in which the premium is paid. Can *only* be one of the two currencies\n        in `pair`.\n    delta_type: FXDeltaMethod, str, :green:`optional (set by 'defaults')`\n        When deriving strike from delta use the equation associated with *'spot'* or *'forward'*\n        delta. If premium currency is LHS of ``pair`` then this will produce\n        **premium adjusted** delta values. If the `premium_ccy` is RHS of ``pair`` then delta values\n        are **unadjusted**.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: 2-tuple of float, :green:`optional`\n        The amount paid for the put and call in order. If not given assumes unpriced\n        *Options* and sets this as mid-market premium during pricing.\n    option_fixings: 2-tuple of float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of each option's :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric : str, :green:`optional (set as \"single_vol\")`\n        The pricing metric returned by the ``rate`` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable, Sequence\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    \"\"\"\n\n    _rate_scalar = 100.0\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        strike: tuple[DualTypes | str, DualTypes | str],\n        pair: str_ = NoInput(0),\n        *,\n        notional: DualTypes_ = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        modifier: str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        delivery_lag: int_ = NoInput(0),\n        premium: tuple[DualTypes_, DualTypes_] = (NoInput(0), NoInput(0)),\n        premium_ccy: str_ = NoInput(0),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        delta_type: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        vol_ = self._parse_vol(vol)\n        notional_ = _drb(defaults.notional, notional)\n        options = [\n            FXPut(\n                pair=pair,\n                expiry=expiry,\n                delivery_lag=delivery_lag,\n                payment_lag=payment_lag,\n                calendar=calendar,\n                modifier=modifier,\n                eom=eom,\n                eval_date=eval_date,\n                strike=strike[0],\n                notional=notional_,\n                option_fixings=option_fixings[0]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                delta_type=delta_type,\n                premium=premium[0],\n                premium_ccy=premium_ccy,\n                curves=curves,\n                vol=vol_[0],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n            FXCall(\n                pair=pair,\n                expiry=expiry,\n                delivery_lag=delivery_lag,\n                payment_lag=payment_lag,\n                calendar=calendar,\n                modifier=modifier,\n                eom=eom,\n                eval_date=eval_date,\n                strike=strike[1],\n                notional=notional_,\n                option_fixings=option_fixings[1]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                delta_type=delta_type,\n                premium=premium[1],\n                premium_ccy=premium_ccy,\n                curves=curves,\n                vol=vol_[1],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n        ]\n        super().__init__(\n            options=options,\n            rate_weight=[1.0, 1.0],\n            rate_weight_vol=[0.5, 0.5],\n            metric=_drb(\"single_vol\", metric),\n            curves=curves,\n            vol=vol_,\n        )\n        self.kwargs.leg1[\"notional\"] = notional_\n        self.kwargs.meta[\"fixed_delta\"] = [\n            isinstance(strike[0], str)\n            and strike[0][-1].lower() == \"d\"\n            and strike[0].lower() != \"atm_forward\",\n            isinstance(strike[1], str)\n            and strike[1][-1].lower() == \"d\"\n            and strike[1].lower() != \"atm_forward\",\n        ]\n        self.kwargs.leg1[\"delivery\"] = self.instruments[0].kwargs.leg1[\"delivery\"]\n        self.kwargs.leg1[\"delta_type\"] = self.instruments[0].kwargs.leg1[\"delta_type\"]\n        self.kwargs.leg1[\"expiry\"] = self.instruments[0].kwargs.leg1[\"expiry\"]\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> tuple[_Vol, _Vol]:  # type: ignore[override]\n        if not isinstance(vol, list | tuple):\n            vol = (vol,) * 2\n        return FXPut._parse_vol(vol[0]), FXCall._parse_vol(vol[1])\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        return self._rate(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            base=base,\n            vol=vol,\n            metric=metric,\n            forward=forward,\n            settlement=settlement,\n        )\n\n    def _rate(\n        self,\n        curves: CurvesT_,\n        solver: Solver_,\n        fx: FXForwards_,\n        base: str_,\n        vol: VolStrat_,\n        metric: str_,\n        settlement: datetime_,\n        forward: datetime_,\n        record_greeks: bool = False,\n    ) -> DualTypes:\n        metric_: str = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n        if metric_ != \"single_vol\" and not any(self.kwargs.meta[\"fixed_delta\"]):\n            # the strikes are explicitly defined and independent across options.\n            # can evaluate separately, therefore the default method will suffice.\n            return super().rate(\n                curves=curves, solver=solver, fx=fx, base=base, vol=vol, metric=metric_\n            )\n        else:\n            # must perform single vol evaluation to determine mkt convention strikes\n            single_vol = self._rate_single_vol(\n                curves=curves, solver=solver, fx=fx, base=base, vol=vol, record_greeks=record_greeks\n            )\n            if metric_ == \"single_vol\":\n                return single_vol\n            elif metric_ in [\"premium\", \"pips_or_%\"]:\n                # return the premiums using the single_vol as the volatility\n                return super().rate(\n                    curves=curves, solver=solver, fx=fx, vol=single_vol, metric=metric_\n                )\n            elif metric_ == \"vol\":\n                # this will return the same value as the single_vol, since the `vol` is\n                # directly specified\n                # return super().rate(\n                #     curves=curves, solver=solver, fx=fx, vol=single_vol, metric=metric_\n                # )\n                return single_vol\n            else:\n                raise ValueError(\n                    f\"Metric {metric_} must be in {{'single_vol', 'premium', 'pips_or_%', 'vol'}}.\"\n                )\n\n    def _rate_single_vol(\n        self,\n        curves: CurvesT_,\n        solver: Solver_,\n        fx: FXForwards_,\n        base: str_,\n        vol: VolStrat_,\n        record_greeks: bool,\n    ) -> DualTypes:\n        \"\"\"\n        Solve the single vol rate metric for a strangle using iterative market convergence routine.\n        \"\"\"\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", False, False, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n\n        v: tuple[tuple[_Vol, _Vol], tuple[_Vol, _Vol], Solver] = _parse_vol(  # type: ignore[assignment]\n            self, vol, solver, True\n        )\n\n        fxf = _validate_fx_as_forwards(_get_fx_forwards_maybe_from_solver(solver=solver, fx=fx))\n\n        vol_0 = _get_fx_vol(True, False, v[0][0], v[1][0], solver)\n        vol_1 = _get_fx_vol(True, False, v[0][1], v[1][1], solver)\n\n        # Get initial data from objects in their native AD order\n        spot: datetime = fxf.pairs_settlement[self.kwargs.leg1[\"pair\"].pair]\n        w_spot: DualTypes = rate_curve[spot]\n        w_deli: DualTypes = rate_curve[self.kwargs.leg1[\"delivery\"]]\n        f_d: DualTypes = fxf.rate(self.kwargs.leg1[\"pair\"], self.kwargs.leg1[\"delivery\"])\n        f_t: DualTypes = fxf.rate(self.kwargs.leg1[\"pair\"], spot)\n        z_w_0 = (\n            1.0\n            if self.kwargs.leg1[\"delta_type\"]\n            in [FXDeltaMethod.ForwardPremiumAdjusted, FXDeltaMethod.Forward]\n            else w_deli / w_spot\n        )\n        f_0 = (\n            f_d\n            if self.kwargs.leg1[\"delta_type\"]\n            in [FXDeltaMethod.ForwardPremiumAdjusted, FXDeltaMethod.Forward]\n            else f_t\n        )\n\n        eta1 = None\n        fzw1zw0: DualTypes = 0.0\n        if isinstance(\n            vol_0, FXDeltaVolSurface | FXDeltaVolSmile\n        ):  # multiple Vol objects cannot be used, will derive conventions from the first one found.\n            eta1 = (\n                -0.5\n                if vol_0.meta.delta_type\n                in [FXDeltaMethod.ForwardPremiumAdjusted, FXDeltaMethod.SpotPremiumAdjusted]\n                else 0.5\n            )\n            z_w_1 = (\n                1.0\n                if vol_0.meta.delta_type\n                in [FXDeltaMethod.ForwardPremiumAdjusted, FXDeltaMethod.Forward]\n                else w_deli / w_spot\n            )\n            fzw1zw0 = f_0 * z_w_1 / z_w_0\n\n        # Determine the initial guess for Newton type iterations\n\n        _ad = _set_ad_order_objects([0] * 5, [vol_0, vol_1, rate_curve, disc_curve, fxf])\n        gks: list[dict[str, Any]] = [\n            self.instruments[0]._analytic_greeks_reduced(\n                curves=[rate_curve, disc_curve],\n                solver=NoInput(0),\n                fx=fxf,\n                base=base,\n                vol=vol_0,\n            ),\n            self.instruments[1]._analytic_greeks_reduced(\n                curves=[rate_curve, disc_curve],\n                solver=NoInput(0),\n                fx=fxf,\n                base=base,\n                vol=vol_1,\n            ),\n        ]\n\n        g0: DualTypes = gks[0][\"__vol\"] * gks[0][\"vega\"] + gks[1][\"__vol\"] * gks[1][\"vega\"]\n        g0 /= gks[0][\"vega\"] + gks[1][\"vega\"]\n\n        put_op_period: _BaseFXOptionPeriod = self.instruments[0]._option\n        call_op_period: _BaseFXOptionPeriod = self.instruments[1]._option\n\n        def root1d(\n            tgt_vol: DualTypes, fzw1zw0: DualTypes, as_float: bool\n        ) -> tuple[DualTypes, DualTypes]:\n            if not as_float:\n                # reset objects to their original order and perform final iterations\n                _set_ad_order_objects(_ad, [vol_0, vol_1, rate_curve, disc_curve, fxf])\n\n            # Determine the greeks of the options with the current tgt_vol iterate\n            gks = [\n                self.instruments[0]._analytic_greeks_reduced(\n                    curves=[rate_curve, disc_curve],\n                    solver=NoInput(0),\n                    fx=fxf,\n                    base=base,\n                    vol=tgt_vol * 100.0,\n                ),\n                self.instruments[1]._analytic_greeks_reduced(\n                    curves=[rate_curve, disc_curve],\n                    solver=NoInput(0),\n                    fx=fxf,\n                    base=base,\n                    vol=tgt_vol * 100.0,\n                ),\n            ]\n\n            # Also determine the greeks of these options measured with the market smile vol.\n            # (note the strikes have been set by previous call, call OptionPeriods direct\n            # to avoid re-determination)\n            s_gks = [\n                put_op_period._base_analytic_greeks(\n                    rate_curve=rate_curve,\n                    disc_curve=disc_curve,\n                    fx=fxf,\n                    fx_vol=vol_0,\n                    _reduced=True,\n                ),\n                call_op_period._base_analytic_greeks(\n                    rate_curve=rate_curve,\n                    disc_curve=disc_curve,\n                    fx=fxf,\n                    fx_vol=vol_1,\n                    _reduced=True,\n                ),\n            ]\n\n            # The value of the root function is derived from the 4 previous calculated prices\n            f0 = s_gks[0][\"__bs76\"] + s_gks[1][\"__bs76\"] - gks[0][\"__bs76\"] - gks[1][\"__bs76\"]\n\n            dc1_dvol1_0 = _d_c_hat_d_sigma_hat(gks[0], self.kwargs.meta[\"fixed_delta\"][0])\n            dcmkt_dvol1_0 = _d_c_mkt_d_sigma_hat(\n                gks[0],\n                s_gks[0],\n                self.kwargs.leg1[\"expiry\"],\n                vol_0,\n                eta1,\n                self.kwargs.meta[\"fixed_delta\"][0],\n                fzw1zw0,\n                fxf,\n            )\n            dc1_dvol1_1 = _d_c_hat_d_sigma_hat(gks[1], self.kwargs.meta[\"fixed_delta\"][1])\n            dcmkt_dvol1_1 = _d_c_mkt_d_sigma_hat(\n                gks[1],\n                s_gks[1],\n                self.kwargs.leg1[\"expiry\"],\n                vol_1,\n                eta1,\n                self.kwargs.meta[\"fixed_delta\"][1],\n                fzw1zw0,\n                fxf,\n            )\n            f1 = dcmkt_dvol1_0 + dcmkt_dvol1_1 - dc1_dvol1_0 - dc1_dvol1_1\n\n            return f0, f1\n\n        root_solver = newton_1dim(\n            root1d,\n            g0,\n            args=(fzw1zw0,),\n            pre_args=(True,),  # solve `as_float` in iterations\n            final_args=(False,),  # capture AD in final iterations\n            raise_on_fail=True,\n            max_iter=10,\n            func_tol=1e-6,\n        )\n        tgt_vol: DualTypes = root_solver[\"g\"] * 100.0\n\n        if record_greeks:  # this needs to be explicitly called since it degrades performance\n            self._greeks[\"strangle\"] = {\n                \"single_vol\": {\n                    \"FXPut\": self.instruments[0].analytic_greeks(curves, solver, fxf, tgt_vol),\n                    \"FXCall\": self.instruments[1].analytic_greeks(curves, solver, fxf, tgt_vol),\n                },\n                \"market_vol\": {\n                    \"FXPut\": put_op_period.analytic_greeks(rate_curve, disc_curve, fxf, vol_0),\n                    \"FXCall\": call_op_period.analytic_greeks(rate_curve, disc_curve, fxf, vol_1),\n                },\n            }\n\n        return tgt_vol\n\n\n# Calculations related to Strange:single_vol\n\n\ndef _d_c_hat_d_sigma_hat(\n    g: dict[str, Any],  # greeks\n    fixed_delta: bool,\n) -> DualTypes:\n    \"\"\"\n    Return the total derivative of option priced with single vol with respect to single\n    vol.\n\n    Parameters\n    ----------\n    g: dict\n        The dict of greeks for the given option period measured against the tgt, single vol.\n    fixed_delta: bool\n        Whether the given FXOption is defined by fixed delta or an explicit strike.\n\n    Returns\n    -------\n    DualTypes\n    \"\"\"\n    if not fixed_delta:\n        # kega is 0.0\n        return g[\"vega\"]  # type: ignore[no-any-return]\n    else:\n        return g[\"_kappa\"] * g[\"_kega\"] + g[\"vega\"]  # type: ignore[no-any-return]\n\n\ndef _d_c_mkt_d_sigma_hat(\n    g: dict[str, Any],  # greeks\n    sg: dict[str, Any],  # smile_greeks\n    expiry: datetime,\n    vol: _FXVolOption,\n    eta1: float | None,\n    fixed_delta: bool,\n    fzw1zw0: DualTypes | None,\n    fxf: FXForwards,\n) -> DualTypes:\n    \"\"\"\n    Return the total derivative of option priced with mkt vol with respect to single\n    vol.\n\n    Parameters\n    ----------\n    g: dict\n        The dict of greeks for the given option period measured against the tgt, single vol.\n    sg: dict\n        The dict of greeks for the given option period measured against the smile.\n    expiry: datetime\n        The expiry of the Option.\n    vol: VolObj\n        The smile object.\n    eta1: float | None\n        The delta type of the Smile if available\n    fixed_delta: bool\n        Whether the option is defined by fixed delta or an explicit strike.\n    fxf: FXForwards,\n        Used by SabrSurface to forecast multiple forward rates for cross-sectional smiles before\n        interpolation.\n\n    Returns\n    -------\n    DualTypes\n    \"\"\"\n    if not fixed_delta:\n        return 0.0  # kega is zero and the mkt vol has no sensitivity to vol_hat.\n    else:\n        if isinstance(vol, FXDeltaVolSurface | FXDeltaVolSmile):\n            if isinstance(vol, FXDeltaVolSurface):\n                vol = vol.get_smile(expiry)\n\n            dvol_ddeltaidx = evaluate(vol.nodes.spline.spline, sg[\"_delta_index\"], 1) * 0.01\n\n            ddeltaidx_dvol1 = sg[\"gamma\"] * fzw1zw0\n            if eta1 < 0:  # type: ignore[operator]\n                # premium adjusted vol smile\n                ddeltaidx_dvol1 += sg[\"_delta_index\"]\n            ddeltaidx_dvol1 *= g[\"_kega\"] / sg[\"__strike\"]\n\n            _ = dual_log(sg[\"__strike\"] / sg[\"__forward\"]) / sg[\"__vol\"]\n            _ += eta1 * sg[\"__vol\"] * sg[\"__sqrt_t\"] ** 2\n            _ *= dvol_ddeltaidx * sg[\"gamma\"] * fzw1zw0\n            ddeltaidx_dvol1 /= 1 + _\n\n            dvol_dvol1: DualTypes = dvol_ddeltaidx * ddeltaidx_dvol1\n        elif isinstance(vol, FXSabrSmile):\n            dvol_dk = vol._d_sabr_d_k_or_f(\n                k=sg[\"__strike\"],\n                f=sg[\"__forward\"],\n                expiry=expiry,\n                as_float=False,\n                derivative=1,\n            )[1]\n            dvol_dvol1 = dvol_dk * g[\"_kega\"]\n        elif isinstance(vol, FXSabrSurface):\n            dvol_dk = vol._d_sabr_d_k_or_f(\n                k=sg[\"__strike\"],\n                f=fxf,\n                expiry=expiry,\n                as_float=False,\n                derivative=1,\n            )[1]\n            dvol_dvol1 = dvol_dk * g[\"_kega\"]\n        else:\n            dvol_dvol1 = 0.0\n\n        return sg[\"_kappa\"] * g[\"_kega\"] + sg[\"vega\"] * dvol_dvol1  # type: ignore[no-any-return]\n"
  },
  {
    "path": "python/rateslib/instruments/fx_options/vol_value.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, NoReturn\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _get_fx_forwards_maybe_from_solver,\n    _get_fx_vol,\n    _parse_vol,\n    _Vol,\n)\nfrom rateslib.periods.utils import _validate_fx_as_forwards\nfrom rateslib.volatility import FXDeltaVolSmile, FXDeltaVolSurface, FXSabrSmile, FXSabrSurface\nfrom rateslib.volatility.ir import _BaseIRCube, _BaseIRSmile\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        datetime_,\n        str_,\n    )\n\n\nclass FXVolValue(_BaseInstrument):\n    \"\"\"\n    A pseudo *Instrument* used to calibrate an *FX Vol Object* within a\n    :class:`~rateslib.solver.Solver`.\n\n    .. rubric:: Examples\n\n    Examples\n    --------\n    The below :class:`~rateslib.volatility.FXDeltaVolSmile` is solved directly\n    from calibrating volatility values.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.volatility import FXDeltaVolSmile\n       from rateslib.instruments import FXVolValue\n       from rateslib.solver import Solver\n\n    .. ipython:: python\n\n       smile = FXDeltaVolSmile(\n           nodes={0.3: 10.0, 0.7: 10.0},\n           eval_date=dt(2023, 3, 16),\n           expiry=dt(2023, 6, 16),\n           delta_type=\"forward\",\n           id=\"VolSmile\",\n       )\n       instruments = [\n           FXVolValue(0.4, vol=\"VolSmile\"),\n           FXVolValue(0.6, vol=smile)\n       ]\n       solver = Solver(curves=[smile], instruments=instruments, s=[8.9, 7.8])\n       smile[0.3]\n       smile[0.4]\n       smile[0.6]\n       smile[0.7]\n\n    .. rubric:: Pricing\n\n    An *FX Vol Value* requires, and will calibrate, just one *FX Vol Object*.\n\n    Allowable inputs are:\n\n    .. code-block:: python\n\n       vol = fx_vol_obj | [fx_vol_obj]  #  a single object is detected\n       vol = {\"fx_vol\": fx_vol_obj}  # dict form is explicit\n\n    Currently the only available ``metric`` is *'vol'* which returns the specific volatility value\n    for the index value, i.e. a delta-index for a *DeltaVol* type object, or a strike for a\n    *SABR* type object.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    index_value : float, Dual, Dual2, :red:`required`\n        The value of some index to the *FXVolSmile* or *FXVolSurface*.\n    expiry: datetime, :green:`optional`\n        The expiry at which to evaluate. This will only be used with *Surfaces*, not *Smiles*.\n    metric: str, :green:`optional (set as 'vol')`\n        The default metric to return from the ``rate`` method.\n    vol: str, FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, :green:`optional`\n        The associated object from which to determine the ``rate``.\n\n    \"\"\"\n\n    _rate_scalar = 1.0\n\n    def __init__(\n        self,\n        index_value: DualTypes,\n        expiry: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n    ):\n        user_args = dict(\n            expiry=expiry,\n            index_value=index_value,\n            vol=self._parse_vol(vol),\n            metric=metric,\n        )\n        default_args = dict(convention=defaults.convention, metric=\"vol\", curves=NoInput(0))\n        self._kwargs = _KWArgs(\n            spec=NoInput(0),\n            user_args=user_args,\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\", \"vol\"],\n        )\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        if isinstance(vol, _Vol):\n            return vol\n        elif isinstance(vol, _BaseIRSmile | _BaseIRCube):\n            raise TypeError(\n                f\"`vol` must be suitable object for FX vol pricing. Got {type(vol).__name__}\"\n            )\n        else:\n            return _Vol(fx_vol=vol)\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return a value derived from a *Curve*.\n\n        Parameters\n        ----------\n        curves : Curve, LineCurve, str or list of such\n            Uses only one *Curve*, the one given or the first in the list.\n        solver : Solver, optional\n            The numerical :class:`~rateslib.solver.Solver` that constructs\n            ``Curves`` from calibrating instruments.\n        fx : float, FXRates, FXForwards, optional\n            Not used.\n        base : str, optional\n            Not used.\n        vol: float, Dual, Dual2, FXDeltaVolSmile or FXDeltaVolSurface\n            The volatility used in calculation.\n        metric: str in {\"curve_value\", \"index_value\", \"cc_zero_rate\"}, optional\n            Configures which type of value to return from the applicable *Curve*.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n\n        \"\"\"\n        v = _parse_vol(self, vol, solver, False)\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n\n        if metric_ == \"vol\":\n            vol_ = _get_fx_vol(False, False, *v)\n            if isinstance(vol_, FXDeltaVolSmile | FXDeltaVolSurface):\n                # Must initialise with an ``expiry`` if a Surface is used\n                return vol_._get_index(\n                    delta_index=self.kwargs.leg1[\"index_value\"], expiry=self.kwargs.leg1[\"expiry\"]\n                )\n            elif isinstance(vol_, FXSabrSmile):\n                fx_ = _validate_fx_as_forwards(\n                    _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n                )\n                # if Sabr VolObj is not initialised with a `pair` this will create an error\n                pair: str = vol_.meta.pair  # type: ignore[assignment]\n                return vol_.get_from_strike(\n                    k=self.kwargs.leg1[\"index_value\"],\n                    f=fx_.rate(pair=pair, settlement=vol_.meta.delivery),\n                    expiry=self.kwargs.leg1[\"expiry\"],\n                )[1]\n            elif isinstance(vol_, FXSabrSurface):\n                fx_ = _validate_fx_as_forwards(\n                    _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n                )\n                # if Sabr VolObj is not initialised with a `pair` this will create an error\n                return vol_.get_from_strike(\n                    k=self.kwargs.leg1[\"index_value\"],\n                    f=fx_,\n                    expiry=self.kwargs.leg1[\"expiry\"],\n                )[1]\n            else:\n                raise RuntimeError(\n                    \"FX Vol type is unmapped. Please report this issue.\"\n                )  # pragma: no cover\n\n        raise ValueError(\"`metric` must be in {'vol'}.\")\n\n    def npv(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`VolValue` instrument has no concept of NPV.\")\n\n    def cashflows(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`VolValue` instrument has no concept of cashflows.\")\n\n    def analytic_delta(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`VolValue` instrument has no concept of analytic delta.\")\n"
  },
  {
    "path": "python/rateslib/instruments/fx_swap.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.data.fixings import _fx_index_set_cross, _get_fx_index\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _get_fx_forwards_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import CustomLeg\nfrom rateslib.periods import Cashflow\nfrom rateslib.scheduling import Schedule\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        FXIndex,\n        FXIndex_,\n        LegFixings,\n        RollDay,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime_,\n        str_,\n    )\n\n\nclass FXSwap(_BaseInstrument):\n    \"\"\"\n    An *FX swap* composing two\n    :class:`~rateslib.legs.CustomLeg`\n    of individual :class:`~rateslib.periods.Cashflow` of different currencies.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from datetime import datetime as dt\n       from rateslib.instruments.fx_swap import FXSwap\n       from rateslib import Curve, FXRates, FXForwards\n\n    Paying a 3M EURUSD *FX Swap* expressed in USD notional at 56.5 swap points.\n\n    .. ipython:: python\n\n       fxs = FXSwap(\n           effective=dt(2022, 1, 19),\n           termination=\"3m\",\n           calendar=\"tgt|fed\",\n           pair=\"eurusd\",\n           leg2_notional=-10e6,\n           split_notional=-10.25e6,\n           fx_rate=1.15,\n           points=56.5,\n       )\n       fxs.cashflows()\n\n    .. rubric:: Pricing\n\n    An *FX Swap* requires a *disc curve* and a *leg2 disc curve* to discount the cashflows\n    of the respective currencies (typically with the same collateral definition).\n    The following input formats are allowed:\n\n    .. code-block:: python\n\n       curves = [disc_curve, leg2_disc_curve]  #  two curves are applied in the given order\n       curves = [None, disc_curve, None, leg2_disc_curve]  # four curves applied to each leg\n       curves = {\"disc_curve\": disc_curve, \"leg2_disc_curve\": leg2_disc_curve}  # dict form is explicit\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The settlement date of the first currency pair.\n    termination : datetime, str, :red:`required`\n        The settlement of the second currency pair. If given as string requires additional\n        scheduling arguments to derive from ``effective``.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        If ``termination`` is str tenor, the roll day for its determination.\n    eom : bool, :green:`optional`\n        If ``termination`` is str tenor, the end-of-month preference if ``roll`` is not specified.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional (set by 'defaults')`\n        If ``termination`` is str tenor, the adjustment to apply to its determination.\n    calendar : calendar, str, :green:`optional (set as 'all')`\n        If ``termination`` is str tenor, the calendar to apply to its determination.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    pair : FXIndex, str, :red:`required`\n        The FX pair of the *Instrument* (6-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        To define the notional of the trade in units of LHS pair use ``notional``.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n        To define the notional of the trade in units of RHS pair use ``leg2_notional``.\n        Only one of ``notional`` or ``leg2_notional`` can be specified.\n    split_notional: float, Variable, :green:`optional`\n        If the second cashflow has a rate adjusted notional to mitigate spot FX risk this is\n        entered as this argument. If not given the *FX Swap* is assumed not to have split notional.\n        Expressed in the same units as that given for either ``notional`` or ``leg2_notional``.\n\n        .. note::\n\n           The following are **rate parameters**. Both must be given simultaneously or not\n           at all.\n\n    fx_rate : float, Dual, Dual2, Variable, :green:`optional`\n        The ``fx_rate`` with direction according to ``pair`` to define the missing notional.\n    points : float, Dual, Dual2, Variable, :green:`optional`\n        The swap points valued (in 10,000ths) to add to ``fx_rate`` to arrive at the\n        FX rate at maturity of the swap.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    Notes\n    -----\n    An *FXSwap* is constructed from two *Legs* where one is non-deliverable. A fully\n    specified *Instrument* is one whose non-deliverable *fx fixings* are set at initialisation\n    via ``points`` and either ``fx_fixings`` or ``leg2_fx_fixings``. If these are not given then\n    these values will be forecast :class:`~rateslib.data.fixings.FXFixing`, which will likely\n    impact risk sensitivity calculations. This is best observed in the following example where\n    two similar *FXSwaps* are created, but their risks (as demonstrated by the Dual gradients)\n    are different.\n\n    .. ipython:: python\n\n       eurusd = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.95})\n       usdusd = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.94})\n       fxf = FXForwards(\n           fx_rates=FXRates({\"eurusd\": 1.15}, settlement=dt(2000, 1, 3)),\n           fx_curves={\"usdusd\": usdusd, \"eureur\": eurusd, \"eurusd\": eurusd},\n       )\n       fxs1 = FXSwap(\n           dt(2000, 1, 10),\n           dt(2000, 4, 10),\n           pair=\"eurusd\",\n           notional=1e6,\n           fx_rate=1.1502327721341274,  # <- mid-market value inserted as float\n           points=30.303287307187343  # <- mid-market value inserted as float\n       )\n       fxs2 = FXSwap(\n           dt(2000, 1, 10),\n           dt(2000, 4, 10),\n           pair=\"eurusd\",\n           notional=1e6,\n       )\n       fxs1.npv(curves=[eurusd, usdusd], fx=fxf)\n       fxs2.npv(curves=[eurusd, usdusd], fx=fxf)\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def leg1(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An FXSwap requires a disc curve and a leg2 disc curve\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_disc_curve=curves.get(\"leg2_disc_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    disc_curve=curves[0],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            raise ValueError(f\"{type(self).__name__} requires 2 curve types. Got 1.\")\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime,\n        termination: datetime | str,\n        pair: FXIndex | str,\n        *,\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        # settlement\n        notional: DualTypes_ = NoInput(0),\n        leg2_notional: DualTypes_ = NoInput(0),\n        split_notional: DualTypes_ = NoInput(0),\n        # rate\n        fx_rate: DualTypes_ = NoInput(0),\n        points: DualTypes_ = NoInput(0),\n        # meta\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ):\n        (\n            fx_index_,\n            notional_,\n            leg2_notional_,\n            fx_fixings_,\n            leg2_fx_fixings_,\n            pair_,\n            leg2_pair_,\n            fx_rate_,\n            points_,\n        ) = _validated_fxswap_input_combinations(\n            pair=pair,\n            notional=notional,\n            leg2_notional=leg2_notional,\n            split_notional=split_notional,\n            fx_rate=fx_rate,\n            points=points,\n            spec=spec,\n        )\n        del pair, notional, leg2_notional, split_notional, fx_rate, points\n\n        schedule = Schedule(\n            effective=effective,\n            termination=termination,\n            frequency=\"Z\",\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n        )\n\n        user_args = dict(\n            effective=schedule.aschedule[0],\n            termination=schedule.aschedule[1],\n            leg2_effective=schedule.aschedule[0],\n            leg2_termination=schedule.aschedule[1],\n            notional=notional_,\n            leg2_notional=leg2_notional_,\n            fx_fixings=fx_fixings_,\n            leg2_fx_fixings=leg2_fx_fixings_,\n            points=points_,\n            curves=self._parse_curves(curves),\n            fx_rate=fx_rate_,\n            pair=pair_,\n            leg2_pair=leg2_pair_,\n        )\n\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            currency=fx_index_.pair[:3],\n            leg2_currency=fx_index_.pair[3:6],\n            vol=_Vol(),\n        )\n        default_args: dict[str, Any] = dict()\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\n                \"curves\",\n                \"points\",\n                \"fx_rate\",\n                \"vol\",\n            ],\n        )\n\n        self._leg1 = CustomLeg(\n            periods=[\n                Cashflow(\n                    currency=self.kwargs.leg1[\"currency\"],\n                    notional=self.kwargs.leg1[\"notional\"][0],\n                    payment=self.kwargs.leg1[\"effective\"],\n                    pair=self.kwargs.leg1[\"pair\"],\n                    fx_fixings=self.kwargs.leg1[\"fx_fixings\"][0],\n                ),\n                Cashflow(\n                    currency=self.kwargs.leg1[\"currency\"],\n                    notional=self.kwargs.leg1[\"notional\"][1],\n                    payment=self.kwargs.leg1[\"termination\"],\n                    pair=self.kwargs.leg1[\"pair\"],\n                    fx_fixings=self.kwargs.leg1[\"fx_fixings\"][1],\n                ),\n            ]\n        )\n        self._leg2 = CustomLeg(\n            periods=[\n                Cashflow(\n                    currency=self.kwargs.leg2[\"currency\"],\n                    notional=self.kwargs.leg2[\"notional\"][0],\n                    payment=self.kwargs.leg2[\"effective\"],\n                    pair=self.kwargs.leg2[\"pair\"],\n                    fx_fixings=self.kwargs.leg2[\"fx_fixings\"][0],\n                ),\n                Cashflow(\n                    currency=self.kwargs.leg2[\"currency\"],\n                    notional=self.kwargs.leg2[\"notional\"][1],\n                    payment=self.kwargs.leg2[\"termination\"],\n                    pair=self.kwargs.leg2[\"pair\"],\n                    fx_fixings=self.kwargs.leg2[\"fx_fixings\"][1],\n                ),\n            ]\n        )\n        self._legs = [self._leg1, self._leg2]\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        if isinstance(self.kwargs.leg1[\"pair\"], NoInput):\n            # then non-deliverability and fx_fixing are on leg2\n            return self._rate_on_leg(\n                core_leg=\"leg1\", nd_leg=\"leg2\", curves=curves, fx=fx, solver=solver\n            )\n        else:\n            # then non-deliverability and fx_fixing are on leg1\n            return self._rate_on_leg(\n                core_leg=\"leg2\", nd_leg=\"leg1\", curves=curves, fx=fx, solver=solver\n            )\n\n    def _rate_on_leg(\n        self,\n        core_leg: str,\n        nd_leg: str,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)\n\n        core_curve = \"\" if core_leg == \"leg1\" else \"leg2_\"\n        nd_curve = \"\" if nd_leg == \"leg1\" else \"leg2_\"\n        core_leg_: CustomLeg = getattr(self, core_leg)\n        nd_leg_: CustomLeg = getattr(self, nd_leg)\n\n        # then non-deliverability and fx_fixing are on leg2\n        disc_curve = _get_curve(f\"{core_curve}disc_curve\", False, False, *c)\n        core_npv: DualTypes = core_leg_.npv(  # type: ignore[assignment]\n            disc_curve=disc_curve,\n            base=self.leg2.settlement_params.currency,\n            fx=fx_,\n            local=False,\n        )\n        nd_disc_curve = _get_curve(f\"{nd_curve}disc_curve\", False, False, *c)\n        nd_cf1_npv = self.leg2.periods[0].local_npv(disc_curve=nd_disc_curve, fx=fx_)\n        net_zero_cf = (core_npv + nd_cf1_npv) / nd_disc_curve[\n            nd_leg_.periods[1].settlement_params.payment\n        ]\n        required_fx = net_zero_cf / nd_leg_.periods[1].settlement_params.notional\n        original_fx = nd_leg_.periods[0].non_deliverable_params.fx_fixing.value_or_forecast(fx=fx_)  # type: ignore[attr-defined]\n        _: DualTypes = (required_fx - original_fx) * 10000.0\n        return _\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n\ndef _validated_fxswap_input_combinations(\n    pair: FXIndex | str_,\n    notional: DualTypes_,\n    leg2_notional: DualTypes_,\n    split_notional: DualTypes_,\n    fx_rate: DualTypes_,\n    points: DualTypes_,\n    spec: str_,\n) -> tuple[\n    FXIndex,\n    list[DualTypes],\n    list[DualTypes],\n    LegFixings,\n    LegFixings,\n    FXIndex_,\n    FXIndex_,\n    DualTypes_,\n    DualTypes_,\n]:\n    \"\"\"Method to handle arg parsing for 2 or 3 currency NDF instruments with default value\n    setting and erroring raising.\n\n    Returns\n    -------\n    (currency, pair, leg2_pair, notional, leg2_notional, fx_rate)\n    \"\"\"\n\n    kw = _KWArgs(\n        user_args=dict(\n            pair=pair,\n            notional=notional,\n            leg2_notional=leg2_notional,\n            split_notional=split_notional,\n            fx_rate=fx_rate,\n            points=points,\n        ),\n        default_args=dict(),\n        spec=spec,\n        meta_args=[\"pair\", \"fx_rate\", \"split_notional\", \"points\"],\n    )\n\n    # FXSwaps are physically settled so do not allow WMR cross methodology to impact\n    # forecast rates for FXFixings.\n    fx_index_ = _fx_index_set_cross(_get_fx_index(kw.meta[\"pair\"]), allow_cross=False)\n\n    if isinstance(kw.leg1[\"notional\"], NoInput) and isinstance(kw.leg2[\"notional\"], NoInput):\n        # set a default\n        kw.leg1[\"notional\"] = defaults.notional\n\n    match (\n        not isinstance(kw.leg1[\"notional\"], NoInput),\n        not isinstance(kw.leg2[\"notional\"], NoInput),\n        not isinstance(kw.meta[\"split_notional\"], NoInput),\n    ):\n        case (True, True, _):\n            raise ValueError(\n                \"The notional of an FXSwap can only be given on one Leg. Got two notionals.\\n\"\n                \"Use one notional and the `fx_rate` of `pair` to establish the implied \"\n                \"transactional opposite notional.\"\n            )\n        case (False, True, False):\n            # then leg2 notional is given\n            kw.leg2[\"notional\"] = [kw.leg2[\"notional\"], -1.0 * kw.leg2[\"notional\"]]\n            kw.leg1[\"notional\"] = [-1.0 * v for v in kw.leg2[\"notional\"]]\n            kw.leg1[\"pair\"], kw.leg2[\"pair\"] = fx_index_, NoInput(0)\n        case (False, True, True):\n            # then leg2 notional as a split\n            if kw.meta[\"split_notional\"] * kw.leg2[\"notional\"] < 0:\n                raise ValueError(\n                    \"A notional and the `split_notional` cannot be given with different signs.\"\n                )\n            kw.leg2[\"notional\"] = [kw.leg2[\"notional\"], -1.0 * kw.meta[\"split_notional\"]]\n            kw.leg1[\"notional\"] = [-1.0 * v for v in kw.leg2[\"notional\"]]\n            kw.leg1[\"pair\"], kw.leg2[\"pair\"] = fx_index_, NoInput(0)\n        case (True, False, False):\n            # then leg1 notional is given\n            kw.leg1[\"notional\"] = [kw.leg1[\"notional\"], -1.0 * kw.leg1[\"notional\"]]\n            kw.leg2[\"notional\"] = [-1.0 * v for v in kw.leg1[\"notional\"]]\n            kw.leg1[\"pair\"], kw.leg2[\"pair\"] = NoInput(0), fx_index_\n        case (True, False, True):\n            kw.leg1[\"notional\"] = [kw.leg1[\"notional\"], -1.0 * kw.meta[\"split_notional\"]]\n            kw.leg2[\"notional\"] = [-1.0 * v for v in kw.leg1[\"notional\"]]\n            kw.leg1[\"pair\"], kw.leg2[\"pair\"] = NoInput(0), fx_index_\n\n    if (not isinstance(kw.meta[\"fx_rate\"], NoInput) and isinstance(kw.meta[\"points\"], NoInput)) or (\n        isinstance(kw.meta[\"fx_rate\"], NoInput) and not isinstance(kw.meta[\"points\"], NoInput)\n    ):\n        raise ValueError(\n            \"For an FXSwap transaction both `fx_rate` and `points` must be given.\\n\"\n            \"Providing only one component is not allowed, please provide the missing element.\\n\"\n            f\"Got for `fx_rate`: {kw.meta['fx_rate']}\\n\"\n            f\"Got for `points`: {kw.meta['points']}\\n\"\n        )\n    elif not isinstance(kw.meta[\"fx_rate\"], NoInput) and not isinstance(kw.meta[\"points\"], NoInput):\n        if not isinstance(kw.leg1[\"pair\"], NoInput):\n            kw.leg1[\"fx_fixings\"] = [\n                kw.meta[\"fx_rate\"],\n                kw.meta[\"fx_rate\"] + kw.meta[\"points\"] / 10000.0,\n            ]\n            kw.leg2[\"fx_fixings\"] = [NoInput(0), NoInput(0)]\n        else:\n            kw.leg1[\"fx_fixings\"] = [NoInput(0), NoInput(0)]\n            kw.leg2[\"fx_fixings\"] = [\n                kw.meta[\"fx_rate\"],\n                kw.meta[\"fx_rate\"] + kw.meta[\"points\"] / 10000.0,\n            ]\n    else:\n        kw.leg1[\"fx_fixings\"] = [NoInput(0), NoInput(0)]\n        kw.leg2[\"fx_fixings\"] = [NoInput(0), NoInput(0)]\n\n    return (\n        fx_index_,\n        kw.leg1[\"notional\"],\n        kw.leg2[\"notional\"],\n        kw.leg1[\"fx_fixings\"],\n        kw.leg2[\"fx_fixings\"],\n        kw.leg1[\"pair\"],\n        kw.leg2[\"pair\"],\n        kw.meta[\"fx_rate\"],\n        kw.meta[\"points\"],\n    )\n"
  },
  {
    "path": "python/rateslib/instruments/iirs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg, FloatLeg\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        IndexMethod,\n        LegFixings,\n        RollDay,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass IIRS(_BaseInstrument):\n    \"\"\"\n    An *indexed interest rate swap (IIRS)* composing a :class:`~rateslib.legs.FixedLeg`\n    and a :class:`~rateslib.legs.FloatLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import IIRS\n       from rateslib import fixings\n       from datetime import datetime as dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"CPI_UK\", Series(index=[dt(1999, 10, 1), dt(1999, 11, 1)], data=[110.0, 112.0]))\n       iirs = IIRS(\n           effective=dt(2000, 1, 1),\n           termination=\"2y\",\n           frequency=\"A\",\n           leg2_frequency=\"S\",\n           index_fixings=\"CPI_UK\",\n           index_lag=3,\n           fixed_rate=2.0,\n       )\n       iirs.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"CPI_UK\")\n\n    .. rubric:: Pricing\n\n    An *IIRS* requires a *disc curve* on both legs (which should be the same *Curve*), an\n    *index curve* for index forecasting on the *FixedLeg*, and a\n    *leg2 rate curve* to forecast rates on the *FloatLeg*. The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = [index_curve, disc_curve, leg2_rate_curve]  #  three curves are applied in order\n       curves = [index_curve, disc_curve, leg2_rate_curve, disc_curve]  # four curves applied to each leg\n       curves = {  # dict form is explicit\n           \"leg2_rate_curve\": leg2_rate_curve,\n           \"disc_curve\": disc_curve,\n           \"index_curve\": index_curve,\n       }\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_convention: str, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional_exchange: bool, :green:`optional (set as False)`\n        Whether to include a final notional exchange on both legs, which affects the PV since\n        the *FixedLeg* has an *indexed* cashflow.\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n    leg2_amortization : float, Dual, Dual2, Variable, str, Amortization, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n    leg2_fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    leg2_fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    leg2_fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    leg2_float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    leg2_spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following parameters define **indexation**.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value applied as the base index value for all *Periods*.\n        If not given and ``index_fixings`` is a string fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The index value for the reference date.\n        Best practice is to supply this value as string identifier relating to the global\n        ``fixings`` object.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg2_float_spread(self) -> DualTypes_:\n        return self.leg2.float_spread\n\n    @leg2_float_spread.setter\n    def leg2_float_spread(self, value: DualTypes) -> None:\n        self.kwargs.leg2[\"float_spread\"] = value\n        self.leg2.float_spread = value\n\n    @property\n    def leg1(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FloatLeg:\n        \"\"\"The :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: int_ = NoInput(1),\n        leg2_payment_lag_exchange: int_ = NoInput(1),\n        leg2_convention: str_ = NoInput(1),\n        leg2_ex_div: int_ = NoInput(1),\n        # settlement params\n        currency: str_ = NoInput(0),\n        notional_exchange: bool = False,\n        notional: float_ = NoInput(0),\n        amortization: float_ = NoInput(0),\n        leg2_notional: float_ = NoInput(-1),\n        leg2_amortization: float_ = NoInput(-1),\n        # index params\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: LegFixings = NoInput(0),\n        # rate params\n        fixed_rate: DualTypes_ = NoInput(0),\n        leg2_float_spread: DualTypes_ = NoInput(0),\n        leg2_spread_compound_method: str_ = NoInput(0),\n        leg2_rate_fixings: LegFixings = NoInput(0),\n        leg2_fixing_method: str_ = NoInput(0),\n        leg2_fixing_frequency: Frequency | str_ = NoInput(0),\n        leg2_fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # meta params\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            fixed_rate=fixed_rate,\n            index_base=index_base,\n            index_lag=index_lag,\n            index_method=index_method,\n            index_fixings=index_fixings,\n            stub=stub,\n            front_stub=front_stub,\n            back_stub=back_stub,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            ex_div=ex_div,\n            notional=notional,\n            currency=currency,\n            amortization=amortization,\n            convention=convention,\n            leg2_float_spread=leg2_float_spread,\n            leg2_spread_compound_method=leg2_spread_compound_method,\n            leg2_rate_fixings=leg2_rate_fixings,\n            leg2_fixing_method=leg2_fixing_method,\n            leg2_fixing_series=leg2_fixing_series,\n            leg2_fixing_frequency=leg2_fixing_frequency,\n            leg2_effective=leg2_effective,\n            leg2_termination=leg2_termination,\n            leg2_frequency=leg2_frequency,\n            leg2_stub=leg2_stub,\n            leg2_front_stub=leg2_front_stub,\n            leg2_back_stub=leg2_back_stub,\n            leg2_roll=leg2_roll,\n            leg2_eom=leg2_eom,\n            leg2_modifier=leg2_modifier,\n            leg2_calendar=leg2_calendar,\n            leg2_payment_lag=leg2_payment_lag,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            leg2_ex_div=leg2_ex_div,\n            leg2_notional=leg2_notional,\n            leg2_amortization=leg2_amortization,\n            leg2_convention=leg2_convention,\n            curves=self._parse_curves(curves),\n            final_exchange=notional_exchange,\n            leg2_final_exchange=notional_exchange,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            leg2_currency=NoInput(1),\n            initial_exchange=False,\n            leg2_initial_exchange=False,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n            index_lag=defaults.index_lag,\n            index_method=defaults.index_method,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"vol\"],\n        )\n\n        self._leg1 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self._leg1, self._leg2]\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        leg2_npv: DualTypes = self.leg2.local_npv(\n            rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c),\n            disc_curve=_get_curve(\"leg2_disc_curve\", False, True, *c),\n            index_curve=NoInput(0),\n            settlement=settlement,\n            forward=forward,\n        )\n\n        # self.leg1.fixed_rate = 0.0\n        # leg1_npv: DualTypes = self.leg1.local_npv(\n        #     rate_curve=NoInput(0),\n        #     disc_curve=_get_maybe_curve_maybe_from_solver(\n        #         self.kwargs.meta[\"curves\"], _curves, \"disc_curve\", solver\n        #     ),\n        #     index_curve=_get_maybe_curve_maybe_from_solver(\n        #         self.kwargs.meta[\"curves\"], _curves, \"index_curve\", solver\n        #     ),\n        #     settlement=settlement,\n        #     forward=forward,\n        # )\n        # self.leg1.fixed_rate = self.kwargs.leg1[\"fixed_rate\"]\n\n        return (\n            self.leg1.spread(\n                target_npv=-leg2_npv,  # - leg1_npv,\n                rate_curve=NoInput(0),\n                disc_curve=_get_curve(\"disc_curve\", False, True, *c),\n                index_curve=_get_curve(\"index_curve\", False, True, *c),\n                settlement=settlement,\n                forward=forward,\n            )\n            / 100\n        )\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        leg1_npv: DualTypes = self.leg1.local_npv(\n            rate_curve=NoInput(0),\n            disc_curve=_get_curve(\"disc_curve\", False, True, *c),\n            index_curve=_get_curve(\"index_curve\", False, True, *c),\n            settlement=settlement,\n            forward=forward,\n        )\n        return self.leg2.spread(\n            target_npv=-leg1_npv,\n            rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c),\n            disc_curve=_get_curve(\"leg2_disc_curve\", False, True, *c),\n            index_curve=NoInput(0),\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IIRS is that its fixed rate is not set.\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            # set a fixed rate for the purpose of generic methods NPV will be zero.\n            mid_market_rate = self.rate(\n                curves=curves,\n                solver=solver,\n                settlement=settlement,\n                forward=forward,\n            )\n            self.leg1.fixed_rate = _dual_float(mid_market_rate)\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An IIRS has three curve requirements: an index_curve, a leg2_rate_curve and a\n        disc_curve used by both legs.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                index_curve=curves.get(\"index_curve\", NoInput(0)),\n                leg2_rate_curve=_drb(\n                    curves.get(\"rate_curve\", NoInput(0)),\n                    curves.get(\"leg2_rate_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 3:\n                return _Curves(\n                    disc_curve=curves[1],\n                    index_curve=curves[0],\n                    leg2_rate_curve=curves[2],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    disc_curve=curves[1],\n                    index_curve=curves[0],\n                    leg2_rate_curve=curves[2],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires 3 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            raise ValueError(f\"{type(self).__name__} requires 3 curve types. Got 1.\")\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/ir_options/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.instruments.ir_options.call_put import IRSCall, IRSPut, _BaseIRSOption\nfrom rateslib.instruments.ir_options.risk_reversal import IRSRiskReversal\nfrom rateslib.instruments.ir_options.straddle import IRSStraddle, _BaseIRSOptionStrat\nfrom rateslib.instruments.ir_options.strangle import IRSStrangle\nfrom rateslib.instruments.ir_options.vol_value import IRVolValue\n\n__all__ = [\n    \"IRSCall\",\n    \"IRSPut\",\n    \"IRSStraddle\",\n    \"IRSStrangle\",\n    \"IRSRiskReversal\",\n    \"IRVolValue\",\n    \"_BaseIRSOption\",\n    \"_BaseIRSOptionStrat\",\n]\n"
  },
  {
    "path": "python/rateslib/instruments/ir_options/call_put.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom abc import ABCMeta\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, NoReturn\n\nfrom rateslib import defaults\nfrom rateslib.curves._parsers import _validate_obj_not_no_input\nfrom rateslib.default import plot\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import (\n    IROptionMetric,\n    SwaptionSettlementMethod,\n    _get_ir_option_metric,\n)\nfrom rateslib.instruments.irs import IRS\nfrom rateslib.instruments.protocols import _BaseInstrument, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _maybe_get_ir_vol_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import CustomLeg\nfrom rateslib.periods import Cashflow, IRSCallPeriod, IRSPutPeriod\nfrom rateslib.periods.utils import (\n    _get_ir_vol_value_and_forward_maybe_from_obj,\n)\nfrom rateslib.volatility.fx import FXVolObj\nfrom rateslib.volatility.ir import _BaseIRSmile\nfrom rateslib.volatility.ir.utils import _get_ir_expiry_and_payment\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        Arr1dF64,\n        CurveOption_,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        IRSSeries,\n        PlotOutput,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseCurve_,\n        _BaseIRSOptionPeriod,\n        _BaseLeg,\n        _IRVolOption_,\n        _IRVolPricingParams,\n        bool_,\n        datetime_,\n        float_,\n        str_,\n    )\n\n\nclass _BaseIRSOption(_BaseInstrument, metaclass=ABCMeta):\n    \"\"\"\n    Abstract base class for implementing *IR Swaptions*.\n\n    See :class:`~rateslib.instruments.IRSCall` and\n    :class:`~rateslib.instruments.IRSPut`.\n    \"\"\"\n\n    _pricing: _IRVolPricingParams\n\n    def analytic_greeks(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n    ) -> dict[str, Any]:\n        \"\"\"\n        Return various pricing metrics of the *FX Option*.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import Curve, FXCall, dt, FXForwards, FXRates, FXDeltaVolSmile\n\n        .. ipython:: python\n\n           eur = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.98})\n           usd = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.96})\n           fxf = FXForwards(\n               fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2020, 1, 3)),\n               fx_curves={\"eureur\": eur, \"eurusd\": eur, \"usdusd\": usd},\n           )\n           fxvs = FXDeltaVolSmile(\n               nodes={0.25: 11.0, 0.5: 9.8, 0.75: 10.7},\n               delta_type=\"forward\",\n               eval_date=dt(2020, 1, 1),\n               expiry=dt(2020, 4, 1)\n           )\n           fxc = FXCall(\n               expiry=\"3m\",\n               strike=1.10,\n               eval_date=dt(2020, 1, 1),\n               spec=\"eurusd_call\",\n           )\n           fxc.analytic_greeks(fx=fxf, curves=[eur, usd], vol=fxvs)\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n\n        Returns\n        -------\n        dict\n        \"\"\"\n        return self._analytic_greeks_set_metrics(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            set_metrics=True,\n        )\n\n    def _analytic_greeks_set_metrics(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        set_metrics: bool_ = True,\n    ) -> dict[str, Any]:\n        \"\"\"\n        Return various pricing metrics of the *FX Option*.\n\n        Returns\n        -------\n        float, Dual, Dual2\n        \"\"\"\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", True, False, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n        index_curve = _get_curve(\"index_curve\", False, False, *c)\n\n        _vol = self._parse_vol(vol)\n\n        ir_vol = _maybe_get_ir_vol_maybe_from_solver(\n            vol=_vol, vol_meta=self.kwargs.meta[\"vol\"], solver=solver\n        )\n\n        if set_metrics:\n            self._set_strike_and_vol(\n                rate_curve=rate_curve, disc_curve=disc_curve, index_curve=index_curve, vol=ir_vol\n            )\n            # self._set_premium(curves, fx)\n\n        return self._option.analytic_greeks(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            ir_vol=ir_vol,\n            premium=NoInput(0),\n            premium_payment=NoInput(0),\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        raise NotImplementedError(\n            \"`local_analytic_rate_fixings` is not implemented for `_BaseIRSOption` types.\"\n        )\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        raise NotImplementedError(\"`spread` is not implemented for `_BaseIRSOption` types.\")\n\n    @property\n    def _rate_scalar(self) -> float:  # type: ignore[override]\n        if type(self.kwargs.meta[\"metric\"]) in [\n            IROptionMetric.BlackVolShift,\n            IROptionMetric.NormalVol,\n        ]:\n            return 100.0\n        else:\n            return 1.0\n\n    @property\n    def leg1(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument* containing the\n        :class:`~rateslib.periods.IROptionPeriod`.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument* containing the\n        premium :class:`~rateslib.periods.Cashflow`.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    @property\n    def _option(self) -> _BaseIRSOptionPeriod:\n        return self.leg1.periods[0]  # type: ignore[return-value]\n\n    @property\n    def _irs(self) -> IRS:\n        return self._option.ir_option_params.option_fixing.irs\n\n    @property\n    def _premium(self) -> Cashflow:\n        return self.leg2.periods[0]  # type: ignore[return-value]\n\n    @classmethod\n    def _parse_curves(cls, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        A Swaption has 3 curve requirements. See **Pricing**.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 1:\n                return _Curves(\n                    rate_curve=curves[0],\n                    index_curve=curves[0],\n                    disc_curve=curves[0],\n                    leg2_disc_curve=curves[0],\n                )\n            elif len(curves) == 2:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    index_curve=curves[1],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 3:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    index_curve=curves[2],\n                    leg2_disc_curve=curves[1],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(cls).__name__} requires only 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                index_curve=curves.get(\"index_curve\", NoInput(0)),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                rate_curve=curves,  # type: ignore[arg-type]\n                disc_curve=curves,  # type: ignore[arg-type]\n                index_curve=curves,  # type: ignore[arg-type]\n                leg2_disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    @classmethod\n    def _parse_vol(cls, vol: VolT_) -> _Vol:\n        \"\"\"\n        IR options requires only a single IRVolObj or a scalar.\n        \"\"\"\n        if isinstance(vol, _Vol):\n            return vol\n        elif isinstance(vol, FXVolObj):\n            raise TypeError(\"`vol` cannot be an FX type vol object and must be IR type vol object.\")\n        else:\n            return _Vol(ir_vol=vol)\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        tenor: datetime | str,\n        strike: DualTypes | str,\n        irs_series: IRSSeries | str,\n        *,\n        notional: DualTypes_ = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        premium: DualTypes_ = NoInput(0),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        settlement_method: SwaptionSettlementMethod | str_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        call: bool = True,\n    ):\n        user_args = dict(\n            tenor=tenor,\n            expiry=expiry,\n            notional=notional,\n            strike=strike,\n            irs_series=irs_series,\n            option_fixings=option_fixings,\n            settlement_method=settlement_method,\n            leg2_payment_lag=payment_lag,\n            leg2_premium=premium,\n            metric=metric,\n            curves=self._parse_curves(curves),\n            vol=self._parse_vol(vol),\n        )\n        # instrument_args: dict[str, Any] = dict()\n        default_args = dict(\n            notional=defaults.notional,\n            metric=defaults.ir_option_metric,\n            settlement_method=defaults.ir_option_settlement,\n        )\n        self._kwargs = _KWArgs(\n            user_args=user_args,\n            default_args=default_args,\n            spec=spec,\n            meta_args=[\"curves\", \"vol\", \"metric\"],\n        )\n\n        # determine the `expiry` and `delivery` as datetimes if derived from other combinations\n        (self.kwargs.leg1[\"expiry\"], self.kwargs.leg2[\"payment\"]) = _get_ir_expiry_and_payment(\n            eval_date=eval_date,\n            expiry=self.kwargs.leg1[\"expiry\"],\n            irs_series=self.kwargs.leg1[\"irs_series\"],\n            payment_lag=self.kwargs.leg2[\"payment_lag\"],\n        )\n\n        # sanitise\n        self.kwargs.meta[\"metric\"] = _get_ir_option_metric(self.kwargs.meta[\"metric\"])\n\n        self._leg1 = CustomLeg(\n            [\n                IRSCallPeriod(  # type: ignore[abstract]\n                    expiry=self.kwargs.leg1[\"expiry\"],\n                    tenor=self.kwargs.leg1[\"tenor\"],\n                    irs_series=self.kwargs.leg1[\"irs_series\"],\n                    strike=NoInput(0)\n                    if isinstance(self.kwargs.leg1[\"strike\"], str)\n                    else self.kwargs.leg1[\"strike\"],\n                    notional=self.kwargs.leg1[\"notional\"],\n                    option_fixings=self.kwargs.leg1[\"option_fixings\"],\n                    metric=self.kwargs.meta[\"metric\"],\n                    settlement_method=self.kwargs.leg1[\"settlement_method\"],\n                )\n                if call\n                else IRSPutPeriod(  # type: ignore[abstract]\n                    expiry=self.kwargs.leg1[\"expiry\"],\n                    tenor=self.kwargs.leg1[\"tenor\"],\n                    irs_series=self.kwargs.leg1[\"irs_series\"],\n                    strike=NoInput(0)\n                    if isinstance(self.kwargs.leg1[\"strike\"], str)\n                    else self.kwargs.leg1[\"strike\"],\n                    notional=self.kwargs.leg1[\"notional\"],\n                    option_fixings=self.kwargs.leg1[\"option_fixings\"],\n                    metric=self.kwargs.meta[\"metric\"],\n                    settlement_method=self.kwargs.leg1[\"settlement_method\"],\n                )\n            ]\n        )\n        self._leg2 = CustomLeg(\n            [\n                Cashflow(\n                    notional=_drb(0.0, self.kwargs.leg2[\"premium\"]),\n                    payment=self.kwargs.leg2[\"payment\"],\n                    currency=self._leg1.settlement_params.currency,\n                ),\n            ]\n        )\n        self._legs = [self._leg1, self._leg2]\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    def _set_strike_and_vol(\n        self,\n        rate_curve: CurveOption_,\n        disc_curve: _BaseCurve_,\n        index_curve: _BaseCurve_,\n        vol: _IRVolOption_,\n    ) -> None:\n        \"\"\"\n        Set the strike, if necessary, and determine pricing metrics from the volatility objects.\n\n        The strike for the *OptionPeriod* is either; string or numeric.\n\n        If it is string, then a numeric strike must be determined with an associated vol.\n\n        If it is numeric then the volatility must be determined for the given strike.\n\n        Pricing elements are captured and cached so they can be used later by subsequent methods.\n        \"\"\"\n        if isinstance(vol, _BaseIRSmile):  # TODO _BaseIRCube\n            eval_date = vol.meta.eval_date\n        else:\n            _ = _validate_obj_not_no_input(disc_curve, \"disc_curve\")\n            eval_date = _.nodes.initial\n\n        _pricing = _get_ir_vol_value_and_forward_maybe_from_obj(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            strike=self.kwargs.leg1[\"strike\"],\n            ir_vol=vol,\n            irs=self._irs,\n            tenor=self._option.ir_option_params.option_fixing.termination,\n            expiry=self._option.ir_option_params.expiry,\n            t_e=self._option.ir_option_params.time_to_expiry(eval_date),\n        )\n\n        # Review section in book regarding Hyper-parameters and Solver interaction\n        self._option.ir_option_params.strike = _pricing.k\n        self._pricing = _pricing\n        # self._option_periods[0].strike = _dual_float(self._pricing.k)\n\n    def _set_premium(\n        self,\n        rate_curve: CurveOption_,\n        disc_curve: _BaseCurve_,\n        index_curve: _BaseCurve_,\n        pricing: _IRVolPricingParams,\n    ) -> None:\n        \"\"\"\n        Set an unspecified premium on the Option to be equal to the mid-market premium.\n        \"\"\"\n        if isinstance(self.kwargs.leg2[\"premium\"], NoInput):\n            # then set the CashFlow to mid-market\n            npv: DualTypes = self._option.npv(  # type: ignore[assignment]\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=index_curve,\n                ir_vol=pricing,\n                local=False,\n                forward=self.kwargs.leg2[\"payment\"],\n            )\n            self._premium.settlement_params._notional = _dual_float(npv)\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", True, False, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n        index_curve = _get_curve(\"index_curve\", False, False, *c)\n\n        _vol = self._parse_vol(vol)\n        del vol\n\n        ir_vol = _maybe_get_ir_vol_maybe_from_solver(\n            vol=_vol, vol_meta=self.kwargs.meta[\"vol\"], solver=solver\n        )\n        self._set_strike_and_vol(\n            rate_curve=rate_curve, disc_curve=disc_curve, index_curve=index_curve, vol=ir_vol\n        )\n\n        # Premium is not required for rate and also sets as float\n        # Review section: \"Hyper-parameters and Solver interaction\" before enabling.\n        # self._set_premium(curves, fx)\n\n        metric_ = _get_ir_option_metric(_drb(self.kwargs.meta[\"metric\"], metric))\n        del metric\n\n        value = self._option.rate(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            ir_vol=self._pricing,\n            metric=metric_,\n        )\n        if (\n            metric_ in [IROptionMetric.Premium(), IROptionMetric.PercentNotional()]\n            and self.leg2.settlement_params.payment != self.leg1.settlement_params.payment\n        ):\n            return (\n                value\n                * disc_curve[self.leg2.settlement_params.payment]\n                / disc_curve[self.leg1.settlement_params.payment]\n            )\n        else:\n            return value\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", True, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n        index_curve = _get_curve(\"index_curve\", False, True, *c)\n\n        _vol = self._parse_vol(vol)\n        del vol\n\n        ir_vol = _maybe_get_ir_vol_maybe_from_solver(\n            vol=_vol, vol_meta=self.kwargs.meta[\"vol\"], solver=solver\n        )\n        self._set_strike_and_vol(\n            rate_curve=rate_curve, disc_curve=disc_curve, index_curve=index_curve, vol=ir_vol\n        )\n\n        self._set_premium(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            pricing=self._pricing,\n        )\n\n        if not local:\n            base_ = _drb(self.legs[0].settlement_params.currency, base)\n        else:\n            base_ = base\n\n        opt_npv = self._option.npv(\n            rate_curve=rate_curve,  # _validate_obj_not_no_input(rate_curve, \"rate curve\"),\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            base=base_,\n            local=local,\n            ir_vol=self._pricing,\n            settlement=settlement,\n            forward=forward,\n        )\n        prem_npv = self._premium.npv(\n            disc_curve=_get_curve(\"leg2_disc_curve\", False, True, *c),\n            fx=fx,\n            base=base_,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n        if local:\n            return {k: opt_npv.get(k, 0) + prem_npv.get(k, 0) for k in set(opt_npv) | set(prem_npv)}  # type:ignore[union-attr, arg-type]\n        else:\n            return opt_npv + prem_npv  # type: ignore[operator]\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", True, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n        index_curve = _get_curve(\"index_curve\", False, True, *c)\n\n        _vol = self._parse_vol(vol)\n        del vol\n\n        try:\n            ir_vol = _maybe_get_ir_vol_maybe_from_solver(\n                vol=_vol, vol_meta=self.kwargs.meta[\"vol\"], solver=solver\n            )\n            self._set_strike_and_vol(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=index_curve,\n                vol=ir_vol,\n            )\n            self._set_premium(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=index_curve,\n                pricing=self._pricing,\n            )\n        except Exception:  # noqa: S110\n            pass  # `cashflows` proceed without pricing determined values\n\n        return self._cashflows_from_legs(\n            curves=c[0],\n            solver=solver,\n            fx=fx,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n            vol=_vol,\n        )\n\n    def analytic_delta(self, *args: Any, leg: int = 1, **kwargs: Any) -> NoReturn:\n        \"\"\"Not implemented for Option types.\n        Use :meth:`~rateslib.instruments._BaseFXOption.analytic_greeks`.\n        \"\"\"\n        raise NotImplementedError(\"For Option types use `analytic_greeks`.\")\n\n    def _plot_payoff(\n        self,\n        window: tuple[float, float] | NoInput = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n    ) -> tuple[Arr1dF64, Arr1dF64]:\n        \"\"\"\n        Mechanics to determine (x,y) coordinates for payoff at expiry plot.\n        \"\"\"\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", True, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n        index_curve = _get_curve(\"index_curve\", False, False, *c)\n\n        _vol = self._parse_vol(vol)\n        del vol\n\n        ir_vol = _maybe_get_ir_vol_maybe_from_solver(\n            vol=_vol, vol_meta=self.kwargs.meta[\"vol\"], solver=solver\n        )\n        self._set_strike_and_vol(\n            rate_curve=rate_curve, disc_curve=disc_curve, index_curve=index_curve, vol=ir_vol\n        )\n\n        # self._set_premium(curves, fx)\n        x, y = self._option._payoff_at_expiry(window)\n        return x, y\n\n    def plot_payoff(\n        self,\n        range: tuple[float, float] | NoInput = NoInput(0),  # noqa: A002\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        base: str_ = NoInput(0),\n        vol: float_ = NoInput(0),\n    ) -> PlotOutput:\n        \"\"\"\n        Return a plot of the payoff at expiry, indexed by the *FXFixing* value.\n\n        Parameters\n        ----------\n        range: list of float, :green:`optional`\n            A range of values for the *FXFixing* value at expiry to use as the x-axis.\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n\n        Returns\n        -------\n        (Figure, Axes, list[Lines2D])\n        \"\"\"\n\n        x, y = self._plot_payoff(window=range, curves=curves, solver=solver, fx=fx, vol=vol)\n        return plot([x], [y])  # type: ignore\n\n    #\n    # def local_analytic_rate_fixings(\n    #     self,\n    #     *,\n    #     curves: CurvesT_ = NoInput(0),\n    #     solver: Solver_ = NoInput(0),\n    #     fx: FXForwards_ = NoInput(0),\n    #     vol: VolT_ = NoInput(0),\n    #     settlement: datetime_ = NoInput(0),\n    #     forward: datetime_ = NoInput(0),\n    # ) -> DataFrame:\n    #     return DataFrame()\n    #\n    # def spread(\n    #     self,\n    #     *,\n    #     curves: CurvesT_ = NoInput(0),\n    #     solver: Solver_ = NoInput(0),\n    #     fx: FXForwards_ = NoInput(0),\n    #     vol: VolT_ = NoInput(0),\n    #     base: str_ = NoInput(0),\n    #     settlement: datetime_ = NoInput(0),\n    #     forward: datetime_ = NoInput(0),\n    # ) -> DualTypes:\n    #     \"\"\"\n    #     Not implemented for Option types. Use :meth:`~rateslib.instruments._BaseFXOption.rate`.\n    #     \"\"\"\n    #     raise NotImplementedError(f\"`spread` is not implemented for type: {type(self).__name__}\")\n\n\nclass IRSCall(_BaseIRSOption):\n    \"\"\"\n    An *IR Payer Swaption*.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import dt, Curve, IRSCall\n\n    .. ipython:: python\n\n       iro = IRSCall(\n           expiry=dt(2027, 2, 16),\n           tenor=\"6m\",\n           strike=3.02,\n           notional=100e6,\n           irs_series=\"usd_irs\",\n           premium=10000.0,\n       )\n       # iro.cashflows()\n\n    .. rubric:: Pricing\n\n    A *Swaption* requires from one to three *Curves*;\n\n    - a ``rate_curve`` used to forecast the rates on the :class:`~rateslib.legs.FloatLeg` of the\n      underlying :class:`~rateslib.instruments.IRS`.\n    - a ``disc_curve`` used to discount the value of the *Swaption* and the premium under the\n      terms of its bilateral collateral agreement.\n    - an ``index_curve`` used as the price alignment index rate for the discounting of the\n      underlying :class:`~rateslib.instruments.IRS`. This does not necessarily need to equal the\n      ``disc_curve``.\n\n    Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = rate_curve | [rate_curve] #  one curve is used as all curves\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order, index_curve is set equal to disc_curve\n       curves = [rate_curve, disc_curve, index_curve]  # three curves applied in the given order\n       curves = {\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve\n           \"index_curve\": index_curve\n       }  # dict form is explicit\n\n    A *Swaption* also requires an *IRVolatility* object or numeric value for the ``vol`` argument.\n    If a numeric value is given it is assumed to be a Black (log-normal) volatility without shift.\n    Allowed inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0     # a specific Black (log-normal) calendar-day annualized vol until expiry\n       vol = vol_obj  # an explicit volatility object, e.g. IRSabrSmile\n\n    The following pricing ``metric`` are available, with examples:\n\n    .. ipython:: python\n\n       curve = Curve(\n           nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n       )\n\n    - **\"BlackVolShift(_)\"**:\n      The *rate* method will make the necessary conversions between the different volatility\n      representations.\n\n      .. ipython:: python\n\n          iro.rate(curves=[curve], vol=25.16, metric=\"BlackVolShift_0\")\n          iro.rate(curves=[curve], vol=25.16, metric=\"BlackVolShift_100\")\n          iro.rate(curves=[curve], vol=25.16, metric=\"BlackVolShift_200\")\n          iro.rate(curves=[curve], vol=25.16, metric=\"BlackVolShift_300\")\n\n    - **\"NormalVol\"**: the equivalent number of basis point volatility used in the Bachelier\n      formula:\n\n      .. ipython:: python\n\n          iro.rate(curves=[curve], vol=25.16, metric=\"NormalVol\")\n\n    - **\"Premium\"**: the cash premium amount applicable to the 'payment' date, expressed in the\n      premium currency.\n\n      .. ipython:: python\n\n          iro.rate(curves=[curve], vol=25.16, metric=\"Premium\")\n\n    - **\"PercentNotional\"**: the cash premium amount expressed as a percentage of the\n      notional.\n\n      .. ipython:: python\n\n          iro.rate(curves=[curve], vol=25.16, metric=\"PercentNotional\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **ir option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    tenor: datetime, str, :red:`required`\n        The parameter defining the maturity of the underlying :class:`~rateslib.instruments.IRS`.\n    irs_series: IRSSeries, str, :red:`required`\n        The standard conventions applied to the underlying :class:`~rateslib.instruments.IRS`.\n    strike: float, Variable, str, :red:`required`\n        The strike value of the option.\n        If str, there are two possibilities; {\"atm\", \"{}bps\"}. \"atm\" will produce a strike equal\n        to the mid-market *IRS* rate, whilst \"20bps\" or \"-50bps\" will yield a strike that number\n        of basis points different to the mid-market rate.\n    notional: float, :green:`optional (set by 'defaults')`\n        The notional amount expressed in units of ``currency`` fo the ``irs_series``.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot.\n    payment_lag: int or datetime, :green:`optional (set as IRS effective)`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    settlement_method: SwaptionSettlementMethod, str, :green:`optional (set by 'default')`\n        The method for deriving the settlement cashflow or underlying value.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: float, :green:`optional`\n        The amount paid for the option. If not given assumes an unpriced *Option* and sets this as\n        mid-market premium during pricing.\n    option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the option :class:`~rateslib.data.fixings.IRSFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric: IROptionMetric, str, :green:`optional` (set by 'default')`\n        The metric used by default in the\n        :meth:`~rateslib.instruments._BaseIRSOption.rate` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    \"\"\"  # noqa: E501\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, call=True, **kwargs)\n\n\nclass IRSPut(_BaseIRSOption):\n    \"\"\"\n    An *IR Receiver Swaption*.\n\n    For parameters and examples see :class:`~rateslib.instruments.IRSCall`.\n    \"\"\"\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, call=False, **kwargs)\n"
  },
  {
    "path": "python/rateslib/instruments/ir_options/risk_reversal.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import IROptionMetric\nfrom rateslib.instruments.ir_options.call_put import IRSCall, IRSPut\nfrom rateslib.instruments.ir_options.straddle import _BaseIRSOptionStrat\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        IRSSeries,\n        SwaptionSettlementMethod,\n        VolStrat_,\n        VolT_,\n        _Vol,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass IRSRiskReversal(_BaseIRSOptionStrat):\n    \"\"\"\n    An *IR Risk Reversal* :class:`~rateslib.instruments._BaseIRSOptionStrat`.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    A *Risk Reversal* is composed of a lower strike :class:`~rateslib.instruments.IRSPut`\n    and a higher strike :class:`~rateslib.instruments.IRSCall` with the same expiry and tenor.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import IRSRiskReversal, Curve, dt\n\n    .. ipython:: python\n\n       irstr = IRSRiskReversal(\n           eval_date=dt(2020, 1, 1),\n           expiry=\"3m\",\n           tenor=\"1Y\",\n           strike=(\"-20bps\", \"+20bps\"),\n           irs_series=\"usd_irs\",\n           notional=1000000,\n       )\n       irstr.cashflows()\n\n    .. rubric:: Pricing\n\n    The pricing mirrors that for an :class:`~rateslib.instruments.IRSCall`. All options use the\n    same ``curves``. Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = rate_curve | [rate_curve] #  one curve is used as all curves\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order, index_curve is set equal to disc_curve\n       curves = [rate_curve, disc_curve, index_curve]  # three curves applied in the given order\n       curves = {\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve\n           \"index_curve\": index_curve\n       }  # dict form is explicit\n\n    A ``vol`` argument must be provided to each *Instrument*. This can either be a single\n    value universally used for all, or an individual item as part of a sequence. Allowed\n    inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0 | vol_obj  # a single item universally applied\n       vol = [12.0, 12.0]  # values for the Put and Call respectively\n\n    The following pricing ``metric`` are available, with examples:\n\n    TODO\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **ir option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    tenor: datetime, str, :red:`required`\n        The parameter defining the maturity of the underlying :class:`~rateslib.instruments.IRS`.\n    irs_series: IRSSeries, str, :red:`required`\n        The standard conventions applied to the underlying :class:`~rateslib.instruments.IRS`.\n    strike: 2-tuple of float, Variable, str, :red:`required`\n        The strike values of each option.\n        If str, there are two possibilities; {\"atm\", \"{}bps\"}. \"atm\" will produce a strike equal\n        to the mid-market *IRS* rate, whilst \"20bps\" or \"-50bps\" will yield a strike that number\n        of basis points different to the mid-market rate.\n    notional: float, :green:`optional (set by 'defaults')`\n        The notional amount expressed in units of ``currency`` of the ``irs_series``.\n        Applies to the higher strike *Call*, the *Put* assumes the negated notional.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot.\n    payment_lag: int or datetime, :green:`optional (set as IRS effective)`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    settlement_method: SwaptionSettlementMethod, str, :green:`optional (set by 'default')`\n        The method for deriving the settlement cashflow or underlying value.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: 2-tuple of float, :green:`optional`\n        The amount paid for the put and call in order. If not given assumes unpriced\n        *Options* and sets this as mid-market premium during pricing.\n    option_fixings: 2-tuple of float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of each option's :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric : str, :green:`optional (set as \"pips_or_%\")`\n        The pricing metric returned by the ``rate`` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable, Sequence\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 100.0\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        tenor: datetime | str,\n        strike: tuple[DualTypes | str, DualTypes | str],\n        irs_series: IRSSeries | str,\n        *,\n        notional: DualTypes_ = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        premium: tuple[DualTypes_, DualTypes_] = (NoInput(0), NoInput(0)),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        settlement_method: SwaptionSettlementMethod | str_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        vol_ = self._parse_vol(vol)\n        notional_ = _drb(defaults.notional, notional)\n        options = [\n            IRSPut(\n                irs_series=irs_series,\n                expiry=expiry,\n                payment_lag=payment_lag,\n                eval_date=eval_date,\n                tenor=tenor,\n                strike=strike[0],\n                notional=-notional_,\n                option_fixings=option_fixings[0]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                settlement_method=settlement_method,\n                premium=premium[0],\n                curves=curves,\n                vol=vol_[0],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n            IRSCall(\n                irs_series=irs_series,\n                expiry=expiry,\n                payment_lag=payment_lag,\n                eval_date=eval_date,\n                tenor=tenor,\n                strike=strike[1],\n                notional=notional_,\n                option_fixings=option_fixings[1]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                settlement_method=settlement_method,\n                premium=premium[1],\n                curves=curves,\n                vol=vol_[1],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n        ]\n        super().__init__(\n            options=options,\n            rate_weight=[-1.0, 1.0],\n            rate_weight_vol=[-1.0, 1.0],\n            metric=metric,\n            curves=curves,\n            vol=vol_,\n        )\n        self.kwargs.leg1[\"notional\"] = notional_\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> tuple[_Vol, _Vol]:  # type: ignore[override]\n        if not isinstance(vol, list | tuple):\n            vol = (vol,) * 2\n        return IRSPut._parse_vol(vol[0]), IRSCall._parse_vol(vol[1])\n\n    def _set_notionals(self, notional: DualTypes) -> None:\n        \"\"\"\n        Set the notionals on each option period. Mainly used by Brokerfly for vega neutral\n        strangle and straddle.\n        \"\"\"\n        for option in self.instruments:\n            option.kwargs.leg1[\"notional\"] = notional\n            option._option.settlement_params._notional = notional\n"
  },
  {
    "path": "python/rateslib/instruments/ir_options/straddle.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom pandas import DataFrame\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import IROptionMetric, _get_ir_option_metric\nfrom rateslib.instruments.ir_options.call_put import IRSCall, IRSPut, _BaseIRSOption\nfrom rateslib.instruments.protocols import _KWArgs\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        IRSSeries,\n        Sequence,\n        Solver_,\n        SwaptionSettlementMethod,\n        VolStrat_,\n        VolT_,\n        _Vol,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass _BaseIRSOptionStrat(_BaseIRSOption):\n    \"\"\"\n    A custom option strategy composed of a list of :class:`~rateslib.instruments._BaseIRSOption`,\n    or other :class:`~rateslib.instruments._BaseIRSOptionStrat` objects, of the same\n    :class:`~rateslib.data.fixings.IRSSeries`.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    Parameters\n    ----------\n    options: list\n        The *IROptions* or *IROptionStrats* which make up the strategy.\n    rate_weight: list\n        The multiplier for non-vol type metrics that sums the options to a final *rate*.\n        E.g. A *RiskReversal* uses [-1.0, 1.0] for a sale and a purchase.\n        E.g. A *Straddle* uses [1.0, 1.0] for summing two premium purchases.\n    rate_weight_vol: list\n        The multiplier for the *'vol'* metric that sums the options to a final *rate*.\n        E.g. A *RiskReversal* uses [-1.0, 1.0] to obtain the vol difference between two options.\n        E.g. A *Straddle* uses [0.5, 0.5] to obtain the volatility at the strike of each option.\n    \"\"\"\n\n    _greeks: dict[str, Any] = {}\n    _strat_elements: tuple[_BaseIRSOption | _BaseIRSOptionStrat, ...]\n\n    @property\n    def kwargs(self) -> _KWArgs:\n        \"\"\"The :class:`~rateslib.instruments.protocols._KWArgs` of the *Instrument*.\"\"\"\n        return self._kwargs\n\n    def __init__(\n        self,\n        options: Sequence[_BaseIRSOption | _BaseIRSOptionStrat],\n        rate_weight: list[float],\n        rate_weight_vol: list[float],\n        metric: IROptionMetric | str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n    ):\n        self._n = len(options)\n        if self._n != len(rate_weight) or self._n != len(rate_weight_vol):\n            raise ValueError(\n                \"`rate_weight` and `rate_weight_vol` must have same length as `options`.\",\n            )\n        self._kwargs = _KWArgs(\n            spec=NoInput(0),\n            user_args=dict(\n                rate_weight=rate_weight,\n                rate_weight_vol=rate_weight_vol,\n                instruments=tuple(options),\n                metric=metric,\n                irs_series=options[0].kwargs.leg1[\"irs_series\"],\n                curves=NoInput(0),\n                vol=vol,\n            ),\n            default_args=dict(\n                metric=defaults.ir_option_metric,\n            ),\n            meta_args=[\"metric\", \"vol\", \"curves\", \"instruments\", \"rate_weight\", \"rate_weight_vol\"],\n        )\n        self.kwargs.meta[\"curves\"] = self._parse_curves(curves)\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> VolStrat_:  # type: ignore[override]\n        raise NotImplementedError(f\"{type(cls).__name__} must implement `_parse_vol`.\")\n\n    @property\n    def instruments(self) -> tuple[_BaseIRSOption | _BaseIRSOptionStrat, ...]:\n        return self.kwargs.meta[\"instruments\"]  # type: ignore[no-any-return]\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n    ) -> DualTypes:\n        vol_: VolStrat_ = self._parse_vol(vol)\n        metric_: IROptionMetric = _get_ir_option_metric(_drb(self.kwargs.meta[\"metric\"], metric))\n        match type(metric_):\n            case IROptionMetric.NormalVol | IROptionMetric.BlackVolShift:\n                weights = self.kwargs.meta[\"rate_weight_vol\"]\n            case IROptionMetric.Premium | IROptionMetric.PercentNotional:\n                weights = self.kwargs.meta[\"rate_weight\"]\n\n        _: DualTypes = 0.0\n        for option, vol__, weight in zip(self.instruments, vol_, weights, strict=True):  # type: ignore[misc, arg-type]\n            _ += (\n                option.rate(\n                    curves=curves,\n                    solver=solver,\n                    fx=fx,\n                    base=base,\n                    vol=vol__,  # type: ignore[arg-type]\n                    metric=metric_,\n                    settlement=settlement,\n                    forward=forward,\n                )\n                * weight\n            )\n        return _\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        vol_ = self._parse_vol(vol)\n\n        results = [\n            option.npv(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                base=base,\n                local=local,\n                vol=vol__,  # type: ignore[arg-type]\n                forward=forward,\n                settlement=settlement,\n            )\n            for (option, vol__) in zip(self.instruments, vol_, strict=True)  # type: ignore[arg-type]\n        ]\n\n        if local:\n            df = DataFrame(results).fillna(0.0)\n            df_sum = df.sum()\n            _: DualTypes | dict[str, DualTypes] = df_sum.to_dict()  # type: ignore[assignment]\n        else:\n            _ = sum(results)  # type: ignore[arg-type]\n        return _\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._cashflows_from_instruments(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n            base=base,\n        )\n\n    def _plot_payoff(\n        self,\n        window: tuple[float, float] | NoInput = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n    ) -> tuple[Any, Any]:\n        vol_ = self._parse_vol(vol)\n\n        y = None\n        for inst, vol__ in zip(self.instruments, vol_, strict=True):  # type: ignore[arg-type]\n            x, y_ = inst._plot_payoff(\n                window=window,\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                vol=vol__,  # type: ignore[arg-type]\n            )\n            if y is None:\n                y = y_\n            else:\n                y += y_\n\n        return x, y\n\n    def analytic_greeks(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolStrat_ = NoInput(0),\n    ) -> dict[str, Any]:\n        # implicitly call set_pricing_mid for unpriced parameters\n        # this may be important for Strategies whose options are\n        # dependent upon each other, (RR and Straddle do not have interdependent options)\n        self.rate(curves=curves, solver=solver, fx=fx, vol=vol)\n\n        vol_: VolStrat_ = self._parse_vol(vol=vol)\n        gks = []\n        for inst, vol_i in zip(self.instruments, vol_, strict=True):  # type: ignore[misc, arg-type]\n            if isinstance(inst, _BaseIRSOptionStrat):\n                gks.append(\n                    inst.analytic_greeks(\n                        curves=curves,\n                        solver=solver,\n                        fx=fx,\n                        vol=vol_i,\n                    )\n                )\n            else:  # option is _BaseIRSOption\n                gks.append(\n                    inst._analytic_greeks_set_metrics(\n                        curves=curves,\n                        solver=solver,\n                        fx=fx,\n                        vol=vol_i,  # type: ignore[arg-type]\n                        set_metrics=False,  # already done in the rate call above\n                    )\n                )\n\n        _unit_attrs = [\"delta\", \"gamma\", \"vega\", \"vomma\", \"vanna\", \"__bs76\", \"__bachelier\"]\n        _: dict[str, Any] = {}\n        for attr in _unit_attrs:\n            tally = 0.0\n            for i, gk in enumerate(gks):\n                if attr not in gk:\n                    continue\n                tally += gk[attr] * self.kwargs.meta[\"rate_weight\"][i]\n            _[attr] = tally\n\n        _notional_attrs = [\n            f\"delta_{self.settlement_params.currency}\",\n            f\"gamma_{self.settlement_params.currency}\",\n            f\"vega_{self.settlement_params.currency}\",\n        ]\n        for attr in _notional_attrs:\n            _[attr] = sum(gk[attr] * self.kwargs.meta[\"rate_weight\"][i] for i, gk in enumerate(gks))\n\n        _.update(\n            {\n                \"__class\": \"IROptionStrat\",\n                \"__options\": gks,\n                \"__notional\": self.kwargs.leg1[\"notional\"],\n            },\n        )\n        return _\n\n\nclass IRSStraddle(_BaseIRSOptionStrat):\n    \"\"\"\n    An *IR Straddle* :class:`~rateslib.instruments._BaseIRSOptionStrat`.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    A *Straddle* is composed of a :class:`~rateslib.instruments.IRSPut`\n    and :class:`~rateslib.instruments.IRSCall` with the same strike, expiry and tenor.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import IRSStraddle, Curve, dt\n\n    .. ipython:: python\n\n       irstr = IRSStraddle(\n           eval_date=dt(2020, 1, 1),\n           expiry=\"3m\",\n           tenor=\"1Y\",\n           strike=\"atm\",\n           irs_series=\"usd_irs\",\n           notional=1000000,\n       )\n       irstr.cashflows()\n\n    .. rubric:: Pricing\n\n    The pricing mirrors that for an :class:`~rateslib.instruments.IRSCall`. All options use the\n    same ``curves``. Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = rate_curve | [rate_curve] #  one curve is used as all curves\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order, index_curve is set equal to disc_curve\n       curves = [rate_curve, disc_curve, index_curve]  # three curves applied in the given order\n       curves = {\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve\n           \"index_curve\": index_curve\n       }  # dict form is explicit\n\n    A ``vol`` argument must be provided to each *Instrument*. This can either be a single\n    value universally used for all, or an individual item as part of a sequence. Allowed\n    inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0 | vol_obj  # a single item universally applied\n       vol = [12.0, 12.0]  # values for the Put and Call respectively\n\n    The following pricing ``metric`` are available, with examples:\n\n    TODO\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **ir option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    tenor: datetime, str, :red:`required`\n        The parameter defining the maturity of the underlying :class:`~rateslib.instruments.IRS`.\n    irs_series: IRSSeries, str, :red:`required`\n        The standard conventions applied to the underlying :class:`~rateslib.instruments.IRS`.\n    strike: float, Variable, str, :red:`required`\n        The strike value of the option.\n        If str, there are two possibilities; {\"atm\", \"{}bps\"}. \"atm\" will produce a strike equal\n        to the mid-market *IRS* rate, whilst \"20bps\" or \"-50bps\" will yield a strike that number\n        of basis points different to the mid-market rate.\n    notional: float, :green:`optional (set by 'defaults')`\n        The notional amount expressed in units of ``currency`` fo the ``irs_series``.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot.\n    payment_lag: int or datetime, :green:`optional (set as IRS effective)`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    settlement_method: SwaptionSettlementMethod, str, :green:`optional (set by 'default')`\n        The method for deriving the settlement cashflow or underlying value.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: 2-tuple of float, :green:`optional`\n        The amount paid for the put and call in order. If not given assumes unpriced\n        *Options* and sets this as mid-market premium during pricing.\n    option_fixings: 2-tuple of float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of each option's :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric : str, :green:`optional (set as \"pips_or_%\")`\n        The pricing metric returned by the ``rate`` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable, Sequence\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 100.0\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        tenor: datetime | str,\n        strike: DualTypes | str,\n        irs_series: IRSSeries | str,\n        *,\n        notional: DualTypes_ = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        premium: tuple[DualTypes_, DualTypes_] = (NoInput(0), NoInput(0)),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        settlement_method: SwaptionSettlementMethod | str_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        vol_ = self._parse_vol(vol)\n        notional_ = _drb(defaults.notional, notional)\n        options = [\n            IRSPut(\n                irs_series=irs_series,\n                expiry=expiry,\n                payment_lag=payment_lag,\n                eval_date=eval_date,\n                tenor=tenor,\n                strike=strike,\n                notional=notional_,\n                option_fixings=option_fixings[0]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                settlement_method=settlement_method,\n                premium=premium[0],\n                curves=curves,\n                vol=vol_[0],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n            IRSCall(\n                irs_series=irs_series,\n                expiry=expiry,\n                payment_lag=payment_lag,\n                eval_date=eval_date,\n                tenor=tenor,\n                strike=strike,\n                notional=notional_,\n                option_fixings=option_fixings[1]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                settlement_method=settlement_method,\n                premium=premium[1],\n                curves=curves,\n                vol=vol_[1],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n        ]\n        super().__init__(\n            options=options,\n            rate_weight=[1.0, 1.0],\n            rate_weight_vol=[0.5, 0.5],\n            metric=metric,\n            curves=curves,\n            vol=vol_,\n        )\n        self.kwargs.leg1[\"notional\"] = notional_\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> tuple[_Vol, _Vol]:  # type: ignore[override]\n        if not isinstance(vol, list | tuple):\n            vol = (vol,) * 2\n        return IRSPut._parse_vol(vol[0]), IRSCall._parse_vol(vol[1])\n\n    def _set_notionals(self, notional: DualTypes) -> None:\n        \"\"\"\n        Set the notionals on each option period. Mainly used by Brokerfly for vega neutral\n        strangle and straddle.\n        \"\"\"\n        for option in self.instruments:\n            option.kwargs.leg1[\"notional\"] = notional\n            option._option.settlement_params._notional = notional\n"
  },
  {
    "path": "python/rateslib/instruments/ir_options/strangle.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import IROptionMetric\nfrom rateslib.instruments.ir_options.call_put import IRSCall, IRSPut\nfrom rateslib.instruments.ir_options.straddle import _BaseIRSOptionStrat\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurvesT_,\n        DualTypes,\n        DualTypes_,\n        IRSSeries,\n        SwaptionSettlementMethod,\n        VolStrat_,\n        VolT_,\n        _Vol,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass IRSStrangle(_BaseIRSOptionStrat):\n    \"\"\"\n    An *IR Strangle* :class:`~rateslib.instruments._BaseIRSOptionStrat`.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    A *Strangle* is composed of a lower strike :class:`~rateslib.instruments.IRSPut`\n    and a higher strike :class:`~rateslib.instruments.IRSCall` with the same expiry and tenor.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import IRSStrangle, Curve, dt\n\n    .. ipython:: python\n\n       irstr = IRSStrangle(\n           eval_date=dt(2020, 1, 1),\n           expiry=\"3m\",\n           tenor=\"1Y\",\n           strike=(\"-20bps\", \"+20bps\"),\n           irs_series=\"usd_irs\",\n           notional=1000000,\n       )\n       irstr.cashflows()\n\n    .. rubric:: Pricing\n\n    The pricing mirrors that for an :class:`~rateslib.instruments.IRSCall`. All options use the\n    same ``curves``. Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = rate_curve | [rate_curve] #  one curve is used as all curves\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order, index_curve is set equal to disc_curve\n       curves = [rate_curve, disc_curve, index_curve]  # three curves applied in the given order\n       curves = {\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve\n           \"index_curve\": index_curve\n       }  # dict form is explicit\n\n    A ``vol`` argument must be provided to each *Instrument*. This can either be a single\n    value universally used for all, or an individual item as part of a sequence. Allowed\n    inputs are:\n\n    .. code-block:: python\n\n       vol = 12.0 | vol_obj  # a single item universally applied\n       vol = [12.0, 12.0]  # values for the Put and Call respectively\n\n    The following pricing ``metric`` are available, with examples:\n\n    TODO\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define **ir option** and generalised **settlement** parameters.\n\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    tenor: datetime, str, :red:`required`\n        The parameter defining the maturity of the underlying :class:`~rateslib.instruments.IRS`.\n    irs_series: IRSSeries, str, :red:`required`\n        The standard conventions applied to the underlying :class:`~rateslib.instruments.IRS`.\n    strike: 2-tuple of float, Variable, str, :red:`required`\n        The strike values of each option.\n        If str, there are two possibilities; {\"atm\", \"{}bps\"}. \"atm\" will produce a strike equal\n        to the mid-market *IRS* rate, whilst \"20bps\" or \"-50bps\" will yield a strike that number\n        of basis points different to the mid-market rate.\n    notional: float, :green:`optional (set by 'defaults')`\n        The notional amount expressed in units of ``currency`` fo the ``irs_series``.\n    eval_date: datetime, :green:`optional`\n        Only required if ``expiry`` is given as string tenor.\n        Should be entered as today (also called horizon) and **not** spot.\n    payment_lag: int or datetime, :green:`optional (set as IRS effective)`\n        The number of business days after expiry to pay premium. If a *datetime* is given this will\n        set the premium date explicitly.\n    settlement_method: SwaptionSettlementMethod, str, :green:`optional (set by 'default')`\n        The method for deriving the settlement cashflow or underlying value.\n\n        .. note::\n\n           The following define additional **rate** parameters.\n\n    premium: 2-tuple of float, :green:`optional`\n        The amount paid for the put and call in order. If not given assumes unpriced\n        *Options* and sets this as mid-market premium during pricing.\n    option_fixings: 2-tuple of float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of each option's :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    metric : str, :green:`optional (set as \"pips_or_%\")`\n        The pricing metric returned by the ``rate`` method. See **Pricing**.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    vol: str, Smile, Surface, float, Dual, Dual2, Variable, Sequence\n        Pricing objects passed directly to the *Instrument's* methods' ``vol`` argument. See\n        **Pricing**.\n    spec : str, optional\n        An identifier to pre-populate many field with conventional values. See\n        :ref:`here<defaults-doc>` for more info and available values.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 100.0\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        tenor: datetime | str,\n        strike: tuple[DualTypes | str, DualTypes | str],\n        irs_series: IRSSeries | str,\n        *,\n        notional: DualTypes_ = NoInput(0),\n        eval_date: datetime | NoInput = NoInput(0),\n        premium: tuple[DualTypes_, DualTypes_] = (NoInput(0), NoInput(0)),\n        payment_lag: str | datetime_ = NoInput(0),\n        option_fixings: DualTypes_ = NoInput(0),\n        settlement_method: SwaptionSettlementMethod | str_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        vol_ = self._parse_vol(vol)\n        notional_ = _drb(defaults.notional, notional)\n        options = [\n            IRSPut(\n                irs_series=irs_series,\n                expiry=expiry,\n                payment_lag=payment_lag,\n                eval_date=eval_date,\n                tenor=tenor,\n                strike=strike[0],\n                notional=notional_,\n                option_fixings=option_fixings[0]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                settlement_method=settlement_method,\n                premium=premium[0],\n                curves=curves,\n                vol=vol_[0],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n            IRSCall(\n                irs_series=irs_series,\n                expiry=expiry,\n                payment_lag=payment_lag,\n                eval_date=eval_date,\n                tenor=tenor,\n                strike=strike[1],\n                notional=notional_,\n                option_fixings=option_fixings[1]\n                if isinstance(option_fixings, tuple | list)\n                else option_fixings,\n                settlement_method=settlement_method,\n                premium=premium[1],\n                curves=curves,\n                vol=vol_[1],\n                metric=NoInput(0),\n                spec=spec,\n            ),\n        ]\n        super().__init__(\n            options=options,\n            rate_weight=[1.0, 1.0],\n            rate_weight_vol=[0.5, 0.5],\n            metric=metric,\n            curves=curves,\n            vol=vol_,\n        )\n        self.kwargs.leg1[\"notional\"] = notional_\n\n    @classmethod\n    def _parse_vol(cls, vol: VolStrat_) -> tuple[_Vol, _Vol]:  # type: ignore[override]\n        if not isinstance(vol, list | tuple):\n            vol = (vol,) * 2\n        return IRSPut._parse_vol(vol[0]), IRSCall._parse_vol(vol[1])\n\n    def _set_notionals(self, notional: DualTypes) -> None:\n        \"\"\"\n        Set the notionals on each option period. Mainly used by Brokerfly for vega neutral\n        strangle and straddle.\n        \"\"\"\n        for option in self.instruments:\n            option.kwargs.leg1[\"notional\"] = notional\n            option._option.settlement_params._notional = notional\n"
  },
  {
    "path": "python/rateslib/instruments/ir_options/vol_value.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, NoReturn\n\nfrom rateslib import defaults\nfrom rateslib.data.fixings import _get_irs_series\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import OptionPricingModel, OptionType, _get_ir_option_metric\nfrom rateslib.instruments.ir_options.call_put import _BaseIRSOption\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _maybe_get_ir_vol_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.periods.parameters import _IROptionParams\nfrom rateslib.periods.utils import (\n    _get_ir_vol_value_and_forward_maybe_from_obj,\n)\nfrom rateslib.rs import IROptionMetric\nfrom rateslib.scheduling import add_tenor\nfrom rateslib.volatility.ir import IRSabrCube, IRSabrSmile\nfrom rateslib.volatility.utils import _OptionModelBachelier, _OptionModelBlack76\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        IRSSeries,\n        Solver_,\n        VolT_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass IRVolValue(_BaseInstrument):\n    \"\"\"\n    A pseudo *Instrument* used to calibrate an *IR Vol Object* within a\n    :class:`~rateslib.solver.Solver`.\n\n    .. rubric:: Examples\n\n    Examples\n    --------\n    The below :class:`~rateslib.volatility.FXDeltaVolSmile` is solved directly\n    from calibrating volatility values.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.volatility import IRSabrSmile\n       from rateslib.instruments import IRVolValue\n       from rateslib.solver import Solver\n\n    ..\n        .. ipython:: python\n\n           smile = IRSabrSmile(\n               nodes={\"alpha\": 0.20, \"beta\": 0.5, \"rho\": 0.05, \"nu\": 0.60},\n               eval_date=dt(2026, 2, 12),\n               tenor=\"1y\",\n               expiry=dt(2027, 2, 12),\n               irs_series=\"usd_irs\",\n               id=\"VolSmile\",\n           )\n           instruments = [\n               IRVolValue(2.5, vol=\"VolSmile\"),\n               IRVolValue(3.5, vol=smile)\n           ]\n           solver = Solver(curves=[smile], instruments=instruments, s=[8.9, 7.8])\n           smile[2.1]\n           smile[2.5]\n           smile[3.5]\n           smile[3.9]\n\n    .. rubric:: Pricing\n\n    An *IR Vol Value* requires, and will calibrate, just one *IR Vol Object*.\n\n    Allowable inputs are:\n\n    .. code-block:: python\n\n       vol = ir_vol_obj | [ir_vol_obj]  #  a single object is detected\n       vol = {\"ir_vol\": ir_vol_obj}  # dict form is explicit\n\n    The ``curves`` must match the pricing for an :class:`~rateslib.instruments.IRS`, since the\n    atm-rate is determined directly from an *IRS* instance.\n\n    The available ``metric`` are:\n\n    - **'normal_vol'**: which returns a normal volatility in bps suitable for the Bachelier pricing\n      formula.\n    - **'black_vol_shift_{}'**: same as above but allowing an explicit shift.\n    - **'alpha', 'beta', 'rho', 'nu'**: returns the SABR parameters explicitly for a SABR based\n      pricing object.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    expiry: datetime, str, :red:`required`\n        The expiry of the option. If given in string tenor format, e.g. \"1M\" requires an\n        ``eval_date``. See **Notes**.\n    tenor: datetime, str, :red:`required`\n        The parameter defining the maturity of the underlying :class:`~rateslib.instruments.IRS`.\n    strike: float, Variable, str, :red:`required`\n        The strike value used as the index value to the pricing model.\n        If str, there are two possibilities; {\"atm\", \"{}bps\"}. \"atm\" will produce a strike equal\n        to the mid-market *IRS* rate, whilst \"20bps\" or \"-50bps\" will yield a strike that number\n        of basis points different to the mid-market rate.\n    irs_series: IRSSeries, str, :red:`required`\n        The standard conventions applied to the underlying :class:`~rateslib.instruments.IRS`.\n    eval_date: datetime, :green:`optional`\n        If expiry is given as string tenor, use eval date to determine the date.\n    metric: str, IROptionMetric, :green:`optional (set as 'normal_vol')`\n        The default metric to return from the ``rate`` method.\n    vol: str, IRVolObj, :green:`optional`\n        The associated object from which to determine the ``rate``.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n\n    \"\"\"\n\n    @property\n    def rate_scalar(self) -> float:\n        metric_ = self.kwargs.meta[\"metric\"].lower()\n        match metric_:\n            case \"alpha\" | \"beta\" | \"rho\" | \"nu\":\n                return 1.0\n            case \"normal_vol\":\n                return 100.0\n            case _ if \"black_vol_shift_\" in metric_:\n                return 100.0\n            case _:\n                raise NotImplementedError(\n                    \"The provided metric for IRVolValue is not rate scalar mapped.\"\n                )\n\n    _rate_scalar = 1.0\n\n    def __init__(\n        self,\n        expiry: datetime | str,\n        tenor: datetime | str,\n        strike: DualTypes | str,\n        irs_series: IRSSeries | str,\n        *,\n        eval_date: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n    ):\n        user_args = dict(\n            tenor=tenor,\n            expiry=expiry,\n            strike=strike,\n            irs_series=irs_series,\n            vol=self._parse_vol(vol),\n            metric=metric,\n            curves=self._parse_curves(curves),\n        )\n        default_args = dict(convention=defaults.convention, metric=\"normal_vol\", curves=NoInput(0))\n        self._kwargs = _KWArgs(\n            spec=NoInput(0),\n            user_args=user_args,\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\", \"vol\", \"curves\"],\n        )\n        if isinstance(self.kwargs.leg1[\"expiry\"], str):\n            if isinstance(eval_date, NoInput):\n                raise ValueError(\"`tenor` as string requires an `eval_date` to quantify.\")\n            series_ = _get_irs_series(self.kwargs.leg1[\"irs_series\"])\n            self.kwargs.leg1[\"expiry\"] = add_tenor(\n                start=eval_date,\n                tenor=self.kwargs.leg1[\"expiry\"],\n                modifier=series_.modifier,\n                calendar=series_.calendar,\n            )\n\n    @cached_property\n    def _ir_option_params(self) -> _IROptionParams:\n        return _IROptionParams(\n            _expiry=self.kwargs.leg1[\"expiry\"],\n            _tenor=self.kwargs.leg1[\"tenor\"],\n            _irs_series=_get_irs_series(self.kwargs.leg1[\"irs_series\"]),\n            _strike=self.kwargs.leg1[\"strike\"],\n            # unused parameters\n            _direction=OptionType.Put,\n            _metric=defaults.ir_option_metric,\n            _option_fixings=NoInput(0),\n            _settlement_method=defaults.ir_option_settlement,\n        )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        ir_vol = _maybe_get_ir_vol_maybe_from_solver(\n            vol=self._parse_vol(vol), vol_meta=self.kwargs.meta[\"vol\"], solver=solver\n        )\n\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n        del metric\n\n        if metric_ in [\"alpha\", \"beta\", \"rho\", \"nu\"]:\n            if isinstance(ir_vol, IRSabrSmile):\n                return getattr(ir_vol.nodes, metric_)  # type: ignore[no-any-return]\n            elif isinstance(ir_vol, IRSabrCube):\n                smile: IRSabrSmile = ir_vol.get_smile(  # type: ignore[assignment]\n                    expiry=self.kwargs.leg1[\"expiry\"],\n                    tenor=self._ir_option_params.option_fixing.termination,\n                )\n                return getattr(smile.nodes, metric_)  # type: ignore[no-any-return]\n            else:\n                raise ValueError(\n                    \"A SABR parameter `metric` can only be obtained from a SABR type vol pricing \"\n                    \"object.\"\n                )\n\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", True, True, *c)\n        # disc_curve: _BaseCurve = _fetch_pricing_curve(\"disc_curve\", False, False, *c)\n        index_curve = _get_curve(\"index_curve\", False, False, *c)\n\n        metric__ = _get_ir_option_metric(metric_)\n        del metric_\n\n        if not hasattr(ir_vol, \"get_from_strike\"):\n            raise TypeError(\"`vol` for IRVolValue must be of type _BaseIRSmile or _BaseIRCube.\")\n\n        params = _get_ir_vol_value_and_forward_maybe_from_obj(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            strike=self.kwargs.leg1[\"strike\"],\n            ir_vol=ir_vol,\n            irs=self._ir_option_params.option_fixing.irs,\n            tenor=self._ir_option_params.option_fixing.termination,\n            expiry=self._ir_option_params.expiry,\n            t_e=ir_vol.meta._t_expiry(self._ir_option_params.expiry),  # type: ignore[union-attr]\n        )\n\n        match type(metric__):\n            case IROptionMetric.Premium | IROptionMetric.PercentNotional:\n                raise ValueError(\n                    \"`metric` cannot be a cash or monetary quantity for this Instrument type\"\n                )\n            case IROptionMetric.NormalVol:\n                if params.pricing_model == OptionPricingModel.Bachelier:\n                    return params.vol\n                else:\n                    return _OptionModelBlack76.convert_to_bachelier(\n                        f=params.f, k=params.k, shift=params.shift, t_e=params.t_e, vol=params.vol\n                    )\n            case IROptionMetric.BlackVolShift:\n                required_shift = metric__.shift()\n                if params.pricing_model == OptionPricingModel.Bachelier:\n                    return _OptionModelBachelier.convert_to_black76(\n                        f=params.f, k=params.k, shift=required_shift, t_e=params.t_e, vol=params.vol\n                    )\n                else:\n                    return _OptionModelBlack76.convert_to_new_shift(\n                        f=params.f,\n                        k=params.k,\n                        old_shift=params.shift,\n                        target_shift=required_shift,\n                        t_e=params.t_e,\n                        vol=params.vol,\n                    )\n            case _:\n                raise RuntimeError(  # pragma: no cover\n                    \"Unexpected error: unmapped IROptionMetric branch - please report.\"\n                )\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        return _BaseIRSOption._parse_curves(curves)\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _BaseIRSOption._parse_vol(vol)\n\n    def npv(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\n            \"`VolValue` instrument has no concept of NPV.\"\n        )  # pragma: no cover\n\n    def cashflows(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\n            \"`VolValue` instrument has no concept of cashflows.\"\n        )  # pragma: no cover\n\n    def analytic_delta(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\n            \"`VolValue` instrument has no concept of analytic delta.\"\n        )  # pragma: no cover\n"
  },
  {
    "path": "python/rateslib/instruments/irs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import LegMtm\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _get_fx_forwards_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg, FloatLeg\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Adjuster,\n        CalInput,\n        Convention,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        LegFixings,\n        RollDay,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass IRS(_BaseInstrument):\n    \"\"\"\n    An *interest rate swap (IRS)* composing a :class:`~rateslib.legs.FixedLeg`\n    and a :class:`~rateslib.legs.FloatLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import IRS\n       from rateslib.data.fixings import FXIndex\n       from datetime import datetime as dt\n       from rateslib import fixings\n       from pandas import Series\n\n    .. ipython:: python\n\n       irs = IRS(\n           effective=dt(2000, 1, 1),\n           termination=\"2y\",\n           spec=\"usd_irs\",\n           fixed_rate=2.0,\n       )\n       irs.cashflows()\n\n    .. rubric:: Pricing\n\n    An *IRS* requires a *disc curve* on both legs (which should be the same *Curve*) and a\n    *leg2 rate curve* to forecast rates on the *FloatLeg*. The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = [None, disc_curve, rate_curve, disc_curve]     # four curves applied to each leg\n       curves = {\"leg2_rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    ``metric`` is unused by *IRS* and is always fixed '*rate*'.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: Convention, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.scheduling.Convention` applied to calculations of period accrual\n        dates. See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_convention: Convention, str, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n    leg2_amortization : float, Dual, Dual2, Variable, str, Amortization, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n    leg2_fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    leg2_fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    leg2_fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    leg2_float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    leg2_spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n    leg2_zero_periods: bool, :green:`optional (set as False)`\n        Used to define whether to use a multi-period IBOR classification. See\n        :class:`~rateslib.legs.FloatLeg` for examples.\n\n        .. note::\n\n           The following define **non-deliverability** parameters. If the swap is\n           directly deliverable do not use these parameters. Review the **notes** section\n           non-deliverability.\n\n    pair: FXIndex, str, :green:`optional`\n        The currency pair for :class:`~rateslib.data.fixings.FXFixing` that determines *Period*\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according\n        to non-deliverability.\n    leg2_fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* on *Leg2*\n        according to non-deliverability.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    Notes\n    -----\n\n    **Non-Deliverable IRS (NDIRS)**\n\n    An *NDIRS* can be constructed by using the ``pair`` argument. The ``currency`` defines the\n    *settlement currency*, whilst the *reference currency* is derived from ``pair`` and the\n    ``notional`` is expressed *reference currency* units.\n\n    The ``fx_fixings`` argument is typically used to provide an FX fixing series from which to\n    extract non-deliverable :class:`~rateslib.data.fixings.FXFixing` data. The ``leg2_fx_fixings``\n    inherits from the former and is likely to always be omitted, unless the fixings are provided\n    as a list (against best practice) and the schedules do not align.\n\n    For **pricing**, whilst a traditional *IRS* can be priced with just one *Curve*, e.g. \"sofr\"\n    for a conventional USD IRS, an ND-IRS will always require 2 different curves: a *leg2 rate\n    curve* for forecasting rates in the non-deliverable reference currency, and a *disc curve* for\n    discounting cashflows in the settlement currency.\n\n    The following is an example of a THB ND-IRS settled in USD with notional of 10mm THB.\n\n    .. ipython:: python\n\n       fixings.add(\"WMR_10AM_TYO_USDTHB\", Series(index=[dt(2000, 6, 30), dt(2001, 1, 2)], data=[35.25, 37.0]))\n       irs = IRS(\n           effective=dt(2000, 1, 1),\n           termination=\"2y\",\n           frequency=\"S\",\n           currency=\"usd\",                              #  <- USD set as the settlement currency\n           pair=FXIndex(\"usdthb\", \"fed\", 1, \"fed\", -1), #  <- THB inferred as the reference currency\n           fx_fixings=\"WMR_10AM_TYO\",\n           fixed_rate=2.0,\n           # all other arguments set as normal IRS\n       )\n       irs.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"WMR_10AM_TYO_USDTHB\")\n\n    Further information is available in the documentation for a :class:`~rateslib.legs.FixedLeg`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg2_float_spread(self) -> DualTypes_:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        return self.leg2.float_spread\n\n    @leg2_float_spread.setter\n    def leg2_float_spread(self, value: DualTypes) -> None:\n        self.kwargs.leg2[\"float_spread\"] = value\n        self.leg2.float_spread = value\n\n    @property\n    def leg1(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FloatLeg:\n        \"\"\"The :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: Adjuster | str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: Adjuster | str | int_ = NoInput(0),\n        payment_lag_exchange: Adjuster | str | int_ = NoInput(0),\n        ex_div: Adjuster | str | int_ = NoInput(0),\n        convention: Convention | str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: Adjuster | str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: Adjuster | str | int_ = NoInput(1),\n        leg2_payment_lag_exchange: Adjuster | str | int_ = NoInput(1),\n        leg2_ex_div: Adjuster | str | int_ = NoInput(1),\n        leg2_convention: Convention | str_ = NoInput(1),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        amortization: float_ = NoInput(0),\n        leg2_notional: float_ = NoInput(-1),\n        leg2_amortization: float_ = NoInput(-1),\n        # non-deliverability\n        pair: str_ = NoInput(0),\n        fx_fixings: LegFixings = NoInput(0),\n        leg2_fx_fixings: LegFixings = NoInput(1),\n        # rate parameters\n        fixed_rate: DualTypes_ = NoInput(0),\n        leg2_float_spread: DualTypes_ = NoInput(0),\n        leg2_spread_compound_method: str_ = NoInput(0),\n        leg2_rate_fixings: LegFixings = NoInput(0),\n        leg2_fixing_method: str_ = NoInput(0),\n        leg2_fixing_frequency: Frequency | str_ = NoInput(0),\n        leg2_fixing_series: FloatRateSeries | str_ = NoInput(0),\n        leg2_zero_periods: bool_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            leg2_effective=leg2_effective,\n            termination=termination,\n            leg2_termination=leg2_termination,\n            frequency=frequency,\n            leg2_frequency=leg2_frequency,\n            stub=stub,\n            leg2_stub=leg2_stub,\n            front_stub=front_stub,\n            leg2_front_stub=leg2_front_stub,\n            back_stub=back_stub,\n            leg2_back_stub=leg2_back_stub,\n            roll=roll,\n            leg2_roll=leg2_roll,\n            eom=eom,\n            leg2_eom=leg2_eom,\n            modifier=modifier,\n            leg2_modifier=leg2_modifier,\n            calendar=calendar,\n            leg2_calendar=leg2_calendar,\n            payment_lag=payment_lag,\n            leg2_payment_lag=leg2_payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            ex_div=ex_div,\n            leg2_ex_div=leg2_ex_div,\n            convention=convention,\n            leg2_convention=leg2_convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            leg2_notional=leg2_notional,\n            amortization=amortization,\n            leg2_amortization=leg2_amortization,\n            # non-deliverability\n            pair=pair,\n            fx_fixings=fx_fixings,\n            leg2_fx_fixings=leg2_fx_fixings,\n            # rate\n            fixed_rate=fixed_rate,\n            leg2_float_spread=leg2_float_spread,\n            leg2_spread_compound_method=leg2_spread_compound_method,\n            leg2_rate_fixings=leg2_rate_fixings,\n            leg2_fixing_method=leg2_fixing_method,\n            leg2_fixing_series=leg2_fixing_series,\n            leg2_fixing_frequency=leg2_fixing_frequency,\n            leg2_zero_periods=leg2_zero_periods,\n            # meta\n            curves=self._parse_curves(curves),\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            leg2_currency=NoInput(1),\n            leg2_pair=NoInput(1),\n            initial_exchange=False,\n            final_exchange=False,\n            leg2_initial_exchange=False,\n            leg2_final_exchange=False,\n            mtm=LegMtm.Payment,\n            leg2_mtm=LegMtm.Payment,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"vol\"],\n        )\n\n        self._leg1 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self.leg1, self.leg2]\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_rate_curve = _get_curve(\"leg2_rate_curve\", True, True, *c)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(solver, fx)\n\n        leg2_npv: DualTypes = self.leg2.local_npv(\n            rate_curve=leg2_rate_curve,\n            disc_curve=leg2_disc_curve,\n            index_curve=NoInput(0),\n            fx=fx_,\n            settlement=settlement,\n            forward=forward,\n        )\n        return (\n            self.leg1.spread(\n                target_npv=-leg2_npv,\n                rate_curve=NoInput(0),\n                disc_curve=disc_curve,\n                fx=fx_,\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n            / 100\n        )\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_rate_curve = _get_curve(\"leg2_rate_curve\", True, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(solver, fx)\n\n        leg1_npv: DualTypes = self.leg1.local_npv(\n            rate_curve=NoInput(0),\n            disc_curve=disc_curve,\n            index_curve=NoInput(0),\n            fx=fx_,\n            settlement=settlement,\n            forward=forward,\n        )\n        return self.leg2.spread(\n            target_npv=-leg1_npv,\n            rate_curve=leg2_rate_curve,\n            fx=fx_,\n            disc_curve=disc_curve,\n            index_curve=NoInput(0),\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            settlement=settlement,\n            forward=forward,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IRS is that its fixed rate is not set.\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            # set a fixed rate for the purpose of generic methods NPV will be zero.\n            mid_market_rate = self.rate(\n                curves=curves,\n                solver=solver,\n                settlement=settlement,\n                forward=forward,\n                fx=fx,\n            )\n            self.leg1.fixed_rate = _dual_float(mid_market_rate)\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    @classmethod\n    def _parse_curves(cls, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An IRS has two curve requirements: a leg2_rate_curve and a disc_curve used by both legs.\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 1:\n                return _Curves(\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[0],\n                    leg2_disc_curve=curves[0],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_rate_curve=curves[2],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(cls).__name__} requires only 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_rate_curve=_drb(\n                    curves.get(\"rate_curve\", NoInput(0)),\n                    curves.get(\"leg2_rate_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                leg2_rate_curve=curves,  # type: ignore[arg-type]\n                disc_curve=curves,  # type: ignore[arg-type]\n                leg2_disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/loan.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual import ift_1dim\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import LegMtm\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg, FloatLeg\nfrom rateslib.scheduling import Frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Adjuster,\n        CalInput,\n        Convention,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FixingsRates_,\n        FloatRateSeries,\n        FXForwards_,\n        IndexMethod,\n        LegFixings,\n        LegIndexBase,\n        RollDay,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass Loan(_BaseInstrument):\n    \"\"\"\n    A *loan obligation* composing either a :class:`~rateslib.legs.FixedLeg` or a\n    :class:`~rateslib.legs.FloatLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import Loan\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       loan = Loan(dt(2022, 1, 4), \"3m\", \"Q\", notional=10e6, fixed_rate=10.0, calendar=\"nyc\")\n       loan.cashflows()\n\n    .. rubric:: Pricing\n\n    A *Loan* with a fixed rate requires one *disc curve* for discounting.\n    A *Loan* with a floating rate may require an additional *rate curve* for forecasting if\n    rate fixings have not been published.\n    A *Loan* that is indexed may require an additional *index curve*.\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = [rate_curve, disc_curve]  #  two curves given in the specified order\n       curves = [rate_curve, disc_curve, index_curve]  #  three curves given in the specified order\n       curves = {  # dict form is explicit\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve,\n           \"index_curve\": index_curve,\n       }\n\n    The *rate* method is not generally implemented for a *Loan*. However, for flexibility,\n    one ``metric`` that is available:\n\n    - *'npv'*: returns the result of the :meth:`~rateslib.instruments.Fee.npv` method.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of leg1 (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set from 'leg2_notional' or 'defaults' )`\n        The initial leg1 notional, defined in units of the currency of the leg. Only one\n        of ``notional`` and ``leg2_notional`` can be given. The alternate leg notional is derived\n        via non-deliverability :class:`~rateslib.data.fixings.FXFixing`.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed : bool, :green:`optional (set as True)`\n        Whether leg1 is a :class:`~rateslib.legs.FixedLeg` or a :class:`~rateslib.legs.FloatLeg`.\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n\n    .. note::\n\n           The following define **non-deliverability** parameters. If the fee is\n           directly deliverable do not use these parameters.\n\n    pair: FXIndex, str, :green:`optional`\n        The currency pair for :class:`~rateslib.data.fixings.FXFixing` that determines *Period*\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    mtm: bool, :green:`optional (set to False)`\n        If *True* use non-deliverability defined by payment date, else use non-deliverability\n        defined by a single fixing related to the effective date.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` according\n        to non-deliverability.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value set of the base index value.\n        If not given and ``index_fixings`` is a str fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The index value for the reference date.\n        If a scalar value this is used directly. If a string identifier will link to the\n        central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n    index_base_type: LegIndexBase, :green:`optional (set as 'initial')`\n        A parameter to define how the ``index_base_date`` is set on each period. See notes.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n    metric: str, :green:`optional (set as 'leg1')`\n        Determines which calculation metric to return by default when using the\n        :meth:`~rateslib.instruments.Loan.rate` method.\n\n    Notes\n    -----\n    How does a :class:`~rateslib.instruments.Loan` compare with\n    a :class:`~rateslib.instruments.FixedRateBond` or :class:`~rateslib.instruments.FloatRateNote`?\n    All of these *Instruments* consist of a single *Leg* with interest payments.\n    However, a :class:`~rateslib.instruments.Loan` is modeled with its initial cashflow and final\n    cashflow, whilst the :class:`~rateslib.instruments.FixedRateBond` and\n    :class:`~rateslib.instruments.FloatRateNote` do not include their initial cashflow.\n\n    This is a conceptual choice. *Bonds* typically trade in the primary and secondary market and\n    therefore the initial cashflow, for the purchase of the security, is a transactional\n    quantity based or price or YTM. Due to this variation the initial cashflow is excluded\n    from a *Bonds* cashflow representation.\n\n    *Loans* are *Instruments* that are considered to be accounting entries,\n    so the initial cashflow is usually well defined between two counterparties, and is therefore\n    included.\n\n    **Indexation**\n\n    The loan payments can be based on some indexed quantity. The ``index_base_date``\n    for each payment will be set according to ``index_base_type``, and follows the\n    logic applied to a :class:`~rateslib.legs.FixedLeg`.\n\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def leg1(self) -> FixedLeg | FloatLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` or :class:`~rateslib.legs.FloatLeg`\n        of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs  # type: ignore[return-value]\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: Adjuster | str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: Adjuster | str | int_ = NoInput(0),\n        payment_lag_exchange: Adjuster | str | int_ = NoInput(0),\n        ex_div: Adjuster | str | int_ = NoInput(0),\n        convention: Convention | str_ = NoInput(0),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        amortization: float_ = NoInput(0),\n        # rate parameters\n        fixed: bool_ = NoInput(0),\n        fixed_rate: DualTypes_ = NoInput(0),\n        float_spread: DualTypes_ = NoInput(0),\n        spread_compound_method: str_ = NoInput(0),\n        rate_fixings: FixingsRates_ = NoInput(0),\n        fixing_method: str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # # non-deliverability\n        pair: str_ = NoInput(0),\n        fx_fixings: LegFixings = NoInput(0),\n        mtm: bool_ = NoInput(0),\n        # index params\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: LegFixings = NoInput(0),\n        index_base_type: LegIndexBase | str_ = NoInput(0),\n        # meta parameters\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            stub=stub,\n            front_stub=front_stub,\n            back_stub=back_stub,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            ex_div=ex_div,\n            convention=convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            amortization=amortization,\n            # non-deliverability\n            pair=pair,\n            fx_fixings=fx_fixings,\n            mtm=mtm,\n            # indexation\n            index_base=index_base,\n            index_lag=index_lag,\n            index_method=index_method,\n            index_fixings=index_fixings,\n            index_base_type=index_base_type,\n            # rate\n            fixed_rate=fixed_rate,\n            float_spread=float_spread,\n            spread_compound_method=spread_compound_method,\n            rate_fixings=rate_fixings,\n            fixing_method=fixing_method,\n            fixing_frequency=fixing_frequency,\n            fixing_series=fixing_series,\n            # meta\n            fixed=fixed,\n            curves=self._parse_curves(curves),\n            metric=metric,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            initial_exchange=True,\n            final_exchange=True,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            currency=defaults.base_currency,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n            fixed=True,\n            mtm=False,\n            metric=\"leg1\",\n        )\n\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\", \"fixed\", \"vol\"],\n        )\n\n        # narrowing of fixed or floating\n        float_attrs = [\n            \"float_spread\",\n            \"spread_compound_method\",\n            \"rate_fixings\",\n            \"fixing_method\",\n            \"fixing_frequency\",\n            \"fixing_series\",\n        ]\n        if self.kwargs.meta[\"fixed\"]:\n            for item in float_attrs:\n                self.kwargs.leg1.pop(item)\n        else:\n            self.kwargs.leg1.pop(\"fixed_rate\")\n\n        # setting non-deliverability\n        self.kwargs.leg1[\"mtm\"] = LegMtm.Payment if self.kwargs.leg1[\"mtm\"] else LegMtm.Initial\n\n        if self.kwargs.meta[\"fixed\"]:\n            self._leg1: FixedLeg | FloatLeg = FixedLeg(\n                **_convert_to_schedule_kwargs(self.kwargs.leg1, 1)\n            )\n        else:\n            self._leg1 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n\n        self._legs = [self._leg1]\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        A FixedRate Loan only requires one curve for discounting.\n        A FloatRate Loan requires upto two, one for discounting and one for forecasting rates.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                index_curve=curves.get(\"index_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                )\n            elif len(curves) == 1:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[0],\n                    index_curve=curves[0],\n                )\n            elif len(curves) == 3:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    index_curve=curves[2],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires upto 3 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                rate_curve=curves,  # type: ignore[arg-type]\n                disc_curve=curves,  # type: ignore[arg-type]\n                index_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n\n        if metric_ == \"npv\":\n            return self.npv(  # type: ignore[return-value]\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                vol=vol,\n                base=base,\n                settlement=settlement,\n                forward=forward,\n                local=False,\n            )\n\n        c = _parse_curves(self, curves, solver)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n        settlement_ = _drb(disc_curve.nodes.initial, settlement)\n        period_index = self.leg1._period_index(settlement_)\n        tgt_notional = -self.leg1._regular_periods[period_index].settlement_params.notional\n\n        if metric_ == \"fixed_rate\":\n            raise NotImplementedError(\"metric 'float_rate' not implemented for Loan.\")\n            if not isinstance(self.leg1, FixedLeg):\n                raise TypeError(\"Can only use 'fixed_rate' for FixedLeg Loan.\")\n\n            fixed_rate_ = self.leg1.fixed_rate\n\n            def s(g):\n                self.leg1.fixed_rate = g\n                pv = self._npv_local_excluding_first_exchange(\n                    curves=curves,\n                    solver=solver,\n                    settlement=settlement_,\n                    forward=forward,\n                )\n                return pv\n\n            result = ift_1dim(\n                s=s,\n                s_tgt=tgt_notional,\n                h=\"ytm_quadratic\",\n                ini_h_args=(-3.0, 2.0, 12.0),\n                func_tol=1e-5,\n                conv_tol=1e-6,\n                max_iter=20,\n            )\n\n            self.leg1.fixed_rate = fixed_rate_\n            return result[\"g\"]\n\n        elif metric == \"float_spread\":\n            raise NotImplementedError(\"metric 'float_rate' not implemented for Loan.\")\n            if not isinstance(self.leg1, FloatLeg):\n                raise TypeError(\"Can only use 'float_spread' for FloatLeg Loan.\")\n\n            float_spread_ = self.leg1.float_spread\n\n            def s(g):\n                self.leg1.float_spread = g\n                pv = self._npv_local_excluding_first_exchange(\n                    curves=curves,\n                    solver=solver,\n                    settlement=settlement_,\n                    forward=forward,\n                )\n                return pv\n\n            result = ift_1dim(\n                s=s,\n                s_tgt=tgt_notional,\n                h=\"ytm_quadratic\",\n                ini_h_args=(-300.0, 200.0, 1200.0),\n                func_tol=1e-5,\n                conv_tol=1e-6,\n                max_iter=20,\n            )\n\n            self.leg1.fixed_rate = float_spread_\n            return result[\"g\"]\n\n        else:\n            raise ValueError(\"`metric`must be in {'npv', 'cashflow'}.\")\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    # def _npv_local_excluding_first_exchange(\n    #     self,\n    #     *,\n    #     curves: CurvesT_ = NoInput(0),\n    #     solver: Solver_ = NoInput(0),\n    #     fx: FXForwards_ = NoInput(0),\n    #     vol: VolT_ = NoInput(0),\n    #     settlement: datetime_ = NoInput(0),\n    #     forward: datetime_ = NoInput(0),\n    # ) -> DualTypes | dict[str, DualTypes]:\n    #     c = _parse_curves(self, curves, solver)\n    #     disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n    #     first_npv = self.leg1.periods[0].npv(\n    #         disc_curve=disc_curve, settlement=settlement, forward=forward\n    #     )\n    #     return (\n    #         super().npv(\n    #             curves=curves,\n    #             solver=solver,\n    #             fx=fx,\n    #             vol=vol,\n    #             settlement=settlement,\n    #             forward=forward,\n    #         )\n    #         - first_npv\n    #     )\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def analytic_delta(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        leg: int = 1,\n    ) -> DualTypes | dict[str, DualTypes]:\n        return super().analytic_delta(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n            leg=leg,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/ndf.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.data.fixings import FXIndex, _get_fx_index\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_fx_forwards_maybe_from_solver,\n    _Vol,\n)\nfrom rateslib.legs import CustomLeg\nfrom rateslib.periods import Cashflow\nfrom rateslib.periods.utils import _validate_fx_as_forwards\nfrom rateslib.scheduling.frequency import _get_fx_expiry_and_delivery_and_payment\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Adjuster,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        FXIndex,\n        FXIndex_,\n        LegFixings,\n        PeriodFixings,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime_,\n        str_,\n    )\n\n\nclass NDF(_BaseInstrument):\n    \"\"\"\n    A *non-deliverable FX forward* (NDF), composing two\n    :class:`~rateslib.legs.CustomLeg`\n    of individual :class:`~rateslib.periods.Cashflow`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import fixings, NDF\n       from datetime import datetime as dt\n       from rateslib.data.fixings import FXIndex\n\n    .. ipython:: python\n\n       ndf = NDF(dt(2026, 1, 5), FXIndex(\"usdbrl\", \"fed\", 2), fx_rate=5.5)\n       ndf.cashflows()\n\n    .. rubric:: Pricing\n\n    The methods of an *NDF* require an :class:`~rateslib.fx.FXForwards` object for ``fx`` .\n\n    They also require a *disc curve*, which is an appropriate curve to discount the\n    cashflows of the deliverable settlement currency. The following input\n    formats are allowed:\n\n    .. code-block:: python\n\n       curves = disc_curve | [disc_curve]  # one curve\n       curves = [None, disc_curve, None, disc_curve]  # four curves\n       curves = {  # dict form is explicit\n           \"disc_curve\": disc_curve,\n           \"leg2_disc_curve\": disc_curve,\n       }\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following are **settlement parameters**.\n\n    settlement : datetime, str, :red:`required`\n        The date of settlement for the currency ``pair`` and payment date.\n    pair : FXIndex, str, :red:`required`\n        The :class:`~rateslib.data.fixings.FXIndex` containing the FX pair implying the\n        reference currencies and notional of *leg1* and *leg2* respectively.\n    currency : str, :green:`optional (set as LHS currency in pair)`\n        The physical *settlement currency* of each leg. If not a currency in ``pair`` then each\n        leg will be non-deliverable (3-digit code).\n    notional : float, :green:`optional`\n        The notional of *leg1* expressed in units of LHS currency of ``pair``. This can be\n        derived from ``fx_rate`` and ``leg2_notional``.\n    leg2_notional : float, :green:`optional`\n        The notional of *leg2* expressed in units of RHS currency of ``pair``. This can be\n        derived from ``fx_rate`` and ``notional``.\n    fx_rate : float, :green:`optional`\n        The transational FX rate of ``pair``. This can be derived from ``notional`` and\n        ``leg2_notional``.\n\n        .. note::\n\n           The following are **scheduling parameters** required only if ``settlement`` given\n           as string tenor.\n\n    eval_date: datetime, :green:`optional`\n        Today's date from which spot and other dates may be determined.\n    modifier: Adjuster, str, :green:`optional`\n        The date adjuster for determining tenor dates under the convention for ``pair``.\n    eom: bool, :green:`optional`\n        Whether tenors under ``pair`` adopt EOM convention or not.\n\n        .. note::\n\n           The following are **FX fixing parameters** defining the settlement of the transaction.\n\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for settlement of *leg1* if\n        that leg is non-deliverable. If a scalar is used directly.\n        If a string identifier will link to the central ``fixings`` object and data loader.\n    reversed: bool, :green:`optional (set as False)`\n        Only used by a 3-currency NDF. Standard direction of the pair is '*settlement:reference*',\n        unless ``reversed`` is *True*, in which case '*reference:settlement*' is used.\n    leg2_fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for settlement of *leg2* if\n        that leg is non-deliverable. If a scalar is used directly.\n        If a string identifier will link to the central ``fixings`` object and data loader.\n    leg2_reversed: bool, :green:`optional (set as False)`\n        Only used by a 3-currency NDF. Standard direction of the pair is '*settlement:reference*',\n        unless ``reversed`` is *True*, in which case '*reference:settlement*' is used.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    Notes\n    -----\n    *NDFs* in *rateslib* replicate an :class:`~rateslib.instruments.FXForward` whose cashflows\n    are paid out netted in a single *settlement currency*. Two types are allowed:\n\n    - A **two currency** *NDF* where one *Leg* is directly deliverable in its own currency and\n      the other *Leg* is non-deliverable.\n    - A **three currency** *NDF* when both *Legs* with cashflow currencies of ``pair`` are\n      non-deliverable into a third ``currency``.\n\n    .. ipython:: python\n\n       fixings.add(\"WMR_10AM_TY0_USDINR\", Series(index=[dt(2026, 2, 16)], data=[92.5]))\n       fixings.add(\"WMR_10AM_TY0_USDSGD\", Series(index=[dt(2026, 2, 16)], data=[1.290]))\n\n    .. tabs::\n\n       .. tab:: Two Currency NDF\n\n          The **required** parameters of a two currency NDF are as follows;\n\n          - A ``pair`` which defines the currency pair and implicitly determines the\n            *reference currency*. The *settlement currency* for both *Legs* is inferred as the\n            LHS, although this can be manually set by using the ``currency`` argument.\n          - A ``notional`` or ``leg2_notional``. Each notional should be expressed in the\n            *reference currency* for that *Leg*. If both are given that defines the\n            transactional ``fx_rate``. If an ``fx_rate`` is given that will imply the missing\n            notional.\n          - ``fx_fixings`` or ``leg2_fx_fixings``. FX fixings can only be added to the\n            non-deliverable *Leg*.\n\n          This example is a USDINR *NDF* in 500mm INR payment with an initially agreed FX rate of\n          USDINR 92.0\n\n          .. ipython:: python\n\n             ndf = NDF(\n                 settlement=dt(2026, 2, 18),\n                 currency=\"usd\",              #  <-  USD settlement currency\n                 pair=\"usdinr\",               #  <-  INR reference currency implied\n                 leg2_notional=500e6,         #  <-  Leg2 is based on the reference currency (INR)\n                 leg2_fx_fixings=\"WMR_10AM_TY0\",\n                 fx_rate=92.0,                #  <-  Leg1 notional is implied as -5.43mm\n             )\n             ndf.cashflows()\n\n       .. tab:: Three Currency NDXCS\n\n          The **required** parameters of a three currency NDXCS are as follows;\n\n          - A ``currency`` which defines the *settlement currency* on both legs.\n          - A ``pair`` which defines the currency pair and implicitly determines\n            the *reference currency 1* and *reference currency 2*.\n          - A ``notional`` or ``leg2_notional``. Each notional should be expressed in the\n            *reference currency* for that *Leg*. If both are given that defines the\n            transactional ``fx_rate``. If an ``fx_rate`` is given that will imply the missing\n            notional.\n          - ``fx_fixings`` and ``leg2_fx_fixings``. Both legs are non-deliverable so FX fixings\n            may be provided to both *Leg*.\n\n          This example is a SGDINR *NDF* in 500mm INR payment with an initially agreed FX rate of\n          SGDINR 70.1\n\n          .. ipython:: python\n\n             ndf = NDF(\n                 settlement=dt(2026, 2, 18),\n                 currency=\"usd\",               #  <-  USD settlement currency\n                 pair=FXIndex(\"SGDINR\", \"mum\", 2),  #  <-  SGD + INR reference currencies\n                 leg2_notional=500e6,          #  <-  INR notional\n                 fx_rate=70.1,                 #  <-  Transaction rate of pair\n                 fx_fixings=\"WMR_10AM_TY0\",       #  <-  Data series tag for FXFixings on Leg1\n                 leg2_fx_fixings=\"WMR_10AM_TY0\",  #  <-  Data series tag for FXFixings on Leg2\n             )\n             ndf.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"WMR_10AM_TY0_USDINR\")\n       fixings.pop(\"WMR_10AM_TY0_USDSGD\")\n\n    \"\"\"\n\n    _rate_scalar = 1.0\n\n    @property\n    def leg1(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> CustomLeg:\n        \"\"\"The :class:`~rateslib.legs.CustomLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An NDF requires 1 disc curve for the cashflows in the delivery currency.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 1:\n                return _Curves(\n                    disc_curve=curves[0],\n                    leg2_disc_curve=curves[0],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires 1 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                disc_curve=curves,  # type: ignore[arg-type]\n                leg2_disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def __init__(\n        self,\n        settlement: datetime,\n        pair: FXIndex | str,\n        *,\n        # settlement and rate\n        currency: str_ = NoInput(0),\n        fx_rate: DualTypes_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        leg2_notional: DualTypes_ = NoInput(0),\n        # scheduling\n        eval_date: datetime_ = NoInput(0),\n        modifier: Adjuster | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        # fx fixings\n        fx_fixings: PeriodFixings = NoInput(0),\n        leg2_fx_fixings: PeriodFixings = NoInput(0),\n        reversed: bool_ = NoInput(0),  # noqa: A002\n        leg2_reversed: bool_ = NoInput(0),\n        # meta\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ):\n        (currency_, pair_, leg2_pair_, notional_, leg2_notional_, fx_rate_, fx_index_) = (\n            _validated_ndf_input_combinations(\n                currency=currency,\n                pair=pair,\n                notional=notional,\n                leg2_notional=leg2_notional,\n                fx_fixings=fx_fixings,\n                leg2_fx_fixings=leg2_fx_fixings,\n                fx_rate=fx_rate,\n                reversed=reversed,\n                leg2_reversed=leg2_reversed,\n                spec=spec,\n            )\n        )\n        del currency, pair, notional, leg2_notional, fx_rate\n\n        user_args = dict(\n            currency=currency_,\n            pair=pair_,\n            leg2_currency=currency_,\n            leg2_pair=leg2_pair_,\n            notional=notional_,\n            leg2_notional=leg2_notional_,\n            fx_rate=fx_rate_,\n            curves=self._parse_curves(curves),\n            eval_date=eval_date,\n            modifier=modifier,\n            eom=eom,\n            settlement=settlement,\n            fx_fixings=fx_fixings,\n            leg2_fx_fixings=leg2_fx_fixings,\n        )\n\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            vol=_Vol(),\n            leg2_settlement=NoInput(1),\n            fx_index=fx_index_,\n        )\n        default_args = dict(\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            modifier=defaults.modifier,\n            eom=defaults.eom_fx,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\n                \"curves\",\n                \"eval_date\",\n                \"calendar\",\n                \"modifier\",\n                \"payment_lag\",\n                \"eom\",\n                \"vol\",\n                \"fx_rate\",\n                \"fx_index\",\n            ],\n        )\n\n        # post input determination for 'settlement'\n        if not isinstance(self.kwargs.leg1[\"settlement\"], datetime):\n            _, settlement_, _ = _get_fx_expiry_and_delivery_and_payment(\n                eval_date=self.kwargs.meta[\"eval_date\"],\n                expiry=self.kwargs.leg1[\"settlement\"],\n                delivery_lag=self.kwargs.meta[\"fx_index\"].settle,\n                calendar=self.kwargs.meta[\"fx_index\"].calendar,\n                modifier=self.kwargs.meta[\"modifier\"],\n                eom=self.kwargs.meta[\"eom\"],\n                payment_lag=0,\n            )\n            self.kwargs.leg1[\"settlement\"] = settlement_\n            self.kwargs.leg2[\"settlement\"] = settlement_\n\n        # construct legs\n        self._leg1 = CustomLeg(\n            periods=[\n                Cashflow(\n                    currency=self.kwargs.leg1[\"currency\"],\n                    notional=-1.0\n                    * (\n                        0.0\n                        if isinstance(self.kwargs.leg1[\"notional\"], NoInput)\n                        else self.kwargs.leg1[\"notional\"]\n                    ),\n                    payment=self.kwargs.leg1[\"settlement\"],\n                    pair=self.kwargs.leg1[\"pair\"],\n                    fx_fixings=self.kwargs.leg1[\"fx_fixings\"],\n                ),\n            ]\n        )\n        self._leg2 = CustomLeg(\n            periods=[\n                Cashflow(\n                    currency=self.kwargs.leg2[\"currency\"],\n                    notional=-1.0\n                    * (\n                        0.0\n                        if isinstance(self.kwargs.leg2[\"notional\"], NoInput)\n                        else self.kwargs.leg2[\"notional\"]\n                    ),\n                    payment=self.kwargs.leg2[\"settlement\"],\n                    pair=self.kwargs.leg2[\"pair\"],\n                    fx_fixings=self.kwargs.leg2[\"fx_fixings\"],\n                )\n            ]\n        )\n        self._legs = [self._leg1, self._leg2]\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        fx_ = _validate_fx_as_forwards(_get_fx_forwards_maybe_from_solver(solver=solver, fx=fx))\n        return fx_.rate(\n            pair=self.kwargs.meta[\"fx_index\"].pair, settlement=self.kwargs.leg1[\"settlement\"]\n        )\n\n    def _set_pricing_mid(\n        self,\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n    ) -> None:\n        if isinstance(self.kwargs.meta[\"fx_rate\"], NoInput):\n            # determine the mid-market FX rate and set the notional of the appropriate leg\n            mid_market_rate = self.rate(fx=fx, solver=solver)\n\n            if isinstance(self.kwargs.leg2[\"notional\"], NoInput):\n                self.leg2.periods[0].settlement_params._notional = _dual_float(\n                    -self.leg1.periods[0].settlement_params.notional * mid_market_rate\n                )\n            elif isinstance(self.kwargs.leg1[\"notional\"], NoInput):\n                self.leg1.periods[0].settlement_params._notional = _dual_float(\n                    -self.leg2.periods[0].settlement_params.notional / mid_market_rate\n                )\n            else:\n                raise RuntimeError(  # pragma: no cover\n                    \"The is no `notional` to determine. Please report this bug. Detailing the\"\n                    \"initialisation of the NDF.\"\n                )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            solver=solver,\n            fx=fx,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n\ndef _validated_ndf_input_combinations(\n    currency: str_,\n    pair: FXIndex | str_,\n    notional: DualTypes_,\n    leg2_notional: DualTypes_,\n    fx_fixings: LegFixings,\n    leg2_fx_fixings: LegFixings,\n    fx_rate: DualTypes_,\n    reversed: bool_,  # noqa: A002\n    leg2_reversed: bool_,\n    spec: str_,\n) -> tuple[str, FXIndex_, FXIndex_, DualTypes_, DualTypes_, DualTypes_, FXIndex]:\n    \"\"\"Method to handle arg parsing for 2 or 3 currency NDF instruments with default value\n    setting and erroring raising.\n\n    Returns\n    -------\n    (currency, pair, leg2_pair, notional, leg2_notional, fx_rate)\n    \"\"\"\n\n    kw = _KWArgs(\n        user_args=dict(\n            currency=currency,\n            leg2_currency=NoInput(1),\n            pair=pair,\n            notional=notional,\n            leg2_notional=leg2_notional,\n            fx_fixings=fx_fixings,\n            leg2_fx_fixings=leg2_fx_fixings,\n            fx_rate=fx_rate,\n            reversed=reversed,\n            leg2_reversed=leg2_reversed,\n        ),\n        default_args=dict(\n            reversed=False,\n            leg2_reversed=False,\n        ),\n        spec=spec,\n        meta_args=[\"pair\", \"fx_rate\"],\n    )\n\n    fx_index_ = _get_fx_index(kw.meta[\"pair\"])\n\n    # set a default settlement `currency` if none is provided\n    if isinstance(kw.leg1[\"currency\"], NoInput):\n        kw.leg1[\"currency\"] = fx_index_.pair[:3]\n        kw.leg2[\"currency\"] = fx_index_.pair[:3]\n    else:\n        kw.leg1[\"currency\"] = kw.leg1[\"currency\"].lower()\n        kw.leg2[\"currency\"] = kw.leg2[\"currency\"].lower()\n\n    if kw.leg1[\"currency\"] not in fx_index_.pair:\n        # then the NDF is a 3-currency instrument\n        return _validated_3ccy_ndf_input_combinations(\n            currency=kw.leg1[\"currency\"],\n            fx_index=fx_index_,\n            notional=kw.leg1[\"notional\"],\n            leg2_notional=kw.leg2[\"notional\"],\n            fx_rate=kw.meta[\"fx_rate\"],\n            reversed=kw.leg1[\"reversed\"],\n            leg2_reversed=kw.leg2[\"reversed\"],\n        )\n    else:\n        return _validated_2ccy_ndf_input_combinations(\n            currency=kw.leg1[\"currency\"],\n            fx_index=fx_index_,\n            notional=kw.leg1[\"notional\"],\n            leg2_notional=kw.leg2[\"notional\"],\n            fx_fixings=kw.leg1[\"fx_fixings\"],\n            leg2_fx_fixings=kw.leg2[\"fx_fixings\"],\n            fx_rate=kw.meta[\"fx_rate\"],\n        )\n\n\ndef _validated_2ccy_ndf_input_combinations(\n    currency: str,\n    fx_index: FXIndex,\n    notional: DualTypes_,\n    leg2_notional: DualTypes_,\n    fx_fixings: LegFixings,\n    leg2_fx_fixings: LegFixings,\n    fx_rate: DualTypes_,\n) -> tuple[str, FXIndex_, FXIndex_, DualTypes_, DualTypes_, DualTypes_, FXIndex]:\n    \"\"\"Method to handle arg parsing for 2 currency NDF instruments with default value\n    setting and erroring raising.\n\n    Notional:\n    if no notional is given then leg1 is set from 'defaults'\n    if both notionals are given then the fx_rate is inferred.\n    if one notional and the fx_rate is given then the alternative notional is inferred.\n    two notionals AND fx_rate imply possible triangulation failure and raise\n    notional can be given on any leg and the alternative notional is inferred from the `fx_rate`\n\n    Returns\n    -------\n    (currency, pair, leg2_pair, notional, leg2_notional, fx_rate)\n    \"\"\"\n    leg1_nd = fx_index.pair[3:] == currency\n    if leg1_nd:\n        pair_: FXIndex_ = fx_index\n        leg2_pair_: FXIndex_ = NoInput(0)\n    else:\n        pair_ = NoInput(0)\n        leg2_pair_ = fx_index\n\n    notional_, leg2_notional_, fx_rate_ = _notional_and_fx_rate_validation(\n        notional, leg2_notional, fx_rate\n    )\n\n    # parse the fixings input: should only be relevant for the single non-deliverable leg\n    if not leg1_nd and not isinstance(fx_fixings, NoInput):\n        raise ValueError(\n            f\"Leg1 of NDF is directly deliverable (reference ccy '{fx_index.pair[:3]}' and \"\n            f\"settlement ccy '{currency}').\\n\"\n            \"Do not supply `fx_fixings` for leg1, perhaps you meant `leg2_fx_fixings`?\"\n        )\n    if leg1_nd and not isinstance(leg2_fx_fixings, NoInput):\n        raise ValueError(\n            f\"Leg2 of NDF is directly deliverable (reference ccy '{fx_index.pair[3:]}' and \"\n            f\"settlement ccy '{currency}').\\n\"\n            \"Do not supply `leg2_fx_fixings` for leg2, perhaps you meant `fx_fixings`?\"\n        )\n\n    return (\n        currency,\n        pair_,\n        leg2_pair_,\n        notional_,\n        leg2_notional_,\n        fx_rate_,\n        fx_index,\n    )\n\n\ndef _validated_3ccy_ndf_input_combinations(\n    currency: str,\n    fx_index: FXIndex,\n    notional: DualTypes_,\n    leg2_notional: DualTypes_,\n    fx_rate: DualTypes_,\n    reversed: bool,  # noqa: A002\n    leg2_reversed: bool,\n) -> tuple[str, FXIndex_, FXIndex_, DualTypes_, DualTypes_, DualTypes_, FXIndex]:\n    \"\"\"Method to handle arg parsing for 3 currency NDF instruments with default value\n    setting and erroring raising.\n\n    Returns\n    -------\n    (currency, pair, leg2_pair, notional, leg2_notional, fx_rate)\n    \"\"\"\n    # both legs are non-deliverable\n    if reversed:\n        pair = f\"{fx_index.pair[:3]}{currency}\"\n    else:\n        pair = f\"{currency}{fx_index.pair[:3]}\"\n\n    if leg2_reversed:\n        leg2_pair = f\"{fx_index.pair[3:]}{currency}\"\n    else:\n        leg2_pair = f\"{currency}{fx_index.pair[3:]}\"\n\n    try:\n        pair_index: FXIndex = _get_fx_index(pair)\n    except ValueError:\n        # no index exists in STATIC, clone from fx_index\n        pair_index = FXIndex(pair=pair, calendar=fx_index.calendar, settle=fx_index.settle)\n    pair_index = FXIndex(\n        pair=pair_index.pair,\n        calendar=pair_index.calendar,\n        settle=pair_index.settle,\n        isda_mtm_calendar=fx_index.isda_mtm_calendar,\n        isda_mtm_settle=fx_index.isda_mtm_settle,\n    )\n\n    try:\n        leg2_pair_index: FXIndex = _get_fx_index(leg2_pair)\n    except ValueError:\n        # no index exists in STATIC, clone from fx_index\n        leg2_pair_index = FXIndex(pair=pair, calendar=fx_index.calendar, settle=fx_index.settle)\n    leg2_pair_index = FXIndex(\n        pair=leg2_pair_index.pair,\n        calendar=leg2_pair_index.calendar,\n        settle=leg2_pair_index.settle,\n        isda_mtm_calendar=fx_index.isda_mtm_calendar,\n        isda_mtm_settle=fx_index.isda_mtm_settle,\n    )\n\n    notional_, leg2_notional_, fx_rate_ = _notional_and_fx_rate_validation(\n        notional, leg2_notional, fx_rate\n    )\n\n    return (\n        currency,\n        pair_index,\n        leg2_pair_index,\n        notional_,\n        leg2_notional_,\n        fx_rate_,\n        fx_index,\n    )\n\n\ndef _notional_and_fx_rate_validation(\n    notional: DualTypes_,\n    leg2_notional: DualTypes_,\n    fx_rate: DualTypes_,\n) -> tuple[DualTypes_, DualTypes_, DualTypes_]:\n    \"\"\"\n    method to parse the input arguments in their various combinations.\n\n    Notional:\n    if no notional is given then leg1 is set from 'defaults'\n    if both notionals are given then the fx_rate is inferred.\n    if one notional and the fx_rate is given then the alternative notional is inferred.\n    two notionals AND fx_rate imply possible triangulation failure and raise\n    notional can be given on any leg and the alternative notional is inferred from the `fx_rate`\n    \"\"\"\n\n    # set a default `notional` if no notional on any leg is given\n    if isinstance(notional, NoInput) and isinstance(leg2_notional, NoInput):\n        notional_: DualTypes_ = defaults.notional\n        leg2_notional_: DualTypes_ = leg2_notional\n    else:\n        notional_ = notional\n        leg2_notional_ = leg2_notional\n    del notional, leg2_notional\n\n    # parse fx_rate / notional / and / leg2_notional\n    if not isinstance(notional_, NoInput) and not isinstance(leg2_notional_, NoInput):\n        if not isinstance(fx_rate, NoInput):\n            raise ValueError(\n                \"`notional`, `leg2_notional` and `fx_rate` cannot all be given simultaneously.\\n\"\n                \"Provide, at most, two of these arguments for an NDF.\"\n            )\n        if notional_ * leg2_notional_ > 0:\n            raise ValueError(\n                \"When providing `notional` and `leg2_notional` on an NDF, the two must be opposite \"\n                \"signs, indicating both a buy and a sell .\"\n            )\n        else:\n            fx_rate_: DualTypes_ = -leg2_notional_ / notional_\n    elif isinstance(notional_, NoInput) and not isinstance(leg2_notional_, NoInput):\n        if isinstance(fx_rate, NoInput):\n            # then the NDF is unpriced and will requiring setting to mid-market at price time\n            fx_rate_ = NoInput(0)\n        else:\n            fx_rate_ = fx_rate\n            notional_ = -leg2_notional_ / fx_rate\n    elif not isinstance(notional_, NoInput) and isinstance(leg2_notional_, NoInput):\n        if isinstance(fx_rate, NoInput):\n            # then the NDF is unpriced and will requiring setting to mid-market at price time\n            fx_rate_ = NoInput(0)\n        else:\n            fx_rate_ = fx_rate\n            leg2_notional_ = -notional_ * fx_rate\n    else:\n        raise RuntimeError(  # pragma: no cover\n            \"This line should never be reached. \"\n            \"Report issue for NDF initialization providing input arguments.\"\n        )\n\n    return notional_, leg2_notional_, fx_rate_\n"
  },
  {
    "path": "python/rateslib/instruments/ndxcs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import LegMtm\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _get_fx_forwards_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg, FloatLeg\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        LegFixings,\n        RollDay,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass NDXCS(_BaseInstrument):\n    \"\"\"\n    A *non-deliverable cross-currency swap (XCS)* composing either\n    :class:`~rateslib.legs.FixedLeg`\n    and/or :class:`~rateslib.legs.FloatLeg` in different currencies.\n\n    .. rubric:: Examples\n\n    An INR NDXCS vs SOFR (IRUSON5 Curncy)\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import NDXCS\n       from datetime import datetime as dt\n       from rateslib import fixings\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"WMR_10AM_TY0_USDINR\", Series(index=[dt(2025, 1, 8), dt(2025, 7, 4)], data=[92.0, 92.5]))\n       ndxcs = NDXCS(\n           effective=dt(2025, 1, 8),\n           termination=\"1y\",\n           frequency=\"S\",\n           currency=\"usd\",\n           pair=\"usdinr\",\n           notional=5e6,           # <- INR Leg\n           fixed=True,\n           fx_fixings=\"WMR_10AM_TY0\",\n           leg2_fx_fixings=91.55,  # <- USD Notional at execution\n           payment_lag=0,\n       )\n       ndxcs.cashflows()\n\n    .. rubric:: Pricing\n\n    The methods of a *NDXCS* require an :class:`~rateslib.fx.FXForwards` object for ``fx`` .\n\n    They also require a *disc curve* for discounting both legs in the *settlement currency*\n    and (if not *FixedLegs*) a *rate curve* and a *leg2 rate curve* for forecasting the floating\n    rates on either *Leg*. The following input formats are allowed:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve, leg2_rate_curve, disc_curve]  # four curves\n       curves = {  # dict form is explicit\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve,\n           \"leg2_rate_curve\": leg2_rate_curve,\n       }\n\n    The available pricing ``metric`` are in *{'leg1', 'leg2'}* which will return a *float spread*\n    or a *fixed rate* on the specified leg, for the appropriate *Leg* type.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_convention: str, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of leg1 (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set from 'leg2_notional' or 'defaults' )`\n        The initial leg1 notional, defined in units of the currency of the leg. Only one\n        of ``notional`` and ``leg2_notional`` can be given. The alternate leg notional is derived\n        via non-deliverability :class:`~rateslib.data.fixings.FXFixing`.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    leg2_currency : str, :red:`required`\n        The currency of the leg2.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n    leg2_amortization : float, Dual, Dual2, Variable, str, Amortization, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are the **non-deliverability parameters**\n\n    pair: str, :red:`required (if 'leg2_pair' not given)`\n        The currency pair for :class:`~rateslib.data.fixings.FXFixing` that determines *Period*\n        settlement on *Leg1*. The *reference currency* is implied from ``pair``.\n        Must include ``currency``. Not required if this leg is not *non-deliverable*.\n    leg2_pair: str, :green:`optional`\n        The currency pair for :class:`~rateslib.data.fixings.FXFixing` that determines *Period*\n        settlement on *Leg2*. Not required if not a 3-currency NDXCS.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according\n        to non-deliverability. Not required if this leg is not *non-deliverable*.\n    leg2_fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* on *Leg2*\n        according to non-deliverability. Not required if this leg is not *non-deliverable*.\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed : bool, :green:`optional (set as False)`\n        Whether leg1 is a :class:`~rateslib.legs.FixedLeg` or a :class:`~rateslib.legs.FloatLeg`.\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n    leg2_fixed : bool, :green:`optional (set as False)`\n    leg2_fixed_rate : float or None\n    leg2_fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n    leg2_fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n    leg2_fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n    leg2_float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n    leg2_spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n    leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n\n       .. note::\n\n          The following are the cross-currency **non-deliverable** parameters. For\n          further details and examples see **Notes**.\n\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according\n        to non-deliverability. This can only be provided if ``leg2_notional`` is given. The\n        currency pair is expressed in direction 'currency:leg2_currency'.\n    mtm: bool, :green:`optional (set to False)`\n        Define the *XCS* is mark-to-market on leg1. Only one leg can be mark-to-market.\n    leg2_fx_fixings:\n        This can only be provided if ``notional`` is given. The\n        currency pair is expressed in direction 'currency:leg2_currency'.\n    leg2_mtm: bool, :green:`optional (set to False)`\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n    metric: str, :green:`optional (set as 'leg1')`\n        Determines which calculation metric to return by default when using the\n        :meth:`~rateslib.instruments.XCS.rate` method.\n\n    Notes\n    -----\n    A non-deliverable *XCS* replicates a non-mtm cross-currency swap\n    whose cashflows are paid out only in one *settlement currency*. This type of swap\n    allows two configurations;\n\n    - A **two currency** *NDXCS* where one leg is based on a *reference currency* and\n      difference *settlement currency*, whilst the other leg is based purely on cashflows\n      generated in the *settlement currency*.\n    - A **three currency** *NDXCS* where one leg is based on *reference currency 1* with a\n      *settlement currency* and the other leg is based on *reference currency 2* but\n      also settling in *settlement currency*.\n\n    .. tabs::\n\n       .. tab:: Two Currency NDXCS\n\n          The **required** parameters of a two currency NDXCS are as follows;\n\n          - A ``currency`` which defines the *settlement currency* on both legs.\n          - A ``pair`` which defines the currency pair and implicitly determines the *reference currency*.\n          - A ``notional`` or ``leg2_notional``. The placement of the notional defines which *Leg* is the\n            one that is based on the *reference currency*. Any notional quantity must be given in units of\n            *reference currency*.\n          - ``fx_fixings`` and ``leg2_fx_fixings``. These are FX fixings that are used by both legs;\n            one leg will have a fixed rate of exchange for all periods (a single entry usually determined\n            when the transaction is agreed), the other leg with base its ND FX Fixings on some future\n            data series.\n\n          This example swaps a 500mm INR *FloatLeg* non-deliverable into USD into a USD *FloatLeg*\n          with an initially agreed FX rate of USDINR 92.0\n\n          .. ipython:: python\n\n             ndxcs = NDXCS(\n                 effective=dt(2026, 1, 1),\n                 termination=\"18M\",\n                 frequency=\"S\",\n                 currency=\"usd\",          #  <-  USD settlement currency\n                 pair=\"usdinr\",           #  <-  INR reference currency implied\n                 notional=500e6,          #  <-  Leg1 is based on the reference currency\n                 fx_fixings=\"WMR_10AM_TY0\",\n                 leg2_fx_fixings=92.0,    #  <-  The USD Leg notional is implied as 5.43mm\n             )\n             ndxcs.cashflows()\n\n          The *Leg* based on the *reference currency* is a non-deliverable *Leg* with a ``mtm``\n          parameter set to *True*, whilst the other *Leg* is non-deliverable with ``mtm`` set to\n          *False* and is based on one single FX rate.\n\n       .. tab:: Three Currency NDXCS\n\n          The **required** parameters of a three currency NDXCS are as follows;\n\n          - A ``currency`` which defines the *settlement currency* on both legs.\n          - A ``pair`` which defines the currency pair and implicitly determines\n            the *reference currency 1*.\n          - A ``leg2_pair`` which defines the currency pair of *Leg2* and implicitly\n            determines the *reference currency 2*.\n          - A ``notional`` and ``leg2_notional``. These must be pre-determined at an appropriate\n            rate of exchange, usually this is agreed at transaction execution. These must be\n            expressed in *reference currency 1* units and *reference currency 2* units\n            respectively.\n          - ``fx_fixings`` and ``leg2_fx_fixings`` which determine the future rates of exchange\n            on both non-deliverable legs.\n\n          This example swaps a 500mm INR *FloatLeg* non-deliverable into USD into a CHF *FloatLeg*\n          non-deliverable into USD with an initial FX rate of CHFINR 125.0.\n\n          .. ipython:: python\n\n             fixings.add(\"WMR_10AM_TY0_USDCHF\", Series(index=[dt(2025, 1, 6)], data=[0.9]))\n             ndxcs = NDXCS(\n                 effective=dt(2026, 1, 1),\n                 termination=\"18M\",\n                 frequency=\"S\",\n                 currency=\"usd\",               #  <-  USD settlement currency\n                 pair=\"usdinr\",                #  <-  INR reference currency 1 implied\n                 leg2_pair=\"usdchf\",           #  <-  CHF reference currency 2 implied\n                 notional=500e6,               #  <-  Leg1 is based on the reference currency 1\n                 leg2_notional=500e6/125.0,    #  <-  Leg2 entered directly in ref currency 2 units\n                 fx_fixings=\"WMR_10AM_TY0\",       #  <-  Data series tag for FXFixings on Leg1\n                 leg2_fx_fixings=\"WMR_10AM_TY0\",  #  <-  Data series tag for FXFixings on Leg2\n             )\n             ndxcs.cashflows()\n\n          Both *Legs* are non-deliverable with their ``mtm`` parameters set to *True*.\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"WMR_10AM_TY0_USDINR\")\n       fixings.pop(\"WMR_10AM_TY0_USDCHF\")\n\n    \"\"\"  # noqa: E501\n\n    def _rate_scalar_calc(self) -> float:\n        if self.kwargs.meta[\"metric\"] == \"leg1\":\n            return 1.0 if isinstance(self.leg1, FixedLeg) else 100.0\n        else:\n            return 1.0 if isinstance(self.leg2, FixedLeg) else 100.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        if isinstance(self.leg1, FixedLeg):\n            return self.leg1.fixed_rate\n        else:\n            raise AttributeError(f\"Leg1 is of type: {type(self.leg1).__name__}\")\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        if isinstance(self.leg1, FixedLeg):\n            self.kwargs.leg1[\"fixed_rate\"] = value\n            self.leg1.fixed_rate = value\n        else:\n            raise AttributeError(f\"Leg1 is of type: {type(self.leg1).__name__}\")\n\n    @property\n    def float_spread(self) -> DualTypes:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        if isinstance(self.leg1, FloatLeg):\n            return self.leg1.float_spread\n        else:\n            raise AttributeError(f\"Leg1 is of type: {type(self.leg1).__name__}\")\n\n    @float_spread.setter\n    def float_spread(self, value: DualTypes) -> None:\n        if isinstance(self.leg1, FloatLeg):\n            self.kwargs.leg1[\"float_spread\"] = value\n            self.leg1.float_spread = value\n        else:\n            raise AttributeError(f\"Leg1 is of type: {type(self.leg1).__name__}\")\n\n    @property\n    def leg2_fixed_rate(self) -> DualTypes_:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        if isinstance(self.leg2, FixedLeg):\n            return self.leg2.fixed_rate\n        else:\n            raise AttributeError(f\"Leg2 is of type: {type(self.leg2).__name__}\")\n\n    @leg2_fixed_rate.setter\n    def leg2_fixed_rate(self, value: DualTypes_) -> None:\n        if isinstance(self.leg2, FixedLeg):\n            self.kwargs.leg2[\"fixed_rate\"] = value\n            self.leg2.fixed_rate = value\n        else:\n            raise AttributeError(f\"Leg2 is of type: {type(self.leg2).__name__}\")\n\n    @property\n    def leg2_float_spread(self) -> DualTypes_:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        if isinstance(self.leg2, FloatLeg):\n            return self.leg2.float_spread\n        else:\n            raise AttributeError(f\"Leg2 is of type: {type(self.leg2).__name__}\")\n\n    @leg2_float_spread.setter\n    def leg2_float_spread(self, value: DualTypes) -> None:\n        if isinstance(self.leg2, FloatLeg):\n            self.kwargs.leg2[\"float_spread\"] = value\n            self.leg2.float_spread = value\n        else:\n            raise AttributeError(f\"Leg2 is of type: {type(self.leg2).__name__}\")\n\n    @property\n    def leg1(self) -> FixedLeg | FloatLeg:\n        \"\"\"The first :class:`~rateslib.legs.FixedLeg` or\n        :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FixedLeg | FloatLeg:\n        \"\"\"The second :class:`~rateslib.legs.FixedLeg` or\n        :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: int_ = NoInput(1),\n        leg2_payment_lag_exchange: int_ = NoInput(1),\n        leg2_ex_div: int_ = NoInput(1),\n        leg2_convention: str_ = NoInput(1),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        amortization: float_ = NoInput(0),\n        leg2_notional: float_ = NoInput(0),\n        leg2_amortization: float_ = NoInput(0),\n        # nondeliverable params\n        pair: str_ = NoInput(0),\n        leg2_pair: str_ = NoInput(0),\n        fx_fixings: LegFixings = NoInput(0),\n        leg2_fx_fixings: LegFixings = NoInput(0),\n        # rate parameters\n        fixed: bool_ = NoInput(0),\n        fixed_rate: DualTypes_ = NoInput(0),\n        float_spread: DualTypes_ = NoInput(0),\n        spread_compound_method: str_ = NoInput(0),\n        rate_fixings: LegFixings = NoInput(0),\n        fixing_method: str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        leg2_fixed: bool_ = NoInput(0),\n        leg2_mtm: bool_ = NoInput(0),\n        leg2_fixed_rate: DualTypes_ = NoInput(0),\n        leg2_float_spread: DualTypes_ = NoInput(0),\n        leg2_spread_compound_method: str_ = NoInput(0),\n        leg2_rate_fixings: LegFixings = NoInput(0),\n        leg2_fixing_method: str_ = NoInput(0),\n        leg2_fixing_frequency: Frequency | str_ = NoInput(0),\n        leg2_fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            leg2_effective=leg2_effective,\n            termination=termination,\n            leg2_termination=leg2_termination,\n            frequency=frequency,\n            leg2_frequency=leg2_frequency,\n            stub=stub,\n            leg2_stub=leg2_stub,\n            front_stub=front_stub,\n            leg2_front_stub=leg2_front_stub,\n            back_stub=back_stub,\n            leg2_back_stub=leg2_back_stub,\n            roll=roll,\n            leg2_roll=leg2_roll,\n            eom=eom,\n            leg2_eom=leg2_eom,\n            modifier=modifier,\n            leg2_modifier=leg2_modifier,\n            calendar=calendar,\n            leg2_calendar=leg2_calendar,\n            payment_lag=payment_lag,\n            leg2_payment_lag=leg2_payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            ex_div=ex_div,\n            leg2_ex_div=leg2_ex_div,\n            convention=convention,\n            leg2_convention=leg2_convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            leg2_notional=leg2_notional,\n            amortization=amortization,\n            leg2_amortization=leg2_amortization,\n            # non-deliverability\n            pair=pair,\n            leg2_pair=leg2_pair,\n            fx_fixings=fx_fixings,\n            leg2_fx_fixings=leg2_fx_fixings,\n            # rate\n            fixed_rate=fixed_rate,\n            float_spread=float_spread,\n            spread_compound_method=spread_compound_method,\n            rate_fixings=rate_fixings,\n            fixing_method=fixing_method,\n            fixing_frequency=fixing_frequency,\n            fixing_series=fixing_series,\n            leg2_fixed_rate=leg2_fixed_rate,\n            leg2_float_spread=leg2_float_spread,\n            leg2_spread_compound_method=leg2_spread_compound_method,\n            leg2_rate_fixings=leg2_rate_fixings,\n            leg2_fixing_method=leg2_fixing_method,\n            leg2_fixing_frequency=leg2_fixing_frequency,\n            leg2_fixing_series=leg2_fixing_series,\n            # meta\n            fixed=fixed,\n            leg2_fixed=leg2_fixed,\n            curves=self._parse_curves(curves),\n            metric=metric,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            leg2_currency=NoInput(1),\n            initial_exchange=True,\n            final_exchange=True,\n            leg2_initial_exchange=True,\n            leg2_final_exchange=True,\n            vol=_Vol(),\n        )\n        default_args = dict(\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n            currency=defaults.base_currency,\n            fixed=False,\n            leg2_fixed=False,\n            metric=\"leg1\",\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\", \"fixed\", \"leg2_fixed\", \"vol\"],\n        )\n\n        # validation of currencies and pairs\n        if isinstance(self.kwargs.leg1[\"notional\"], NoInput) and isinstance(\n            self.kwargs.leg2[\"notional\"], NoInput\n        ):\n            self.kwargs.leg1[\"notional\"] = defaults.notional\n\n        (\n            self.kwargs.leg1[\"mtm\"],\n            self.kwargs.leg2[\"mtm\"],\n            self.kwargs.leg1[\"pair\"],\n            self.kwargs.leg2[\"pair\"],\n        ) = self._init_args(\n            currency=self.kwargs.leg1[\"currency\"].lower(),\n            pair=self.kwargs.leg1[\"pair\"],\n            leg2_pair=self.kwargs.leg2[\"pair\"],\n            notional=self.kwargs.leg1[\"notional\"],\n            leg2_notional=self.kwargs.leg2[\"notional\"],\n        )\n\n        # narrowing of fixed or floating\n        float_attrs = [\n            \"float_spread\",\n            \"spread_compound_method\",\n            \"rate_fixings\",\n            \"fixing_method\",\n            \"fixing_frequency\",\n            \"fixing_series\",\n        ]\n        if self.kwargs.meta[\"fixed\"]:\n            for item in float_attrs:\n                self.kwargs.leg1.pop(item)\n        else:\n            self.kwargs.leg1.pop(\"fixed_rate\")\n        if self.kwargs.meta[\"leg2_fixed\"]:\n            for item in float_attrs:\n                self.kwargs.leg2.pop(item)\n        else:\n            self.kwargs.leg2.pop(\"fixed_rate\")\n\n        # populate non-deliverable leg, based on which leg notional is given\n        if isinstance(self.kwargs.leg1[\"notional\"], NoInput):\n            self._kwargs.leg1[\"notional\"] = -1.0 * self._kwargs.leg2[\"notional\"]\n            self._kwargs.leg1[\"amortization\"] = (\n                NoInput(0)\n                if isinstance(self._kwargs.leg2[\"amortization\"], NoInput)\n                else -1.0 * self._kwargs.leg2[\"amortization\"]\n            )\n        if isinstance(self.kwargs.leg2[\"notional\"], NoInput):\n            self._kwargs.leg2[\"notional\"] = -1.0 * self._kwargs.leg1[\"notional\"]\n            self._kwargs.leg2[\"amortization\"] = (\n                NoInput(0)\n                if isinstance(self._kwargs.leg1[\"amortization\"], NoInput)\n                else -1.0 * self._kwargs.leg1[\"amortization\"]\n            )\n\n        if self.kwargs.meta[\"fixed\"]:\n            self._leg1: FixedLeg | FloatLeg = FixedLeg(\n                **_convert_to_schedule_kwargs(self.kwargs.leg1, 1)\n            )\n        else:\n            self._leg1 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        if self.kwargs.meta[\"leg2_fixed\"]:\n            self._leg2: FixedLeg | FloatLeg = FixedLeg(\n                **_convert_to_schedule_kwargs(self.kwargs.leg2, 1)\n            )\n        else:\n            self._leg2 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self.leg1, self.leg2]\n        self._rate_scalar = self._rate_scalar_calc()\n\n    def _init_args(\n        self,\n        currency: str,\n        pair: str_,\n        leg2_pair: str_,\n        notional: DualTypes_,\n        leg2_notional: DualTypes_,\n    ) -> tuple[LegMtm, LegMtm, str, str]:\n        if isinstance(pair, NoInput):\n            raise ValueError(\"`pair` must be given when creating a NDXCS.\")\n        else:\n            pair_: str = pair.lower()\n            if currency not in pair_:\n                raise ValueError(f\"`pair` must contain {currency}.\")\n\n        if isinstance(leg2_pair, str):\n            leg2_pair_: str = leg2_pair.lower()\n            if currency not in leg2_pair_:\n                raise ValueError(f\"`leg2_pair` must contain {currency}.\")\n            return NDXCS._init_three_currency(pair_, leg2_pair_, notional, leg2_notional)\n        else:\n            return NDXCS._init_two_currency(pair_, notional, leg2_notional)\n\n    @staticmethod\n    def _init_two_currency(\n        pair: str,\n        notional: DualTypes_,\n        leg2_notional: DualTypes_,\n    ) -> tuple[LegMtm, LegMtm, str, str]:\n        if isinstance(notional, NoInput):\n            # then reference Leg is leg2\n            mtm, leg2_mtm = LegMtm.Initial, LegMtm.Payment\n        else:\n            if not isinstance(leg2_notional, NoInput):\n                raise ValueError(\n                    \"Only one of `notional` or `leg2_notional` can be given for a two-currency \"\n                    \"NDXCS.\\nIf you are trying to set either notional based on a transacted \"\n                    \"FX rate, then:\\n1) Set the notional in reference currency units on the \"\n                    \"reference currency leg.\\n2) Set the ``fx_fixing`` or ``leg2_fx_fixing`` value \"\n                    \"as this scalar for the leg that is solely based on the settlement currency.\"\n                )\n            mtm, leg2_mtm = LegMtm.Payment, LegMtm.Initial\n        return mtm, leg2_mtm, pair, pair\n\n    @staticmethod\n    def _init_three_currency(\n        pair: str,\n        leg2_pair: str,\n        notional: DualTypes_,\n        leg2_notional: DualTypes_,\n    ) -> tuple[LegMtm, LegMtm, str, str]:\n        if isinstance(notional, NoInput) or isinstance(leg2_notional, NoInput):\n            raise ValueError(\n                \"A three-currency NDXCS requires both `notional` and `leg2_notional` to be given.\\n\"\n                \"These should be given in their relevant reference currencies, according to the \"\n                \"initially agreed FX Rate between them.\"\n            )\n        return LegMtm.Payment, LegMtm.Payment, pair, leg2_pair\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_rate_curve = _get_curve(\"leg2_rate_curve\", True, True, *c)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, True, *c)\n        rate_curve = _get_curve(\"rate_curve\", True, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric)\n\n        fx_ = _get_fx_forwards_maybe_from_solver(fx=fx, solver=solver)\n\n        if metric_ == \"leg1\":\n            leg2_npv: DualTypes = self.leg2.npv(  # type: ignore[assignment]\n                rate_curve=leg2_rate_curve,\n                disc_curve=leg2_disc_curve,\n                base=self.leg1.settlement_params.currency,\n                fx=fx_,\n                settlement=settlement,\n                forward=forward,\n            )\n            spread = self.leg1.spread(\n                target_npv=-leg2_npv,\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                settlement=settlement,\n                fx=fx_,\n                forward=forward,\n            )\n            if self.kwargs.meta[\"fixed\"]:\n                return spread / 100.0\n            else:\n                return spread\n        elif metric_ == \"leg2\":\n            leg1_npv: DualTypes = self.leg1.npv(  # type: ignore[assignment]\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                base=self.leg2.settlement_params.currency,\n                fx=fx_,\n                settlement=settlement,\n                forward=forward,\n            )\n            spread = self.leg2.spread(\n                target_npv=-leg1_npv,\n                rate_curve=leg2_rate_curve,\n                disc_curve=leg2_disc_curve,\n                settlement=settlement,\n                forward=forward,\n                fx=fx_,\n            )\n            if self.kwargs.meta[\"leg2_fixed\"]:\n                return spread / 100.0\n            else:\n                return spread\n        else:\n            raise ValueError(\"`metric` must be in {'leg1', 'leg2'}\")\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        return self.rate(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n            metric=metric,\n        )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n            fx=fx,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # all float_spread are assumed to be equal to zero if not given.\n        # missing fixed rates will be priced and set if possible.\n\n        if isinstance(self.leg1, FixedLeg) and isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            if isinstance(self.leg2, FixedLeg) and isinstance(\n                self.kwargs.leg2[\"fixed_rate\"], NoInput\n            ):\n                raise ValueError(\"At least one leg must have a defined `fixed_rate`.\")\n\n            mid_price = self.rate(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                settlement=settlement,\n                forward=forward,\n                metric=\"leg1\",\n            )\n            self.leg1.fixed_rate = _dual_float(mid_price)\n\n        elif isinstance(self.leg2, FixedLeg) and isinstance(\n            self.kwargs.leg2[\"fixed_rate\"], NoInput\n        ):\n            # leg1 cannot be fixed with NoInput - this branch is covered above\n            mid_price = self.rate(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                settlement=settlement,\n                forward=forward,\n                metric=\"leg2\",\n            )\n            self.leg2.fixed_rate = _dual_float(mid_price)\n\n        elif (\n            isinstance(self.leg1, FloatLeg)\n            and isinstance(self.kwargs.leg1[\"float_spread\"], NoInput)\n            and isinstance(self.leg2, FloatLeg)\n            and isinstance(self.kwargs.leg2[\"float_spread\"], NoInput)\n        ):\n            # then no FloatLeg pricing parameters are provided\n            mid_price = self.rate(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                settlement=settlement,\n                forward=forward,\n            )\n            if self.kwargs.meta[\"metric\"].lower() == \"leg1\":\n                self.leg1.float_spread = _dual_float(mid_price)\n            else:\n                self.leg2.float_spread = _dual_float(mid_price)\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        A XCS requires 4 curves (mostly if float-float, otherwise it needs 2)\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_rate_curve=curves.get(\"leg2_rate_curve\", NoInput(0)),\n                leg2_disc_curve=curves.get(\"leg2_disc_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 4:\n                return _Curves(\n                    rate_curve=NoInput(0) if curves[0] is None else curves[0],\n                    disc_curve=curves[1],\n                    leg2_rate_curve=NoInput(0) if curves[2] is None else curves[2],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires 4 curve type input. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:\n            raise ValueError(f\"{type(self).__name__} requires 4 curve type input. Got 1.\")\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/portfolio.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\nfrom typing import TYPE_CHECKING, NoReturn\n\nfrom pandas import DataFrame\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.pricing import (\n    _get_fx_maybe_from_solver,\n)\nfrom rateslib.periods.utils import _maybe_fx_converted\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        datetime_,\n        str_,\n    )\n\n\ndef _instrument_npv(\n    instrument: _BaseInstrument, *args: Any, **kwargs: Any\n) -> DualTypes | dict[str, DualTypes]:  # pragma: no cover\n    # this function is captured by TestPortfolio pooling but is not registered as a parallel process\n    # used for parallel processing with Portfolio.npv\n    return instrument.npv(*args, **kwargs)\n\n\nclass Portfolio(_BaseInstrument):\n    \"\"\"\n    A collection of :class:`~rateslib.instruments.protocols._BaseInstrument`.\n\n    .. rubric:: Examples\n\n    The following initialises a *Portfolio* of *IRSs*.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import Portfolio, IRS\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       pf = Portfolio(instruments=[\n           IRS(dt(2000, 1, 1), \"1y\", notional=10e3, spec=\"eur_irs\", curves=[\"estr\"]),\n           IRS(dt(2000, 1, 1), \"2y\", notional=10e3, spec=\"eur_irs\", curves=[\"estr\"]),\n           IRS(dt(2000, 1, 1), \"3y\", notional=10e3, spec=\"eur_irs\", curves=[\"estr\"]),\n       ])\n       pf.cashflows()\n\n    .. rubric:: Pricing\n\n    Each :class:`~rateslib.instruments.protocols._BaseInstrument` should have\n    its own ``curves`` and ``vol`` objects set at its initialisation, according to the\n    documentation for that *Instrument*. For the pricing methods ``curves`` and ``vol`` objects,\n    these can be universally passed to each *Instrument* but in many cases that would be\n    technically impossible since each *Instrument* might require difference pricing objects, e.g.\n    if the *Instruments* have difference currencies. For a *Portfolio*\n    of three *IRS* in the same currency this would be possible, however.\n\n    Parameters\n    ----------\n    instruments : list of _BaseInstrument\n        The collection of *Instruments*.\n\n    Notes\n    -----\n    A *Portfolio* is just a container for multiple\n    :class:`~rateslib.instruments.protocols._BaseInstrument`.\n    There is no concept of a :meth:`~rateslib.instruments.Portfolio.rate`.\n\n    \"\"\"\n\n    _instruments: Sequence[_BaseInstrument]\n\n    @property\n    def instruments(self) -> Sequence[_BaseInstrument]:\n        \"\"\"The *Instruments* contained within the *Portfolio*.\"\"\"\n        return self._instruments\n\n    def __init__(self, instruments: Sequence[_BaseInstrument]) -> None:\n        if not isinstance(instruments, Sequence):\n            raise ValueError(\"`instruments` should be a list of Instruments.\")\n        self._instruments = instruments\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Return the NPV of the *Portfolio* by summing individual *Instrument* NPVs.\n        \"\"\"\n        # if the pool is 1 do not do any parallel processing and return the single core func\n        if defaults.pool == 1:\n            local_npv: dict[str, DualTypes] = self._npv_single_core(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                vol=vol,\n                base=base,\n                settlement=settlement,\n                forward=forward,\n            )\n        else:\n            from functools import partial\n            from multiprocessing import Pool\n\n            func = partial(\n                _instrument_npv,\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                vol=vol,\n                base=base,\n                local=True,\n                forward=forward,\n                settlement=settlement,\n            )\n            p = Pool(defaults.pool)\n            results = p.map(func, self.instruments)\n            p.close()\n\n            # Aggregate results:\n            _ = DataFrame(results).fillna(0.0)\n            _ = _.sum()\n            local_npv = _.to_dict()  # type: ignore[assignment]\n\n            # ret = {}\n            # for result in results:\n            #     for ccy in result:\n            #         if ccy in ret:\n            #             ret[ccy] += result[ccy]\n            #         else:\n            #             ret[ccy] = result[ccy]\n\n        if not local:\n            single_value: DualTypes = 0.0\n            base_ = _drb(self.settlement_params.currency, base)\n            for k, v in local_npv.items():\n                single_value += _maybe_fx_converted(\n                    value=v,\n                    currency=k,\n                    fx=_get_fx_maybe_from_solver(fx=fx, solver=solver),\n                    base=base_,\n                    forward=forward,\n                )\n            return single_value\n        else:\n            return local_npv\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_instruments(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._cashflows_from_instruments(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n            base=base,\n        )\n\n    def rate(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`rate` is not defined for Portfolio.\")\n\n    def analytic_delta(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`analytic_delta` is not defined for Portfolio.\")\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom abc import ABCMeta\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.instruments.protocols.analytic_delta import _WithAnalyticDelta\nfrom rateslib.instruments.protocols.analytic_fixings import _WithAnalyticRateFixings\nfrom rateslib.instruments.protocols.cashflows import _WithCashflows\nfrom rateslib.instruments.protocols.fixings import _WithFixings\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.npv import _WithNPV\nfrom rateslib.instruments.protocols.rate import _WithRate\nfrom rateslib.instruments.protocols.sensitivities import _WithSensitivities\n\nif TYPE_CHECKING:\n    pass\n    # from rateslib.typing import ()\n\n\nclass _BaseInstrument(\n    _WithSensitivities,\n    _WithNPV,\n    _WithRate,\n    _WithCashflows,\n    _WithFixings,\n    _WithAnalyticDelta,\n    _WithAnalyticRateFixings,\n    metaclass=ABCMeta,\n):\n    \"\"\"Abstract base class used in the construction of *Instruments*.\"\"\"\n\n\n__all__ = [\n    \"_KWArgs\",\n    \"_WithNPV\",\n    \"_WithRate\",\n    \"_WithCashflows\",\n    \"_WithFixings\",\n    \"_WithAnalyticDelta\",\n    \"_WithAnalyticRateFixings\",\n    \"_WithSensitivities\",\n    \"_BaseInstrument\",\n]\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/analytic_delta.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.instruments.protocols.pricing import (\n    _get_curve,\n    _get_fx_forwards_maybe_from_solver,\n    _get_fx_vol,\n    _parse_curves,\n    _parse_vol,\n    _WithPricingObjs,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        _KWArgs,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithAnalyticDelta(_WithPricingObjs, Protocol):\n    \"\"\"\n    Protocol to determine the *analytic rate delta* of a particular *Leg* of an *Instrument*.\n    \"\"\"\n\n    @property\n    def kwargs(self) -> _KWArgs: ...\n\n    def analytic_delta(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        leg: int = 1,\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Calculate the analytic rate delta of a *Leg* of the *Instrument*.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, Curve, IRS\n\n        .. ipython:: python\n\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.75})\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", fixed_rate=1.0, curves=[curve])\n           irs.analytic_delta()\n           irs.analytic_delta(local=True)\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        local: bool, :green:`optional (set as False)`\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n        leg: int, :green:`optional (set as 1)`\n            The *Leg* over which to calculate the analytic rate delta.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable or dict of such indexed by string currency.\n        \"\"\"\n        c = _parse_curves(self, curves, solver)  # type: ignore[arg-type]\n        v = _parse_vol(self, vol, solver, False)  # type: ignore[call-overload, misc]\n\n        prefix = \"\" if leg == 1 else \"leg2_\"\n\n        if hasattr(self, \"legs\"):\n            rate_curve = _get_curve(f\"{prefix}rate_curve\", True, True, *c)\n            disc_curve = _get_curve(f\"{prefix}disc_curve\", False, True, *c)\n            index_curve = _get_curve(f\"{prefix}index_curve\", False, True, *c)\n            value: DualTypes | dict[str, DualTypes] = self.legs[leg - 1].analytic_delta(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=index_curve,\n                fx_vol=_get_fx_vol(True, True, *v),\n                fx=_get_fx_forwards_maybe_from_solver(fx=fx, solver=solver),\n                base=base,\n                local=local,\n                settlement=settlement,\n                forward=forward,\n            )\n        else:\n            raise NotImplementedError(\"`analytic_delta` can only called on Leg based Instruments.\")\n\n        return value\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/analytic_fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame, DatetimeIndex, concat\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.instruments.protocols.pricing import (\n    _get_curve,\n    _get_fx_maybe_from_solver,\n    _get_fx_vol,\n    _parse_curves,\n    _parse_vol,\n    _WithPricingObjs,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurvesT_,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        _KWArgs,\n        _Vol,\n        datetime_,\n    )\n\n\ndef _composit_fixings_table(df_result: DataFrame, df: DataFrame) -> DataFrame:\n    \"\"\"\n    Add a DataFrame to an existing fixings table by extending or adding to relevant columns.\n\n    Parameters\n    ----------\n    df_result: The main DataFrame that will be updated\n    df: The incoming DataFrame with new data to merge\n\n    Returns\n    -------\n    DataFrame\n    \"\"\"\n    # reindex the result DataFrame\n    if df_result.empty:\n        return df\n    else:\n        df_result = df_result.reindex(index=df_result.index.union(df.index))\n\n    # # update existing columns with missing data from the new available data\n    # for c in [c for c in df.columns if c in df_result.columns and c[1] in [\"dcf\", \"rates\"]]:\n    #     df_result[c] = df_result[c].combine_first(df[c])\n\n    # merge by addition existing values with missing filled to zero\n    m = [c for c in df.columns if c in df_result.columns]\n    if len(m) > 0:\n        df_result[m] = df_result[m].add(df[m], fill_value=0.0)\n\n    # append new columns without additional calculation\n    a = [c for c in df.columns if c not in df_result.columns]\n    if len(a) > 0:\n        df_result[a] = df[a]\n\n    # df_result.columns = MultiIndex.from_tuples(df_result.columns)\n    return df_result\n\n\nclass _WithAnalyticRateFixings(_WithPricingObjs, Protocol):\n    \"\"\"\n    Protocol to determine the *analytic rate fixings' sensitivity* of a particular *Instrument*.\n    \"\"\"\n\n    @property\n    def kwargs(self) -> _KWArgs: ...\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Calculate the sensitivity to rate fixings of the *Instrument*, expressed in local\n        settlement currency per basis point.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, Curve, IRS\n\n        .. ipython:: python\n\n           curve1 = Curve({dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.75}, id=\"Eur1mCurve\")\n           curve3 = Curve({dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.70}, id=\"Eur3mCurve\")\n           irs = IRS(dt(2000, 1, 1), \"20m\", spec=\"eur_irs3\", curves=[{\"1m\": curve1, \"3m\": curve3}, curve1])\n           irs.local_analytic_rate_fixings()\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n\n        Notes\n        -----\n        This analytic method will index the sensitivities with series identifier according to the\n        *Curve* id which has forecast the fixing.\n        \"\"\"  # noqa: E501\n        raise NotImplementedError(\n            f\"{type(self).__name__} must implement `local_analytic_rate_fixings`\"\n        )\n\n    def _local_analytic_rate_fixings_from_legs(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        assert hasattr(self, \"legs\")  # noqa: S101\n\n        # this is a generic implementation to handle 2 legs.\n        c = _parse_curves(self, curves, solver)  # type: ignore[arg-type]\n        v = _parse_vol(self, vol, solver, False)  # type: ignore[call-overload, misc]\n        fx_vol = _get_fx_vol(True, True, *v)\n\n        _vol_meta: _Vol = self.kwargs.meta[\"vol\"]\n        _fx_maybe_from_solver = _get_fx_maybe_from_solver(fx=fx, solver=solver)\n\n        dfs: list[DataFrame] = []\n        for leg, names in zip(\n            self.legs,\n            [\n                (\"rate_curve\", \"disc_curve\", \"index_curve\"),\n                (\"leg2_rate_curve\", \"leg2_disc_curve\", \"leg2_index_curve\"),\n            ],\n            strict=False,\n        ):\n            rate_curve = _get_curve(names[0], True, True, *c)\n            disc_curve = _get_curve(names[1], False, True, *c)\n            index_curve = _get_curve(names[2], False, True, *c)\n            dfs.append(\n                leg.local_analytic_rate_fixings(\n                    rate_curve=rate_curve,\n                    disc_curve=disc_curve,\n                    index_curve=index_curve,\n                    fx=_fx_maybe_from_solver,\n                    fx_vol=fx_vol,\n                    settlement=settlement,\n                    forward=forward,\n                )\n            )\n\n        with warnings.catch_warnings():\n            # TODO: pandas 2.1.0 has a FutureWarning for concatenating DataFrames with Null entries\n            warnings.filterwarnings(\"ignore\", category=FutureWarning)\n            df = concat(dfs)\n            return df.sort_index()\n\n    def _local_analytic_rate_fixings_from_instruments(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        assert hasattr(self, \"instruments\")  # noqa: S101\n\n        df_result = DataFrame(index=DatetimeIndex([], name=\"obs_dates\"))\n        for inst in self.instruments:\n            try:\n                df = inst.local_analytic_rate_fixings(\n                    curves=curves,\n                    solver=solver,\n                    fx=fx,\n                    vol=vol,\n                    forward=forward,\n                    settlement=settlement,\n                )\n            except AttributeError:\n                continue\n            df_result = _composit_fixings_table(df_result, df)\n        return df_result\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/cashflows.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame, concat, isna\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _get_curve,\n    _get_fx_maybe_from_solver,\n    _get_fx_vol,\n    _maybe_get_ir_vol_maybe_from_solver,\n    _parse_curves,\n    _parse_vol,\n    _WithPricingObjs,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        CurvesT_,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithCashflows(_WithPricingObjs, Protocol):\n    \"\"\"\n    Protocol to determine cashflows for any *Instrument* type.\n    \"\"\"\n\n    _kwargs: _KWArgs\n\n    @property\n    def kwargs(self) -> _KWArgs:\n        return self._kwargs\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Return aggregated cashflow data for the *Instrument*.\n\n        .. warning::\n\n           This method is a convenience method to provide a visual representation of all\n           associated calculation data. Calling this method to extract certain values\n           should be avoided. It is more efficient to source relevant parameters or calculations\n           from object attributes or other methods directly.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, Curve, IRS\n\n        .. ipython:: python\n\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", fixed_rate=1.0)\n           irs.cashflows()\n\n        Providing relevant pricing objects will ensure all data that can be calculated is returned.\n\n        .. ipython:: python\n\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.75})\n           irs.cashflows(curves=[curve])\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        raise NotImplementedError(f\"{type(self).__name__} must implement `cashflows`.\")\n\n    def _cashflows_from_legs(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Return aggregated cashflow data for the *Period*.\n\n        .. warning::\n\n           This method is a convenience method to provide a visual representation of all\n           associated calculation data. Calling this method to extracting certain values\n           should be avoided. It is more efficent to source relevant parameters or calculations\n           from object attributes or other methods directly.\n\n        Returns\n        -------\n        dict of values\n        \"\"\"\n        # this is a generalist implementation of an NPV function for an instrument with 2 legs.\n        # most instruments may be likely to implement NPV directly to benefit from optimisations\n        # specific to that instrument\n        assert hasattr(self, \"legs\")  # noqa: S101\n\n        c = _parse_curves(self, curves, solver)  # type: ignore[arg-type]\n        v = _parse_vol(self, vol, solver, False)  # type: ignore[call-overload, misc]\n        fx_vol = _get_fx_vol(True, True, *v)\n\n        _fx_maybe_from_solver = _get_fx_maybe_from_solver(fx=fx, solver=solver)\n\n        ir_vol = _maybe_get_ir_vol_maybe_from_solver(self.kwargs.meta[\"vol\"], v[0], solver)\n        legs_df = [\n            self.legs[0].cashflows(\n                rate_curve=_get_curve(\"rate_curve\", True, True, *c),\n                disc_curve=_get_curve(\"disc_curve\", False, True, *c),\n                index_curve=_get_curve(\"index_curve\", False, True, *c),\n                fx=_fx_maybe_from_solver,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n                settlement=settlement,\n                forward=forward,\n                base=base,\n            )\n        ]\n\n        if len(self.legs) > 1:\n            legs_df.append(\n                self.legs[1].cashflows(\n                    rate_curve=_get_curve(\"leg2_rate_curve\", True, True, *c),\n                    disc_curve=_get_curve(\"leg2_disc_curve\", False, True, *c),\n                    index_curve=_get_curve(\"leg2_index_curve\", False, True, *c),\n                    fx=_fx_maybe_from_solver,\n                    fx_vol=fx_vol,\n                    settlement=settlement,\n                    forward=forward,\n                    base=base,\n                )\n            )\n\n        # filter empty or all NaN\n        dfs_filtered = [_ for _ in legs_df if not (_.empty or isna(_).all(axis=None))]\n\n        with warnings.catch_warnings():\n            # TODO: pandas 2.1.0 has a FutureWarning for concatenating DataFrames with Null entries\n            warnings.filterwarnings(\"ignore\", category=FutureWarning)\n            _: DataFrame = concat(\n                dfs_filtered, keys=[f\"leg{i + 1}\" for i in range(len(dfs_filtered))]\n            )\n        return _\n\n    def _cashflows_from_instruments(self, *args: Any, **kwargs: Any) -> DataFrame:\n        # this is a generalist implementation of an NPV function for an instrument with 2 legs.\n        # most instruments may be likely to implement NPV directly to benefit from optimisations\n        # specific to that instrument\n        assert hasattr(self, \"instruments\")  # noqa: S101\n\n        with warnings.catch_warnings():\n            # TODO: pandas 2.1.0 has a FutureWarning for concatenating DataFrames with Null entries\n            warnings.filterwarnings(\"ignore\", category=FutureWarning)\n            _: DataFrame = concat(\n                [_.cashflows(*args, **kwargs) for _ in self.instruments],\n                keys=[f\"inst{i}\" for i in range(len(self.instruments))],\n            )\n        return _\n\n    def cashflows_table(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Aggregate the values derived from a\n        :meth:`~rateslib.instruments.protocols._WithCashflows.cashflows`, grouped by date,\n        settlement currency and collateral.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, Curve, IRS\n\n        .. ipython:: python\n\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", fixed_rate=1.0)\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.75})\n           irs.cashflows_table(curves=[curve])\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        local: bool, :green:`optional (set as False)`\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        cashflows = self.cashflows(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n        cashflows = cashflows[\n            [\n                defaults.headers[\"currency\"],\n                defaults.headers[\"collateral\"],\n                defaults.headers[\"payment\"],\n                defaults.headers[\"cashflow\"],\n            ]\n        ]\n        _: DataFrame = cashflows.groupby(  # type: ignore[assignment]\n            [\n                defaults.headers[\"currency\"],\n                defaults.headers[\"collateral\"],\n                defaults.headers[\"payment\"],\n            ],\n            dropna=False,\n        )\n        _ = _.sum().unstack([0, 1]).droplevel(0, axis=1)\n        _.columns.names = [\"local_ccy\", \"collateral_ccy\"]\n        _.index.names = [\"payment\"]\n        _ = _.sort_index(ascending=True, axis=0).infer_objects().fillna(0.0)\n        return _\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame, Series\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.periods.protocols.fixings import (\n    _replace_fixings_with_ad_variables,\n    _reset_fixings_data,\n    _structure_sensitivity_data,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Sequence,\n        Solver_,\n        VolT_,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass _WithFixings(Protocol):\n    \"\"\"\n    Protocol for determining fixing sensitivity for a *Period* with AD.\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithFixings.reset_fixings\n\n    \"\"\"\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]: ...\n\n    def reset_fixings(self, state: int_ = NoInput(0)) -> None:\n        \"\"\"\n        Resets any fixings values of the *Instrument* derived using the given data state.\n\n        .. role:: green\n\n        Parameters\n        ----------\n        state: int, :green:`optional`\n            The *state id* of the data series that set the fixing. Only fixings determined by this\n            data will be reset. If not given resets all fixings.\n\n        Returns\n        -------\n        None\n        \"\"\"\n        if hasattr(self, \"legs\"):\n            for leg in self.legs:\n                leg.reset_fixings(state)\n        elif hasattr(self, \"instruments\"):\n            for inst in self.instruments:\n                inst.reset_fixings(state)\n\n    def local_fixings(\n        self,\n        identifiers: Sequence[tuple[str, Series]],\n        scalars: Sequence[float] | NoInput = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Calculate the sensitivity to fixings of the *Instrument*, expressed in local\n        settlement currency.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        identifiers: Sequence of tuple[str, Series], :red:`required`\n            These are the series string identifiers and the data values that will be used in each\n            Series to determine the sensitivity against.\n        scalars: Sequence of floats, :green:`optional (each set as 1.0)`\n            A sequence of scalars to multiply the sensitivities by for each on of the\n            ``identifiers``.\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        original_data, index, state = _replace_fixings_with_ad_variables(identifiers)\n        # Extract sensitivity data\n        pv: dict[str, DualTypes] = self.npv(  # type: ignore[assignment]\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n            local=True,\n        )\n        df = _structure_sensitivity_data(pv, index, identifiers, scalars)\n        _reset_fixings_data(self, original_data, state, identifiers)\n        return df\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/kwargs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.scheduling import Schedule\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        str_,\n    )\n\n\ndef _get_args_from_spec(spec: str_) -> dict[str, Any]:\n    \"\"\"\n    Get ``spec`` args from ``defaults`` or empty dict.\n    \"\"\"\n    if isinstance(spec, NoInput):\n        return {}\n    return defaults.spec.get(spec.lower(), {})\n\n\ndef _update_not_noinput(base_kwargs: dict[str, Any], new_kwargs: dict[str, Any]) -> dict[str, Any]:\n    \"\"\"\n    Update the `base_kwargs` with `new_kwargs` (user values) unless those new values are NoInput.\n    \"\"\"\n    updaters = {\n        k: v for k, v in new_kwargs.items() if k not in base_kwargs or not isinstance(v, NoInput)\n    }\n    return {**base_kwargs, **updaters}\n\n\ndef _update_with_defaults(\n    base_kwargs: dict[str, Any], default_kwargs: dict[str, Any]\n) -> dict[str, Any]:\n    \"\"\"\n    Update the `base_kwargs` with `default_kwargs` if the base_values are NoInput.blank.\n    \"\"\"\n    updaters = {\n        k: v\n        for k, v in default_kwargs.items()\n        if k in base_kwargs and base_kwargs[k] is NoInput.blank\n    }\n    return {**base_kwargs, **updaters}\n\n\ndef _inherit_or_negate(kwargs: dict[str, Any], ignore_blank: bool = False) -> dict[str, Any]:\n    \"\"\"Amend the values of leg2 kwargs if they are defaulted to inherit or negate from leg1.\"\"\"\n\n    def _replace(k: str, v: Any) -> Any:\n        # either inherit or negate the value in leg2 from that in leg1\n        if \"leg2_\" in k:\n            if not isinstance(v, NoInput):\n                return v  # do nothing if the attribute is an input\n\n            try:\n                leg1_v = kwargs[k[5:]]\n            except KeyError:\n                return v\n\n            if leg1_v is NoInput.blank:\n                if ignore_blank:\n                    return v  # this allows an inheritor or negator to be called a second time\n                else:\n                    return NoInput(0)\n\n            if v is NoInput(-1):\n                if isinstance(leg1_v, list):\n                    return [_ * -1.0 for _ in leg1_v]\n                elif isinstance(leg1_v, tuple):\n                    return tuple([_ * -1.0 for _ in leg1_v])\n                else:\n                    return leg1_v * -1.0\n            elif v is NoInput(1):\n                return leg1_v\n        return v  # do nothing to leg1 attributes\n\n    return {k: _replace(k, v) for k, v in kwargs.items()}\n\n\ndef _convert_to_schedule_kwargs(kwargs: dict[str, Any], leg: int) -> dict[str, Any]:\n    _ = \"\" if leg == 1 else \"leg2_\"\n\n    ex_div = kwargs.pop(f\"{_}ex_div\", NoInput(0))\n    if isinstance(ex_div, int):\n        ex_div = -1 * ex_div  # negate this input for business days backwards\n\n    kwargs[f\"{_}schedule\"] = Schedule(\n        effective=kwargs.pop(f\"{_}effective\", NoInput(0)),\n        termination=kwargs.pop(f\"{_}termination\", NoInput(0)),\n        frequency=kwargs.pop(f\"{_}frequency\", NoInput(0)),\n        stub=kwargs.pop(f\"{_}stub\", NoInput(0)),\n        front_stub=kwargs.pop(f\"{_}front_stub\", NoInput(0)),\n        back_stub=kwargs.pop(f\"{_}back_stub\", NoInput(0)),\n        roll=kwargs.pop(f\"{_}roll\", NoInput(0)),\n        eom=kwargs.pop(f\"{_}eom\", NoInput(0)),\n        modifier=kwargs.pop(f\"{_}modifier\", NoInput(0)),\n        calendar=kwargs.pop(f\"{_}calendar\", NoInput(0)),\n        payment_lag=kwargs.pop(f\"{_}payment_lag\", NoInput(0)),\n        payment_lag_exchange=kwargs.pop(f\"{_}payment_lag_exchange\", NoInput(0)),\n        extra_lag=ex_div,\n    )\n    return kwargs\n\n\nclass _KWArgs:\n    \"\"\"\n    Class to manage keyword argument population of *Leg* based *Instruments*.\n\n    This will first populate any provided ``spec`` arguments if given.\n    Second, the user input arguments that are specific values will overwrite these.\n    Thridly, system ``defaults`` wil be populated.\n    Finally, any remaining NoInput arguments of leg2 that are set to `inherit` or `negate` will\n    derive their values from leg1.\n    \"\"\"\n\n    @property\n    def leg1(self) -> dict[str, Any]:\n        \"\"\"Keyword arguments pass to construction of *Leg1*.\"\"\"\n        return self._leg1_args\n\n    @property\n    def leg2(self) -> dict[str, Any]:\n        \"\"\"Keyword arguments pass to construction of *Leg2*.\"\"\"\n        return self._leg2_args\n\n    @property\n    def meta(self) -> dict[str, Any]:\n        \"\"\"Meta keyword arguments associated with the *Instrument*.\"\"\"\n        return self._meta_args\n\n    def __init__(\n        self,\n        user_args: dict[str, Any],\n        default_args: dict[str, Any] | None = None,\n        meta_args: list[str] | None = None,\n        spec: str_ = NoInput(0),\n    ) -> None:\n        default_args_ = default_args or {}\n        meta_args_ = meta_args or []\n\n        kwargs = _get_args_from_spec(spec)\n        kwargs = _update_not_noinput(kwargs, user_args)\n        kwargs = _update_with_defaults(kwargs, default_args_)\n        kwargs = _inherit_or_negate(kwargs)\n\n        self._meta_args = {}\n        for k in meta_args_:\n            if k in kwargs:\n                self._meta_args[k] = kwargs.pop(k)\n        self._leg2_args = {k[5:]: v for k, v in kwargs.items() if \"leg2_\" in k}\n        self._leg1_args = {k: v for k, v in kwargs.items() if \"leg2_\" not in k}\n\n    def __eq__(self, other: Any) -> bool:\n        if not isinstance(other, _KWArgs):\n            return False\n        else:\n            # bools = [\n            #     self.leg1.keys() == other.leg1.keys(),\n            #     self.leg2.keys() == other.leg2.keys(),\n            #     self.meta.keys() == other.meta.keys(),\n            #     all(self.leg1[k] == other.leg1[k] for k in self.leg1.keys()),\n            #     all(self.leg2[k] == other.leg2[k] for k in self.leg2.keys()),\n            #     all(self.meta[k] == other.meta[k] for k in self.meta.keys()),\n            # ]\n            bools = [\n                self.leg1 == other.leg1,\n                self.leg2 == other.leg2,\n                self.meta == other.meta,\n            ]\n            return all(bools)\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/npv.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _get_curve,\n    _get_fx_maybe_from_solver,\n    _get_fx_vol,\n    _parse_curves,\n    _parse_vol,\n    _WithPricingObjs,\n)\nfrom rateslib.periods.utils import _maybe_fx_converted\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        _SettlementParams,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithNPV(_WithPricingObjs, Protocol):\n    \"\"\"\n    Protocol to establish value of any *Instrument* type.\n    \"\"\"\n\n    _kwargs: _KWArgs\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"\n        The default :class:`~rateslib.periods.parameters._SettlementParams` of the *Instrument*.\n\n        This is used to define a ``base`` currency when one is not specified.\n        \"\"\"\n        if hasattr(self, \"legs\"):\n            return self.legs[0].settlement_params  # type: ignore[no-any-return]\n        elif hasattr(self, \"instruments\"):\n            return self.instruments[0].settlement_params  # type: ignore[no-any-return]\n        else:\n            raise NotImplementedError(\n                f\"`settlement_params` not implemented for type {type(self).__name__}\"\n            )\n\n    @property\n    def kwargs(self) -> _KWArgs:\n        \"\"\"The :class:`~rateslib.instruments.protocols._KWArgs` container for\n        the *Instrument*.\"\"\"\n        return self._kwargs\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Calculate the NPV of the *Instrument* converted to any other *base* accounting currency.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, Curve, IRS\n\n        .. ipython:: python\n\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.75})\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", fixed_rate=1.0, curves=[curve])\n           irs.npv()\n           irs.npv(local=True)\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        local: bool, :green:`optional (set as False)`\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable or dict of such indexed by string currency.\n\n        Notes\n        -----\n        If ``base`` is not given then this function will return the value obtained from\n        determining the PV in local *settlement currency*.\n\n        If ``base`` is provided this then an :class:`~rateslib.fx.FXForwards` object may be\n        required to perform conversions. An :class:`~rateslib.fx.FXRates` object is also allowed\n        for this conversion although best practice does not recommend it due to possible\n        settlement date conflicts.\n        \"\"\"\n        # this is a generalist implementation of an NPV function for an instrument with 2 legs.\n        # most instruments may be likely to implement NPV directly to benefit from optimisations\n        # specific to that instrument\n        assert hasattr(self, \"legs\")  # noqa: S101\n\n        c = _parse_curves(self, curves, solver)  # type: ignore[arg-type]\n        v = _parse_vol(self, vol, solver, False)  # type: ignore[call-overload, misc]\n        fx_vol = _get_fx_vol(True, True, *v)\n\n        _fx_maybe_from_solver = _get_fx_maybe_from_solver(fx=fx, solver=solver)\n\n        local_npv: dict[str, DualTypes] = {}\n        for leg, names in zip(\n            self.legs,\n            [\n                (\"rate_curve\", \"disc_curve\", \"index_curve\"),\n                (\"leg2_rate_curve\", \"leg2_disc_curve\", \"leg2_index_curve\"),\n            ],\n            strict=False,\n        ):\n            leg_local_npv = leg.local_npv(\n                rate_curve=_get_curve(names[0], True, True, *c),\n                disc_curve=_get_curve(names[1], False, True, *c),\n                index_curve=_get_curve(names[2], False, True, *c),\n                fx=_fx_maybe_from_solver,\n                fx_vol=fx_vol,\n                settlement=settlement,\n                forward=forward,\n            )\n            if leg.settlement_params.currency in local_npv:\n                local_npv[leg.settlement_params.currency] += leg_local_npv\n            else:\n                local_npv[leg.settlement_params.currency] = leg_local_npv\n\n        if not local:\n            single_value: DualTypes = 0.0\n            base_ = _drb(self.settlement_params.currency, base)\n            for k_, v_ in local_npv.items():\n                single_value += _maybe_fx_converted(\n                    value=v_,\n                    currency=k_,\n                    fx=_fx_maybe_from_solver,\n                    base=base_,\n                    forward=forward,\n                )\n            return single_value\n        else:\n            return local_npv\n\n    def _npv_single_core(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> dict[str, DualTypes]:\n        \"\"\"\n        Private NPV summation function used with a single thread, over all `self.instruments`.\n\n        Returns a dict type: local = True\n        \"\"\"\n        assert hasattr(self, \"instruments\")  # noqa: S101\n\n        local_npv: dict[str, DualTypes] = {}\n        for instrument in self.instruments:\n            inst_local_npv = instrument.npv(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                vol=vol,\n                base=base,\n                local=True,\n                settlement=settlement,\n                forward=forward,\n            )\n\n            for k, v in inst_local_npv.items():\n                if k in local_npv:\n                    local_npv[k] += v\n                else:\n                    local_npv[k] = v\n        return local_npv\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/pricing.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom typing import TYPE_CHECKING, Literal, Protocol, overload\n\nfrom rateslib import defaults\nfrom rateslib.curves import MultiCsaCurve, ProxyCurve\nfrom rateslib.dual import Dual, Dual2, Variable\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.volatility import _BaseIRCube, _BaseIRSmile\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        FX_,\n        Any,\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        FXVol_,\n        IRVol_,\n        NoInput,\n        Solver,\n        Solver_,\n        VolStrat_,\n        VolT_,\n        _BaseCurve,\n        _BaseCurve_,\n        _BaseCurveOrDict,\n        _BaseCurveOrDict_,\n        _BaseCurveOrId,\n        _BaseCurveOrIdOrIdDict,\n        _BaseCurveOrIdOrIdDict_,\n        _BaseInstrument,\n        _FXVolObj,\n        _IRVolObj,\n        _IRVolOption_,\n    )\n\n\nclass _WithPricingObjs(Protocol):\n    \"\"\"\n    Protocol to determine individual *curves* and *vol* inputs for each *Instrument*.\n\n    This protocol contains two internal methods for parsing ``curves`` and ``vol`` inputs\n    according to individual *Instruments* for pricing methods, such as\n    :meth:`~rateslib.instruments.protocols._WithNpv.npv` and\n    :meth:`~rateslib.instruments.protocols._WithRate.rate`.\n    \"\"\"\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"Method is needed to map the `curves` argument input for any individual *Instrument* into\n        the more defined :class:`~rateslib.curves._parsers._Curves` structure.\n        \"\"\"\n        raise NotImplementedError(\n            f\"{type(self).__name__} must implement `_parse_curves` of class `_WithPricingObjs`.\"\n        )\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        \"\"\"Method is needed to map the `vol` argument input for any individual *Instrument* into\n        the more defined :class:`~rateslib.curves._parsers._Vol` structure.\n        \"\"\"\n        raise NotImplementedError(\n            f\"{type(self).__name__} must implement `_parse_vol` of class `_WithPricingObjs`.\"\n        )\n\n\nclass _Curves:\n    \"\"\"\n    Container for a pricing object providing a mapping for curves.\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        rate_curve: _BaseCurveOrIdOrIdDict_ = NoInput(0),\n        disc_curve: _BaseCurveOrIdOrIdDict_ = NoInput(0),\n        index_curve: _BaseCurveOrIdOrIdDict_ = NoInput(0),\n        leg2_rate_curve: _BaseCurveOrIdOrIdDict_ = NoInput(0),\n        leg2_disc_curve: _BaseCurveOrIdOrIdDict_ = NoInput(0),\n        leg2_index_curve: _BaseCurveOrIdOrIdDict_ = NoInput(0),\n    ):\n        self._rate_curve = rate_curve\n        self._disc_curve = disc_curve\n        self._index_curve = index_curve\n        self._leg2_rate_curve = leg2_rate_curve\n        self._leg2_disc_curve = leg2_disc_curve\n        self._leg2_index_curve = leg2_index_curve\n\n    def __eq__(self, other: Any) -> bool:\n        if not isinstance(other, _Curves):\n            return False\n        else:\n            bools = [\n                self.disc_curve == other.disc_curve,\n                self.index_curve == other.index_curve,\n                self.rate_curve == other.rate_curve,\n                self.leg2_rate_curve == other.leg2_rate_curve,\n                self.leg2_disc_curve == other.leg2_disc_curve,\n                self.leg2_index_curve == other.leg2_index_curve,\n            ]\n            return all(bools)\n\n    @property\n    def rate_curve(self) -> _BaseCurveOrIdOrIdDict_:\n        \"\"\"The curve used for floating rate or hazard rate forecasting on leg1.\"\"\"\n        return self._rate_curve\n\n    @property\n    def disc_curve(self) -> _BaseCurveOrIdOrIdDict_:\n        \"\"\"The curve used for discounting on leg1.\"\"\"\n        return self._disc_curve\n\n    @property\n    def index_curve(self) -> _BaseCurveOrIdOrIdDict_:\n        \"\"\"The index curve used for forecasting index values on leg1.\"\"\"\n        return self._index_curve\n\n    @property\n    def leg2_rate_curve(self) -> _BaseCurveOrIdOrIdDict_:\n        \"\"\"The curve used for floating rate or hazard rate forecasting on leg2.\"\"\"\n        return self._leg2_rate_curve\n\n    @property\n    def leg2_disc_curve(self) -> _BaseCurveOrIdOrIdDict_:\n        \"\"\"The curve used for discounting on leg2.\"\"\"\n        return self._leg2_disc_curve\n\n    @property\n    def leg2_index_curve(self) -> _BaseCurveOrIdOrIdDict_:\n        \"\"\"The index curve used for forecasting index values on leg2.\"\"\"\n        return self._leg2_index_curve\n\n\nclass _Vol:\n    \"\"\"\n    Container for a pricing object providing a mapping for volatility.\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        fx_vol: FXVol_ = NoInput(0),\n        ir_vol: IRVol_ = NoInput(0),\n    ):\n        self._fx_vol = fx_vol\n        self._ir_vol = ir_vol\n\n    @property\n    def fx_vol(self) -> FXVol_:\n        \"\"\"The FX vol object used for modelling FX volatility.\"\"\"\n        return self._fx_vol\n\n    @property\n    def ir_vol(self) -> IRVol_:\n        \"\"\"The IR vol object used for modelling IR volatility.\"\"\"\n        return self._ir_vol\n\n    def __eq__(self, other: Any) -> bool:\n        if not isinstance(other, _Vol):\n            return False\n        else:\n            return self.fx_vol == other.fx_vol and self.ir_vol == other.ir_vol\n\n\ndef _parse_curves(\n    obj: _BaseInstrument, curves: CurvesT_, solver: Solver_\n) -> tuple[_Curves, _Curves, Solver_]:\n    return (obj._parse_curves(curves), obj.kwargs.meta[\"curves\"], solver)\n\n\n@overload\ndef _parse_vol(\n    obj: _BaseInstrument,\n    vol: VolT_,\n    solver: Solver_,\n    sequence: Literal[False],\n) -> tuple[_Vol, _Vol, Solver_]: ...\n\n\n@overload\ndef _parse_vol(\n    obj: _BaseInstrument,\n    vol: VolStrat_,\n    solver: Solver_,\n    sequence: Literal[True],\n) -> tuple[VolStrat_, VolStrat_, Solver_]: ...\n\n\ndef _parse_vol(\n    obj: _BaseInstrument,\n    vol: VolT_ | VolStrat_,\n    solver: Solver_,\n    sequence: bool,\n) -> tuple[_Vol | VolStrat_, _Vol | VolStrat_, Solver_]:\n    return obj._parse_vol(vol), obj.kwargs.meta[\"vol\"], solver  # type: ignore[arg-type]\n\n\n# Solver and Curve mapping\n\n\n@overload\ndef _get_curve(\n    name: str,\n    allow_dict: Literal[False],\n    allow_no_input: Literal[True],\n    curves: _Curves,\n    curves_meta: _Curves,\n    solver: Solver_,\n) -> _BaseCurve_: ...\n\n\n@overload\ndef _get_curve(\n    name: str,\n    allow_dict: Literal[False],\n    allow_no_input: Literal[False],\n    curves: _Curves,\n    curves_meta: _Curves,\n    solver: Solver_,\n) -> _BaseCurve: ...\n\n\n@overload\ndef _get_curve(\n    name: str,\n    allow_dict: Literal[True],\n    allow_no_input: Literal[True],\n    curves: _Curves,\n    curves_meta: _Curves,\n    solver: Solver_,\n) -> _BaseCurveOrDict_: ...\n\n\n@overload\ndef _get_curve(\n    name: str,\n    allow_dict: Literal[True],\n    allow_no_input: Literal[False],\n    curves: _Curves,\n    curves_meta: _Curves,\n    solver: Solver_,\n) -> _BaseCurveOrDict: ...\n\n\ndef _get_curve(\n    name: str,\n    allow_dict: bool,\n    allow_no_input: bool,\n    curves: _Curves,\n    curves_meta: _Curves,\n    solver: Solver_,\n) -> _BaseCurveOrDict_:\n    curve: _BaseCurveOrIdOrIdDict_ = _drb(getattr(curves_meta, name), getattr(curves, name))\n    if isinstance(curve, NoInput) or curve is None:\n        if allow_no_input:\n            return NoInput(0)\n        else:\n            raise ValueError(f\"`{name}` must be provided. Got NoInput.\")\n    elif isinstance(solver, NoInput):\n        return _validate_base_curve_or_dict(  # type: ignore[no-any-return, call-overload]\n            curve=curve, allow_dict=allow_dict, allow_no_input=allow_no_input\n        )\n    else:\n        return _get_curve_from_solver(  # type: ignore[no-any-return, call-overload]\n            curve=curve,\n            solver=solver,\n            allow_dict=allow_dict,\n        )\n\n\n@overload\ndef _validate_base_curve_or_dict(\n    curve: _BaseCurveOrIdOrIdDict,\n    allow_dict: Literal[True],\n    allow_no_input: Literal[True],\n) -> _BaseCurveOrDict_: ...\n\n\n@overload\ndef _validate_base_curve_or_dict(\n    curve: _BaseCurveOrIdOrIdDict,\n    allow_dict: Literal[True],\n    allow_no_input: Literal[False],\n) -> _BaseCurveOrDict: ...\n\n\n@overload\ndef _validate_base_curve_or_dict(\n    curve: _BaseCurveOrIdOrIdDict,\n    allow_dict: Literal[False],\n    allow_no_input: Literal[True],\n) -> _BaseCurve_: ...\n\n\n@overload\ndef _validate_base_curve_or_dict(\n    curve: _BaseCurveOrIdOrIdDict,\n    allow_dict: Literal[False],\n    allow_no_input: Literal[False],\n) -> _BaseCurve: ...\n\n\ndef _validate_base_curve_or_dict(\n    curve: _BaseCurveOrIdOrIdDict,\n    allow_dict: bool,\n    allow_no_input: bool,\n) -> _BaseCurveOrDict_:\n    \"\"\"\n    Validate that a curve input is an object and not a string id.\n    \"\"\"\n    if isinstance(curve, dict):\n        if not allow_dict:\n            raise ValueError(\"Cannot supply a dict type object as this `curve`.\")\n        else:\n            return {\n                k: _validate_base_curve(v, allow_no_input=allow_no_input)  # type: ignore[call-overload]\n                for k, v in curve.items()\n            }\n    else:\n        return _validate_base_curve(curve, allow_no_input=allow_no_input)  # type: ignore[no-any-return, call-overload]\n\n\n@overload\ndef _validate_base_curve(curve: _BaseCurveOrId, allow_no_input: Literal[False]) -> _BaseCurve: ...\n\n\n@overload\ndef _validate_base_curve(curve: _BaseCurveOrId, allow_no_input: Literal[True]) -> _BaseCurve_: ...\n\n\ndef _validate_base_curve(curve: _BaseCurveOrId, allow_no_input: bool) -> _BaseCurve_:\n    if isinstance(curve, str):\n        if allow_no_input:\n            return NoInput(0)\n        else:\n            raise ValueError(\n                f\"`curves` must contain _BaseCurve, not str, if `solver` not given. \"\n                f\"Got id: '{curve}'\"\n            )\n    return curve\n\n\n@overload\ndef _get_curve_from_solver(\n    curve: _BaseCurveOrIdOrIdDict, solver: Solver, allow_dict: Literal[True]\n) -> _BaseCurveOrDict: ...\n\n\n@overload\ndef _get_curve_from_solver(\n    curve: _BaseCurveOrIdOrIdDict, solver: Solver, allow_dict: Literal[False]\n) -> _BaseCurve: ...\n\n\ndef _get_curve_from_solver(\n    curve: _BaseCurveOrIdOrIdDict, solver: Solver, allow_dict: bool\n) -> _BaseCurveOrDict:\n    \"\"\"\n    Maps a \"Curve | str | dict[str, Curve | str]\" to a \"Curve | dict[str, Curve]\" via a Solver.\n\n    If curve input involves strings get objects directly from solver curves mapping.\n\n    This is the explicit variety which does not handle NoInput.\n    \"\"\"\n    if isinstance(curve, dict):\n        if not allow_dict:\n            raise ValueError(\"Cannot supply a dict type object as this `curve`.\")\n        parsed_dict: dict[str, _BaseCurve] = {\n            k: _parse_curve_or_id_from_solver_(curve=v, solver=solver) for k, v in curve.items()\n        }\n        return parsed_dict\n    else:\n        return _parse_curve_or_id_from_solver_(curve, solver)\n\n\ndef _parse_curve_or_id_from_solver_(curve: _BaseCurveOrId, solver: Solver) -> _BaseCurve:\n    \"\"\"\n    Maps a \"Curve | str\" to a \"Curve\" via a Solver mapping.\n\n    If a Curve, runs a check against whether that Curve is associated with the given Solver,\n    and perform an action based on `defaults.curve_not_in_solver`\n    \"\"\"\n    if isinstance(curve, str):\n        return solver._get_pre_curve(curve)\n    elif type(curve) is ProxyCurve or type(curve) is MultiCsaCurve:\n        # TODO: (mid) consider also adding CompositeCurves as exceptions under the same rule\n        # Proxy curves and MultiCsaCurves can exist outside of Solvers but be constructed\n        # directly from an FXForwards object tied to a Solver using only a Solver's\n        # dependent curves and AD variables.\n        return curve  # type: ignore[no-any-return]  # mypy error\n    else:\n        try:\n            # it is a safeguard to load curves from solvers when a solver is\n            # provided and multiple curves might have the same id\n            __: _BaseCurve = solver._get_pre_curve(curve.id)\n            if id(__) != id(curve):  # Python id() is a memory id, not a string label id.\n                raise ValueError(\n                    \"A curve has been supplied, as part of ``curves``, which has the same \"\n                    f\"`id` ('{curve.id}'),\\nas one of the curves available as part of the \"\n                    \"Solver's collection but is not the same object.\\n\"\n                    \"This is ambiguous and cannot price.\\n\"\n                    \"Either refactor the arguments as follows:\\n\"\n                    \"1) remove the conflicting curve: [curves=[..], solver=<Solver>] -> \"\n                    \"[curves=None, solver=<Solver>]\\n\"\n                    \"2) change the `id` of the supplied curve and ensure the rateslib.defaults \"\n                    \"option 'curve_not_in_solver' is set to 'ignore'.\\n\"\n                    \"   This will remove the ability to accurately price risk metrics.\",\n                )\n            return __\n        except AttributeError:\n            raise AttributeError(\n                \"`curve` has no attribute `id`, likely it not a valid object, got: \"\n                f\"{curve}.\\nSince a solver is provided have you missed labelling the `curves` \"\n                f\"of the instrument or supplying `curves` directly?\",\n            )\n        except KeyError:\n            if defaults.curve_not_in_solver == \"ignore\":\n                return curve\n            elif defaults.curve_not_in_solver == \"warn\":\n                warnings.warn(\"`curve` not found in `solver`.\", UserWarning)\n                return curve\n            else:\n                raise ValueError(\"`curve` must be in `solver`.\")\n\n\n# Solver and FX Vol mapping\n\n\n@overload\ndef _get_fx_vol(\n    allow_numeric: Literal[True],\n    allow_no_input: Literal[True],\n    vol: _Vol,\n    vol_meta: _Vol,\n    solver: Solver_,\n) -> _FXVolObj | DualTypes | NoInput: ...\n\n\n@overload\ndef _get_fx_vol(\n    allow_numeric: Literal[True],\n    allow_no_input: Literal[False],\n    vol: _Vol,\n    vol_meta: _Vol,\n    solver: Solver_,\n) -> _FXVolObj | DualTypes: ...\n\n\n@overload\ndef _get_fx_vol(\n    allow_numeric: Literal[False],\n    allow_no_input: Literal[True],\n    vol: _Vol,\n    vol_meta: _Vol,\n    solver: Solver_,\n) -> _FXVolObj | NoInput: ...\n\n\n@overload\ndef _get_fx_vol(\n    allow_numeric: Literal[False],\n    allow_no_input: Literal[False],\n    vol: _Vol,\n    vol_meta: _Vol,\n    solver: Solver_,\n) -> _FXVolObj: ...\n\n\ndef _get_fx_vol(\n    allow_numeric: bool,\n    allow_no_input: bool,\n    vol: _Vol,\n    vol_meta: _Vol,\n    solver: Solver_,\n) -> _FXVolObj | DualTypes | NoInput:\n    fx_vol_ = _drb(vol_meta.fx_vol, vol.fx_vol)\n    if isinstance(fx_vol_, NoInput) or fx_vol_ is None:\n        if allow_no_input:\n            return NoInput(0)\n        else:\n            raise ValueError(\"`fx_vol` must be provided. Got NoInput.\")\n    elif isinstance(fx_vol_, float | Dual | Dual2 | Variable):\n        if allow_numeric:\n            return fx_vol_\n        else:\n            raise ValueError(\"`fx_vol` must be an object. Got numeric quantity.\")\n    elif isinstance(solver, NoInput):\n        return _validate_base_fx_vol(fx_vol=fx_vol_, allow_no_input=allow_no_input)  # type: ignore[no-any-return, call-overload]\n    else:\n        return _get_fx_vol_from_solver(fx_vol=fx_vol_, solver=solver)\n\n\n@overload\ndef _validate_base_fx_vol(fx_vol: _FXVolObj | str, allow_no_input: Literal[False]) -> _FXVolObj: ...\n\n\n@overload\ndef _validate_base_fx_vol(\n    fx_vol: _FXVolObj | str, allow_no_input: Literal[True]\n) -> _FXVolObj | NoInput: ...\n\n\ndef _validate_base_fx_vol(fx_vol: _FXVolObj | str, allow_no_input: bool) -> _FXVolObj | NoInput:\n    if isinstance(fx_vol, str):\n        if allow_no_input:\n            return NoInput(0)\n        else:\n            raise ValueError(\n                f\"`fx_vol` must contain FXVol object, not str, if `solver` not given. \"\n                f\"Got id: '{fx_vol}'\"\n            )\n    return fx_vol\n\n\ndef _get_fx_vol_from_solver(fx_vol: _FXVolObj | str, solver: Solver) -> _FXVolObj:\n    if isinstance(fx_vol, str):\n        return solver._get_pre_fxvol(fx_vol)\n\n    try:\n        # it is a safeguard to load curves from solvers when a solver is\n        # provided and multiple curves might have the same id\n        __: _FXVolObj = solver._get_pre_fxvol(fx_vol.id)\n        if id(__) != id(fx_vol):  # Python id() is a memory id, not a string label id.\n            raise ValueError(\n                \"An FXVol object has been supplied, as part of ``vol``, which has the same \"\n                f\"`id` ('{fx_vol.id}'),\\nas one of the curves available as part of the \"\n                \"Solver's collection but is not the same object.\\n\"\n                \"This is ambiguous and cannot price.\\n\"\n                \"Either refactor the arguments as follows:\\n\"\n                \"1) remove the conflicting object: [vol=[..], solver=<Solver>] -> \"\n                \"[vol=None, solver=<Solver>]\\n\"\n                \"2) change the `id` of the supplied FXVol object and ensure the rateslib.defaults \"\n                \"option 'curve_not_in_solver' is set to 'ignore'.\\n\"\n                \"   This will remove the ability to accurately price risk metrics.\",\n            )\n        return __\n    except AttributeError:\n        raise AttributeError(\n            \"FXVol object has no attribute `id`, likely it is not a valid object, got: \"\n            f\"{fx_vol}.\\nSince a solver is provided have you missed labelling the `curves` \"\n            f\"of the instrument or supplying `curves` directly?\",\n        )\n    except KeyError:\n        if defaults.curve_not_in_solver == \"ignore\":\n            return fx_vol\n        elif defaults.curve_not_in_solver == \"warn\":\n            warnings.warn(\"FXVol object not found in `solver`.\", UserWarning)\n            return fx_vol\n        else:\n            raise ValueError(\"FXVol object must be in `solver`.\")\n\n\n# Solver and IR Vol mapping\n\n\ndef _maybe_get_ir_vol_maybe_from_solver(\n    vol_meta: _Vol,\n    vol: _Vol,\n    # name: str, = \"fx_vol\"\n    solver: Solver_,\n) -> _IRVolOption_:\n    ir_vol_ = _drb(vol_meta.ir_vol, vol.ir_vol)\n    if isinstance(ir_vol_, NoInput | float | Dual | Dual2 | Variable):\n        return ir_vol_\n    elif isinstance(solver, NoInput):\n        return _validate_ir_vol_is_not_id(ir_vol=ir_vol_)\n    else:\n        return _get_ir_vol_from_solver(ir_vol=ir_vol_, solver=solver)\n\n\ndef _get_ir_vol_from_solver(\n    ir_vol: _BaseIRSmile | _BaseIRCube[Any] | str, solver: Solver\n) -> _BaseIRSmile | _BaseIRCube[Any]:\n    if isinstance(ir_vol, str):\n        return solver._get_pre_irvol(ir_vol)\n\n    try:\n        # it is a safeguard to load curves from solvers when a solver is\n        # provided and multiple curves might have the same id\n        __: _IRVolObj = solver._get_pre_irvol(ir_vol.id)\n        if id(__) != id(ir_vol):  # Python id() is a memory id, not a string label id.\n            raise ValueError(\n                \"An IRVol object has been supplied, as part of ``vol``, which has the same \"\n                f\"`id` ('{ir_vol.id}'),\\nas one of the curves available as part of the \"\n                \"Solver's collection but is not the same object.\\n\"\n                \"This is ambiguous and cannot price.\\n\"\n                \"Either refactor the arguments as follows:\\n\"\n                \"1) remove the conflicting object: [vol=[..], solver=<Solver>] -> \"\n                \"[vol=None, solver=<Solver>]\\n\"\n                \"2) change the `id` of the supplied IRVol object and ensure the rateslib.defaults \"\n                \"option 'curve_not_in_solver' is set to 'ignore'.\\n\"\n                \"   This will remove the ability to accurately price risk metrics.\",\n            )\n        return __\n    except AttributeError:\n        raise AttributeError(\n            \"IRVol object has no attribute `id`, likely it is not a valid object, got: \"\n            f\"{ir_vol}.\\nSince a solver is provided have you missed labelling the `curves` \"\n            f\"of the instrument or supplying `curves` directly?\",\n        )\n    except KeyError:\n        if defaults.curve_not_in_solver == \"ignore\":\n            return ir_vol\n        elif defaults.curve_not_in_solver == \"warn\":\n            warnings.warn(\"FXVol object not found in `solver`.\", UserWarning)\n            return ir_vol\n        else:\n            raise ValueError(\"FXVol object must be in `solver`.\")\n\n\ndef _validate_ir_vol_is_not_id(ir_vol: _IRVolObj | str) -> _IRVolObj:\n    if isinstance(ir_vol, str):  # curve is a str ID\n        raise ValueError(\n            f\"`vol` must contain IRVol object, not str, if `solver` not given. Got id: '{ir_vol}'\"\n        )\n    return ir_vol\n\n\n# FX and Solver mapping\n\n\ndef _get_fx_forwards_maybe_from_solver(solver: Solver_, fx: FXForwards_) -> FXForwards_:\n    if isinstance(fx, NoInput):\n        if isinstance(solver, NoInput):\n            fx_: FXForwards_ = NoInput(0)\n        else:\n            if isinstance(solver.fx, NoInput):\n                fx_ = NoInput(0)\n            else:\n                # TODO disallow `fx` on Solver as FXRates. Only allow FXForwards.\n                fx_ = solver._get_fx()\n    else:\n        fx_ = fx\n        if (\n            not isinstance(solver, NoInput)\n            and not isinstance(solver.fx, NoInput)\n            and id(fx) != id(solver.fx)\n        ):\n            warnings.warn(\n                \"Solver contains an `fx` attribute but an `fx` argument has been \"\n                \"supplied which will be used but is not the same. This can lead \"\n                \"to calculation inconsistencies, mathematically.\",\n                UserWarning,\n            )\n\n    return fx_\n\n\ndef _get_fx_maybe_from_solver(solver: Solver_, fx: FXForwards_) -> FXForwards_:\n    if isinstance(fx, NoInput):\n        if isinstance(solver, NoInput):\n            fx_: FX_ = NoInput(0)\n        else:\n            if isinstance(solver.fx, NoInput):\n                fx_ = NoInput(0)\n            else:\n                fx_ = solver._get_fx()  # will validate the state\n    else:\n        fx_ = fx\n        if (\n            not isinstance(solver, NoInput)\n            and not isinstance(solver.fx, NoInput)\n            and id(fx) != id(solver.fx)\n        ):\n            warnings.warn(\n                \"Solver contains an `fx` attribute but an `fx` argument has been \"\n                \"supplied which will be used but is not the same. This can lead \"\n                \"to calculation inconsistencies, mathematically.\",\n                UserWarning,\n            )\n\n    return fx_  # type: ignore[return-value]\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/rate.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.enums.generics import NoInput\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithRate(Protocol):\n    \"\"\"\n    Protocol to establish a *rate* pricing metric of any *Instrument* type.\n    \"\"\"\n\n    _rate_scalar: float\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        # Overloaded rate docs are for: IndexFixedRateBond\n        \"\"\"\n        Calculate some pricing rate metric for the *Instrument*.\n\n        .. rubric:: Examples\n\n        The default metric for an :class:`~rateslib.instruments.irs.IRS` is its fixed *'rate'*.\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, Curve, IRS\n\n        .. ipython:: python\n\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.75})\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", curves=[curve], fixed_rate=2.0)\n           irs.rate()       # <- `fixed_rate` on fixed leg to equate value with float leg\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        local: bool, :green:`optional (set as False)`\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n        metric: str, :green:`optional`\n            The specific calculation to perform and the value to return.\n            See **Pricing** on each *Instrument* for details of allowed inputs.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        raise NotImplementedError(f\"`rate` must be implemented for type: {type(self).__name__}\")\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Calculate some pricing spread metric for the *Instrument*.\n\n        This calculation may be an alias for :meth:`~rateslib.instruments.protocols._WithRate.rate`\n        with a specific `metric` and is designated at an *Instrument* level.\n\n        .. rubric:: Examples\n\n        The *'spread'* on an :class:`~rateslib.instruments.irs.IRS` is the float leg spread to\n        equate value with the fixed leg.\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import dt, Curve, IRS\n\n        .. ipython:: python\n\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.75})\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", curves=[curve], fixed_rate=2.0)\n           irs.spread()       # <- `spread` on float leg to equate value with fixed leg\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :green:`optional`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        local: bool, :green:`optional (set as False)`\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        raise NotImplementedError(f\"`spread` is not implemented for type: {type(self).__name__}\")\n\n    @property\n    def rate_scalar(self) -> float:\n        \"\"\"\n        A scaling quantity associated with the :class:`~rateslib.solver.Solver` risk calculations.\n        \"\"\"\n        return self._rate_scalar\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/sensitivities.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.instruments.protocols.npv import _WithNPV\nfrom rateslib.instruments.protocols.pricing import (\n    _get_fx_forwards_maybe_from_solver,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurvesT_,\n        DataFrame,\n        Dual,\n        Dual2,\n        FXForwards_,\n        NoInput,\n        Solver_,\n        VolT_,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithSensitivities(_WithNPV, Protocol):\n    \"\"\"\n    Protocol to establish **delta** and **gamma** calculations using a\n    :class:`~rateslib.solver.Solver` of any *Instrument* type.\n    \"\"\"\n\n    def delta(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Calculate delta risk of an *Instrument* against the calibrating instruments in a\n        :class:`~rateslib.solver.Solver`.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import IRS, Curve, Solver, dt\n\n        .. ipython:: python\n\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.85, dt(2010, 1, 1): 0.75})\n           solver = Solver(\n               curves=[curve],\n               instruments=[\n                   IRS(dt(2000, 1, 1), \"2Y\", spec=\"usd_irs\", curves=[curve]),\n                   IRS(dt(2000, 1, 1), \"5Y\", spec=\"usd_irs\", curves=[curve]),\n               ],\n               s=[2.0, 2.25],\n               instrument_labels=[\"2Y\", \"5Y\"],\n               id=\"US_RATES\"\n           )\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", curves=[curve])\n           irs.delta(solver=solver)\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :red:`required`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n\n        Notes\n        -----\n        **Delta** measures the sensitivity of the *PV* to a change in any of the calibrating\n        instruments of the given :class:`~rateslib.solver.Solver`. Values are returned\n        according to the ``rate_scalar`` quantity at an *Instrument* level and according to the\n        ``metric`` used to derive the :meth:`~rateslib.instruments.protocols._WithRate.rate`\n        method of each *Instrument*.\n\n        \"\"\"\n        if isinstance(solver, NoInput):\n            raise ValueError(\"`solver` is required for delta/gamma methods.\")\n        npv: dict[str, Dual] = self.npv(  # type: ignore[assignment]\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            forward=forward,\n            settlement=settlement,\n            local=True,\n        )\n        return solver.delta(\n            npv=npv, base=base, fx=_get_fx_forwards_maybe_from_solver(fx=fx, solver=solver)\n        )\n\n    def exo_delta(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        vars: list[str],  # noqa: A002\n        vars_scalar: list[float] | NoInput = NoInput(0),\n        vars_labels: list[str] | NoInput = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Calculate delta risk of an *Instrument* against some exogenous user created *Variables*,\n        via a :class:`~rateslib.solver.Solver`.\n\n        See :ref:`What are exogenous variables? <cook-exogenous-doc>` in the cookbook.\n\n        .. rubric:: Examples\n\n        This example calculates the risk of the fixed rate increasing by 1bp and the notional\n        increasing by 1mm. Mathematically this should be equivalent to the `npv` and the\n        `analytic delta` (although the calculation is based on AD and is completely independent\n        of the solver).\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import IRS, Curve, Solver, dt, Variable\n\n        .. ipython:: python\n\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.85, dt(2010, 1, 1): 0.75})\n           solver = Solver(\n               curves=[curve],\n               instruments=[\n                   IRS(dt(2000, 1, 1), \"2Y\", spec=\"usd_irs\", curves=[curve]),\n                   IRS(dt(2000, 1, 1), \"5Y\", spec=\"usd_irs\", curves=[curve]),\n               ],\n               s=[2.0, 2.25],\n               instrument_labels=[\"2Y\", \"5Y\"],\n               id=\"US_RATES\"\n           )\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", fixed_rate=Variable(3.0, [\"R\"]), notional=Variable(1e6, [\"N\"]), curves=[curve])\n           irs.exo_delta(solver=solver, vars=[\"R\", \"N\"], vars_scalar=[1e-2, 1e6])\n           irs.analytic_delta()\n           irs.npv()\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :red:`required`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n        vars : list[str], :red:`required`\n            The variable tags which to determine sensitivities for.\n        vars_scalar : list[float], :green:`optional`\n            Scaling factors for each variable, for example converting rates to basis point etc.\n            Defaults to ones.\n        vars_labels : list[str], :green:`optional`\n            Alternative names to relabel variables in DataFrames.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"  # noqa: E501\n        if isinstance(solver, NoInput):\n            raise ValueError(\"`solver` is required for delta/gamma methods.\")\n        npv: dict[str, Dual | Dual2] = self.npv(  # type: ignore[assignment]\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            forward=forward,\n            settlement=settlement,\n            local=True,\n        )\n        return solver.exo_delta(\n            npv=npv,\n            vars=vars,\n            base=base,\n            fx=_get_fx_forwards_maybe_from_solver(fx=fx, solver=solver),\n            vars_scalar=vars_scalar,\n            vars_labels=vars_labels,\n        )\n\n    def gamma(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Calculate cross-gamma risk of an *Instrument* against the calibrating instruments of a\n        :class:`~rateslib.solver.Solver`.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import IRS, Curve, Solver, dt\n\n        .. ipython:: python\n\n           curve = Curve({dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.85, dt(2010, 1, 1): 0.75})\n           solver = Solver(\n               curves=[curve],\n               instruments=[\n                   IRS(dt(2000, 1, 1), \"2Y\", spec=\"usd_irs\", curves=[curve]),\n                   IRS(dt(2000, 1, 1), \"5Y\", spec=\"usd_irs\", curves=[curve]),\n               ],\n               s=[2.0, 2.25],\n               instrument_labels=[\"2Y\", \"5Y\"],\n               id=\"US_RATES\"\n           )\n           irs = IRS(dt(2000, 1, 1), \"3Y\", spec=\"usd_irs\", curves=[curve])\n           irs.gamma(solver=solver)\n\n        Parameters\n        ----------\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        solver: Solver, :red:`required`\n            A :class:`~rateslib.solver.Solver` object containing *Curve*, *Smile*, *Surface*, or\n            *Cube* mappings for pricing.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting FX rates, if necessary.\n        vol: _Vol, :green:`optional`\n            Pricing objects. See **Pricing** on each *Instrument* for details of allowed inputs.\n        base: str, :green:`optional (set to settlement currency)`\n            The currency to convert the *local settlement* NPV to.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n\n        Notes\n        -----\n        **Gamma** measures the second order cross-sensitivity of the *PV* to a change in any\n        of the calibrating instruments of the given :class:`~rateslib.solver.Solver`. Values are\n        returned according to the ``rate_scalar`` quantity at an *Instrument* level and according\n        to the ``metric`` used to derive the :meth:`~rateslib.instruments.protocols._WithRate.rate`\n        method of each *Instrument*.\n        \"\"\"\n        if isinstance(solver, NoInput):\n            raise ValueError(\"`solver` is required for delta/gamma methods.\")\n\n        fx_ = _get_fx_forwards_maybe_from_solver(fx=fx, solver=solver)\n        # store original order\n        if id(solver.fx) != id(fx_) and isinstance(fx_, FXRates | FXForwards):\n            # then the fx_ object is available on solver but that is not being used.\n            _ad_fx = fx_._ad\n            fx_._set_ad_order(2)\n\n        _ad_svr = solver._ad\n        solver._set_ad_order(2)\n\n        npv: dict[str, Dual2] = self.npv(  # type: ignore[assignment]\n            curves=curves,\n            solver=solver,\n            fx=fx_,\n            vol=vol,\n            base=NoInput(0),  # local override\n            settlement=settlement,\n            forward=forward,\n            local=True,\n        )\n        grad_s_sT_P: DataFrame = solver.gamma(npv, base, fx_)\n\n        # reset original order\n        if id(solver.fx) != id(fx_) and isinstance(fx_, FXRates | FXForwards):\n            fx_._set_ad_order(_ad_fx)\n        solver._set_ad_order(_ad_svr)\n\n        return grad_s_sT_P\n"
  },
  {
    "path": "python/rateslib/instruments/protocols/utils.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, TypeVar\n\nfrom rateslib.enums.generics import NoInput\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurveOption_,\n    )\n\n\n# def _get_fx_maybe_from_solver(\n#     fx: FX_,\n#     solver: Solver_,\n# ) -> FX_:\n#     # Get the `fx` from Solver only if not directly provided and Solver exists.\n#     fx_: FXForwards_\n#     if isinstance(fx, NoInput):\n#         if not isinstance(solver, NoInput):\n#             fx_ = solver.fx\n#         else:\n#             fx_ = NoInput(0)\n#     else:\n#         fx_ = fx\n#     return fx_\n\n\nT = TypeVar(\"T\")\n\n\ndef _validate_obj_not_no_input(obj: T | NoInput, name: str) -> T:\n    if isinstance(obj, NoInput):\n        raise ValueError(f\"`{name}` must be supplied. Got NoInput or None.\")\n    return obj\n\n\ndef _maybe_set_ad_order(\n    curve: CurveOption_, order: int | dict[str, int | None] | None\n) -> int | dict[str, int | None] | None:\n    \"\"\"method is used internally to set AD order and then later revert the curve to its original\"\"\"\n    if isinstance(curve, NoInput) or order is None:\n        return None  # do nothing\n    else:\n        if isinstance(curve, dict):\n            # method will return a dict of orders if a dict of curves is provided as input\n            if isinstance(order, dict):\n                return {\n                    k: _maybe_set_ad_order(v, order[k])  # type: ignore[misc]\n                    for k, v in curve.items()\n                }\n            else:\n                return {\n                    k: _maybe_set_ad_order(v, order)  # type: ignore[misc]\n                    for k, v in curve.items()\n                }\n        else:\n            try:\n                original_order = curve.ad\n                curve._set_ad_order(order)  # type: ignore[arg-type]\n            except AttributeError:\n                # Curve has no method (possibly a custom curve and not a subclass of _BaseCurve)\n                return None\n            return original_order\n\n\n# def _map_fx_vol_or_id_from_solver_(curve: CurveOrId, solver: Solver) -> _BaseCurve:\n#     \"\"\"\n#     Maps a \"FXVol | str\" to a \"Curve\" via a Solver mapping.\n#\n#     If a Curve, runs a check against whether that Curve is associated with the given Solver,\n#     and perform an action based on `defaults.curve_not_in_solver`\n#     \"\"\"\n#     if isinstance(curve, str):\n#         return solver._get_pre_curve(curve)\n#     elif type(curve) is ProxyCurve or type(curve) is MultiCsaCurve:\n#         # TODO: (mid) consider also adding CompositeCurves as exceptions under the same rule\n#         # Proxy curves and MultiCsaCurves can exist outside of Solvers but be constructed\n#         # directly from an FXForwards object tied to a Solver using only a Solver's\n#         # dependent curves and AD variables.\n#         return curve\n#     else:\n#         try:\n#             # it is a safeguard to load curves from solvers when a solver is\n#             # provided and multiple curves might have the same id\n#             __: _BaseCurve = solver._get_pre_curve(curve.id)\n#             if id(__) != id(curve):  # Python id() is a memory id, not a string label id.\n#                 raise ValueError(\n#                     \"A curve has been supplied, as part of ``curves``, which has the same \"\n#                     f\"`id` ('{curve.id}'),\\nas one of the curves available as part of the \"\n#                     \"Solver's collection but is not the same object.\\n\"\n#                     \"This is ambiguous and cannot price.\\n\"\n#                     \"Either refactor the arguments as follows:\\n\"\n#                     \"1) remove the conflicting curve: [curves=[..], solver=<Solver>] -> \"\n#                     \"[curves=None, solver=<Solver>]\\n\"\n#                     \"2) change the `id` of the supplied curve and ensure the rateslib.defaults \"\n#                     \"option 'curve_not_in_solver' is set to 'ignore'.\\n\"\n#                     \"   This will remove the ability to accurately price risk metrics.\",\n#                 )\n#             return __\n#         except AttributeError:\n#             raise AttributeError(\n#                 \"`curve` has no attribute `id`, likely it not a valid object, got: \"\n#                 f\"{curve}.\\nSince a solver is provided have you missed labelling the `curves` \"\n#                 f\"of the instrument or supplying `curves` directly?\",\n#             )\n#         except KeyError:\n#             if defaults.curve_not_in_solver == \"ignore\":\n#                 return curve\n#             elif defaults.curve_not_in_solver == \"warn\":\n#                 warnings.warn(\"`curve` not found in `solver`.\", UserWarning)\n#                 return curve\n#             else:\n#                 raise ValueError(\"`curve` must be in `solver`.\")\n"
  },
  {
    "path": "python/rateslib/instruments/sbs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FloatLeg\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        LegFixings,\n        RollDay,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass SBS(_BaseInstrument):\n    \"\"\"\n    A *single currency basis swap (SBS)* composing a :class:`~rateslib.legs.FloatLeg`\n    and a :class:`~rateslib.legs.FloatLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import SBS\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       sbs = SBS(\n           effective=dt(2000, 1, 1),\n           termination=\"1y\",\n           spec=\"eur_sbs36\",\n           float_spread=9.5,\n       )\n       sbs.cashflows()\n\n    .. rubric:: Pricing\n\n    An *SBS* requires a *disc curve* on both legs (which should be the same *Curve*) and a\n    *rate curve* and *leg2 rate curve* to forecast rates on each *FloatLeg*. The following input\n    formats are allowed:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve, leg2_rate_curve]              # three curves\n       curves = [rate_curve, disc_curve, leg2_rate_curve, disc_curve]  # four curves\n       curves = {  # dict form is explicit\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve,\n           \"leg2_rate_curve\": leg2_rate_curve,\n       }\n\n    The available pricing ``metric`` are in *{'leg1', 'leg2'}* which will return a *float spread*\n    on the specified leg. The default is to price the spread on *leg1*.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_convention: str, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n    leg2_amortization : float, Dual, Dual2, Variable, str, Amortization, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n    leg2_fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n    leg2_fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n    leg2_fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n    leg2_float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n    leg2_spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n    leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    metric : str, :green:`optional` (set by 'defaults')\n        The pricing metric returned by :meth:`~rateslib.instruments.SBS.rate`.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 100.0\n\n    @property\n    def float_spread(self) -> DualTypes_:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        return self.leg1.float_spread\n\n    @float_spread.setter\n    def float_spread(self, value: DualTypes) -> None:\n        self.kwargs.leg1[\"float_spread\"] = value\n        self.leg1.float_spread = value\n\n    @property\n    def leg2_float_spread(self) -> DualTypes_:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        return self.leg2.float_spread\n\n    @leg2_float_spread.setter\n    def leg2_float_spread(self, value: DualTypes) -> None:\n        self.kwargs.leg2[\"float_spread\"] = value\n        self.leg2.float_spread = value\n\n    @property\n    def leg1(self) -> FloatLeg:\n        \"\"\"The :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FloatLeg:\n        \"\"\"The second :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: int_ = NoInput(1),\n        leg2_payment_lag_exchange: int_ = NoInput(1),\n        leg2_ex_div: int_ = NoInput(1),\n        leg2_convention: str_ = NoInput(1),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        amortization: float_ = NoInput(0),\n        leg2_notional: float_ = NoInput(-1),\n        leg2_amortization: float_ = NoInput(-1),\n        # rate parameters\n        float_spread: DualTypes_ = NoInput(0),\n        spread_compound_method: str_ = NoInput(0),\n        rate_fixings: LegFixings = NoInput(0),\n        fixing_method: str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        leg2_float_spread: DualTypes_ = NoInput(0),\n        leg2_spread_compound_method: str_ = NoInput(0),\n        leg2_rate_fixings: LegFixings = NoInput(0),\n        leg2_fixing_method: str_ = NoInput(0),\n        leg2_fixing_frequency: Frequency | str_ = NoInput(0),\n        leg2_fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            float_spread=float_spread,\n            spread_compound_method=spread_compound_method,\n            rate_fixings=rate_fixings,\n            fixing_method=fixing_method,\n            stub=stub,\n            front_stub=front_stub,\n            back_stub=back_stub,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            ex_div=ex_div,\n            notional=notional,\n            currency=currency,\n            amortization=amortization,\n            convention=convention,\n            fixing_frequency=fixing_frequency,\n            fixing_series=fixing_series,\n            leg2_fixing_frequency=leg2_fixing_frequency,\n            leg2_fixing_series=leg2_fixing_series,\n            leg2_float_spread=leg2_float_spread,\n            leg2_spread_compound_method=leg2_spread_compound_method,\n            leg2_rate_fixings=leg2_rate_fixings,\n            leg2_fixing_method=leg2_fixing_method,\n            leg2_effective=leg2_effective,\n            leg2_termination=leg2_termination,\n            leg2_frequency=leg2_frequency,\n            leg2_stub=leg2_stub,\n            leg2_front_stub=leg2_front_stub,\n            leg2_back_stub=leg2_back_stub,\n            leg2_roll=leg2_roll,\n            leg2_eom=leg2_eom,\n            leg2_modifier=leg2_modifier,\n            leg2_calendar=leg2_calendar,\n            leg2_payment_lag=leg2_payment_lag,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            leg2_ex_div=leg2_ex_div,\n            leg2_notional=leg2_notional,\n            leg2_amortization=leg2_amortization,\n            leg2_convention=leg2_convention,\n            curves=self._parse_curves(curves),\n            metric=metric,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            leg2_currency=NoInput(1),\n            initial_exchange=False,\n            final_exchange=False,\n            leg2_initial_exchange=False,\n            leg2_final_exchange=False,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n            metric=defaults.metric[type(self).__name__],\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\", \"vol\"],\n        )\n\n        self._leg1 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self._leg1, self._leg2]\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        rate_curve = _get_curve(\"rate_curve\", True, True, *c)\n        leg2_rate_curve = _get_curve(\"leg2_rate_curve\", True, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, True, *c)\n\n        metric_: str = _drb(self.kwargs.meta[\"metric\"], metric)\n        if metric_.lower() == \"leg1\":\n            leg2_npv: DualTypes = self.leg2.local_npv(\n                rate_curve=leg2_rate_curve,\n                disc_curve=leg2_disc_curve,\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n            return self.leg1.spread(\n                target_npv=-leg2_npv,\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n        else:  # metric == \"leg2\"\n            leg1_npv: DualTypes = self.leg1.local_npv(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n            return self.leg2.spread(\n                target_npv=-leg1_npv,\n                rate_curve=leg2_rate_curve,\n                disc_curve=leg2_disc_curve,\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        return self.rate(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n            metric=metric,\n        )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IRS is that its fixed rate is not set.\n        if self.kwargs.meta[\"metric\"].lower() == \"leg1\":\n            if isinstance(self.kwargs.leg1[\"float_spread\"], NoInput):\n                # set a fixed rate for the purpose of generic methods NPV will be zero.\n                mid_market_rate = self.rate(\n                    curves=curves,\n                    solver=solver,\n                    settlement=settlement,\n                    forward=forward,\n                )\n                self.leg1.float_spread = _dual_float(mid_market_rate)\n        else:  # metric == \"leg2\"\n            if isinstance(self.kwargs.leg2[\"float_spread\"], NoInput):\n                # set a fixed rate for the purpose of generic methods NPV will be zero.\n                mid_market_rate = self.rate(\n                    curves=curves,\n                    solver=solver,\n                    settlement=settlement,\n                    forward=forward,\n                )\n                self.leg2.float_spread = _dual_float(mid_market_rate)\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An SBS has three curve requirements:\n\n        - a rate_curve\n        - a disc_curve\n        - a leg2_rate_curve\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements this will raise an Exception.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_rate_curve=_drb(\n                    curves.get(\"rate_curve\", NoInput(0)),\n                    curves.get(\"leg2_rate_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2 or len(curves) == 1 or len(curves) > 4:\n                raise TypeError(f\"Number of `curves` for an SBS must be 3 or 4. Got {len(curves)}.\")\n            elif len(curves) == 3:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_rate_curve=curves[2],\n                    leg2_disc_curve=curves[1],\n                )\n            else:  # == 4\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_rate_curve=curves[2],\n                    leg2_disc_curve=curves[3],\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input\n            raise TypeError(\"Number of `curves` for an SBS must be 3 or 4. Got 1.\")\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/spread.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\nfrom typing import TYPE_CHECKING, NoReturn\n\nfrom pandas import DataFrame\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.pricing import (\n    _get_fx_maybe_from_solver,\n)\nfrom rateslib.periods.utils import _maybe_fx_converted\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        datetime_,\n        str_,\n    )\n\n\nclass Spread(_BaseInstrument):\n    \"\"\"\n    A *Spread* of :class:`~rateslib.instruments.protocols._BaseInstrument`.\n\n    .. rubric:: Examples\n\n    The following initialises a purchased bond asset swap *Instrument* whose *rate* is\n    the difference between the *IRS* rate and the *fixed rate bond* YTM.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import Spread, IRS, FixedRateBond\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       irs = IRS(dt(2025, 12, 1), dt(2030, 12, 7), notional=1e6, spec=\"gbp_irs\", curves=[\"uk_sonia\"])\n       ukt = FixedRateBond(dt(2024, 12, 7), dt(2030, 12, 7), notional=-1e6, fixed_rate=4.75, spec=\"uk_gb\", metric=\"ytm\", curves=[\"uk_gb\"])\n       asw = Spread(ukt, irs)\n       asw.cashflows()\n\n    .. rubric:: Pricing\n\n    Each :class:`~rateslib.instruments.protocols._BaseInstrument` should have\n    its own ``curves`` and ``vol`` objects set at its initialisation, according to the\n    documentation for that *Instrument*. For the pricing methods ``curves`` and ``vol`` objects,\n    these can be universally passed to each *Instrument* but in many cases that would be\n    technically impossible since each *Instrument* might require difference pricing objects.\n    In the above example a bond *Curve* and a swap *Curve* are required separately. For a *Spread*\n    of two *IRS* in the same currency this would be possible, however.\n\n    Parameters\n    ----------\n    instrument1 : _BaseInstrument\n        The *Instrument* with the shortest maturity.\n    instrument2 : _BaseInstrument\n        The *Instrument* with the longest maturity.\n\n    Notes\n    -----\n    A *Spread* is just a container for two\n    :class:`~rateslib.instruments.protocols._BaseInstrument`, with an overload\n    for the :meth:`~rateslib.instruments.Spread.rate` method to calculate the\n    longer rate minus the shorter (whatever metric is in use for each *Instrument*), which allows\n    it to offer a lot of flexibility in *pseudo Instrument* creation.\n\n    \"\"\"  # noqa: E501\n\n    _instruments: Sequence[_BaseInstrument]\n    _rate_scalar = 100.0\n\n    @property\n    def instruments(self) -> Sequence[_BaseInstrument]:\n        \"\"\"The *Instruments* contained within the *Portfolio*.\"\"\"\n        return self._instruments\n\n    def __init__(\n        self,\n        instrument1: _BaseInstrument,\n        instrument2: _BaseInstrument,\n    ) -> None:\n        self._instruments = [instrument1, instrument2]\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Return the NPV of the *Portfolio* by summing individual *Instrument* NPVs.\n        \"\"\"\n        local_npv = self._npv_single_core(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n        )\n        if not local:\n            single_value: DualTypes = 0.0\n            for k, v in local_npv.items():\n                single_value += _maybe_fx_converted(\n                    value=v,\n                    currency=k,\n                    fx=_get_fx_maybe_from_solver(fx=fx, solver=solver),\n                    base=base,\n                    forward=forward,\n                )\n            return single_value\n        else:\n            return local_npv\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_instruments(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._cashflows_from_instruments(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n            base=base,\n        )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        rates: list[DualTypes] = []\n        for inst in self.instruments:\n            rates.append(\n                inst.rate(\n                    curves=curves,\n                    solver=solver,\n                    fx=fx,\n                    vol=vol,\n                    base=base,\n                    settlement=settlement,\n                    forward=forward,\n                    metric=metric,\n                )\n            )\n        return (rates[1] - rates[0]) * 100.0\n\n    def analytic_delta(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`analytic_delta` is not defined for Portfolio.\")\n"
  },
  {
    "path": "python/rateslib/instruments/stir_future.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _get_fx_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg, FloatLeg\nfrom rateslib.periods.utils import (\n    _maybe_fx_converted,\n    _maybe_local,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FixingsRates_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        RollDay,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass STIRFuture(_BaseInstrument):\n    \"\"\"\n    A *short term interest rate (STIR) future* compositing a\n    :class:`~rateslib.legs.FixedLeg` and :class:`~rateslib.legs.FloatLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import STIRFuture\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       stir = STIRFuture(\n           effective=dt(2022, 3, 16),\n           termination=dt(2022, 6, 15),\n           spec=\"usd_stir\",\n           price=99.50,\n           contracts=10,\n       )\n       stir.cashflows()\n\n    .. rubric:: Pricing\n\n    A *STIRFuture* requires a *disc curve* on both legs (which should be the same *Curve*) and a\n    *leg2 rate curve* to forecast rates on the *FloatLeg*. The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = [None, disc_curve, rate_curve, disc_curve]     # four curves applied to each leg\n       curves = {\"leg2_rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    The available pricing ``metric`` are in *{'rate', 'price'}* which will return the future's\n    market price in the respective terms.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    contracts : int\n        The number of traded contracts.\n    nominal : float\n        The nominal value of the contract. See **Notes**.\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    price : float\n        The traded price of the future. Defined as 100 minus the fixed rate.\n    leg2_fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    leg2_fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    leg2_fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    leg2_float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    leg2_spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    metric : str, :green:`optional` (set by 'defaults')\n        The pricing metric returned by :meth:`~rateslib.instruments.STIRFuture.rate`.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    Notes\n    -----\n    A *STIRFuture* is modelled as a single period *IRS* whose payment date is overloaded to always\n    result in immediate settlement, thus replicating the behaviour of traditional exchanges.\n    The immediate date is derived from the discount curve used during pricing.\n\n    The ``nominal`` for one contract should be set according to the ``convention`` so that the\n    correct amount of risk is allocated is to 1bp. For example, for a CME SOFR 3M future, setting\n    a convention of *ActActICMA* yields a DCF of 0.25 and therefore a ``nominal`` of 1mm USD\n    yields a 1bp sensitivity of 25 USD for any contract, as per the CME contract specification. The\n    ``leg2_fixing_series`` argument allows full specification of the floating rate index\n    conventions.\n\n    \"\"\"\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg1(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FloatLeg:\n        \"\"\"The :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An STIRFuture has two curve requirements: a leg2_rate_curve and a disc_curve used by\n        both legs.\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 1:\n                return _Curves(\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[0],\n                    leg2_disc_curve=curves[0],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_rate_curve=curves[2],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_rate_curve=_drb(\n                    curves.get(\"rate_curve\", NoInput(0)),\n                    curves.get(\"leg2_rate_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                leg2_rate_curve=curves,  # type: ignore[arg-type]\n                disc_curve=curves,  # type: ignore[arg-type]\n                leg2_disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        contracts: int = 1,\n        nominal: float | NoInput = NoInput(0),\n        # rate parameters\n        price: DualTypes_ = NoInput(0),\n        leg2_float_spread: DualTypes_ = NoInput(0),\n        leg2_spread_compound_method: str_ = NoInput(0),\n        leg2_rate_fixings: FixingsRates_ = NoInput(0),\n        leg2_fixing_method: str_ = NoInput(0),\n        leg2_fixing_frequency: Frequency | str_ = NoInput(0),\n        leg2_fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            ex_div=ex_div,\n            convention=convention,\n            # settlement\n            currency=currency,\n            nominal=nominal,\n            contracts=contracts,\n            # rate\n            price=price,\n            leg2_float_spread=leg2_float_spread,\n            leg2_spread_compound_method=leg2_spread_compound_method,\n            leg2_rate_fixings=leg2_rate_fixings,\n            leg2_fixing_method=leg2_fixing_method,\n            leg2_fixing_series=leg2_fixing_series,\n            leg2_fixing_frequency=leg2_fixing_frequency,\n            # meta\n            curves=self._parse_curves(curves),\n            metric=metric,\n        )\n        instrument_args = dict(\n            leg2_effective=NoInput.inherit,\n            leg2_termination=NoInput.inherit,\n            leg2_frequency=NoInput.inherit,\n            leg2_roll=NoInput.inherit,\n            leg2_eom=NoInput.inherit,\n            leg2_modifier=NoInput.inherit,\n            leg2_calendar=NoInput.inherit,\n            leg2_payment_lag=NoInput.inherit,\n            leg2_ex_div=NoInput.inherit,\n            leg2_convention=NoInput.inherit,\n            leg2_currency=NoInput.inherit,\n            fixed_rate=NoInput(0) if isinstance(price, NoInput) else 100 - price,\n            vol=_Vol(),\n        )\n        default_args = dict(\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            nominal=defaults.notional,\n            metric=\"rate\",\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"contracts\", \"nominal\", \"price\", \"metric\", \"vol\"],\n        )\n        self._kwargs.leg1[\"notional\"] = -self.kwargs.meta[\"nominal\"] * self.kwargs.meta[\"contracts\"]\n        self._kwargs.leg2[\"notional\"] = self.kwargs.meta[\"nominal\"] * self.kwargs.meta[\"contracts\"]\n\n        self._leg1 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self.leg1, self.leg2]\n\n        if self._leg1.schedule.n_periods != 1:\n            raise ValueError(\n                \"The scheduling parameters of the STIRFuture must define exactly \"\n                f\"one regular period. Got '{self.leg1.schedule.n_periods}'.\"\n            )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(curves=curves, solver=solver, settlement=settlement, forward=forward)\n        local_npv = super().npv(  # type: ignore[index]\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=True,\n            settlement=settlement,\n            forward=forward,\n        )[self.leg1.settlement_params.currency]\n\n        c = _parse_curves(self, curves, solver)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n\n        npv_immediate = local_npv / disc_curve[self.leg1.settlement_params.payment]\n\n        if not local:\n            return _maybe_fx_converted(\n                value=npv_immediate,\n                currency=self.leg1.settlement_params.currency,\n                fx=_get_fx_maybe_from_solver(solver=solver, fx=fx),\n                base=_drb(self.leg1.settlement_params.currency, base),\n                forward=forward,\n            )\n        else:\n            return {self.leg1.settlement_params.currency: npv_immediate}\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IRS is that its fixed rate is not set.\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            # set a fixed rate for the purpose of generic methods NPV will be zero.\n            mid_market_rate = self.rate(\n                curves=curves,\n                solver=solver,\n                settlement=settlement,\n                forward=forward,\n                metric=\"rate\",\n            )\n            self.leg1.fixed_rate = _dual_float(mid_market_rate)\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_rate_curve = _get_curve(\"leg2_rate_curve\", True, True, *c)\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n        rate = self.leg2._regular_periods[0].rate(rate_curve=leg2_rate_curve)\n        if metric_ == \"price\":\n            return 100 - rate\n        elif metric_ == \"rate\":\n            return rate\n        else:\n            raise ValueError(\"`metric` must be in {'rate', 'price'}.\")\n\n    def analytic_delta(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        leg: int = 1,\n    ) -> DualTypes | dict[str, DualTypes]:\n        unadjusted_local_analytic_delta = super().analytic_delta(  # type: ignore[index]\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=True,\n            settlement=settlement,\n            forward=forward,\n            leg=leg,\n        )[self.leg1.settlement_params.currency]\n\n        c = _parse_curves(self, curves, solver)\n\n        prefix = \"\" if leg == 1 else \"leg2_\"\n        disc_curve = _get_curve(f\"{prefix}disc_curve\", False, False, *c)\n\n        adjusted_local_analytic_delta = (\n            unadjusted_local_analytic_delta / disc_curve[self.leg1.settlement_params.payment]\n        )\n        return _maybe_local(\n            value=adjusted_local_analytic_delta,\n            local=local,\n            currency=self.leg1.settlement_params.currency,\n            fx=_get_fx_maybe_from_solver(solver=solver, fx=fx),\n            base=base,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        df = self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n        c = _parse_curves(self, curves, solver)\n        disc_curve = _get_curve(\"leg2_disc_curve\", False, False, *c)\n        return df / disc_curve[self.leg1.settlement_params.payment]  # type: ignore[operator]\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        df = super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n        df[defaults.headers[\"payment\"]] = None\n\n        c = _parse_curves(self, curves, solver)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        if isinstance(disc_curve, NoInput):\n            pass\n        else:\n            df[defaults.headers[\"payment\"]] = disc_curve.nodes.initial\n            df[defaults.headers[\"npv\"]] = df[defaults.headers[\"npv\"]] / df[defaults.headers[\"df\"]]\n            df[defaults.headers[\"npv_fx\"]] = (\n                df[defaults.headers[\"npv_fx\"]] / df[defaults.headers[\"df\"]]\n            )\n            df[defaults.headers[\"df\"]] = 1.0\n\n        return df\n"
  },
  {
    "path": "python/rateslib/instruments/value.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, NoReturn\n\nfrom rateslib.curves.utils import _CurveType\nfrom rateslib.dual.utils import dual_log\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import IndexMethod\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _KWArgs\nfrom rateslib.instruments.protocols.pricing import _Curves, _get_curve, _parse_curves\nfrom rateslib.scheduling import dcf\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CurvesT_,\n        DualTypes,\n        FXForwards_,\n        Solver_,\n        VolT_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass Value(_BaseInstrument):\n    \"\"\"\n    A pseudo *Instrument* used to calibrate a *Curve* within a :class:`~rateslib.solver.Solver`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import Value\n       from datetime import datetime as dt\n       from rateslib import Curve, Solver\n\n    The below :class:`~rateslib.curves.Curve` is solved directly\n    from a calibrating DF value on 1st Nov 2022.\n\n    .. ipython:: python\n\n       val = Value(dt(2022, 11, 1), curves=[\"v\"], metric=\"curve_value\")\n       curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"v\")\n       solver = Solver(curves=[curve], instruments=[val], s=[0.99])\n       curve[dt(2022, 11, 1)]\n\n    .. rubric:: Pricing\n\n    A *Value* requires, and will calibrate, just one *Curve*. This *Curve*, appropriating\n    a *rate curve* or an *index curve*, is dependent upon the ``metric``.\n    Allowable inputs are:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = {\"rate_curve\": rate_curve} | {\"index_curve\": index_curve}  # dict form is explicit\n\n    The various *rate* ``metric`` that can be calculated for a *Curve* are as follows;\n\n    - *'curve_value'*: returns the discount factor or a value from a DF-based or value-based\n      *rate curve*.\n    - *'index_value'*: returns a daily interpolated index value using an index lag derived from the\n      *index curve*.\n    - *'cc_zero_rate'*: returns a continuously compounded zero rate to the provided *effective*\n      date from a DF based *rate curve*.\n    - *'o/n_rate'*: returns a 1 calendar day rate starting on the effective date with the provided\n      *convention* from a *rate curve*.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    effective : datetime, :red:`required`\n        The datetime index for which the `rate`, which is just the curve value, is\n        returned.\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    metric : str, :green:`optional` (set as 'curve_value')\n        The pricing metric returned by :meth:`~rateslib.instruments.Value.rate`. See\n        **Pricing**.\n\n    \"\"\"\n\n    _rate_scalars = {\n        \"curve_value\": 100.0,\n        \"index_value\": 100.0,\n        \"cc_zero_rate\": 1.0,\n        \"o/n_rate\": 1.0,\n    }\n\n    def __init__(\n        self,\n        effective: datetime,\n        *,\n        metric: str_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            effective=effective,\n            curves=self._parse_curves(curves),\n            metric=metric,\n        )\n        default_args = dict(metric=\"curve_value\")\n        self._kwargs = _KWArgs(\n            spec=NoInput(0),\n            user_args=user_args,\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\"],\n        )\n\n        self._rate_scalar = self._rate_scalars.get(self.kwargs.meta[\"metric\"], 1.0)\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        A Value requires only one 1 curve, which is set as all element values\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                index_curve=curves.get(\"index_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) != 1:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 1 curve types. Got {len(curves)}.\"\n                )\n            else:\n                return _Curves(\n                    rate_curve=curves[0],\n                    disc_curve=curves[0],\n                    index_curve=curves[0],\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input\n            return _Curves(\n                rate_curve=curves,  # type: ignore[arg-type]\n                disc_curve=curves,  # type: ignore[arg-type]\n                index_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric).lower()\n\n        effective: datetime = self.kwargs.leg1[\"effective\"]\n        if metric_ == \"curve_value\":\n            curve = _get_curve(\"rate_curve\", False, False, *c)\n            ret: DualTypes = curve[effective]\n\n        elif metric_ == \"cc_zero_rate\":\n            curve = _get_curve(\"rate_curve\", False, False, *c)\n            if curve._base_type != _CurveType.dfs:\n                raise TypeError(\n                    \"`curve` used with `metric`='cc_zero_rate' must be discount factor based.\",\n                )\n            dcf_ = dcf(start=curve.nodes.initial, end=effective, convention=curve.meta.convention)\n            ret = (dual_log(curve[effective]) / -dcf_) * 100\n\n        elif metric_ == \"index_value\":\n            curve = _get_curve(\"index_curve\", False, False, *c)\n            ret = curve.index_value(\n                index_date=effective,\n                index_lag=curve.meta.index_lag,\n                index_method=IndexMethod.Daily,\n            )\n\n        elif metric_ == \"o/n_rate\":\n            curve = _get_curve(\"rate_curve\", False, False, *c)\n            ret = curve.rate(effective, \"1D\")  # type: ignore[assignment]\n\n        else:\n            raise ValueError(\n                \"`metric`must be in {'curve_value', 'cc_zero_rate', 'index_value', 'o/n_rate'}.\"\n            )\n        return ret\n\n    def npv(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`Value` instrument has no concept of NPV.\")\n\n    def cashflows(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`Value` instrument has no concept of cashflows.\")\n\n    def analytic_delta(self, *args: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"`Value` instrument has no concept of analytic delta.\")\n"
  },
  {
    "path": "python/rateslib/instruments/xcs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.data.fixings import _get_fx_index\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import LegMtm\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _get_fx_forwards_maybe_from_solver,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg, FloatLeg\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FixingsRates_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        FXIndex,\n        LegFixings,\n        RollDay,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass XCS(_BaseInstrument):\n    \"\"\"\n    A *cross-currency swap (XCS)* composing either\n    :class:`~rateslib.legs.FixedLeg`\n    and/or :class:`~rateslib.legs.FloatLeg` in different currencies.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import XCS\n       from datetime import datetime as dt\n       from rateslib import fixings\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"WMR_LDN11AM_EURUSD\", Series(index=[dt(2025, 4, 4)], data=[1.175]))\n       xcs = XCS(\n           effective=dt(2025, 1, 8),\n           termination=\"6m\",\n           spec=\"eurusd_xcs\",\n           notional=5e6,\n           leg2_fx_fixings=(1.15, \"WMR_LDN11AM\"),\n           leg2_mtm=True,\n       )\n       xcs.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"WMR_LDN11AM_EURUSD\")\n\n    .. rubric:: Pricing\n\n    The methods of a *XCS* require an :class:`~rateslib.fx.FXForwards` object for ``fx`` .\n\n    They also require a *disc curve* and a *leg2 disc curve* which are appropriate curves for the\n    relevant currency, typically under the same collateral. For *FloatLegs*, an additional\n    *rate curve* and *leg2 rate curve* are required. The following input\n    formats are allowed:\n\n    .. code-block:: python\n\n       curves = [rate_curve, disc_curve, leg2_rate_curve, leg2_disc_curve]  # four curves\n       curves = {  # dict form is explicit\n           \"rate_curve\": rate_curve,\n           \"disc_curve\": disc_curve,\n           \"leg2_rate_curve\": leg2_rate_curve,\n           \"leg2_disc_curve\": leg2_disc_curve,\n       }\n\n    The available pricing ``metric`` are in *{'leg1', 'leg2'}* which will return a *float spread*\n    or a *fixed rate* on the specified leg, for the appropriate *Leg* type.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_convention: str, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of leg1 (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set from 'leg2_notional' or 'defaults' )`\n        The initial leg1 notional, defined in units of the currency of the leg. Only one\n        of ``notional`` and ``leg2_notional`` can be given. The alternate leg notional is derived\n        via non-deliverability :class:`~rateslib.data.fixings.FXFixing`.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    pair: FXIndex, str, :red:`required`\n        The :class:`~rateslib.data.fixings.FXIndex` implying the *leg2 currency*.\n        Must include ``currency`` as either LHS or RHS.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n    leg2_amortization : float, Dual, Dual2, Variable, str, Amortization, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed : bool, :green:`optional (set as False)`\n        Whether leg1 is a :class:`~rateslib.legs.FixedLeg` or a :class:`~rateslib.legs.FloatLeg`.\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n    leg2_fixed : bool, :green:`optional (set as False)`\n    leg2_fixed_rate : float or None\n    leg2_fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n    leg2_fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n    leg2_fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n    leg2_float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n    leg2_spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n    leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n\n       .. note::\n\n          The following are the cross-currency **non-deliverable** parameters.\n\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according\n        to non-deliverability. This can only be provided if ``leg2_notional`` is given. The\n        currency pair is expressed in direction 'currency:leg2_currency'.\n    mtm: bool, :green:`optional (set to False)`\n        Define the *XCS* is mark-to-market on leg1. Only one leg can be mark-to-market.\n    leg2_fx_fixings:\n        This can only be provided if ``notional`` is given. The\n        currency pair is expressed in direction 'currency:leg2_currency'.\n    leg2_mtm: bool, :green:`optional (set to False)`\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n    metric: str, :green:`optional (set as 'leg1')`\n        Determines which calculation metric to return by default when using the\n        :meth:`~rateslib.instruments.XCS.rate` method.\n\n    Notes\n    -----\n    A *XCS* is a flexible instrument.\n\n    - Each *Leg* can either be ``fixed`` or, rather, floating.\n    - One *Leg* can be ``mtm`` or both legs can be non-mtm.\n\n      *Legs* are handled by using the mechanics of non-deliverability. If a *Leg* is set to be\n      *mtm* then the ``notional`` on the opposing *Leg* **must** be specified: this is because\n      a *mtm-Leg* has a varying notional which must be derived from some fixed reference notional.\n      Values should always be expressed in currency units of that *Leg* itself.\n\n    - ``fx_fixings`` are required on the *Leg* which does not specify a *notional*. This is\n      true either for a *mtm* or *non-mtm Leg*. It is common for the initial rate of exchange\n      to be agreed at execution time, meaning the most common form of entry for ``fx_fixings`` is\n      as a tuple: an arbitrary execution rate and the fixing series, e.g. *(1.224, \"WMR_LDN11AM\")*.\n      Fixings should always be expressed according to the direction in ``pair``.\n\n    - ``amortization`` can be added in the normal way on the same *Leg* as a *notional* is\n      specified.\n    - The pricing ``metric`` can specify which *Leg* a mid-market price is returned by the\n      :meth:`~rateslib.instruments.XCS.rate` method.\n\n    **Is it USD/CAD or CAD/USD or EUR/USD or USD/EUR?**\n\n    Actually any *XCS* can be constructed systematically:\n\n    i. Set the FX ``pair`` that is standard for the fixings, e.g. *'USDCAD'*.\n    ii. Set the ``currency`` required on *Leg1* and set the ``mtm`` or ``leg2_mtm``\n        flag respectively if required.\n    iii. Set the ``notional`` or ``leg2_notional`` as necessary or chosen.\n    iv. Set the ``fx_fixings`` or ``leg2_fx_fixings`` as necessary.\n    v. Set the ``metric``.\n\n    **For example**, we initialize a MTM GBP/USD XCS in £100m. The MTM leg is USD so the notional\n    must be expressed on the GBP leg. The pricing spread is applied to the GBP leg.\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.add(\"WMR_LDN11AM_GBPUSD\", Series(index=[dt(1999, 1, 1)], data=[100.0]))\n       fixings.add(\"WMR_LDN11AM_USDJPY\", Series(index=[dt(1999, 1, 1)], data=[100.0]))\n\n    .. ipython:: python\n\n       xcs = XCS(\n           effective=dt(2025, 1, 8),\n           termination=\"6m\",\n           frequency=\"Q\",\n           currency=\"gbp\",\n           notional=100e6,\n           leg2_mtm=True,\n           pair=\"gbpusd\",\n           leg2_fx_fixings=(1.35, \"WMR_LDN11AM\"),\n           metric=\"leg1\",\n       )\n\n    Or, we initialise a MTM USD/JPY XCS in ¥1bn with ¥100m amortization. The MTM leg is USD so the\n    notional must be expressed on the JPY leg. The pricing spread is applied to the JPY leg.\n\n    .. ipython:: python\n\n       xcs = XCS(\n           effective=dt(2025, 1, 8),\n           termination=\"6m\",\n           frequency=\"Q\",\n           currency=\"usd\",\n           mtm=True,\n           fx_fixings=(155.0, \"WMR_LDN11AM\"),\n           pair=\"usdjpy\",\n           leg2_notional=1e9,\n           leg2_amortization=100e6,\n           metric=\"leg2\",\n       )\n       xcs.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"WMR_LDN11AM_GBPUSD\")\n       fixings.pop(\"WMR_LDN11AM_USDJPY\")\n\n    \"\"\"  # noqa: E501\n\n    def _rate_scalar_calc(self) -> float:\n        if self.kwargs.meta[\"metric\"] == \"leg1\":\n            return 1.0 if isinstance(self.leg1, FixedLeg) else 100.0\n        else:\n            return 1.0 if isinstance(self.leg2, FixedLeg) else 100.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        if isinstance(self.leg1, FixedLeg):\n            return self.leg1.fixed_rate\n        else:\n            raise AttributeError(f\"Leg1 is of type: {type(self.leg1).__name__}\")\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        if isinstance(self.leg1, FixedLeg):\n            self.kwargs.leg1[\"fixed_rate\"] = value\n            self.leg1.fixed_rate = value\n        else:\n            raise AttributeError(f\"Leg1 is of type: {type(self.leg1).__name__}\")\n\n    @property\n    def float_spread(self) -> DualTypes:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        if isinstance(self.leg1, FloatLeg):\n            return self.leg1.float_spread\n        else:\n            raise AttributeError(f\"Leg1 is of type: {type(self.leg1).__name__}\")\n\n    @float_spread.setter\n    def float_spread(self, value: DualTypes) -> None:\n        if isinstance(self.leg1, FloatLeg):\n            self.kwargs.leg1[\"float_spread\"] = value\n            self.leg1.float_spread = value\n        else:\n            raise AttributeError(f\"Leg1 is of type: {type(self.leg1).__name__}\")\n\n    @property\n    def leg2_fixed_rate(self) -> DualTypes_:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        if isinstance(self.leg2, FixedLeg):\n            return self.leg2.fixed_rate\n        else:\n            raise AttributeError(f\"Leg2 is of type: {type(self.leg2).__name__}\")\n\n    @leg2_fixed_rate.setter\n    def leg2_fixed_rate(self, value: DualTypes_) -> None:\n        if isinstance(self.leg2, FixedLeg):\n            self.kwargs.leg2[\"fixed_rate\"] = value\n            self.leg2.fixed_rate = value\n        else:\n            raise AttributeError(f\"Leg2 is of type: {type(self.leg2).__name__}\")\n\n    @property\n    def leg2_float_spread(self) -> DualTypes_:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        if isinstance(self.leg2, FloatLeg):\n            return self.leg2.float_spread\n        else:\n            raise AttributeError(f\"Leg2 is of type: {type(self.leg2).__name__}\")\n\n    @leg2_float_spread.setter\n    def leg2_float_spread(self, value: DualTypes) -> None:\n        if isinstance(self.leg2, FloatLeg):\n            self.kwargs.leg2[\"float_spread\"] = value\n            self.leg2.float_spread = value\n        else:\n            raise AttributeError(f\"Leg2 is of type: {type(self.leg2).__name__}\")\n\n    @property\n    def leg1(self) -> FixedLeg | FloatLeg:\n        \"\"\"The first :class:`~rateslib.legs.FixedLeg` or\n        :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FixedLeg | FloatLeg:\n        \"\"\"The second :class:`~rateslib.legs.FixedLeg` or\n        :class:`~rateslib.legs.FloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: int_ = NoInput(1),\n        leg2_payment_lag_exchange: int_ = NoInput(1),\n        leg2_ex_div: int_ = NoInput(1),\n        leg2_convention: str_ = NoInput(1),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        amortization: float_ = NoInput(0),\n        pair: FXIndex | str_ = NoInput(0),\n        leg2_notional: DualTypes_ = NoInput(0),\n        leg2_amortization: float_ = NoInput(0),\n        # rate parameters\n        fixed: bool_ = NoInput(0),\n        mtm: bool_ = NoInput(0),\n        fixed_rate: DualTypes_ = NoInput(0),\n        float_spread: DualTypes_ = NoInput(0),\n        spread_compound_method: str_ = NoInput(0),\n        rate_fixings: FixingsRates_ = NoInput(0),\n        fixing_method: str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        fx_fixings: LegFixings = NoInput(0),\n        leg2_fixed: bool_ = NoInput(0),\n        leg2_mtm: bool_ = NoInput(0),\n        leg2_fixed_rate: DualTypes_ = NoInput(0),\n        leg2_float_spread: DualTypes_ = NoInput(0),\n        leg2_spread_compound_method: str_ = NoInput(0),\n        leg2_rate_fixings: LegFixings = NoInput(0),\n        leg2_fixing_method: str_ = NoInput(0),\n        leg2_fixing_frequency: Frequency | str_ = NoInput(0),\n        leg2_fixing_series: FloatRateSeries | str_ = NoInput(0),\n        leg2_fx_fixings: LegFixings = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> None:\n        currency_, leg2_currency_, pair_, mtm_, leg2_mtm_, notional_, leg2_notional_ = (\n            _validated_xcs_input_combinations(\n                currency=currency,\n                pair=pair,\n                mtm=mtm,\n                leg2_mtm=leg2_mtm,\n                notional=notional,\n                leg2_notional=leg2_notional,\n                fx_fixings=fx_fixings,\n                leg2_fx_fixings=leg2_fx_fixings,\n                spec=spec,\n            )\n        )\n        del mtm\n        del leg2_mtm\n        del pair\n        del currency\n        del notional\n        del leg2_notional\n\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            leg2_effective=leg2_effective,\n            termination=termination,\n            leg2_termination=leg2_termination,\n            frequency=frequency,\n            leg2_frequency=leg2_frequency,\n            stub=stub,\n            leg2_stub=leg2_stub,\n            front_stub=front_stub,\n            leg2_front_stub=leg2_front_stub,\n            back_stub=back_stub,\n            leg2_back_stub=leg2_back_stub,\n            roll=roll,\n            leg2_roll=leg2_roll,\n            eom=eom,\n            leg2_eom=leg2_eom,\n            modifier=modifier,\n            leg2_modifier=leg2_modifier,\n            calendar=calendar,\n            leg2_calendar=leg2_calendar,\n            payment_lag=payment_lag,\n            leg2_payment_lag=leg2_payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            ex_div=ex_div,\n            leg2_ex_div=leg2_ex_div,\n            convention=convention,\n            leg2_convention=leg2_convention,\n            # settlement\n            currency=currency_,\n            leg2_currency=leg2_currency_,\n            notional=notional_,\n            leg2_notional=leg2_notional_,\n            amortization=amortization,\n            leg2_amortization=leg2_amortization,\n            # non-deliverability\n            fx_fixings=fx_fixings,\n            leg2_fx_fixings=leg2_fx_fixings,\n            mtm=mtm_,\n            leg2_mtm=leg2_mtm_,\n            # rate\n            fixed_rate=fixed_rate,\n            float_spread=float_spread,\n            spread_compound_method=spread_compound_method,\n            rate_fixings=rate_fixings,\n            fixing_method=fixing_method,\n            fixing_frequency=fixing_frequency,\n            fixing_series=fixing_series,\n            leg2_fixed_rate=leg2_fixed_rate,\n            leg2_float_spread=leg2_float_spread,\n            leg2_spread_compound_method=leg2_spread_compound_method,\n            leg2_rate_fixings=leg2_rate_fixings,\n            leg2_fixing_method=leg2_fixing_method,\n            leg2_fixing_frequency=leg2_fixing_frequency,\n            leg2_fixing_series=leg2_fixing_series,\n            # meta\n            pair=pair_,\n            fixed=fixed,\n            leg2_fixed=leg2_fixed,\n            curves=self._parse_curves(curves),\n            metric=metric,\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            initial_exchange=True,\n            final_exchange=True,\n            leg2_initial_exchange=True,\n            leg2_final_exchange=True,\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            currency=defaults.base_currency,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n            mtm=False,\n            leg2_mtm=False,\n            fixed=False,\n            leg2_fixed=False,\n            metric=\"leg1\",\n        )\n\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"metric\", \"fixed\", \"leg2_fixed\", \"vol\", \"pair\"],\n        )\n\n        # narrowing of fixed or floating\n        float_attrs = [\n            \"float_spread\",\n            \"spread_compound_method\",\n            \"rate_fixings\",\n            \"fixing_method\",\n            \"fixing_frequency\",\n            \"fixing_series\",\n        ]\n        if self.kwargs.meta[\"fixed\"]:\n            for item in float_attrs:\n                self.kwargs.leg1.pop(item)\n        else:\n            self.kwargs.leg1.pop(\"fixed_rate\")\n        if self.kwargs.meta[\"leg2_fixed\"]:\n            for item in float_attrs:\n                self.kwargs.leg2.pop(item)\n        else:\n            self.kwargs.leg2.pop(\"fixed_rate\")\n\n        # populate non-deliverable leg, based on which leg notional is given\n        if isinstance(self.kwargs.leg1[\"notional\"], NoInput):\n            self._kwargs.leg1[\"notional\"] = -1.0 * self._kwargs.leg2[\"notional\"]\n            self._kwargs.leg1[\"amortization\"] = (\n                NoInput(0)\n                if isinstance(self._kwargs.leg2[\"amortization\"], NoInput)\n                else -1.0 * self._kwargs.leg2[\"amortization\"]\n            )\n            self._kwargs.leg1[\"pair\"] = self.kwargs.meta[\"pair\"]\n        if isinstance(self.kwargs.leg2[\"notional\"], NoInput):\n            self._kwargs.leg2[\"notional\"] = -1.0 * self._kwargs.leg1[\"notional\"]\n            self._kwargs.leg2[\"amortization\"] = (\n                NoInput(0)\n                if isinstance(self._kwargs.leg1[\"amortization\"], NoInput)\n                else -1.0 * self._kwargs.leg1[\"amortization\"]\n            )\n            self._kwargs.leg2[\"pair\"] = self.kwargs.meta[\"pair\"]\n\n        if self.kwargs.meta[\"fixed\"]:\n            self._leg1: FixedLeg | FloatLeg = FixedLeg(\n                **_convert_to_schedule_kwargs(self.kwargs.leg1, 1)\n            )\n        else:\n            self._leg1 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        if self.kwargs.meta[\"leg2_fixed\"]:\n            self._leg2: FixedLeg | FloatLeg = FixedLeg(\n                **_convert_to_schedule_kwargs(self.kwargs.leg2, 1)\n            )\n        else:\n            self._leg2 = FloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self.leg1, self.leg2]\n        self._rate_scalar = self._rate_scalar_calc()\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n\n        leg2_rate_curve = _get_curve(\"leg2_rate_curve\", True, True, *c)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, True, *c)\n        rate_curve = _get_curve(\"rate_curve\", True, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        metric_ = _drb(self.kwargs.meta[\"metric\"], metric)\n        fx_ = _get_fx_forwards_maybe_from_solver(fx=fx, solver=solver)\n\n        if metric_ == \"leg1\":\n            leg2_npv: DualTypes = self.leg2.npv(  # type: ignore[assignment]\n                rate_curve=leg2_rate_curve,\n                disc_curve=leg2_disc_curve,\n                base=self.leg1.settlement_params.currency,\n                fx=fx_,\n                settlement=settlement,\n                forward=forward,\n            )\n            spread = self.leg1.spread(\n                target_npv=-leg2_npv,\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                settlement=settlement,\n                fx=fx_,\n                forward=forward,\n            )\n            if self.kwargs.meta[\"fixed\"]:\n                return spread / 100.0\n            else:\n                return spread\n        elif metric_ == \"leg2\":\n            leg1_npv: DualTypes = self.leg1.npv(  # type: ignore[assignment]\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                base=self.leg2.settlement_params.currency,\n                fx=fx_,\n                settlement=settlement,\n                forward=forward,\n            )\n            spread = self.leg2.spread(\n                target_npv=-leg1_npv,\n                rate_curve=leg2_rate_curve,\n                disc_curve=leg2_disc_curve,\n                settlement=settlement,\n                forward=forward,\n                fx=fx_,\n            )\n            if self.kwargs.meta[\"leg2_fixed\"]:\n                return spread / 100.0\n            else:\n                return spread\n        else:\n            raise ValueError(\"`metric` must be in {'leg1', 'leg2'}\")\n\n    # def _set_rate(self, value: DualTypes, leg: int) -> DualTypes:\n    #     if leg == 1:\n    #         if self.kwargs.meta[\"fixed\"]:\n    #             ret = self.leg1.fixed_rate\n    #             self.leg1.fixed_rate = value\n    #         else:\n    #             ret = self.leg1.float_spread\n    #             self.leg1.float_spread = value\n    #     else:  # leg 2\n    #         if self.kwargs.meta[\"leg2_fixed\"]:\n    #             ret = self.leg2.fixed_rate\n    #             self.leg2.fixed_rate = value\n    #         else:\n    #             ret = self.leg2.float_spread\n    #             self.leg2.float_spread = value\n    #     return ret\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        return self.rate(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n            metric=metric,\n        )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n            fx=fx,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # all float_spread are assumed to be equal to zero if not given.\n        # missing fixed rates will be priced and set if possible.\n\n        if isinstance(self.leg1, FixedLeg) and isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            if isinstance(self.leg2, FixedLeg) and isinstance(\n                self.kwargs.leg2[\"fixed_rate\"], NoInput\n            ):\n                raise ValueError(\"At least one leg must have a defined `fixed_rate`.\")\n\n            mid_price = self.rate(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                settlement=settlement,\n                forward=forward,\n                metric=\"leg1\",\n            )\n            self.leg1.fixed_rate = _dual_float(mid_price)\n\n        elif isinstance(self.leg2, FixedLeg) and isinstance(\n            self.kwargs.leg2[\"fixed_rate\"], NoInput\n        ):\n            # leg1 cannot be fixed with NoInput - this branch is covered above\n            mid_price = self.rate(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                settlement=settlement,\n                forward=forward,\n                metric=\"leg2\",\n            )\n            self.leg2.fixed_rate = _dual_float(mid_price)\n\n        elif (\n            isinstance(self.leg1, FloatLeg)\n            and isinstance(self.kwargs.leg1[\"float_spread\"], NoInput)\n            and isinstance(self.leg2, FloatLeg)\n            and isinstance(self.kwargs.leg2[\"float_spread\"], NoInput)\n        ):\n            # then no FloatLeg pricing parameters are provided\n            mid_price = self.rate(\n                curves=curves,\n                solver=solver,\n                fx=fx,\n                settlement=settlement,\n                forward=forward,\n            )\n            if self.kwargs.meta[\"metric\"].lower() == \"leg1\":\n                self.leg1.float_spread = _dual_float(mid_price)\n            else:\n                self.leg2.float_spread = _dual_float(mid_price)\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        A XCS requires 4 curves (mostly if float-float, otherwise it needs 2)\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_rate_curve=curves.get(\"leg2_rate_curve\", NoInput(0)),\n                leg2_disc_curve=curves.get(\"leg2_disc_curve\", NoInput(0)),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 4:\n                return _Curves(\n                    rate_curve=NoInput(0) if curves[0] is None else curves[0],\n                    disc_curve=curves[1],\n                    leg2_rate_curve=NoInput(0) if curves[2] is None else curves[2],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires 4 curve type input. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:\n            raise ValueError(f\"{type(self).__name__} requires 4 curve type input. Got 1.\")\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n\n\ndef _validated_xcs_input_combinations(\n    currency: str_,\n    pair: FXIndex | str_,\n    mtm: bool_,\n    leg2_mtm: bool_,\n    notional: DualTypes_,\n    leg2_notional: DualTypes_,\n    fx_fixings: LegFixings,\n    leg2_fx_fixings: LegFixings,\n    spec: str_,\n) -> tuple[str, str, FXIndex, LegMtm, LegMtm, DualTypes_, DualTypes_]:\n    kw = _KWArgs(\n        user_args=dict(\n            currency=currency,\n            pair=pair,\n            mtm=mtm,\n            leg2_mtm=leg2_mtm,\n            notional=notional,\n            leg2_notional=leg2_notional,\n            fx_fixings=fx_fixings,\n            leg2_fx_fixings=leg2_fx_fixings,\n        ),\n        default_args=dict(\n            mtm=False,\n            leg2_mtm=False,\n        ),\n        spec=spec,\n        meta_args=[\"pair\"],\n    )\n\n    if kw.leg1[\"mtm\"] and kw.leg2[\"mtm\"]:\n        raise ValueError(\"`mtm` and `leg2_mtm` must define at most one MTM leg.\")\n    mtm_obj: LegMtm = LegMtm.XCS if kw.leg1[\"mtm\"] else LegMtm.Initial\n    leg2_mtm_obj: LegMtm = LegMtm.XCS if kw.leg2[\"mtm\"] else LegMtm.Initial\n\n    # set a default `notional` if no notional on any leg is given\n    if isinstance(kw.leg1[\"notional\"], NoInput) and isinstance(kw.leg2[\"notional\"], NoInput):\n        notional_: DualTypes_ = defaults.notional\n        leg2_notional_: DualTypes_ = leg2_notional\n    elif not isinstance(kw.leg1[\"notional\"], NoInput) and not isinstance(\n        kw.leg2[\"notional\"], NoInput\n    ):\n        raise ValueError(\n            \"The `notional` can only be provided on one leg, expressed in its `currency`.\\n\"\n            \"For a XCS, the other leg's cashflows are derived via `fx_fixings` and \"\n            \"non-deliverability.\"\n        )\n    else:\n        notional_ = notional\n        leg2_notional_ = leg2_notional\n\n    if not isinstance(notional_, NoInput) and not isinstance(kw.leg1[\"fx_fixings\"], NoInput):\n        raise ValueError(\n            \"When `notional` is given, that leg is assumed to be deliverable and `fx_fixings` \"\n            \"should not be given.\\nOnly `leg2_fx_fixings` are required to derive \"\n            \"cashflows on leg2 via non-deliverability from leg1's `notional`.\"\n        )\n    if not isinstance(leg2_notional_, NoInput) and not isinstance(kw.leg2[\"fx_fixings\"], NoInput):\n        raise ValueError(\n            \"When `leg2_notional` is given, that leg is assumed to be deliverable and \"\n            \"`leg2_fx_fixings` should not be given.\\nOnly `fx_fixings` are required to derive \"\n            \"cashflows on leg1 via non-deliverability from leg2's `notional`.\"\n        )\n\n    if isinstance(kw.meta[\"pair\"], NoInput):\n        raise ValueError(\n            \"A `pair` must be supplied to a XCS along with the leg1 `currency` to imply the \"\n            \"second currency.\"\n        )\n    fx_index_ = _get_fx_index(kw.meta[\"pair\"])\n    currency_ = _drb(defaults.base_currency, kw.leg1[\"currency\"]).lower()\n    if currency_ not in fx_index_.pair:\n        raise ValueError(\n            \"For a XCS, the `currency` must be one of the currencies in the FX index `pair`.\\n\"\n            f\"Got '{currency_}' and '{fx_index_.pair}'.\"\n        )\n    leg2_currency_ = fx_index_.pair[:3] if currency_ == fx_index_.pair[3:] else fx_index_.pair[3:]\n\n    return (\n        currency_,\n        leg2_currency_,\n        fx_index_,\n        mtm_obj,\n        leg2_mtm_obj,\n        notional_,\n        leg2_notional_,\n    )\n"
  },
  {
    "path": "python/rateslib/instruments/yoyis.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import LegIndexBase\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        Frequency,\n        FXForwards_,\n        IndexMethod,\n        LegFixings,\n        RollDay,\n        Sequence,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass YoYIS(_BaseInstrument):\n    r\"\"\"\n    A *year-on-year indexed swap (YoYIS)* composing two :class:`~rateslib.legs.FixedLeg` with the\n    second having ``index_params``.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import YoYIS\n       from rateslib import fixings\n       from datetime import datetime as dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"CPI_UK\", Series(index=[dt(1999, 10, 1), dt(2000, 10, 1), dt(2001, 10, 1), dt(2002, 10, 1)], data=[110.0, 120.0, 125.0, 127.0]))\n       yoyis = YoYIS(\n           effective=dt(2000, 1, 1),\n           termination=\"3y\",\n           frequency=\"A\",\n           fixed_rate=2.0,\n           convention=\"One\",\n           leg2_index_fixings=\"CPI_UK\",\n           leg2_index_lag=3,\n           leg2_index_method=\"monthly\",\n       )\n       yoyis.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"CPI_UK\")\n\n    .. rubric:: Pricing\n\n    An *YoYIS* requires a *disc curve* on both legs (which should be the same *Curve*), and a\n    *leg2 index curve* for index forecasting on the second *FixedLeg*.\n    The following input formats are allowed:\n\n    .. code-block:: python\n\n       curves = [index_curve, disc_curve]  #  two curves are applied in order\n       curves = {  # dict form is explicit\n           \"disc_curve\": disc_curve,\n           \"leg2_index_curve\": index_curve,\n       }\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_convention: str, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n    leg2_amortization : float, Dual, Dual2, Variable, str, Amortization, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n\n        .. note::\n\n           The following parameters define **indexation**.\n\n    leg2_index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    leg2_index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    leg2_index_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The index value for the reference date.\n        Best practice is to supply this value as string identifier relating to the global\n        ``fixings`` object.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    Notes\n    -------\n\n    A *YoYIS* has a nominal :class:`~rateslib.legs.FixedLeg` with a specific ``fixed_rate``, and\n    a second *Leg*  whose cash flows are defined by some index values. *Rateslib* constructs this\n    object as a second :class:`~rateslib.legs.FixedLeg` which inherits specific properties,\n    namely:\n\n    - The ``leg2_index_base_type`` is *LegIndexBase.PeriodOnPeriod*, to ensure that indexing is not\n      calculated from one single ``leg2_index_base`` value, but by consecutive dates.\n    - The ``leg2_fixed_rate`` is 100% to provide a coupon amount that matches the notional.\n    - The ``leg2_index_only`` parameter is *True* to ensure that the cashflow paid only accounts for\n      indexation and does not pay that 100% of notional.\n\n    Under this definition the unindexed reference cashflow of each period of *Leg2* is the notional\n    adjusted by the DCF:\n\n    .. math::\n\n       \\mathbb{E^Q} [\\bar{C}_t] = -N_i d_i\n\n    and the indexed reference cashflow, accounting for indexation only, is:\n\n    .. math::\n\n       -N_i d_i ( \\frac{I_v(m_i)}{I_v(m_{i-1})} - 1 )\n\n    which matches the definition of the indexed *Leg* of a *YoYIS*.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate of *Leg1*.\"\"\"\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg1(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.FixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FixedLeg:\n        \"\"\"The second :class:`~rateslib.legs.FixedLeg` of the *Instrument* with indexation.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> Sequence[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: int_ = NoInput(1),\n        leg2_payment_lag_exchange: int_ = NoInput(1),\n        leg2_convention: str_ = NoInput(1),\n        leg2_ex_div: int_ = NoInput(1),\n        # settlement params\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        amortization: float_ = NoInput(0),\n        leg2_notional: float_ = NoInput(-1),\n        leg2_amortization: float_ = NoInput(-1),\n        # index params\n        leg2_index_lag: int_ = NoInput(0),\n        leg2_index_method: IndexMethod | str_ = NoInput(0),\n        leg2_index_fixings: LegFixings = NoInput(0),\n        # rate params\n        fixed_rate: DualTypes_ = NoInput(0),\n        # meta params\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            effective=effective,\n            termination=termination,\n            frequency=frequency,\n            fixed_rate=fixed_rate,\n            leg2_index_lag=leg2_index_lag,\n            leg2_index_method=leg2_index_method,\n            leg2_index_fixings=leg2_index_fixings,\n            stub=stub,\n            front_stub=front_stub,\n            back_stub=back_stub,\n            roll=roll,\n            eom=eom,\n            modifier=modifier,\n            calendar=calendar,\n            payment_lag=payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            ex_div=ex_div,\n            notional=notional,\n            currency=currency,\n            amortization=amortization,\n            convention=convention,\n            leg2_effective=leg2_effective,\n            leg2_termination=leg2_termination,\n            leg2_frequency=leg2_frequency,\n            leg2_stub=leg2_stub,\n            leg2_front_stub=leg2_front_stub,\n            leg2_back_stub=leg2_back_stub,\n            leg2_roll=leg2_roll,\n            leg2_eom=leg2_eom,\n            leg2_modifier=leg2_modifier,\n            leg2_calendar=leg2_calendar,\n            leg2_payment_lag=leg2_payment_lag,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            leg2_ex_div=leg2_ex_div,\n            leg2_notional=leg2_notional,\n            leg2_amortization=leg2_amortization,\n            leg2_convention=leg2_convention,\n            curves=self._parse_curves(curves),\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            leg2_currency=NoInput(1),\n            initial_exchange=False,\n            leg2_initial_exchange=False,\n            final_exchange=False,\n            leg2_final_exchange=False,\n            leg2_index_base_type=LegIndexBase.PeriodOnPeriod,\n            leg2_fixed_rate=100.0,  # combined with index_only this acts similarly to a cashflow\n            leg2_index_only=True,  # but it is impacted by the DCF of the period.\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n            index_lag=defaults.index_lag,\n            index_method=defaults.index_method,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"vol\"],\n        )\n\n        self._leg1 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self._leg1, self._leg2]\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, True, *c)\n        leg2_index_curve = _get_curve(\"leg2_index_curve\", False, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        leg2_npv: DualTypes = self.leg2.local_npv(\n            rate_curve=NoInput(0),\n            disc_curve=leg2_disc_curve,\n            index_curve=leg2_index_curve,\n            settlement=settlement,\n            forward=forward,\n        )\n        return (\n            self.leg1.spread(\n                target_npv=-leg2_npv,  # - leg1_npv,\n                rate_curve=NoInput(0),\n                disc_curve=disc_curve,\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n            / 100\n        )\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, True, *c)\n        leg2_index_curve = _get_curve(\"leg2_index_curve\", False, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        leg1_npv: DualTypes = self.leg1.local_npv(\n            rate_curve=NoInput(0),\n            disc_curve=disc_curve,\n            index_curve=NoInput(0),\n            settlement=settlement,\n            forward=forward,\n        )\n        return self.leg2.spread(\n            target_npv=-leg1_npv,\n            rate_curve=NoInput(0),\n            disc_curve=leg2_disc_curve,\n            index_curve=leg2_index_curve,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IIRS is that its fixed rate is not set.\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            # set a fixed rate for the purpose of generic methods NPV will be zero.\n            mid_market_rate = self.rate(\n                curves=curves,\n                solver=solver,\n                settlement=settlement,\n                forward=forward,\n            )\n            self.leg1.fixed_rate = _dual_float(mid_market_rate)\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An IIRS has three curve requirements: an index_curve, a leg2_rate_curve and a\n        disc_curve used by both legs.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                index_curve=curves.get(\"index_curve\", NoInput(0)),\n                leg2_index_curve=_drb(\n                    curves.get(\"index_curve\", NoInput(0)),\n                    curves.get(\"leg2_index_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    disc_curve=curves[1],\n                    leg2_index_curve=curves[0],\n                    leg2_disc_curve=curves[1],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            raise ValueError(f\"{type(self).__name__} requires 2 curve types. Got 1.\")\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/zcis.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import FixedLeg, ZeroFixedLeg\nfrom rateslib.scheduling import Frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        Frequency,\n        FXForwards_,\n        IndexMethod,\n        LegFixings,\n        RollDay,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass ZCIS(_BaseInstrument):\n    \"\"\"\n    An *indexed zero coupon swap (ZCIS)* composing a :class:`~rateslib.legs.ZeroFixedLeg`\n    and a :class:`~rateslib.legs.ZeroIndexLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import ZCIS\n       from datetime import datetime as dt\n       from rateslib import fixings\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"CPI_UK\", Series(index=[dt(1999, 10, 1), dt(1999, 11, 1)], data=[110.0, 112.0]))\n       zcis = ZCIS(\n           effective=dt(2000, 1, 10),\n           termination=\"2Y\",\n           frequency=\"A\",\n           fixed_rate=3.5,\n           currency=\"gbp\",\n           leg2_index_fixings=\"CPI_UK\",\n           leg2_index_method=\"daily\",\n       )\n       zcis.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"CPI_UK\")\n\n    .. rubric:: Pricing\n\n    The methods of a *ZCIS* require a *disc curve* applicable to both legs and a *leg2 index curve*.\n    The following input formats are allowed:\n\n    .. code-block:: python\n\n       curves = [index_curve, disc_curve]                         # two curves\n       curves = [None, disc_curve, leg2_index_curve, disc_curve]  # four curves\n       curves = {  # dict form is explicit\n           \"disc_curve\": disc_curve,\n           \"leg2_index_curve\": leg2_index_curve,\n       }\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_convention: str, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.ZeroFixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n\n        .. note::\n\n           The following parameters define **indexation**.\n\n    leg2_index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    leg2_index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    leg2_index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value applied as the base index value for all *Periods*.\n        If not given and ``index_fixings`` is a string fixings identifier that will be\n        used to determine the base index value.\n    leg2_index_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The index value for the reference date.\n        Best practice is to supply this value as string identifier relating to the global\n        ``fixings`` object.\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    @property\n    def leg1(self) -> ZeroFixedLeg:\n        \"\"\"The :class:`~rateslib.legs.ZeroFixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> FixedLeg:\n        \"\"\"The :class:`~rateslib.legs.ZeroFloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        # leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: int_ = NoInput(1),\n        leg2_payment_lag_exchange: int_ = NoInput(1),\n        leg2_ex_div: int_ = NoInput(1),\n        leg2_convention: str_ = NoInput(1),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        leg2_notional: float_ = NoInput(-1),\n        # rate parameters\n        fixed_rate: DualTypes_ = NoInput(0),\n        # indexing\n        leg2_index_base: DualTypes_ = NoInput(0),\n        leg2_index_lag: int_ = NoInput(0),\n        leg2_index_method: IndexMethod | str_ = NoInput(0),\n        leg2_index_fixings: LegFixings = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            leg2_effective=leg2_effective,\n            termination=termination,\n            leg2_termination=leg2_termination,\n            frequency=frequency,\n            # leg2_frequency=leg2_frequency,\n            stub=stub,\n            leg2_stub=leg2_stub,\n            front_stub=front_stub,\n            leg2_front_stub=leg2_front_stub,\n            back_stub=back_stub,\n            leg2_back_stub=leg2_back_stub,\n            roll=roll,\n            leg2_roll=leg2_roll,\n            eom=eom,\n            leg2_eom=leg2_eom,\n            modifier=modifier,\n            leg2_modifier=leg2_modifier,\n            calendar=calendar,\n            leg2_calendar=leg2_calendar,\n            payment_lag=payment_lag,\n            leg2_payment_lag=leg2_payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            ex_div=ex_div,\n            leg2_ex_div=leg2_ex_div,\n            convention=convention,\n            leg2_convention=leg2_convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            leg2_notional=leg2_notional,\n            # rate\n            fixed_rate=fixed_rate,\n            # indexing\n            leg2_index_base=leg2_index_base,\n            leg2_index_lag=leg2_index_lag,\n            leg2_index_method=leg2_index_method,\n            leg2_index_fixings=leg2_index_fixings,\n            # meta\n            curves=self._parse_curves(curves),\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            leg2_currency=NoInput(1),\n            initial_exchange=False,\n            final_exchange=False,\n            leg2_initial_exchange=False,\n            leg2_final_exchange=True,\n            leg2_index_only=True,\n            leg2_fixed_rate=0.0,\n            leg2_frequency=Frequency.Zero(),\n            vol=_Vol(),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n            leg2_index_lag=defaults.index_lag,\n            leg2_index_method=defaults.index_method,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"vol\"],\n        )\n\n        self._leg1 = ZeroFixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = FixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self.leg1, self.leg2]\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, True, *c)\n        leg2_index_curve = _get_curve(\"leg2_index_curve\", False, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, True, *c)\n\n        leg2_npv: DualTypes = self.leg2.local_npv(\n            rate_curve=NoInput(0),\n            disc_curve=leg2_disc_curve,\n            index_curve=leg2_index_curve,\n            settlement=settlement,\n            forward=forward,\n        )\n        return (\n            self.leg1.spread(\n                target_npv=-leg2_npv,\n                rate_curve=NoInput(0),\n                disc_curve=disc_curve,\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n            / 100\n        )\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        raise NotImplementedError(\"ZCIS has no concept of `spread` - use `rate` instead.\")\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IRS is that its fixed rate is not set.\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            # set a fixed rate for the purpose of generic methods NPV will be zero.\n            mid_market_rate = self.rate(\n                curves=curves,\n                solver=solver,\n                settlement=settlement,\n                forward=forward,\n            )\n            self.leg1.fixed_rate = _dual_float(mid_market_rate)\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An ZCIS has two curve requirements: a leg2_index_curve and a disc_curve used by both legs.\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        if isinstance(curves, dict):\n            return _Curves(\n                index_curve=curves.get(\"index_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_index_curve=_drb(\n                    curves.get(\"index_curve\", NoInput(0)),\n                    curves.get(\"leg2_index_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    leg2_index_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 4:\n                return _Curves(\n                    leg2_index_curve=curves[2],\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[3],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:\n            raise ValueError(f\"{type(self).__name__} requires only 2 curve types. Got 1.\")\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/instruments/zcs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.instruments.protocols import _BaseInstrument\nfrom rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _get_curve,\n    _parse_curves,\n    _Vol,\n)\nfrom rateslib.legs import ZeroFixedLeg, ZeroFloatLeg\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        CurvesT_,\n        DataFrame,\n        DualTypes,\n        DualTypes_,\n        FixingsRates_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        RollDay,\n        Solver_,\n        VolT_,\n        _BaseLeg,\n        bool_,\n        datetime,\n        datetime_,\n        float_,\n        int_,\n        str_,\n    )\n\n\nclass ZCS(_BaseInstrument):\n    \"\"\"\n    A *zero coupon swap (ZCS)* composing a :class:`~rateslib.legs.ZeroFixedLeg`\n    and a :class:`~rateslib.legs.ZeroFloatLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.instruments import ZCS\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       zcs = ZCS(\n           effective=dt(2000, 1, 1),\n           termination=\"2y\",\n           frequency=\"S\",\n           fixed_rate=2.0,\n       )\n       zcs.cashflows()\n\n    .. rubric:: Pricing\n\n    A *ZCS* requires a *disc curve* on both legs (which should be the same *Curve*) and a\n    *leg2 rate curve* to forecast rates on the *ZeroFloatLeg*. The following input formats are\n    allowed:\n\n    .. code-block:: python\n\n       curves = curve | [curve]           #  a single curve is repeated for all required curves\n       curves = [rate_curve, disc_curve]  #  two curves are applied in the given order\n       curves = [None, disc_curve, rate_curve, disc_curve]     # four curves applied to each leg\n       curves = {\"leg2_rate_curve\": rate_curve, \"disc_curve\": disc_curve}  # dict form is explicit\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n\n        .. note::\n\n           The following define generalised **scheduling** parameters.\n\n    effective : datetime, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    ex_div: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n    leg2_effective : datetime, :green:`optional (inherited from leg1)`\n    leg2_termination : datetime, str, :green:`optional (inherited from leg1)`\n    leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)`\n    leg2_stub : StubInference, str, :green:`optional (inherited from leg1)`\n    leg2_front_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_back_stub : datetime, :green:`optional (inherited from leg1)`\n    leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)`\n    leg2_eom : bool, :green:`optional (inherited from leg1)`\n    leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)`\n    leg2_calendar : calendar, str, :green:`optional (inherited from leg1)`\n    leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)`\n    leg2_convention: str, :green:`optional (inherited from leg1)`\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the *Instrument* (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)`\n\n        .. note::\n\n           The following are **rate parameters**.\n\n    fixed_rate : float or None\n        The fixed rate applied to the :class:`~rateslib.legs.ZeroFixedLeg`. If `None`\n        will be set to mid-market when curves are provided.\n    leg2_fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    leg2_fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    leg2_fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    leg2_float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    leg2_spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n\n        .. note::\n\n           The following are **meta parameters**.\n\n    curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional`\n        Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See\n        **Pricing**.\n    spec: str, :green:`optional`\n        A collective group of parameters. See\n        :ref:`default argument specifications <defaults-arg-input>`.\n\n    \"\"\"  # noqa: E501\n\n    _rate_scalar = 1.0\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.legs.FixedLeg`.\"\"\"\n        return self.leg1.fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self.kwargs.leg1[\"fixed_rate\"] = value\n        self.leg1.fixed_rate = value\n\n    # @property\n    # def float_spread(self) -> NoReturn:\n    #     \"\"\"The float spread parameter of the composited\n    #     :class:`~rateslib.legs.FloatLeg`.\"\"\"\n    #     raise AttributeError(f\"Attribute not available on {type(self).__name__}\")\n\n    # @property\n    # def leg2_fixed_rate(self) -> NoReturn:\n    #     raise AttributeError(f\"Attribute not available on {type(self).__name__}\")\n\n    @property\n    def leg2_float_spread(self) -> DualTypes_:\n        \"\"\"The float spread parameter of the composited\n        :class:`~rateslib.legs.FloatLeg`.\"\"\"\n        return self.leg2.float_spread\n\n    @leg2_float_spread.setter\n    def leg2_float_spread(self, value: DualTypes) -> None:\n        self.kwargs.leg2[\"float_spread\"] = value\n        self.leg2.float_spread = value\n\n    @property\n    def leg1(self) -> ZeroFixedLeg:\n        \"\"\"The :class:`~rateslib.legs.ZeroFixedLeg` of the *Instrument*.\"\"\"\n        return self._leg1\n\n    @property\n    def leg2(self) -> ZeroFloatLeg:\n        \"\"\"The :class:`~rateslib.legs.ZeroFloatLeg` of the *Instrument*.\"\"\"\n        return self._leg2\n\n    @property\n    def legs(self) -> list[_BaseLeg]:\n        \"\"\"A list of the *Legs* of the *Instrument*.\"\"\"\n        return self._legs\n\n    def __init__(\n        self,\n        # scheduling\n        effective: datetime_ = NoInput(0),\n        termination: datetime | str_ = NoInput(0),\n        frequency: Frequency | str_ = NoInput(0),\n        *,\n        stub: str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: int | RollDay | str_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: int_ = NoInput(0),\n        payment_lag_exchange: int_ = NoInput(0),\n        ex_div: int_ = NoInput(0),\n        convention: str_ = NoInput(0),\n        leg2_effective: datetime_ = NoInput(1),\n        leg2_termination: datetime | str_ = NoInput(1),\n        leg2_frequency: Frequency | str_ = NoInput(1),\n        leg2_stub: str_ = NoInput(1),\n        leg2_front_stub: datetime_ = NoInput(1),\n        leg2_back_stub: datetime_ = NoInput(1),\n        leg2_roll: int | RollDay | str_ = NoInput(1),\n        leg2_eom: bool_ = NoInput(1),\n        leg2_modifier: str_ = NoInput(1),\n        leg2_calendar: CalInput = NoInput(1),\n        leg2_payment_lag: int_ = NoInput(1),\n        leg2_payment_lag_exchange: int_ = NoInput(1),\n        leg2_ex_div: int_ = NoInput(1),\n        leg2_convention: str_ = NoInput(1),\n        # settlement parameters\n        currency: str_ = NoInput(0),\n        notional: float_ = NoInput(0),\n        # amortization: float_ = NoInput(0),\n        leg2_notional: float_ = NoInput(-1),\n        # leg2_amortization: float_ = NoInput(-1),\n        # rate parameters\n        fixed_rate: DualTypes_ = NoInput(0),\n        leg2_float_spread: DualTypes_ = NoInput(0),\n        leg2_spread_compound_method: str_ = NoInput(0),\n        leg2_rate_fixings: FixingsRates_ = NoInput(0),\n        leg2_fixing_method: str_ = NoInput(0),\n        leg2_fixing_frequency: Frequency | str_ = NoInput(0),\n        leg2_fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # meta parameters\n        curves: CurvesT_ = NoInput(0),\n        spec: str_ = NoInput(0),\n    ) -> None:\n        user_args = dict(\n            # scheduling\n            effective=effective,\n            leg2_effective=leg2_effective,\n            termination=termination,\n            leg2_termination=leg2_termination,\n            frequency=frequency,\n            leg2_frequency=leg2_frequency,\n            stub=stub,\n            leg2_stub=leg2_stub,\n            front_stub=front_stub,\n            leg2_front_stub=leg2_front_stub,\n            back_stub=back_stub,\n            leg2_back_stub=leg2_back_stub,\n            roll=roll,\n            leg2_roll=leg2_roll,\n            eom=eom,\n            leg2_eom=leg2_eom,\n            modifier=modifier,\n            leg2_modifier=leg2_modifier,\n            calendar=calendar,\n            leg2_calendar=leg2_calendar,\n            payment_lag=payment_lag,\n            leg2_payment_lag=leg2_payment_lag,\n            payment_lag_exchange=payment_lag_exchange,\n            leg2_payment_lag_exchange=leg2_payment_lag_exchange,\n            ex_div=ex_div,\n            leg2_ex_div=leg2_ex_div,\n            convention=convention,\n            leg2_convention=leg2_convention,\n            # settlement\n            currency=currency,\n            notional=notional,\n            leg2_notional=leg2_notional,\n            # rate\n            fixed_rate=fixed_rate,\n            leg2_float_spread=leg2_float_spread,\n            leg2_spread_compound_method=leg2_spread_compound_method,\n            leg2_rate_fixings=leg2_rate_fixings,\n            leg2_fixing_method=leg2_fixing_method,\n            leg2_fixing_series=leg2_fixing_series,\n            leg2_fixing_frequency=leg2_fixing_frequency,\n            # meta\n            curves=self._parse_curves(curves),\n        )\n        instrument_args = dict(  # these are hard coded arguments specific to this instrument\n            leg2_currency=NoInput(1),\n            initial_exchange=False,\n            final_exchange=False,\n            leg2_initial_exchange=False,\n            leg2_final_exchange=False,\n            vol=_Vol(),\n            # amortization=NoInput(0),\n            # leg2_amortization=NoInput(0),\n        )\n\n        default_args = dict(\n            notional=defaults.notional,\n            payment_lag=defaults.payment_lag_specific[type(self).__name__],\n            payment_lag_exchange=defaults.payment_lag_exchange,\n        )\n        self._kwargs = _KWArgs(\n            spec=spec,\n            user_args={**user_args, **instrument_args},\n            default_args=default_args,\n            meta_args=[\"curves\", \"vol\"],\n        )\n\n        self._leg1 = ZeroFixedLeg(**_convert_to_schedule_kwargs(self.kwargs.leg1, 1))\n        self._leg2 = ZeroFloatLeg(**_convert_to_schedule_kwargs(self.kwargs.leg2, 1))\n        self._legs = [self.leg1, self.leg2]\n\n    def rate(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n        metric: str_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_rate_curve = _get_curve(\"leg2_rate_curve\", True, True, *c)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, False, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n\n        leg2_npv: DualTypes = self.leg2.local_npv(\n            rate_curve=leg2_rate_curve,\n            disc_curve=leg2_disc_curve,\n            index_curve=NoInput(0),\n            settlement=settlement,\n            forward=forward,\n        )\n        return (\n            self.leg1.spread(\n                target_npv=-leg2_npv,\n                rate_curve=NoInput(0),\n                disc_curve=disc_curve,\n                index_curve=NoInput(0),\n                settlement=settlement,\n                forward=forward,\n            )\n            / 100\n        )\n\n    def spread(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        c = _parse_curves(self, curves, solver)\n        leg2_rate_curve = _get_curve(\"leg2_rate_curve\", True, True, *c)\n        disc_curve = _get_curve(\"disc_curve\", False, False, *c)\n        leg2_disc_curve = _get_curve(\"leg2_disc_curve\", False, False, *c)\n\n        leg1_npv: DualTypes = self.leg1.local_npv(\n            rate_curve=NoInput(0),\n            disc_curve=disc_curve,\n            index_curve=NoInput(0),\n            settlement=settlement,\n            forward=forward,\n        )\n        return self.leg2.spread(\n            target_npv=-leg1_npv,\n            rate_curve=leg2_rate_curve,\n            disc_curve=leg2_disc_curve,\n            index_curve=NoInput(0),\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def npv(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        self._set_pricing_mid(\n            curves=curves,\n            solver=solver,\n            settlement=settlement,\n            forward=forward,\n        )\n        return super().npv(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            local=local,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def _set_pricing_mid(\n        self,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> None:\n        # the test for an unpriced IRS is that its fixed rate is not set.\n        if isinstance(self.kwargs.leg1[\"fixed_rate\"], NoInput):\n            # set a fixed rate for the purpose of generic methods NPV will be zero.\n            mid_market_rate = self.rate(\n                curves=curves,\n                solver=solver,\n                settlement=settlement,\n                forward=forward,\n            )\n            self.leg1.fixed_rate = _dual_float(mid_market_rate)\n\n    def _parse_curves(self, curves: CurvesT_) -> _Curves:\n        \"\"\"\n        An ZCS has two curve requirements: a leg2_rate_curve and a disc_curve used by both legs.\n\n        When given as only 1 element this curve is applied to all of the those components\n\n        When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve.\n        \"\"\"\n        if isinstance(curves, NoInput):\n            return _Curves()\n        elif isinstance(curves, dict):\n            return _Curves(\n                rate_curve=curves.get(\"rate_curve\", NoInput(0)),\n                disc_curve=curves.get(\"disc_curve\", NoInput(0)),\n                leg2_rate_curve=_drb(\n                    curves.get(\"rate_curve\", NoInput(0)),\n                    curves.get(\"leg2_rate_curve\", NoInput(0)),\n                ),\n                leg2_disc_curve=_drb(\n                    curves.get(\"disc_curve\", NoInput(0)),\n                    curves.get(\"leg2_disc_curve\", NoInput(0)),\n                ),\n            )\n        elif isinstance(curves, list | tuple):\n            if len(curves) == 2:\n                return _Curves(\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[1],\n                    leg2_disc_curve=curves[1],\n                )\n            elif len(curves) == 1:\n                return _Curves(\n                    leg2_rate_curve=curves[0],\n                    disc_curve=curves[0],\n                    leg2_disc_curve=curves[0],\n                )\n            else:\n                raise ValueError(\n                    f\"{type(self).__name__} requires only 2 curve types. Got {len(curves)}.\"\n                )\n        elif isinstance(curves, _Curves):\n            return curves\n        else:  # `curves` is just a single input which is copied across all curves\n            return _Curves(\n                leg2_rate_curve=curves,  # type: ignore[arg-type]\n                disc_curve=curves,  # type: ignore[arg-type]\n                leg2_disc_curve=curves,  # type: ignore[arg-type]\n            )\n\n    def _parse_vol(self, vol: VolT_) -> _Vol:\n        return _Vol()\n\n    def cashflows(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return super()._cashflows_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            base=base,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        curves: CurvesT_ = NoInput(0),\n        solver: Solver_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        vol: VolT_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        return self._local_analytic_rate_fixings_from_legs(\n            curves=curves,\n            solver=solver,\n            fx=fx,\n            vol=vol,\n            settlement=settlement,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/legs/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.legs.amortization import Amortization\nfrom rateslib.legs.credit import CreditPremiumLeg, CreditProtectionLeg\nfrom rateslib.legs.custom import CustomLeg\nfrom rateslib.legs.fixed import FixedLeg, ZeroFixedLeg\nfrom rateslib.legs.float import FloatLeg, ZeroFloatLeg\nfrom rateslib.legs.protocols import _BaseLeg\n\n__all__ = [\n    \"FixedLeg\",\n    \"FloatLeg\",\n    \"ZeroFixedLeg\",\n    \"ZeroFloatLeg\",\n    \"CreditPremiumLeg\",\n    \"CreditProtectionLeg\",\n    \"CustomLeg\",\n    \"Amortization\",\n    \"_BaseLeg\",\n]\n"
  },
  {
    "path": "python/rateslib/legs/amortization.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom enum import Enum\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.enums.generics import NoInput\n\nif TYPE_CHECKING:\n    from rateslib.local_types import DualTypes, DualTypes_, NoInput  # pragma: no cover\n\n\nclass _AmortizationType(Enum):\n    \"\"\"\n    Enumerable type to define the possible types of amortization that some legs can handle.\n    \"\"\"\n\n    NoAmortization = 0\n    ConstantPeriod = 1\n    CustomSchedule = 2\n\n\nclass Amortization:\n    \"\"\"\n    An amortization schedule for any :class:`~rateslib.legs._BaseLeg`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.legs import Amortization\n\n    .. ipython:: python\n\n       obj = Amortization(n=5, initial=1e6, amortization=\"to_zero\")\n       obj.outstanding\n       obj.amortization\n\n    Parameters\n    ----------\n    n: int\n        The number of periods in the schedule.\n    initial: float, Dual, Dual2, Variable\n        The notional applied to the first period in the schedule.\n    amortization: float, Dual, Dual2, Variable, list, tuple, str, optional\n        The amortization structure to apply to the schedule.\n\n    Notes\n    -----\n    If ``amortization`` is:\n\n    - not specified then the schedule is assumed to have no amortization.\n    - some scalar then the amortization amount will be a constant value per period.\n    - a list or tuple of *n-1* scalars, then this is defines a custome amortization schedule.\n    - a string flag then an amortization schedule will be calculated directly:\n\n      - *\"to_zero\"*: each period will be a constant value ending with zero implied ending balance.\n      - *\"{float}%\"*: each period will amortize by a constant percentage of the outstanding balance.\n\n\n    .. rubric:: Using Amortization with Instruments\n\n    This section exemplifies how to use :class:`~rateslib.legs.Amortization` with instruments.\n\n      **Key Points**\n\n      - Amortization can be added to *Instruments* using the per leg ``amortization`` argument.\n      - It supports constant notional amortization, or custom schedules or\n        the :class:`~rateslib.legs.Amortization` class can be used to calculate other simple\n        structures.\n      - Some *Instruments* have not yet integrated amortization into their calculation, such as\n        *Bonds*.\n\n    **Standard Amortization**\n\n    The :class:`~rateslib.legs.FixedLeg` and :class:`~rateslib.legs.FloatLeg` classes both\n    have ``amortization`` as an input argument. An :class:`~rateslib.legs.Amortization` class\n    can be directly supplied or other values are internally passed to this class for\n    syntactic convenience.\n\n    The simplest, and most common, type of ``amortization`` to apply is a constant notional\n    per period.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import XCS, IRS, IndexFixedRateBond, FixedRateBond\n       from rateslib.legs import FixedLeg, FloatLeg, Amortization\n       from rateslib.scheduling import Schedule\n       from datetime import datetime as dt\n\n    .. tabs::\n\n       .. tab:: FixedLeg\n\n          .. ipython:: python\n\n             fxl = FixedLeg(\n                 schedule=Schedule(dt(2000, 1, 1), \"1y\", \"Q\"),\n                 notional=10e6,\n                 amortization=1e6,      # <- 1mm reduction per period\n             )\n             fxl.cashflows()[[\"Type\", \"Acc Start\", \"Notional\"]]\n\n       .. tab:: FloatLeg\n\n          .. ipython:: python\n\n             fll = FloatLeg(\n                 schedule=Schedule(dt(2000, 1, 1), \"1y\", \"M\"),\n                 notional=10e6,\n                 amortization=0.5e6,    # 0.5mm reduction per period\n             )\n             fll.cashflows()[[\"Type\", \"Acc Start\", \"Notional\"]]\n\n    Here, the *amortization* is expressed in a specific notional amount reduction per period so,\n    when applied to an :class:`~rateslib.instruments.IRS`, each leg with different\n    frequencies should be input directly.\n\n    If a *Leg* has a *final notional exchange* then any amortized amount would, under standard\n    convention, be paid out at the same time as the notional change. The final cashflow will be\n    reduced by the amount of interim exchanges that have already occurred. This can be\n    exemplified on a :class:`~rateslib.instruments.XCS`.\n\n    .. tabs::\n\n       .. tab:: IRS\n\n          .. ipython:: python\n\n             irs = IRS(\n                 effective=dt(2000, 1, 1),\n                 termination=\"1Y\",\n                 frequency=\"Q\",\n                 leg2_frequency=\"S\",\n                 notional=1e6,\n                 amortization=2e5,       # <- Reduces notional on 1st July to 600,000\n                 leg2_amortization=-4e5, # <- Aligns the notional on 1st July\n             )\n             irs.cashflows()[[\"Type\", \"Acc Start\", \"Notional\"]]\n\n       .. tab:: Non-MTM XCS\n\n          .. ipython:: python\n\n             xcs = XCS(\n                 effective=dt(2000, 1, 1),\n                 termination=\"1y\",\n                 spec=\"eurusd_xcs\",\n                 notional=5e6,\n                 amortization=1e6,      # <- 1mm reduction and notional exchange per period\n                 leg2_mtm=False,\n             )\n             xcs.cashflows()[[\"Type\", \"Period\", \"Acc Start\", \"Payment\", \"Ccy\", \"Notional\", \"Reference Ccy\"]]\n\n       .. tab:: MTM XCS\n\n          Mark-to-market :class:`~rateslib.instruments.XCS` also support ``amortization`` which\n          affects the MTM cashflows respectively.\n\n          .. ipython:: python\n\n             xcs = XCS(\n                 effective=dt(2000, 1, 1),\n                 termination=\"1y\",\n                 spec=\"eurusd_xcs\",\n                 notional=5e6,\n                 amortization=1e6,      # <- 1mm reduction and notional exchange per period\n                 leg2_mtm=True,\n             )\n             xcs.cashflows()[[\"Type\", \"Period\", \"Acc Start\", \"Payment\", \"Ccy\", \"Notional\", \"Reference Ccy\"]]\n\n    .. rubric:: Custom Amortization\n\n    By using the :class:`~rateslib.legs.Amortization` class custom amortization can be directly\n    input to an *Instrument*. The following examples are the same, with the first being\n    syntactic convenience for the second. The above examples are also syntactic convenience for\n    applying the same amortization amount each period.\n\n    .. tabs::\n\n       .. tab:: Amortization List\n\n          .. ipython:: python\n\n             irs = IRS(\n                 effective=dt(2000, 1, 1),\n                 termination=\"1Y\",\n                 frequency=\"Q\",\n                 leg2_frequency=\"S\",\n                 notional=1e6,\n                 amortization=[100000, 300000, -5000],    # <- Reduces notional on 1st July to 600,000\n                 leg2_amortization=[-400000], # <- Aligns the notional on 1st July\n             )\n             irs.cashflows()[[\"Type\", \"Acc Start\", \"Notional\"]]\n\n       .. tab:: Amortization Object\n\n          .. ipython:: python\n\n             irs = IRS(\n                 effective=dt(2000, 1, 1),\n                 termination=\"1Y\",\n                 frequency=\"Q\",\n                 leg2_frequency=\"S\",\n                 notional=1e6,\n                 amortization=Amortization(4, 1e6, [100000, 300000, -5000]),\n                 leg2_amortization=Amortization(2, -1e6, [-400000])\n             )\n             irs.cashflows()[[\"Type\", \"Acc Start\", \"Notional\"]]\n\n    .. rubric:: Unsupported Instruments\n\n    *Instruments* that currently do **not** support amortization are *Bonds*.\n\n    .. tabs::\n\n       .. tab:: FixedRateBond\n\n          .. ipython:: python\n\n             try:\n                 FixedRateBond(\n                     effective=dt(2000, 1, 1),\n                     termination=\"1y\",\n                     spec=\"us_gb\",\n                     notional=5e6,\n                     amortization=1e6,\n                     fixed_rate=2.0,\n                 )\n             except Exception as e:\n                 print(e)\n\n       .. tab:: IndexFixedRateBond\n\n          .. ipython:: python\n\n             try:\n                 IndexFixedRateBond(\n                     effective=dt(2000, 1, 1),\n                     termination=\"1y\",\n                     spec=\"us_gb\",\n                     notional=5e6,\n                     amortization=1e6,\n                     fixed_rate=2.0,\n                     index_base=100.0,\n                 )\n             except Exception as e:\n                 print(e)\n\n    \"\"\"  # noqa: E501\n\n    _type: _AmortizationType\n\n    @property\n    def amortization(self) -> tuple[DualTypes, ...]:\n        \"\"\"A tuple of (n-1) amortization amounts for each *Period*.\"\"\"\n        return self._amortization\n\n    @property\n    def outstanding(self) -> tuple[DualTypes, ...]:\n        \"\"\"A tuple of n outstanding notional amounts for each *Period*.\"\"\"\n        return self._outstanding\n\n    def __init__(\n        self,\n        n: int,\n        initial: DualTypes,\n        amortization: DualTypes_ | list[DualTypes] | tuple[DualTypes, ...] | str = NoInput(0),\n    ) -> None:\n        if isinstance(amortization, NoInput):\n            self._type = _AmortizationType.NoAmortization\n            self._amortization: tuple[DualTypes, ...] = (0.0,) * (n - 1)\n            self._outstanding: tuple[DualTypes, ...] = (initial,) * n\n        elif isinstance(amortization, list | tuple):\n            self._type = _AmortizationType.CustomSchedule\n            if len(amortization) != (n - 1):\n                raise ValueError(\n                    \"Custom amortisation schedules must have `n-1` amortization amounts for `n` \"\n                    f\"periods.\\nGot '{len(amortization)}' amounts for '{n}' periods.\"\n                )\n            self._amortization = tuple(amortization)\n            outstanding = [initial]\n            for value in amortization:\n                outstanding.append(outstanding[-1] - value)\n            self._outstanding = tuple(outstanding)\n        elif isinstance(amortization, str):\n            if amortization.lower() == \"to_zero\":\n                self._type = _AmortizationType.ConstantPeriod\n                self._amortization = (initial / n,) * (n - 1)\n                self._outstanding = (initial,) + tuple([initial * (1 - i / n) for i in range(1, n)])\n            elif amortization[-1] == \"%\":\n                self._type = _AmortizationType.CustomSchedule\n                amortization_ = [initial * float(amortization[:-1]) / 100]\n                outstanding_ = [initial]\n                for i in range(1, n):\n                    outstanding_.append(outstanding_[-1] - amortization_[-1])\n                    if i != n - 1:\n                        amortization_.append(outstanding_[-1] * float(amortization[:-1]) / 100)\n                self._outstanding = tuple(outstanding_)\n                self._amortization = tuple(amortization_)\n            else:\n                raise ValueError(\"`amortization` as string must be one of 'to_zero', '{float}%'.\")\n        else:  # isinstance(amortization, DualTypes)\n            self._type = _AmortizationType.ConstantPeriod\n            self._amortization = (amortization,) * (n - 1)\n            self._outstanding = (initial,) + tuple(\n                [initial - amortization * i for i in range(1, n)]\n            )\n\n    def __mul__(self, other: DualTypes) -> Amortization:\n        return Amortization(\n            n=len(self.outstanding),\n            initial=self.outstanding[0] * other,\n            amortization=[_ * other for _ in self.amortization],\n        )\n\n    def __rmul__(self, other: DualTypes) -> Amortization:\n        return self.__mul__(other)\n\n\ndef _get_amortization(\n    amortization: DualTypes_ | list[DualTypes] | tuple[DualTypes, ...] | str | Amortization,\n    initial: DualTypes,\n    n: int,\n) -> Amortization:\n    if isinstance(amortization, Amortization):\n        return amortization\n    else:\n        return Amortization(n, initial, amortization)\n"
  },
  {
    "path": "python/rateslib/legs/credit.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.curves import index_left\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.legs.amortization import Amortization, _get_amortization\nfrom rateslib.legs.protocols import _BaseLeg, _WithExDiv\nfrom rateslib.periods import CreditPremiumPeriod, CreditProtectionPeriod\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        Schedule,\n        _BaseCurve_,\n        _FXVolOption_,\n        _SettlementParams,\n        bool_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass CreditPremiumLeg(_BaseLeg, _WithExDiv):\n    \"\"\"\n    A *Leg* containing :class:`~rateslib.periods.CreditPremiumPeriod`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import Schedule\n       from rateslib.legs import CreditPremiumLeg\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       cpl = CreditPremiumLeg(\n           schedule=Schedule(\n                effective=dt(2000, 3, 20),\n                termination=dt(2001, 3, 20),\n                frequency=\"Q\",\n                modifier=\"FEX\",\n           ),\n           convention=\"Act360\",\n           fixed_rate=1.0,\n           notional=10e6,\n       )\n       cpl.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    schedule: Schedule, :red:`required`\n        The :class:`~rateslib.scheduling.Schedule` object which structures contiguous *Periods*.\n        The schedule object also contains data for payment dates, payment dates for notional\n        exchanges and ex-dividend dates for each period.\n\n        .. note::\n\n           The following are **period parameters** combined with the ``schedule``.\n\n    convention: str, optional\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the leg (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n\n        .. note::\n\n           The following define **rate parameters**.\n\n    fixed_rate: float, Dual, Dual2, Variable, :green:`optional`\n        The fixed rate of each composited :class:`~rateslib.periods.CreditPremiumPeriod`.\n\n        .. note::\n\n           The following parameters define **credit specific** elements.\n\n    premium_accrued: bool, :green:`optional (set by 'defaults')`\n        Whether an accrued premium is paid on the event of mid-period credit default.\n\n    \"\"\"\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` associated with\n        the first :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._regular_periods[0].settlement_params\n\n    @property\n    def periods(self) -> list[CreditPremiumPeriod]:\n        \"\"\"Combine all period collection types into an ordered list.\"\"\"\n        return list(self._regular_periods)\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of each composited\n        :class:`~rateslib.periods.CreditPremiumPeriod`.\"\"\"\n        return self._fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self._fixed_rate = value\n        for period in self._regular_periods:\n            period.rate_params.fixed_rate = value\n\n    @property\n    def schedule(self) -> Schedule:\n        \"\"\"The :class:`~rateslib.scheduling.Schedule` object of *Leg*.\"\"\"\n        return self._schedule\n\n    @property\n    def amortization(self) -> Amortization:\n        \"\"\"\n        The :class:`~rateslib.legs.Amortization` object associated with the schedule.\n        \"\"\"\n        return self._amortization\n\n    def accrued(self, settlement: datetime) -> DualTypes:\n        \"\"\"\n        Calculate the amount of premium accrued until a specific date within the relevant *Period*.\n\n        Parameters\n        ----------\n        settlement: datetime\n            The date against which accrued is measured.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        _ = index_left(\n            self.schedule.uschedule,\n            len(self.schedule.uschedule),\n            settlement,\n        )\n        # This index is valid because this Leg only contains CreditPremiumPeriods and no exchanges.\n        return self.periods[_].accrued(settlement)\n\n    def __init__(\n        self,\n        schedule: Schedule,\n        *,\n        fixed_rate: NoInput = NoInput(0),\n        premium_accrued: bool_ = NoInput(0),\n        # settlement and currency\n        notional: DualTypes_ = NoInput(0),\n        amortization: DualTypes_ | list[DualTypes] | Amortization | str = NoInput(0),\n        currency: str_ = NoInput(0),\n        # period\n        convention: str_ = NoInput(0),\n    ) -> None:\n        self._fixed_rate = fixed_rate\n        self._schedule = schedule\n        self._notional: DualTypes = _drb(defaults.notional, notional)\n        self._amortization: Amortization = _get_amortization(\n            amortization, self._notional, self.schedule.n_periods\n        )\n        self._currency: str = _drb(defaults.base_currency, currency).lower()\n        self._convention: str = _drb(defaults.convention, convention)\n\n        self._regular_periods = tuple(\n            [\n                CreditPremiumPeriod(\n                    fixed_rate=fixed_rate,\n                    premium_accrued=premium_accrued,\n                    # currency args\n                    payment=self.schedule.pschedule[i + 1],\n                    currency=self._currency,\n                    notional=self.amortization.outstanding[i],\n                    ex_dividend=self.schedule.pschedule3[i + 1],\n                    # period params\n                    start=self.schedule.aschedule[i],\n                    end=self.schedule.aschedule[i + 1],\n                    frequency=self.schedule.frequency_obj,\n                    convention=self._convention,\n                    termination=self.schedule.aschedule[-1],\n                    stub=self.schedule._stubs[i],\n                    roll=NoInput(0),  #  defined by Frequency\n                    calendar=self.schedule.calendar,\n                    adjuster=self.schedule.accrual_adjuster,\n                )\n                for i in range(self.schedule.n_periods)\n            ]\n        )\n\n        # # No amortization exchanges\n        # self._interim_exchange_periods = None\n        # self._exchange_periods = (None, None)\n        # self._mtm_exchange_periods = None\n\n    def spread(\n        self,\n        *,\n        target_npv: DualTypes,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        a_delta = self.local_analytic_delta(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            forward=forward,\n            settlement=settlement,\n        )\n        return -target_npv / a_delta\n\n\nclass CreditProtectionLeg(_BaseLeg):\n    \"\"\"\n    A *Leg* containing :class:`~rateslib.periods.CreditProtectionPeriod`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import dt, CreditProtectionLeg, Schedule\n\n    .. ipython:: python\n\n       cpl = CreditProtectionLeg(\n           schedule=Schedule(\n               effective=dt(2000, 3, 20),\n               termination=dt(2001, 3, 30),\n               frequency=\"Z\",\n           ),\n           notional=10e6,\n       )\n       cpl.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    schedule: Schedule, :red:`required`\n        The :class:`~rateslib.scheduling.Schedule` object which structures contiguous *Periods*.\n        The schedule object also contains data for payment dates, payment dates for notional\n        exchanges and ex-dividend dates for each period.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the leg (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` associated with\n        the first :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._regular_periods[0].settlement_params\n\n    @property\n    def periods(self) -> list[CreditProtectionPeriod]:\n        \"\"\"Combine all period collection types into an ordered list.\"\"\"\n        return list(self._regular_periods)\n\n    @property\n    def schedule(self) -> Schedule:\n        \"\"\"The :class:`~rateslib.scheduling.Schedule` object of *Leg*.\"\"\"\n        return self._schedule\n\n    @property\n    def amortization(self) -> Amortization:\n        \"\"\"\n        The :class:`~rateslib.legs.Amortization` object associated with the schedule.\n        \"\"\"\n        return self._amortization\n\n    def __init__(\n        self,\n        schedule: Schedule,\n        *,\n        # settlement and currency\n        notional: DualTypes_ = NoInput(0),\n        amortization: DualTypes_ | list[DualTypes] | Amortization | str = NoInput(0),\n        currency: str_ = NoInput(0),\n        # period\n        # convention: str_ = NoInput(0),\n    ) -> None:\n        self._schedule = schedule\n        self._notional: DualTypes = _drb(defaults.notional, notional)\n        self._amortization: Amortization = _get_amortization(\n            amortization, self._notional, self.schedule.n_periods\n        )\n        self._currency: str = _drb(defaults.base_currency, currency).lower()\n        # self._convention: str = _drb(defaults.convention, convention)\n\n        self._regular_periods = tuple(\n            [\n                CreditProtectionPeriod(\n                    # currency args\n                    payment=self.schedule.pschedule[i + 1],\n                    currency=self._currency,\n                    notional=self.amortization.outstanding[i],\n                    ex_dividend=self.schedule.pschedule3[i + 1],\n                    # period params\n                    start=self.schedule.aschedule[i],\n                    end=self.schedule.aschedule[i + 1],\n                    frequency=self.schedule.frequency_obj,\n                    # convention=self._convention,\n                    termination=self.schedule.aschedule[-1],\n                    stub=self.schedule._stubs[i],\n                    roll=NoInput(0),  #  defined by Frequency\n                    calendar=self.schedule.calendar,\n                    adjuster=self.schedule.accrual_adjuster,\n                )\n                for i in range(self.schedule.n_periods)\n            ]\n        )\n\n        # # No amortization exchanges\n        # self._interim_exchange_periods = None\n        # self._exchange_periods = (None, None)\n        # self._mtm_exchange_periods = None\n\n    def analytic_rec_risk(\n        self,\n        rate_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        base: str_ = NoInput(0),\n    ) -> float:\n        \"\"\"\n        Return the analytic recovery risk of the *CreditProtectionLeg* via summing all periods.\n\n        For arguments see\n        :meth:`BasePeriod.analytic_delta()<rateslib.periods.BasePeriod.analytic_delta>`.\n        \"\"\"\n        _ = (\n            period.analytic_rec_risk(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                fx=fx,\n                base=base,\n            )\n            for period in self.periods\n        )\n        ret: float = sum(_)\n        return ret\n\n    def spread(self, *args: Any, **kwargs: Any) -> DualTypes:\n        raise NotImplementedError(f\"{type(self).__name__} does not implement `spread`.\")\n"
  },
  {
    "path": "python/rateslib/legs/custom.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.legs.protocols import _BaseLeg\nfrom rateslib.periods.protocols import _BasePeriod\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        DualTypes,\n        Sequence,\n    )\n\n\nclass CustomLeg(_BaseLeg):\n    \"\"\"\n    A *Leg* containing user specified :class:`~rateslib.periods._BasePeriod`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.legs import CustomLeg\n       from rateslib.periods import FixedPeriod\n\n    .. ipython:: python\n\n       fp1 = FixedPeriod(\n           start=dt(2021,1,1),\n           end=dt(2021,7,1),\n           payment=dt(2021,7,2),\n           frequency=\"Q\",\n           notional=1e6,\n           convention=\"Act365F\",\n           fixed_rate=2.10\n       )\n       fp2 = FixedPeriod(\n           start=dt(2021,3,7),\n           end=dt(2021,9,7),\n           payment=dt(2021,9,8),\n           frequency=\"Q\",\n           notional=-5e6,\n           convention=\"Act365F\",\n           fixed_rate=3.10\n       )\n       custom_leg = CustomLeg(periods=[fp1, fp2])\n       custom_leg.cashflows()\n\n    Parameters\n    ----------\n    periods : iterable of _BasePeriod\n        A sequence of *Periods* to attach to the leg.\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def periods(self) -> Sequence[_BasePeriod]:\n        \"\"\"Combine all period collection types into an ordered list.\"\"\"\n        return self._periods\n\n    def __init__(self, periods: Sequence[_BasePeriod]) -> None:\n        if not all(isinstance(p, _BasePeriod) for p in periods):\n            raise ValueError(\n                \"Each object in `periods` must be an instance of `_BasePeriod`.\",\n            )\n        self._periods = periods\n\n    def spread(self, *args: Any, **kwargs: Any) -> DualTypes:\n        return super().spread(*args, **kwargs)  # type: ignore[safe-super]\n"
  },
  {
    "path": "python/rateslib/legs/fixed.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.curves._parsers import (\n    _disc_required_maybe_from_curve,\n)\nfrom rateslib.data.fixings import _leg_fixings_to_list\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import LegIndexBase, LegMtm, _get_leg_index_base, _get_leg_mtm\nfrom rateslib.legs.amortization import Amortization, _AmortizationType, _get_amortization\nfrom rateslib.legs.protocols import (\n    _BaseLeg,\n    _WithExDiv,\n)\nfrom rateslib.periods import (\n    Cashflow,\n    FixedPeriod,\n    MtmCashflow,\n    ZeroFixedPeriod,\n)\nfrom rateslib.periods.protocols import (\n    _BasePeriod,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        FXIndex,\n        IndexMethod,\n        LegFixings,\n        Schedule,\n        Series,\n        _BaseCurve_,\n        _FXVolOption_,\n        _SettlementParams,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass FixedLeg(_BaseLeg, _WithExDiv):\n    \"\"\"\n    A *Leg* containing :class:`~rateslib.periods.FixedPeriod`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import fixings, Schedule\n       from pandas import Series\n       from rateslib.legs import FixedLeg\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fl = FixedLeg(\n           schedule=Schedule(\n                effective=dt(2000, 2, 1),\n                termination=dt(2002, 2, 1),\n                frequency=\"S\",\n           ),\n           convention=\"ActActICMA\",\n           fixed_rate=2.5,\n           notional=10e6,\n       )\n       fl.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    schedule: Schedule, :red:`required`\n        The :class:`~rateslib.scheduling.Schedule` object which structures contiguous *Periods*.\n        The schedule object also contains data for payment dates, payment dates for notional\n        exchanges and ex-dividend dates for each period.\n\n        .. note::\n\n           The following are **period parameters** combined with the ``schedule``.\n\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the leg (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    initial_exchange : bool, :green:`optional (set as False)`\n        Whether to also include an initial notional exchange. If *True* then ``final_exchange``\n        **will** also be set to *True*.\n    final_exchange : bool, :green:`optional (set as initial_exchange)`\n        Whether to also include a final notional exchange and interim amortization\n        notional exchanges.\n\n        .. note::\n\n           The following define **rate parameters**.\n\n    fixed_rate: float, Dual, Dual2, Variable, :green:`optional`\n        The fixed rate of each composited :class:`~rateslib.periods.FixedPeriod`.\n\n        .. note::\n\n           The following define **non-deliverable** parameters. If the *Leg* is directly\n           deliverable then do not set a non-deliverable ``pair`` or any ``fx_fixings``.\n\n    pair: FXIndex, str, :green:`optional`\n        The :class:`~rateslib.data.fixings.FXIndex` for :class:`~rateslib.data.fixings.FXFixing`\n        defining the currency pair that determines *Period*\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according\n        to non-deliverability. Review the **notes** section non-deliverability, and\n        :ref:`fixings <fixings-doc>`. This should only ever be entered as either:\n\n        - scalar value: 1.15,\n        - fixings series: \"Reuters_ZBS\",\n        - tuple of transaction rate and fixing series: (1.25, \"Reuters_ZBC\")\n    mtm: LegMtm or str, :green:`optional (set to 'initial')`\n        Define how the fixing dates are determined for each :class:`~rateslib.data.fixings.FXFixing`\n        See **Notes** regarding non-deliverability.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value applied as the base index value for all *Periods*.\n        If not given and ``index_fixings`` is a string fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The index value for the reference date.\n        Best practice is to supply this value as string identifier relating to the global\n        ``fixings`` object. See :ref:`fixings <fixings-doc>`.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which indicates that the nominal amount is deducted from the cashflow leaving only\n        the indexed up quantity.\n    index_base_type: LegIndexBase, str, :green:`optional (set as 'initial')`\n        A parameter to define how the ``index_base_date`` is set on each period. See notes.\n\n    Notes\n    -----\n    **Typical Fixed Legs**\n\n    A typical *FixedLeg* has no amortization, no indexation, is directly deliverable and offers\n    no notional exchanges. This represents one component of, for example, an\n    :class:`~rateslib.instruments.IRS`.\n\n    .. ipython:: python\n\n       leg = FixedLeg(\n           schedule=Schedule(dt(2000, 1, 1), dt(2000, 7, 1), \"Q\"),\n           fixed_rate=2.0,\n           convention=\"Act360\",\n           notional=5000000,\n       )\n       print(leg.cashflows())\n\n    **Notional Exchanges**\n\n    Notional exchanges are common elements on securities, e.g. a\n    :class:`~rateslib.instruments.FixedRateBond`. These can be specifically included using the\n    ``final_exchange`` and ``initial_exchange`` parameters.\n\n    .. ipython:: python\n\n       leg = FixedLeg(\n           schedule=Schedule(dt(2000, 1, 1), dt(2000, 7, 1), \"Q\"),\n           fixed_rate=2.0,\n           convention=\"Act360\",\n           notional=5000000,\n           final_exchange=True,\n       )\n       print(leg.cashflows())\n\n    Initial and final notional exchanges have opposite directions.\n\n    **Amortization**\n\n    Amortization can be applied either with customised schedules, or with simpler consistent\n    amounts per period.\n\n    If ``final_exchange`` is *True* then amortization will also create interim notional exchange\n    cashflows. Note that a same sign ``amortization`` value is translated into\n    a notional reduction. If ``final_exchange`` is *False*, or amortization is zero, there are no\n    interim notional exchange cashflows generated.\n\n    .. ipython:: python\n\n       leg = FixedLeg(\n           schedule=Schedule(dt(2000, 1, 1), dt(2000, 7, 1), \"Q\"),\n           fixed_rate=2.0,\n           convention=\"Act360\",\n           notional=5000000,\n           amortization=1000000,\n           final_exchange=True,\n       )\n       print(leg.cashflows())\n\n    **Indexation**\n\n    An :class:`~rateslib.instruments.IndexFixedRateBond` is the most common instrument that\n    uses an index-linked *FixedLeg*. Setting *index* parameters creates the necessary\n    indexation of cashflows. Note that all previous features such as notional exchanges and\n    amortization are all adjusted appropriately.\n\n    .. ipython:: python\n\n       fixings.add(\"MY_RPI\", Series(\n           index=[dt(2000, 1, 1), dt(2000, 4, 1), dt(2000, 7, 1)],\n           data=[101.0, 102.0, 103.0]\n       ))\n       leg = FixedLeg(\n           schedule=Schedule(dt(2000, 1, 1), dt(2000, 7, 1), \"Q\"),\n           fixed_rate=2.0,\n           convention=\"Act360\",\n           notional=5000000,\n           amortization=1000000,\n           final_exchange=True,\n           index_fixings=\"MY_RPI\",\n           index_lag=0,\n           index_method=\"monthly\",\n       )\n       print(leg.cashflows())\n\n    Any interim notional exchange cashflows generated by ``amortization`` are also indexed.\n\n    If ``index_base_type`` is set to :class:`LegIndexBase.Initial` then every period will have its\n    *index base date* set to the first date of the adjusted accrual schedule (``aschedule[0]``).\n    If ``index_base_type`` is set to :class:`LegIndexBase.PeriodOnPeriod` then each *index base\n    date* is set to the adjusted accrual date of the start of each period.\n\n    **Non-Deliverability**\n\n    The leg uses a ``mtm`` argument to define the types of non-deliverability that it can\n    construct. Currently there are three kinds which cater to the various type of requirements\n    for, *ND-IRS*, *MTM-XCS*, *non-MTM XCS*, *ND-XCS*\n\n    .. tabs::\n\n       .. tab:: Initial\n\n          This uses the *Initial* variant of a :class:`~rateslib.enums.LegMtm` and it\n          defines all :class:`~rateslib.data.fixings.FXFixing` on the *Leg* to be a single date\n          at the start of the *Leg* (derived from ``schedule.pschedule2[0]``). Usually this fixing is\n          directly specified being agreed at execution of the transaction and not dependent\n          upon a published financial fixing.\n\n          This type of *non-deliverability* is suitable to define a *Leg* of one currency, but\n          expressed by a notional in another currency, and is used for a *non-MTM XCS*.\n\n          Since only one fixing is required, ``fx_fixings`` can be entered either as\n          a known scalar value or string series identifier.\n\n          .. ipython:: python\n\n             leg = FixedLeg(\n                 schedule=Schedule(\n                     effective=dt(2000, 1, 1),\n                     termination=dt(2000, 7, 1),\n                     frequency=\"Q\",\n                     payment_lag=1,\n                     payment_lag_exchange=0,\n                 ),\n                 fixed_rate=1.0,\n                 initial_exchange=True,\n                 mtm=\"initial\",\n                 currency=\"usd\",\n                 pair=\"eurusd\",\n                 notional=10e6,      # <- Leg is a USD leg but expressed with a EUR notional\n                 fx_fixings=1.25,    # <- All periods are treated as 12.5mm USD\n             )\n             print(leg.cashflows())\n\n       .. tab:: Payment\n\n          Under the *Payment* variant of a :class:`~rateslib.enums.LegMtm`\n          all reference currency cashflows are converted to settlement\n          currency using an :class:`~rateslib.data.fixings.FXFixing` with a date of the payment.\n          This is probably the most traditional type of non-deliverability and is suitable\n          for *NDIRS* and *NDXCS* *Instruments*.\n\n          The best practice entry for ``fx_fixings`` depends if the *Leg* has\n          notional exchanges or not. If there is an initial notional exchange then\n          a 2-tuple, with the first element being the transacted exchange rate and\n          the second element referring to the fixing\n          series for future *FX Fixings*. If only future fixings are required then a string\n          series is used.\n\n          .. ipython:: python\n\n             fixings.add(\"WMR_10AM_TY0_T+2_EURUSD\", Series(\n                 index=[dt(2000, 1, 1), dt(2000, 4, 2), dt(2000, 7, 1), dt(2000, 7, 2)],\n                 data=[1.26, 1.27, 1.29, 1.295])\n             )\n             leg = FixedLeg(\n                 schedule=Schedule(\n                     effective=dt(2000, 1, 1),\n                     termination=dt(2000, 7, 1),\n                     frequency=\"Q\",\n                     payment_lag=1,\n                     payment_lag_exchange=0,\n                 ),\n                 fixed_rate=1.0,\n                 initial_exchange=True,\n                 mtm=\"payment\",\n                 currency=\"usd\",\n                 pair=\"eurusd\",\n                 notional=10e6,                          # <- Leg settles in USD leg but reference cashflows in EUR\n                 fx_fixings=(1.25, \"WMR_10AM_TY0_T+2\"),  # <- Initial exchange rate and future fixings\n             )\n             print(leg.cashflows())\n\n       .. tab:: XCS\n\n          The *XCS* variant of a :class:`~rateslib.enums.LegMtm` is specially configured\n          for *MTM-XCS*. These *Legs* have their\n          cashflows determined with :class:`~rateslib.data.fixings.FXFixing` at the start of\n          each *Period*, in a manner slightly similar  to the *Initial* variant, and specifically\n          generated :class:`~rateslib.periods.MtmCashflow` *Periods* adjusting the value of the\n          notional by an *FXFixing* at the end of each *Period*.\n\n          The best practice entry for ``fx_fixings`` is as a 2-tuple, with the first\n          element the transacted exchange rate and the second element referring to the fixing\n          series for future *FX Fixings*.\n\n          .. ipython:: python\n\n             fixings.add(\"WMR_4PM_GMT_T+2_EURUSD\", Series(\n                 index=[dt(2000, 4, 1), dt(2000, 4, 2), dt(2000, 7, 2)],\n                 data=[1.265, 1.27, 1.29])\n             )\n             leg = FixedLeg(\n                 schedule=Schedule(\n                     effective=dt(2000, 1, 1),\n                     termination=dt(2000, 7, 1),\n                     frequency=\"Q\",\n                     payment_lag=1,\n                     payment_lag_exchange=0,\n                 ),\n                 fixed_rate=1.0,\n                 initial_exchange=True,\n                 currency=\"usd\",\n                 pair=\"eurusd\",\n                 mtm=\"xcs\",\n                 notional=10e6,\n                 fx_fixings=(1.25, \"WMR_4PM_GMT_T+2\"),\n             )\n             print(leg.cashflows())\n\n    **Amortization and Non-Deliverability**\n\n    When amortization is combined with non-deliverability, the interim notional exchange cashflows\n    are adjusted appropriately in both the non-mtm and mtm cases.\n\n    .. tabs::\n\n       .. tab:: Initial\n\n          Amortization under this method adopts the same singular fixing as all other *Periods*.\n\n          .. ipython:: python\n\n             leg = FixedLeg(\n                 schedule=Schedule(\n                     effective=dt(2000, 1, 1),\n                     termination=dt(2000, 7, 1),\n                     frequency=\"Q\",\n                     payment_lag=1,\n                     payment_lag_exchange=0,\n                 ),\n                 fixed_rate=1.0,\n                 initial_exchange=True,\n                 mtm=\"initial\",\n                 currency=\"usd\",\n                 pair=\"eurusd\",\n                 notional=10e6,      # <- Leg is a USD leg but expressed with a EUR notional\n                 amortization=4e6,\n                 fx_fixings=1.25,    # <- All periods are treated as 12.5mm USD\n             )\n             print(leg.cashflows())\n\n       .. tab:: Payment\n\n          Amortization under this method settles according to the payment date.\n\n          .. ipython:: python\n\n             leg = FixedLeg(\n                 schedule=Schedule(\n                     effective=dt(2000, 1, 1),\n                     termination=dt(2000, 7, 1),\n                     frequency=\"Q\",\n                     payment_lag=1,\n                     payment_lag_exchange=0,\n                 ),\n                 fixed_rate=1.0,\n                 initial_exchange=True,\n                 mtm=\"payment\",\n                 currency=\"usd\",\n                 pair=\"eurusd\",\n                 notional=10e6,                     # <- Leg settles in USD leg but reference cashflows in EUR\n                 amortization=4e6,\n                 fx_fixings=(1.25, \"WMR_10AM_TY0_T+2\"),  # <- Initial exchange rate and future fixings\n             )\n             print(leg.cashflows())\n\n       .. tab:: XCS\n\n          Amortization for a *XCS* takes places after the :class:`~rateslib.periods.MtmCashflow`.\n\n          .. ipython:: python\n\n             leg = FixedLeg(\n                 schedule=Schedule(\n                     effective=dt(2000, 1, 1),\n                     termination=dt(2000, 7, 1),\n                     frequency=\"Q\",\n                     payment_lag=1,\n                     payment_lag_exchange=0,\n                 ),\n                 fixed_rate=1.0,\n                 initial_exchange=True,\n                 currency=\"usd\",\n                 pair=\"eurusd\",\n                 mtm=\"xcs\",\n                 notional=10e6,\n                 amortization=4e6,\n                 fx_fixings=(1.25, \"WMR_4PM_GMT_T+2\"),\n             )\n             print(leg.cashflows())\n\n    **Indexation, Non-Deliverability and Amortization**\n\n    In the most complicated case, which rarely even relates to real tradable instruments all\n    of the parameters may be combined. The :meth:`~rateslib.legs.FixedLeg.cashflows`\n    method outlines the relevant fixing values and dates used in calculations.\n\n    .. ipython:: python\n\n       leg = FixedLeg(\n           schedule=Schedule(\n               effective=dt(2000, 1, 1),\n               termination=dt(2000, 7, 1),\n               frequency=\"Q\",\n               payment_lag=2,\n               payment_lag_exchange=1\n           ),\n           fixed_rate=1.0,\n           currency=\"usd\",\n           pair=\"eurusd\",\n           initial_exchange=True,\n           notional=5e6,\n           amortization=1000000,\n           mtm=\"xcs\",\n           fx_fixings=(1.25, \"WMR_10AM_TY0_T+2\"),\n           index_lag=0,\n           index_fixings=\"MY_RPI\",\n           index_method=\"monthly\",\n       )\n       print(leg.cashflows())\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"WMR_10AM_TY0_T+2_EURUSD\")\n       fixings.pop(\"WMR_4PM_GMT_T+2_EURUSD\")\n       fixings.pop(\"MY_RPI\")\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` associated with\n        the first :class:`~rateslib.periods.FixedPeriod`.\"\"\"\n        return self._regular_periods[0].settlement_params\n\n    @cached_property\n    def periods(self) -> list[_BasePeriod]:\n        \"\"\"A list of all contained *Periods*.\"\"\"\n        periods_: list[_BasePeriod] = []\n\n        if self._exchange_periods[0] is not None:\n            periods_.append(self._exchange_periods[0])\n\n        args: tuple[tuple[_BasePeriod], ...] = (self._regular_periods[:-1],)  # type: ignore[assignment]\n        if self._mtm_exchange_periods is not None:\n            args = args + (self._mtm_exchange_periods,)  # type: ignore[operator]\n        if self._amortization_exchange_periods is not None:\n            args = args + (self._amortization_exchange_periods,)  # type: ignore[operator]\n        interleaved_periods_: list[_BasePeriod] = [\n            item for combination in zip(*args, strict=True) for item in combination\n        ]\n        interleaved_periods_.append(self._regular_periods[-1])  # add last regular period\n        periods_.extend(interleaved_periods_)\n\n        if self._exchange_periods[1] is not None:\n            periods_.append(self._exchange_periods[1])\n\n        return periods_\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of each composited\n        :class:`~rateslib.periods.FixedPeriod`.\"\"\"\n        return self._fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self._fixed_rate = value\n        for period in self._regular_periods:\n            period.rate_params.fixed_rate = value\n\n    @property\n    def schedule(self) -> Schedule:\n        \"\"\"The :class:`~rateslib.scheduling.Schedule` object of *Leg*.\"\"\"\n        return self._schedule\n\n    @property\n    def amortization(self) -> Amortization:\n        \"\"\"\n        The :class:`~rateslib.legs.Amortization` object associated with the schedule.\n        \"\"\"\n        return self._amortization\n\n    def __init__(\n        self,\n        schedule: Schedule,\n        *,\n        # settlement and currency\n        notional: DualTypes_ = NoInput(0),\n        amortization: DualTypes_ | list[DualTypes] | Amortization | str = NoInput(0),\n        currency: str_ = NoInput(0),\n        # non-deliverable\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: LegFixings = NoInput(0),\n        mtm: LegMtm | str = LegMtm.Initial,\n        # period\n        convention: str_ = NoInput(0),\n        initial_exchange: bool = False,\n        final_exchange: bool = False,\n        # rate params\n        fixed_rate: NoInput = NoInput(0),\n        # index params\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: LegFixings = NoInput(0),\n        index_only: bool = False,\n        index_base_type: LegIndexBase | str_ = NoInput(0),\n    ) -> None:\n        self._fixed_rate = fixed_rate\n        del fixed_rate\n        self._schedule = schedule\n        del schedule\n        self._notional: DualTypes = _drb(defaults.notional, notional)\n        del notional\n        self._amortization: Amortization = _get_amortization(\n            amortization, self._notional, self.schedule.n_periods\n        )\n        del amortization\n        self._currency: str = _drb(defaults.base_currency, currency).lower()\n        del currency\n        self._convention: str = _drb(defaults.convention, convention)\n        del convention\n        self._mtm = _get_leg_mtm(mtm)\n        del mtm\n\n        index_fixings_ = _leg_fixings_to_list(index_fixings, self.schedule.n_periods)\n        del index_fixings\n\n        index_base_type_ = _get_leg_index_base(_drb(defaults.index_base_type, index_base_type))\n        del index_base_type\n\n        # if initial and final exchange with MtM.Payment then there is an extra fixing date\n        _mtm_param = 1 if (self._mtm == LegMtm.Payment and initial_exchange) else 0\n        fx_fixings_ = _leg_fixings_to_list(fx_fixings, self.schedule.n_periods + _mtm_param)\n        del fx_fixings\n\n        # Exchange periods\n        if not initial_exchange:\n            _ini_cf: Cashflow | None = None\n        else:\n            _ini_cf = Cashflow(\n                payment=self.schedule.pschedule2[0],\n                notional=-self._amortization.outstanding[0],\n                currency=self._currency,\n                ex_dividend=self.schedule.pschedule3[0],\n                # non-deliverable\n                pair=pair,\n                fx_fixings=fx_fixings_[0],\n                delivery=self.schedule.pschedule2[0],\n                # index params\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[0],\n                index_base_date=self.schedule.aschedule[0],\n                index_reference_date=self.schedule.aschedule[0],\n                index_only=index_only,\n            )\n        final_exchange_ = final_exchange or initial_exchange\n        if not final_exchange_:\n            _final_cf: Cashflow | None = None\n        else:\n            delivery_ = {\n                LegMtm.Initial: self.schedule.pschedule2[0],\n                LegMtm.XCS: self.schedule.pschedule2[-2],\n                LegMtm.Payment: self.schedule.pschedule2[-1],\n            }\n            _final_cf = Cashflow(\n                payment=self.schedule.pschedule2[-1],\n                notional=self._amortization.outstanding[-1],\n                currency=self._currency,\n                ex_dividend=self.schedule.pschedule3[-1],\n                # non-deliverable\n                pair=pair,\n                fx_fixings=fx_fixings_[0] if self._mtm == LegMtm.Initial else fx_fixings_[-1],\n                delivery=delivery_[self._mtm],\n                # index parameters\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[-1],\n                index_base_date=self.schedule.aschedule[0]\n                if index_base_type_ is LegIndexBase.Initial\n                else self.schedule.aschedule[-2],\n                index_reference_date=self.schedule.aschedule[-1],\n                index_only=index_only,\n            )\n        self._exchange_periods = (_ini_cf, _final_cf)\n\n        self._regular_periods: tuple[FixedPeriod, ...] = tuple(\n            [\n                FixedPeriod(\n                    fixed_rate=self.fixed_rate,\n                    # currency args\n                    payment=self.schedule.pschedule[i + 1],\n                    currency=self._currency,\n                    notional=self.amortization.outstanding[i],\n                    ex_dividend=self.schedule.pschedule3[i + 1],\n                    # period params\n                    start=self.schedule.aschedule[i],\n                    end=self.schedule.aschedule[i + 1],\n                    frequency=self.schedule.frequency_obj,\n                    convention=self._convention,\n                    termination=self.schedule.aschedule[-1],\n                    stub=self.schedule._stubs[i],\n                    roll=NoInput(0),  #  defined by Frequency\n                    calendar=self.schedule.calendar,\n                    adjuster=self.schedule.accrual_adjuster,\n                    # non-deliverable : Not allowed with notional exchange\n                    pair=pair,\n                    fx_fixings=fx_fixings_[0]\n                    if self._mtm == LegMtm.Initial\n                    else fx_fixings_[i + _mtm_param],\n                    delivery=_fx_delivery(i, self._mtm, self.schedule, False, False),\n                    # index params\n                    index_base=index_base,\n                    index_lag=index_lag,\n                    index_method=index_method,\n                    index_fixings=index_fixings_[i],\n                    index_base_date=self.schedule.aschedule[0]\n                    if index_base_type_ is LegIndexBase.Initial\n                    else self.schedule.aschedule[i],\n                    index_reference_date=self.schedule.aschedule[i + 1],\n                    index_only=index_only,\n                )\n                for i in range(self.schedule.n_periods)\n            ]\n        )\n\n        # amortization exchanges\n        if not final_exchange_ or self.amortization._type == _AmortizationType.NoAmortization:\n            self._amortization_exchange_periods: tuple[_BasePeriod, ...] | None = None\n        else:\n            # only with notional exchange and some Amortization amount\n            self._amortization_exchange_periods = tuple(\n                [\n                    Cashflow(\n                        notional=self.amortization.amortization[i],\n                        payment=self.schedule.pschedule2[i + 1],\n                        currency=self._currency,\n                        ex_dividend=self.schedule.pschedule3[i + 1],\n                        # non-deliverable params\n                        pair=pair,\n                        fx_fixings=fx_fixings_[0]\n                        if self._mtm == LegMtm.Initial\n                        else fx_fixings_[i + 1],\n                        delivery=_fx_delivery(\n                            i, self._mtm, self.schedule, True, True\n                        ),  # schedule for exchanges\n                        # index params\n                        index_base=index_base,\n                        index_lag=index_lag,\n                        index_method=index_method,\n                        index_fixings=index_fixings_[i],\n                        index_base_date=self.schedule.aschedule[0]\n                        if index_base_type_ is LegIndexBase.Initial\n                        else self.schedule.aschedule[i],\n                        index_reference_date=self.schedule.aschedule[i + 1],\n                        index_only=index_only,\n                    )\n                    for i in range(self.schedule.n_periods - 1)\n                ]\n            )\n\n        # mtm exchanges\n        if self._mtm == LegMtm.XCS and final_exchange_:\n            if isinstance(pair, NoInput):\n                raise ValueError(err.VE_PAIR_AND_LEG_MTM)\n            self._mtm_exchange_periods: tuple[_BasePeriod, ...] | None = tuple(\n                [\n                    MtmCashflow(\n                        payment=self.schedule.pschedule2[i + 1],\n                        notional=-self.amortization.outstanding[i],\n                        pair=pair,\n                        start=self.schedule.pschedule2[i],\n                        end=self.schedule.pschedule2[i + 1],\n                        currency=self._currency,\n                        ex_dividend=self.schedule.pschedule3[i + 1],\n                        fx_fixings_start=fx_fixings_[i],\n                        fx_fixings_end=fx_fixings_[i + 1],\n                        # index params\n                        index_base=index_base,\n                        index_lag=index_lag,\n                        index_method=index_method,\n                        index_fixings=index_fixings_[i],\n                        index_base_date=self.schedule.aschedule[0]\n                        if index_base_type_ is LegIndexBase.Initial\n                        else self.schedule.aschedule[i],\n                        index_reference_date=self.schedule.aschedule[i + 1],\n                        index_only=index_only,\n                    )\n                    for i in range(self.schedule.n_periods - 1)\n                ]\n            )\n        else:\n            self._mtm_exchange_periods = None\n\n    def spread(\n        self,\n        *,\n        target_npv: DualTypes,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        # local_npv is calculated to identify the isolated NPV component of cashflow exchanges.\n        _ = self.fixed_rate\n        self.fixed_rate = 0.0\n        local_npv = self.local_npv(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            forward=forward,\n            settlement=settlement,\n        )\n        self.fixed_rate = _\n\n        a_delta = self.local_analytic_delta(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            forward=forward,\n            settlement=settlement,\n        )\n        return -(target_npv - local_npv) / a_delta\n\n\nclass ZeroFixedLeg(_BaseLeg):\n    \"\"\"\n    A zero coupon *Leg* composed of a single\n    :class:`~rateslib.periods.ZeroFixedPeriod` .\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.legs import ZeroFixedLeg\n       from rateslib.scheduling import Schedule\n       from datetime import datetime as dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       zfl = ZeroFixedLeg(\n           schedule=Schedule(\n                effective=dt(2000, 2, 1),\n                termination=dt(2002, 2, 1),\n                frequency=\"S\",\n           ),\n           fixed_rate=2.5,\n           notional=10e6,\n       )\n       zfl.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    schedule: Schedule, :red:`required`\n        The :class:`~rateslib.scheduling.Schedule` object which structures contiguous *Periods*.\n        The schedule object also contains data for payment dates, payment dates for notional\n        exchanges and ex-dividend dates for each period.\n\n        .. note::\n\n           The following are **period parameters** combined with the ``schedule``.\n\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the leg (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    initial_exchange : bool, :green:`optional (set as False)`\n        Whether to also include an initial notional exchange. If *True* then ``final_exchange``\n        **will** also be set to *True*.\n    final_exchange : bool, :green:`optional (set as initial_exchange)`\n        Whether to also include a final notional exchange and interim amortization\n        notional exchanges.\n\n        .. note::\n\n           The following define **rate parameters**.\n\n    fixed_rate: float, Dual, Dual2, Variable, :green:`optional`\n        The IRR of the composited :class:`~rateslib.periods.ZeroFixedPeriod`.\n\n        .. note::\n\n           The following define **non-deliverable** parameters. If the *Leg* is directly\n           deliverable then do not set a non-deliverable ``pair`` or any ``fx_fixings``.\n\n    pair: FXIndex, str, :green:`optional`\n        The :class:`~rateslib.data.fixings.FXIndex` for :class:`~rateslib.data.fixings.FXFixing`\n        defining the currency pair that determines *Period*\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according\n        to non-deliverability.\n        Review the **notes** section non-deliverability on a :class:`~rateslib.legs.FixedLeg`.\n        See also :ref:`fixings <fixings-doc>`.\n    mtm: LegMtm or str, :green:`optional (set to 'initial')`\n        Define how the fixing dates are determined for each :class:`~rateslib.data.fixings.FXFixing`\n        See **Notes** regarding non-deliverability. *XCS* is not allowed on a *Zero* type *Leg*.\n        Review the **notes** section non-deliverability on a :class:`~rateslib.legs.FixedLeg`.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value applied as the base index value for all *Periods*.\n        If not given and ``index_fixings`` is a string fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The index value for the reference date.\n        Best practice is to supply this value as string identifier relating to the global\n        ``fixings`` object. See :ref:`fixings <fixings-doc>`.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which indicates that the nominal amount is deducted from the cashflow leaving only\n        the indexed up quantity.\n    \"\"\"\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` associated with\n        the :class:`~rateslib.periods.ZeroFixedPeriod`.\"\"\"\n        return self._regular_periods[0].settlement_params\n\n    @cached_property\n    def periods(self) -> list[_BasePeriod]:\n        \"\"\"A list of all contained *Periods*.\"\"\"\n        periods_: list[_BasePeriod] = []\n\n        if self._exchange_periods[0] is not None:\n            periods_.append(self._exchange_periods[0])\n        periods_.extend(self._regular_periods)\n        if self._exchange_periods[1] is not None:\n            periods_.append(self._exchange_periods[1])\n\n        return periods_\n\n    @property\n    def schedule(self) -> Schedule:\n        \"\"\"The :class:`~rateslib.scheduling.Schedule` object of *Leg*.\"\"\"\n        return self._schedule\n\n    @property\n    def amortization(self) -> Amortization:\n        \"\"\"\n        The :class:`~rateslib.legs.Amortization` object associated with the schedule.\n        \"\"\"\n        return self._amortization\n\n    def __init__(\n        self,\n        schedule: Schedule,\n        *,\n        # period\n        convention: str_ = NoInput(0),\n        # rate params\n        fixed_rate: NoInput = NoInput(0),\n        # settlement and currency\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        initial_exchange: bool = False,\n        final_exchange: bool = False,\n        # non-deliverable\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: LegFixings = NoInput(0),\n        mtm: LegMtm | str_ = NoInput(0),\n        # index params\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        index_only: bool = False,\n    ) -> None:\n        mtm_ = _get_leg_mtm(_drb(\"initial\", mtm))\n        del mtm\n        if mtm_ is LegMtm.XCS:\n            raise ValueError(\"`mtm` cannot be XCS variant for a ZeroFixedLeg type.\")\n\n        self._schedule = schedule\n        if self.schedule.frequency == \"Z\":\n            raise ValueError(\n                \"`frequency` for a ZeroFixedLeg should not be 'Z'. The Leg is zero frequency by \"\n                \"construction. Set the `frequency` equal to the compounding frequency of the \"\n                \"expressed fixed rate, e.g. 'S' for semi-annual compounding.\",\n            )\n        self._notional: DualTypes = _drb(defaults.notional, notional)\n        self._currency: str = _drb(defaults.base_currency, currency).lower()\n        self._convention: str = _drb(defaults.convention, convention)\n        self._amortization = Amortization(n=self.schedule.n_periods, initial=self._notional)\n\n        index_fixings_ = _leg_fixings_to_list(index_fixings, self.schedule.n_periods)\n        fx_fixings_ = _leg_fixings_to_list(fx_fixings, self.schedule.n_periods)\n\n        # Exchange periods\n        if not initial_exchange:\n            _ini_cf: Cashflow | None = None\n        else:\n            _ini_cf = Cashflow(\n                payment=self.schedule.pschedule2[0],\n                notional=-self._amortization.outstanding[0],\n                currency=self._currency,\n                ex_dividend=self.schedule.pschedule3[0],\n                # non-deliverable\n                pair=pair,\n                fx_fixings=fx_fixings_[0],\n                delivery=self.schedule.pschedule2[0],\n                # index params\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[0],\n                index_base_date=self.schedule.aschedule[0],\n                index_reference_date=self.schedule.aschedule[0],\n                index_only=index_only,\n            )\n        final_exchange_ = final_exchange or initial_exchange\n        if not final_exchange_:\n            _final_cf: Cashflow | None = None\n        else:\n            _final_cf = Cashflow(\n                payment=self.schedule.pschedule2[-1],\n                notional=self._amortization.outstanding[-1],\n                currency=self._currency,\n                ex_dividend=self.schedule.pschedule3[-1],\n                # non-deliverable\n                pair=pair,\n                fx_fixings=fx_fixings_[0] if mtm_ is LegMtm.Initial else fx_fixings_[-1],\n                delivery=self.schedule.pschedule2[0]\n                if mtm_ is LegMtm.Initial\n                else self.schedule.pschedule2[-1],\n                # index parameters\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[0],\n                index_base_date=self.schedule.aschedule[0],\n                index_reference_date=self.schedule.aschedule[-1],\n                index_only=index_only,\n            )\n        self._exchange_periods = (_ini_cf, _final_cf)\n\n        self._regular_periods = (\n            ZeroFixedPeriod(\n                fixed_rate=NoInput(0),\n                schedule=self.schedule,\n                # currency args\n                currency=self._currency,\n                notional=self._notional,\n                # period params\n                convention=self._convention,\n                # non-deliverable : Not allowed with notional exchange\n                pair=pair,\n                fx_fixings=fx_fixings_[0],\n                delivery=self.schedule.pschedule2[0]\n                if mtm_ is LegMtm.Initial\n                else self.schedule.pschedule2[-1],\n                # index params\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[0],\n                index_only=index_only,\n            ),\n        )\n\n        self.fixed_rate = fixed_rate\n\n    @property\n    def fixed_rate(self) -> DualTypes_:\n        \"\"\"The fixed rate parameter of the composited\n        :class:`~rateslib.periods.ZeroFixedPeriod`.\"\"\"\n        return self._fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self._fixed_rate = value\n        for period in self._regular_periods:\n            period.rate_params.fixed_rate = value\n\n    def spread(\n        self,\n        *,\n        target_npv: DualTypes,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        # scale target_npv accounting for notional exchanges\n        _ = self.fixed_rate\n        self.fixed_rate = 0.0\n        local_npv = self.local_npv(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            forward=forward,\n            settlement=settlement,\n        )\n        self.fixed_rate = _\n        rate_target_npv = target_npv - local_npv\n\n        # evaluate settlement relative to ex-div\n        disc_curve_ = _disc_required_maybe_from_curve(rate_curve, disc_curve)\n        if not isinstance(settlement, NoInput):\n            if settlement > self.settlement_params.ex_dividend:\n                raise ZeroDivisionError(\n                    \"A `spread` cannot be determined when the *Leg* always has zero value.\\n\"\n                    \"The given `settlement` is after the `ex_dividend` date.\"\n                )\n            else:\n                w_fwd = disc_curve_[_drb(settlement, forward)]\n        else:\n            if isinstance(forward, NoInput):\n                w_fwd = 1.0\n            else:\n                w_fwd = disc_curve_[forward]\n\n        immediate_target_npv = rate_target_npv * w_fwd\n        unindexed_target_npv = immediate_target_npv / self._regular_periods[0].index_up(\n            1.0, index_curve=index_curve\n        )\n        unindexed_reference_target_npv = unindexed_target_npv / self._regular_periods[\n            0\n        ].convert_deliverable(1.0, fx=fx)\n        target_cashflow = (\n            unindexed_reference_target_npv / disc_curve_[self.settlement_params.payment]\n        )\n\n        f = self.schedule.periods_per_annum\n        d = self._regular_periods[0].dcf\n        N = self.settlement_params.notional\n\n        R = ((-target_cashflow / N + 1) ** (1 / (d * f)) - 1) * f * 10000.0\n        return R\n\n\ndef _fx_delivery(\n    i: int,\n    mtm: LegMtm,\n    schedule: Schedule,\n    is_exchange: bool,\n    is_amortisation: bool,\n) -> datetime:\n    \"\"\"Based on the `mtm` parameter determine the FX fixing dates for regular period 'i'.\"\"\"\n    if mtm == LegMtm.Initial:\n        # then ND type is a one-fixing only, so is determined by only a single rate of exchange\n        # this date is set to the initial payment exchange date of the schedule\n        return schedule.pschedule2[0]\n    elif mtm == LegMtm.Payment:\n        # then the ND type is a NDXCS or a NDIRS which determines FX at payment\n        if is_exchange:\n            return schedule.pschedule2[i + 1]\n        else:\n            return schedule.pschedule[i + 1]\n    else:  # LegMtm.XCS\n        # then the ND type is a MTM-XCS which has special MTMCashflow periods\n        # the relevant FX fixing is set in advance of the period using notional exchange dates\n        if is_amortisation:\n            return schedule.pschedule2[i + 1]\n        else:\n            return schedule.pschedule2[i]\n"
  },
  {
    "path": "python/rateslib/legs/float.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nfrom pandas import Series\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.data.fixings import _leg_fixings_to_list\nfrom rateslib.dual import ift_1dim\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import (\n    FloatFixingMethod,\n    LegIndexBase,\n    LegMtm,\n    SpreadCompoundMethod,\n    _get_float_fixing_method,\n    _get_leg_index_base,\n    _get_leg_mtm,\n)\nfrom rateslib.legs.amortization import Amortization, _AmortizationType, _get_amortization\nfrom rateslib.legs.custom import CustomLeg\nfrom rateslib.legs.fixed import _fx_delivery\nfrom rateslib.legs.protocols import _BaseLeg, _WithExDiv\nfrom rateslib.periods import Cashflow, FloatPeriod, MtmCashflow, ZeroFloatPeriod\nfrom rateslib.periods.parameters import _FloatRateParams, _SettlementParams\nfrom rateslib.periods.parameters.rate import _init_float_rate_series\nfrom rateslib.scheduling.schedule import Schedule\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        FloatRateSeries,\n        Frequency,\n        FXForwards_,\n        FXIndex,\n        IndexMethod,\n        LegFixings,\n        Sequence,\n        _BaseCurve_,\n        _BasePeriod,\n        _FXVolOption_,\n        bool_,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass FloatLeg(_BaseLeg, _WithExDiv):\n    \"\"\"\n    A *Leg* containing :class:`~rateslib.periods.FloatPeriod`\n    (or optionally multiple :class:`~rateslib.periods.ZeroFloatPeriod`).\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import fixings, Schedule, Curve, FloatRateSeries\n       from pandas import Series\n       from rateslib.legs import FloatLeg, CustomLeg\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fl = FloatLeg(\n           schedule=Schedule(\n                effective=dt(2000, 2, 1),\n                termination=dt(2002, 2, 1),\n                frequency=\"S\",\n           ),\n           convention=\"Act360\",\n           float_spread=25.0,\n           notional=10e6,\n       )\n       fl.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    schedule: Schedule, :red:`required`\n        The :class:`~rateslib.scheduling.Schedule` object which structures contiguous *Periods*.\n        The schedule object also contains data for payment dates, payment dates for notional\n        exchanges and ex-dividend dates for each period.\n\n        .. note::\n\n           The following are **period parameters** combined with the ``schedule``.\n\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the leg (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)`\n        Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of\n        each successive period by that same value. Should have\n        sign equal to that of notional if the notional is to reduce towards zero.\n    initial_exchange : bool, :green:`optional (set as False)`\n        Whether to also include an initial notional exchange. If *True* then ``final_exchange``\n        **will** also be set to *True*.\n    final_exchange : bool, :green:`optional (set as initial_exchange)`\n        Whether to also include a final notional exchange and interim amortization\n        notional exchanges.\n\n        .. note::\n\n           The following define **rate parameters**.\n\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with (non-averaged)\n        RFR type ``fixing_method``, and when ``zero_periods`` is False.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader.\n    zero_periods: bool, :green:`optional (set as False)`\n        If *True* a :class:`~rateslib.periods.ZeroFloatPeriod` is used as the regular period\n        instead of a :class:`~rateslib.periods.FloatPeriod`. See notes.\n\n        .. note::\n\n           The following define **non-deliverable** parameters. If the *Leg* is directly\n           deliverable then do not set a non-deliverable ``pair`` or any ``fx_fixings``.\n\n    pair: FXIndex, str, :green:`optional`\n        The :class:`~rateslib.data.fixings.FXIndex` for :class:`~rateslib.data.fixings.FXFixing`\n        defining the currency pair that determines *Period*\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according\n        to non-deliverability.\n        Review the **notes** section non-deliverability on a :class:`~rateslib.legs.FixedLeg`, and\n        see also :ref:`fixings <fixings-doc>`.\n        This should only ever be entered as either:\n\n        - scalar value: 1.15,\n        - fixings series: \"Reuters_ZBS\",\n        - tuple of transaction rate and fixing series: (1.25, \"Reuters_ZBC\")\n    mtm: LegMtm or str, :green:`optional (set to 'initial')`\n        Define how the fixing dates are determined for each :class:`~rateslib.data.fixings.FXFixing`\n        See **Notes** regarding non-deliverability.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value applied as the base index value for all *Periods*.\n        If not given and ``index_fixings`` is a string fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The index value for the reference date.\n        Best practice is to supply this value as string identifier relating to the global\n        ``fixings`` object.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which indicates that the nominal amount is deducted from the cashflow leaving only\n        the indexed up quantity.\n    index_base_type: LegIndexBase, str, :green:`optional (set as 'initial')`\n        A parameter to define how the ``index_base_date`` is set on each period. See notes.\n\n    Notes\n    -----\n    The various combinations of **amortisation**, **non-deliverability**, **indexation**,\n    and **notional exchanges** are identical to, and demonstrated in the documentation for, a\n    :class:`~rateslib.legs.FixedLeg` object.\n\n    **Classifications**\n\n    There are generally five types of index classification that can be constructed with this *Leg*.\n\n    .. ipython:: python\n       :suppress:\n\n       curve = Curve({dt(2026, 1, 1): 1.0, dt(2029, 1, 1): 0.86070797}, calendar=\"nyc\", convention=\"act360\")\n\n    .. tabs::\n\n       .. tab:: RFR\n\n          To construct a standard **RFR** (otherwise known as OIS) type leg, use\n          any of the non-averaging *'RFR'* variants of the\n          :class:`~rateslib.enums.FloatFixingMethod` for the ``fixing_method`` parameter.\n\n          Using this ``fixing_method`` the ``fixing_frequency`` is always assumed to be *'1B'* for\n          overnight (o/n) rates.\n\n          Any ``spread_compound_method`` can be used in combination with these ``fixing_method``.\n\n          Each :class:`~rateslib.periods.FloatPeriod` has an **RFR** classification.\n\n          Below is an example of the conventional float leg on a USD-SOFR IRS.\n\n          .. ipython:: python\n\n             rfr_standard = FloatLeg(\n                 schedule=Schedule(\n                     effective=dt(2026, 1, 22),\n                     termination=\"2Y\",\n                     frequency=\"A\",\n                     calendar=\"nyc\",\n                     payment_lag=2\n                 ),\n                 convention=\"Act360\",\n                 fixing_method=\"rfr_payment_delay\",\n             )\n             rfr_standard.cashflows(rate_curve=curve)\n\n          .. warning::\n\n             Do **not** use ``zero_periods`` in the construction of RFR type legs.\n             Although it is, technically, possible to construct this type of *Leg* using\n             ``zero_periods``. Doing so creates an individual :class:`~rateslib.periods.FloatPeriod`\n             for every single overnight RFR fixing making up each\n             :class:`~rateslib.periods.ZeroFloatPeriod`. This is inefficient and removes\n             other features.\n\n       .. tab:: RFR Avg.\n\n          To construct an **RFR averaged** type use an *'average'* type variant of the\n          :class:`~rateslib.enums.FloatFixingMethod` for the ``fixing_method`` parameter.\n\n          Each :class:`~rateslib.periods.FloatPeriod` has an **average RFR** classification.\n\n          Below is an example of the conventional float leg on an averaged USD-SOFR IRS.\n\n          .. ipython:: python\n\n             rfr_averaged = FloatLeg(\n                 schedule=Schedule(\n                     effective=dt(2026, 1, 22),\n                     termination=\"2Y\",\n                     frequency=\"A\",\n                     calendar=\"nyc\",\n                     payment_lag=2\n                 ),\n                 convention=\"Act360\",\n                 fixing_method=\"rfr_payment_delay_avg\",\n             )\n             rfr_averaged.cashflows(rate_curve=curve)\n\n          .. warning::\n\n             Rates are calculated directly from the provided ``rate_curve``. There are *no\n             convexity adjustments* applied to account for the difference between compounded\n             numéraire and averaged result.\n\n       .. tab:: IBOR\n\n          To construct a standard **IBOR** type leg use the *'ibor'* variant of\n          the :class:`~rateslib.enums.FloatFixingMethod` for the ``fixing_method`` parameter.\n          The ``fixing_frequency`` defining tenor of the index will default to that of the schedule.\n\n          Each :class:`~rateslib.periods.FloatPeriod` has an **IBOR** or **IBOR Stub**\n          classification. Stubs can only appear at the front or back of the *Leg* and depend upon\n          the ``schedule`` directly identifying those periods as *stubs*.\n\n          Below is an example of a standard EURIBOR 3M float leg.\n\n          .. ipython:: python\n\n             ibor_standard = FloatLeg(\n                 schedule=Schedule(\n                     effective=dt(2026, 1, 22),\n                     termination=\"1Y\",\n                     frequency=\"Q\",\n                     calendar=\"tgt\",\n                     payment_lag=0\n                 ),\n                 currency=\"eur\",\n                 convention=\"Act360\",\n                 fixing_method=\"ibor(2)\",\n             )\n             ibor_standard.cashflows(rate_curve=curve)\n\n       .. tab:: Unaligned IBOR\n\n          To construct a *Leg* with a different tenor **IBOR** index to that of the schedule,\n          specify the ``fixing_frequency`` directly.\n\n          Each :class:`~rateslib.periods.FloatPeriod` has an **Misaligned IBOR** or **IBOR Stub**\n          classification. Stubs can only appear at the front or back of the *Leg* and depend upon\n          the ``schedule`` directly identifying those periods as *stubs*. Stub *Periods* will have\n          the usual tenor interpolation applied, as with regular IBOR *Legs*, and does not factor\n          the misalignment into the calculation.\n\n          Below is an example of a 1Y float leg with quarterly payments with each fixing to\n          four distinct EURIBOR 6M rates.\n\n          .. ipython:: python\n\n             ibor_misaligned = FloatLeg(\n                 schedule=Schedule(\n                     effective=dt(2026, 1, 22),\n                     termination=\"1Y\",\n                     frequency=\"Q\",\n                     calendar=\"tgt\",\n                     payment_lag=0,\n                 ),\n                 convention=\"Act360\",\n                 fixing_method=\"ibor(2)\",\n                 fixing_series=\"eur_ibor\",\n                 fixing_frequency=\"S\",  # <- frequency of fixing does not match schedule.\n             )\n             ibor_misaligned.cashflows(rate_curve=curve)\n\n       .. tab:: Multi-IBOR\n\n          To construct a *Leg* with multiple IBOR tenor indexes compounded over a single\n          *Period* set ``zero_periods`` to True. Each *Period* will then be a\n          :class:`~rateslib.periods.ZeroFloatPeriod`.\n\n          This means that each :class:`~rateslib.periods.ZeroFloatPeriod` will need to construct\n          a sub- :class:`~rateslib.scheduling.Schedule` to define its IBOR publications. Each\n          sub- :class:`~rateslib.scheduling.Schedule` has a *frequency* equal to\n          ``fixing_frequency`` and each *effective* and *termination* dates match the\n          *start* and *end* unadjusted accrual dates for each *Period* of the main\n          ``schedule``. When a stub is required, these sub-schedules take steer directly from the\n          :class:`~rateslib.data.fixings.FloatRateSeries` parameters.\n\n          Note the ``float_spread`` is added to each individual\n          :class:`~rateslib.periods.FloatPeriod` and then all resultant rates are compounded to\n          yield the final rate for the :class:`~rateslib.periods.ZeroFloatPeriod` (this an\n          ISDA compounded type calculation).\n\n          Two use cases of this have been identified;\n\n          - Legacy US-LIBOR single currency basis swaps where the 3M-LIBOR was compounded over\n            a 6M period to net cashflows with the 6M *Leg*. An example is below:\n\n            .. ipython:: python\n\n               float_leg = FloatLeg(\n                   schedule=Schedule(\n                       effective=dt(2026, 1, 22),\n                       termination=\"1Y\",\n                       frequency=\"S\",\n                       calendar=\"nyc\",\n                       payment_lag=0\n                   ),\n                   convention=\"Act360\",\n                   fixing_series=\"usd_ibor\",\n                   fixing_method=\"ibor(2)\",\n                   zero_periods=True,\n                   fixing_frequency=\"Q\",\n                   float_spread=75.0,\n               )\n               float_leg.cashflows(rate_curve=curve)\n               CustomLeg(float_leg.periods[0].float_periods).cashflows(rate_curve=curve)\n\n          - CNY *IRS* with quarterly payments setting to 7D tenor rate. Note that these periods\n            are often not perfectly divisible, resulting in stub periods within each\n            :class:`~rateslib.periods.ZeroFloatPeriod`. The position and treatment of these\n            stubs can be controlled under the :class:`~rateslib.data.fixings.FloatRateSeries`.\n\n            .. ipython:: python\n\n               float_leg = FloatLeg(\n                   schedule=Schedule(\n                       effective=dt(2026, 1, 21),\n                       termination=dt(2027, 1, 21),\n                       frequency=\"Q\",\n                       calendar=\"bjs\",\n                   ),\n                   currency=\"CNY\",\n                   fixing_frequency=\"7d\",\n                   fixing_method=\"ibor(1)\",\n                   fixing_series=FloatRateSeries(\n                       lag=1,\n                       convention=\"Act365F\",\n                       calendar=\"bjs\",\n                       tenors=[\"7D\"],\n                       zero_period_stub=\"shortback\",\n                       modifier=\"F\",\n                       eom=False,\n                   ),\n                   zero_periods=True,\n               )\n\n            The individual fixing dates of each of these 7D periods are stored on each\n            *rate fixing* of each :class:`~rateslib.periods.FloatPeriod`.\n\n            .. ipython:: python\n\n               for float_period in float_leg.periods[0].float_periods:\n                   print(float_period.rate_params.rate_fixing.date)\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def rate_params(self) -> _FloatRateParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._FloatRateParams` associated with\n        the first :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._regular_periods[0].rate_params\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` associated with\n        the first :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._regular_periods[0].settlement_params\n\n    @property\n    def periods(self) -> list[_BasePeriod]:\n        \"\"\"A list of all contained *Periods*.\"\"\"\n        periods_: list[_BasePeriod] = []\n\n        if self._exchange_periods[0] is not None:\n            periods_.append(self._exchange_periods[0])\n\n        args: tuple[tuple[ZeroFloatPeriod | FloatPeriod | MtmCashflow | Cashflow, ...], ...] = (\n            self._regular_periods[:-1],\n        )\n        if self._mtm_exchange_periods is not None:\n            args += (self._mtm_exchange_periods,)\n        if self._amortization_exchange_periods is not None:\n            args += (self._amortization_exchange_periods,)\n        interleaved_periods_: list[_BasePeriod] = [\n            item for combination in zip(*args, strict=True) for item in combination\n        ]\n        interleaved_periods_.append(self._regular_periods[-1])  # add last regular period\n        periods_.extend(interleaved_periods_)\n\n        if self._exchange_periods[1] is not None:\n            periods_.append(self._exchange_periods[1])\n\n        return periods_\n\n    @property\n    def float_spread(self) -> DualTypes:\n        \"\"\"The float spread parameter of each composited\n        :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._regular_periods[0].rate_params.float_spread\n\n    @float_spread.setter\n    def float_spread(self, value: DualTypes) -> None:\n        for period in self._regular_periods:\n            period.rate_params.float_spread = value\n\n    @property\n    def schedule(self) -> Schedule:\n        \"\"\"The :class:`~rateslib.scheduling.Schedule` object of *Leg*.\"\"\"\n        return self._schedule\n\n    @property\n    def amortization(self) -> Amortization:\n        \"\"\"\n        The :class:`~rateslib.legs.Amortization` object associated with the schedule.\n        \"\"\"\n        return self._amortization\n\n    def __init__(\n        self,\n        schedule: Schedule,\n        *,\n        # settlement and currency\n        notional: DualTypes_ = NoInput(0),\n        amortization: DualTypes_ | list[DualTypes] | Amortization | str = NoInput(0),\n        currency: str_ = NoInput(0),\n        # non-deliverable\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: LegFixings = NoInput(0),\n        mtm: LegMtm | str = LegMtm.Initial,\n        # period\n        convention: str_ = NoInput(0),\n        initial_exchange: bool = False,\n        final_exchange: bool = False,\n        # rate params\n        float_spread: DualTypes_ = NoInput(0),\n        rate_fixings: LegFixings = NoInput(0),\n        fixing_method: FloatFixingMethod | str_ = NoInput(0),\n        spread_compound_method: SpreadCompoundMethod | str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        zero_periods: bool_ = NoInput(0),\n        # index params\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: LegFixings = NoInput(0),\n        index_only: bool = False,\n        index_base_type: LegIndexBase | str_ = NoInput(0),\n    ) -> None:\n        zero_periods_ = _drb(False, zero_periods)\n        del zero_periods\n        fixing_method_ = _get_float_fixing_method(\n            method=_drb(defaults.fixing_method, fixing_method)\n        )\n        del fixing_method\n\n        self._schedule = schedule\n        del schedule\n        self._notional: DualTypes = _drb(defaults.notional, notional)\n        del notional\n        self._amortization: Amortization = _get_amortization(\n            amortization, self._notional, self._schedule.n_periods\n        )\n        del amortization\n        self._currency: str = _drb(defaults.base_currency, currency).lower()\n        del currency\n        self._convention: str = _drb(defaults.convention, convention)\n        del convention\n\n        self._mtm = _get_leg_mtm(mtm)\n        del mtm\n\n        index_fixings_ = _leg_fixings_to_list(index_fixings, self.schedule.n_periods)\n        del index_fixings\n\n        index_base_type_ = _get_leg_index_base(_drb(defaults.index_base_type, index_base_type))\n        del index_base_type\n\n        # if initial and final exchange with MtM.Payment then there is an extra fixing date\n        _mtm_param = 1 if (self._mtm == LegMtm.Payment and initial_exchange) else 0\n        fx_fixings_ = _leg_fixings_to_list(fx_fixings, self.schedule.n_periods + _mtm_param)\n        del fx_fixings\n\n        # Exchange periods\n        if not initial_exchange:\n            _ini_cf: Cashflow | None = None\n        else:\n            _ini_cf = Cashflow(\n                payment=self.schedule.pschedule2[0],\n                notional=-self._amortization.outstanding[0],\n                currency=self._currency,\n                ex_dividend=self.schedule.pschedule3[0],\n                # non-deliverable\n                pair=pair,\n                fx_fixings=fx_fixings_[0],\n                delivery=self.schedule.pschedule2[0],\n                # index params\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[0],\n                index_base_date=self.schedule.aschedule[0],\n                index_reference_date=self.schedule.aschedule[0],\n                index_only=index_only,\n            )\n        final_exchange_ = final_exchange or initial_exchange\n        if not final_exchange_:\n            _final_cf: Cashflow | None = None\n        else:\n            delivery_ = {\n                LegMtm.Initial: self.schedule.pschedule2[0],\n                LegMtm.XCS: self.schedule.pschedule2[-2],\n                LegMtm.Payment: self.schedule.pschedule2[-1],\n            }\n            _final_cf = Cashflow(\n                payment=self.schedule.pschedule2[-1],\n                notional=self._amortization.outstanding[-1],\n                currency=self._currency,\n                ex_dividend=self.schedule.pschedule3[-1],\n                # non-deliverable\n                pair=pair,\n                fx_fixings=fx_fixings_[0] if self._mtm == LegMtm.Initial else fx_fixings_[-1],\n                delivery=delivery_[self._mtm],\n                # index parameters\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[-1],\n                index_base_date=self.schedule.aschedule[0]\n                if index_base_type_ is LegIndexBase.Initial\n                else self.schedule.aschedule[-2],\n                index_reference_date=self.schedule.aschedule[-1],\n                index_only=index_only,\n            )\n        self._exchange_periods = (_ini_cf, _final_cf)\n\n        if not zero_periods_:\n            rate_fixings_list = _leg_fixings_to_list(rate_fixings, self._schedule.n_periods)\n            self._regular_periods: tuple[FloatPeriod | ZeroFloatPeriod, ...] = tuple(\n                [\n                    FloatPeriod(\n                        float_spread=float_spread,\n                        rate_fixings=rate_fixings_list[i],\n                        fixing_method=fixing_method_,\n                        spread_compound_method=spread_compound_method,\n                        fixing_frequency=fixing_frequency,\n                        fixing_series=fixing_series,\n                        # currency args\n                        payment=self.schedule.pschedule[i + 1],\n                        currency=self._currency,\n                        notional=self.amortization.outstanding[i],\n                        ex_dividend=self.schedule.pschedule3[i + 1],\n                        # period params\n                        start=self.schedule.aschedule[i],\n                        end=self.schedule.aschedule[i + 1],\n                        frequency=self.schedule.frequency_obj,\n                        convention=self._convention,\n                        termination=self.schedule.aschedule[-1],\n                        stub=self.schedule._stubs[i],\n                        roll=NoInput(0),  #  defined by Frequency\n                        calendar=self.schedule.calendar,\n                        adjuster=self.schedule.accrual_adjuster,\n                        # non-deliverable : Not allowed with notional exchange\n                        pair=pair,\n                        fx_fixings=fx_fixings_[0]\n                        if self._mtm == LegMtm.Initial\n                        else fx_fixings_[i + _mtm_param],\n                        delivery=_fx_delivery(i, self._mtm, self.schedule, False, False),\n                        # index params\n                        index_base=index_base,\n                        index_lag=index_lag,\n                        index_method=index_method,\n                        index_fixings=index_fixings_[i],\n                        index_base_date=self.schedule.aschedule[0]\n                        if index_base_type_ is LegIndexBase.Initial\n                        else self.schedule.aschedule[i],\n                        index_reference_date=self._schedule.aschedule[i + 1],\n                        index_only=index_only,\n                    )\n                    for i in range(self._schedule.n_periods)\n                ]\n            )\n        else:\n            if isinstance(fixing_frequency, NoInput):\n                raise ValueError(\n                    \"A `fixing_frequency` must be given to `FloatLeg` when \"\n                    \"`zero_periods` is True.\\nWhen using `zero_periods` the intention is to \"\n                    \"create multiple floating rate periods on the leg which themselves are \"\n                    \"constructed from multiple floating rate fixings compounded up.\\n\"\n                    \"Therefore more parameters are required to properly specify the scheduling.\\n\"\n                    \"See Notes.\"\n                )\n            fixing_series_ = _init_float_rate_series(\n                fixing_series=fixing_series,\n                calendar=self._schedule.calendar,\n                convention=self._convention,\n                fixing_method=fixing_method_,\n                adjuster=self.schedule.accrual_adjuster,\n            )\n            del fixing_series\n            # TODO: this fixings to list must account for sub zero periods - quite tricky\n            rate_fixings_list = _leg_fixings_to_list(rate_fixings, self._schedule.n_periods)\n            self._regular_periods = tuple(\n                [\n                    ZeroFloatPeriod(\n                        schedule=Schedule(\n                            # BBG appears to use the `aschedule` for defining these periods.\n                            # rateslib uses the `uschedule` because it is more consistent from\n                            # outer period to outer period, but more real life example are\n                            # required to fully qualify what should be used here.\n                            # Additionally if adjusted dates were used, rateslib inference means it\n                            # might assert unadjusted start dates which may not align with the\n                            # outer schedule. Matching unadjusted dates mitigates inconsistency.\n                            effective=self.schedule.uschedule[i],\n                            termination=self.schedule.uschedule[i + 1],\n                            frequency=fixing_frequency,\n                            payment_lag=self.schedule.payment_adjuster,\n                            payment_lag_exchange=self.schedule.payment_adjuster2,\n                            extra_lag=self.schedule.payment_adjuster3\n                            if self.schedule.payment_adjuster3 is not None\n                            else NoInput(0),\n                            calendar=self.schedule.calendar,\n                            stub=fixing_series_.zero_period_stub,\n                        ),\n                        float_spread=float_spread,\n                        rate_fixings=rate_fixings_list[i],\n                        fixing_method=fixing_method_,\n                        spread_compound_method=spread_compound_method,\n                        fixing_frequency=fixing_frequency,\n                        fixing_series=fixing_series_,\n                        # currency args\n                        currency=self._currency,\n                        notional=self.amortization.outstanding[i],\n                        # period params\n                        convention=self._convention,\n                        # non-deliverable : Not allowed with notional exchange\n                        pair=pair,\n                        fx_fixings=fx_fixings_[0]\n                        if self._mtm == LegMtm.Initial\n                        else fx_fixings_[i + _mtm_param],\n                        delivery=_fx_delivery(i, self._mtm, self.schedule, False, False),\n                        # index params\n                        index_base=index_base,\n                        index_lag=index_lag,\n                        index_method=index_method,\n                        index_fixings=index_fixings_[i],\n                        index_base_date=self.schedule.aschedule[0]\n                        if index_base_type_ is LegIndexBase.Initial\n                        else self.schedule.aschedule[i],\n                        index_reference_date=self._schedule.aschedule[i + 1],\n                        index_only=index_only,\n                        # meta\n                        metric=\"simple\",  # to ensure correct cals in the cashflow for the Leg\n                    )\n                    for i in range(self._schedule.n_periods)\n                ]\n            )\n\n        # amortization exchanges\n        if not final_exchange_ or self.amortization._type == _AmortizationType.NoAmortization:\n            self._amortization_exchange_periods: tuple[Cashflow, ...] | None = None\n        else:\n            self._amortization_exchange_periods = tuple(\n                [\n                    Cashflow(\n                        notional=self.amortization.amortization[i],\n                        payment=self.schedule.pschedule2[i + 1],\n                        currency=self._currency,\n                        ex_dividend=self.schedule.pschedule3[i + 1],\n                        # non-deliverable params\n                        pair=pair,\n                        fx_fixings=fx_fixings_[0]\n                        if self._mtm == LegMtm.Initial\n                        else fx_fixings_[i + 1],\n                        delivery=_fx_delivery(\n                            i, self._mtm, self.schedule, True, True\n                        ),  # schedule for exchanges\n                        # index params\n                        index_base=index_base,\n                        index_lag=index_lag,\n                        index_method=index_method,\n                        index_fixings=index_fixings_[i],\n                        index_base_date=self.schedule.aschedule[0]\n                        if index_base_type_ is LegIndexBase.Initial\n                        else self.schedule.aschedule[i],\n                        index_reference_date=self._schedule.aschedule[i + 1],\n                        index_only=True,\n                    )\n                    for i in range(self._schedule.n_periods - 1)\n                ]\n            )\n\n        # mtm exchanges\n        if self._mtm == LegMtm.XCS and final_exchange_:\n            if isinstance(pair, NoInput):\n                raise ValueError(err.VE_PAIR_AND_LEG_MTM)\n            self._mtm_exchange_periods: tuple[MtmCashflow, ...] | None = tuple(\n                [\n                    MtmCashflow(\n                        payment=self.schedule.pschedule2[i + 1],\n                        notional=-self.amortization.outstanding[i],\n                        pair=pair,\n                        start=self.schedule.pschedule2[i],\n                        end=self.schedule.pschedule2[i + 1],\n                        currency=self._currency,\n                        ex_dividend=self.schedule.pschedule3[i + 1],\n                        fx_fixings_start=fx_fixings_[i],\n                        fx_fixings_end=fx_fixings_[i + 1],\n                        # index params\n                        index_base=index_base,\n                        index_lag=index_lag,\n                        index_method=index_method,\n                        index_fixings=index_fixings_[i],\n                        index_base_date=self.schedule.aschedule[0]\n                        if index_base_type_ is LegIndexBase.Initial\n                        else self.schedule.aschedule[i],\n                        index_reference_date=self.schedule.aschedule[i + 1],\n                        index_only=index_only,\n                    )\n                    for i in range(self.schedule.n_periods - 1)\n                ]\n            )\n        else:\n            self._mtm_exchange_periods = None\n\n    @property\n    def _is_linear(self) -> bool:\n        \"\"\"\n        Tests if analytic delta spread is a linear function affecting NPV.\n\n        This is non-linear if the spread is itself compounded, which only occurs\n        on RFR trades with *\"isda_compounding\"* or *\"isda_flat_compounding\"*, which\n        should typically be avoided anyway.\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        # ruff: noqa: SIM103\n        if (\n            not isinstance(self.rate_params.fixing_method, FloatFixingMethod.IBOR)\n            and self.rate_params.spread_compound_method != SpreadCompoundMethod.NoneSimple\n        ):\n            return False\n        return True\n\n    def spread(\n        self,\n        *,\n        target_npv: DualTypes,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        if self._is_linear:\n            local_npv = self.local_npv(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=index_curve,\n                fx=fx,\n                forward=forward,\n                settlement=settlement,\n            )\n            a_delta = self.local_analytic_delta(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=index_curve,\n                fx=fx,\n                forward=forward,\n                settlement=settlement,\n            )\n            return -(target_npv - local_npv) / a_delta + self.float_spread\n        else:\n            original_z = self.float_spread\n\n            def s(g: DualTypes) -> DualTypes:\n                \"\"\"\n                This determines the NPV change subject to a given float spread change denoted, g.\n                \"\"\"\n                self.float_spread = g\n                return self.local_npv(\n                    rate_curve=rate_curve,\n                    disc_curve=disc_curve,\n                    index_curve=index_curve,\n                    fx=fx,\n                    forward=forward,\n                    settlement=settlement,\n                )\n\n            result = ift_1dim(\n                s=s,\n                s_tgt=target_npv,\n                h=\"ytm_quadratic\",\n                ini_h_args=(-300, 300, 1200),\n                # h=\"modified_brent\",\n                # ini_h_args=(-10000, 10000),\n                func_tol=1e-6,\n                conv_tol=1e-6,\n            )\n\n            self.float_spread = original_z\n            _: DualTypes = result[\"g\"]\n            return _\n\n\nclass ZeroFloatLeg(_BaseLeg):\n    \"\"\"\n    A zero coupon *Leg* composed of a single :class:`~rateslib.periods.ZeroFloatPeriod`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.legs import ZeroFloatLeg\n       from rateslib.scheduling import Schedule\n       from datetime import datetime as dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       zfl = ZeroFloatLeg(\n           schedule=Schedule(\n                effective=dt(2000, 2, 1),\n                termination=dt(2002, 2, 1),\n                frequency=\"S\",\n           ),\n           notional=10e6,\n       )\n       zfl.cashflows()\n       zfl.float_periods.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    schedule: Schedule, :red:`required`\n        The :class:`~rateslib.scheduling.Schedule` object which structures contiguous *Periods*.\n        The schedule object also contains data for payment dates, payment dates for notional\n        exchanges and ex-dividend dates for each period.\n\n        .. note::\n\n           The following are **period parameters** combined with the ``schedule``.\n\n    convention: str, :green:`optional (set by 'defaults')`\n        The day count convention applied to calculations of period accrual dates.\n        See :meth:`~rateslib.scheduling.dcf`.\n\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency : str, :green:`optional (set by 'defaults')`\n        The local settlement currency of the leg (3-digit code).\n    notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The initial leg notional, defined in units of *reference currency*.\n    initial_exchange : bool, :green:`optional (set as False)`\n        Whether to also include an initial notional exchange. If *True* then ``final_exchange``\n        **will** also be set to *True*.\n    final_exchange : bool, :green:`optional (set as initial_exchange)`\n        Whether to also include a final notional exchange and interim amortization\n        notional exchanges.\n\n        .. note::\n\n           The following define **rate parameters**.\n\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for each period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the schedule for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in each period rate determination.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        See :ref:`Fixings <fixings-doc>`.\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader. See also :ref:`fixings <fixings-doc>`.\n\n        .. note::\n\n           The following define **non-deliverable** parameters. If the *Leg* is directly\n           deliverable then do not set a non-deliverable ``pair`` or any ``fx_fixings``.\n\n    pair: FXIndex, str, :green:`optional`\n        The :class:`~rateslib.data.fixings.FXIndex` for :class:`~rateslib.data.fixings.FXFixing`\n        defining the currency pair that determines *Period*\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according\n        to non-deliverability. Review the **notes** section non-deliverability\n        on a :class:`~rateslib.legs.FixedLeg` and see also :ref:`fixings <fixings-doc>`.\n    mtm: bool, :green:`optional (set to False)`\n        Define whether the non-deliverability depends on a single\n        :class:`~rateslib.data.fixings.FXFixing` defined at the start of the *Leg*, or the end.\n        Review the **notes** section non-deliverability on a :class:`~rateslib.legs.FixedLeg`.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value applied as the base index value for all *Periods*.\n        If not given and ``index_fixings`` is a string fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`\n        The index value for the reference date.\n        Best practice is to supply this value as string identifier relating to the global\n        ``fixings`` object. See also :ref:`fixings <fixings-doc>`.\n    \"\"\"\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` associated with\n        the first :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._regular_periods[0].settlement_params\n\n    @cached_property\n    def periods(self) -> Sequence[_BasePeriod]:\n        \"\"\"A list of all contained *Periods*.\"\"\"\n        periods_: list[_BasePeriod] = []\n\n        if self._exchange_periods[0] is not None:\n            periods_.append(self._exchange_periods[0])\n        periods_.extend(self._regular_periods)\n        if self._exchange_periods[1] is not None:\n            periods_.append(self._exchange_periods[1])\n\n        return periods_\n\n    @property\n    def schedule(self) -> Schedule:\n        \"\"\"The :class:`~rateslib.scheduling.Schedule` object of *Leg*.\"\"\"\n        return self._schedule\n\n    @property\n    def amortization(self) -> Amortization:\n        \"\"\"\n        The :class:`~rateslib.legs.Amortization` object associated with the schedule.\n        \"\"\"\n        return self._amortization\n\n    @property\n    def rate_params(self) -> _FloatRateParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._FloatRateParams` associated with\n        the first :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._regular_periods[0].rate_params\n\n    @property\n    def float_spread(self) -> DualTypes:\n        \"\"\"The float spread parameter of each composited\n        :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._regular_periods[0].rate_params.float_spread\n\n    @float_spread.setter\n    def float_spread(self, value: DualTypes) -> None:\n        for period in self._regular_periods:\n            period.rate_params.float_spread = value\n\n    @property\n    def float_periods(self) -> CustomLeg:\n        \"\"\"A :class:`~rateslib.legs.CustomLeg` containing the individual\n        :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return CustomLeg(self._regular_periods[0].float_periods)\n\n    def __init__(\n        self,\n        schedule: Schedule,\n        *,\n        float_spread: DualTypes_ = NoInput(0),\n        rate_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        fixing_method: FloatFixingMethod | str_ = NoInput(0),\n        spread_compound_method: SpreadCompoundMethod | str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # settlement and currency\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        # non-deliverable\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: LegFixings = NoInput(0),\n        mtm: bool = False,\n        # period\n        convention: str_ = NoInput(0),\n        initial_exchange: bool = False,\n        final_exchange: bool = False,\n        # index params\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n    ) -> None:\n        self._schedule = schedule\n        if self.schedule.frequency == \"Z\":\n            raise ValueError(\n                \"`frequency` for a ZeroFloatLeg should not be 'Z'. The Leg is zero frequency by \"\n                \"construction. Set the `frequency` equal to the compounding frequency of the \"\n                \"expressed fixed rate, e.g. 'S' for semi-annual compounding.\",\n            )\n        self._notional: DualTypes = _drb(defaults.notional, notional)\n        self._currency: str = _drb(defaults.base_currency, currency).lower()\n        self._convention: str = _drb(defaults.convention, convention)\n        self._amortization = Amortization(n=self.schedule.n_periods, initial=self._notional)\n\n        index_fixings_ = _leg_fixings_to_list(index_fixings, self.schedule.n_periods)\n        fx_fixings_ = _leg_fixings_to_list(fx_fixings, self.schedule.n_periods)\n\n        # Exchange periods\n        if not initial_exchange:\n            _ini_cf: Cashflow | None = None\n        else:\n            _ini_cf = Cashflow(\n                payment=self.schedule.pschedule2[0],\n                notional=-self._amortization.outstanding[0],\n                currency=self._currency,\n                ex_dividend=self.schedule.pschedule3[0],\n                # non-deliverable\n                pair=pair,\n                fx_fixings=fx_fixings_[0],\n                delivery=self.schedule.pschedule2[0],\n                # index params\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[0],\n                index_base_date=self.schedule.aschedule[0],\n                index_reference_date=self.schedule.aschedule[0],\n            )\n        final_exchange_ = final_exchange or initial_exchange\n        if not final_exchange_:\n            _final_cf: Cashflow | None = None\n        else:\n            _final_cf = Cashflow(\n                payment=self.schedule.pschedule2[-1],\n                notional=self._amortization.outstanding[-1],\n                currency=self._currency,\n                ex_dividend=self.schedule.pschedule3[-1],\n                # non-deliverable\n                pair=pair,\n                fx_fixings=fx_fixings_[0] if not mtm else fx_fixings_[-1],\n                delivery=self.schedule.pschedule2[0] if not mtm else self.schedule.pschedule2[-2],\n                # index parameters\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[-1],\n                index_base_date=self.schedule.aschedule[0],\n                index_reference_date=self.schedule.aschedule[-1],\n            )\n        self._exchange_periods = (_ini_cf, _final_cf)\n\n        self._regular_periods = (\n            ZeroFloatPeriod(\n                float_spread=float_spread,\n                rate_fixings=rate_fixings,\n                fixing_method=fixing_method,\n                spread_compound_method=spread_compound_method,\n                fixing_frequency=fixing_frequency,\n                fixing_series=fixing_series,\n                schedule=self.schedule,\n                # currency args\n                currency=self._currency,\n                notional=self._notional,\n                # period params\n                convention=self._convention,\n                # non-deliverable: Not allowed with notional exchange\n                pair=pair,\n                fx_fixings=fx_fixings_[0],\n                delivery=self.schedule.pschedule2[0]\n                if (not mtm or final_exchange)\n                else self.schedule.pschedule2[-1],\n                # index params\n                index_base=index_base,\n                index_lag=index_lag,\n                index_method=index_method,\n                index_fixings=index_fixings_[0],\n            ),\n        )\n\n    def spread(\n        self,\n        *,\n        target_npv: DualTypes,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        original_z = self.float_spread\n\n        def s(g: DualTypes) -> DualTypes:\n            \"\"\"\n            This determines the NPV of the *Leg* subject to a given float spread change denoted, g.\n            \"\"\"\n            self.float_spread = g\n            iteration_local_npv = self.local_npv(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=index_curve,\n                fx=fx,\n                forward=forward,\n                settlement=settlement,\n            )\n            return iteration_local_npv\n\n        result = ift_1dim(\n            s=s,\n            s_tgt=target_npv,\n            h=\"ytm_quadratic\",\n            ini_h_args=(-300, 300, 1200),\n            # h=\"modified_brent\",\n            # ini_h_args=(-10000, 10000),\n            func_tol=1e-6,\n            conv_tol=1e-6,\n        )\n\n        self.float_spread = original_z\n        _: DualTypes = result[\"g\"]\n        return _\n"
  },
  {
    "path": "python/rateslib/legs/protocols/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom abc import ABCMeta\n\nfrom rateslib.legs.protocols.analytic_delta import _WithAnalyticDelta\nfrom rateslib.legs.protocols.analytic_fixings import _WithAnalyticRateFixings\nfrom rateslib.legs.protocols.cashflows import _WithCashflows, _WithExDiv\nfrom rateslib.legs.protocols.fixings import _WithFixings\nfrom rateslib.legs.protocols.npv import _WithNPV\n\n\nclass _BaseLeg(\n    _WithFixings,  # inherits _WIthNPV so first in MRO\n    _WithNPV,\n    _WithCashflows,\n    _WithAnalyticDelta,\n    _WithAnalyticRateFixings,\n    metaclass=ABCMeta,\n):\n    \"\"\"Abstract base class used in the construction of *Legs*.\"\"\"\n\n    pass\n\n\n__all__ = [\n    \"_WithNPV\",\n    \"_WithCashflows\",\n    \"_WithFixings\",\n    \"_WithAnalyticDelta\",\n    \"_WithAnalyticRateFixings\",\n    \"_WithExDiv\",\n    \"_BaseLeg\",\n]\n"
  },
  {
    "path": "python/rateslib/legs/protocols/analytic_delta.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.periods.utils import (\n    _maybe_local,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurveOption_,\n        DualTypes,\n        FXForwards_,\n        Sequence,\n        _BaseCurve_,\n        _BasePeriod,\n        _FXVolOption_,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithAnalyticDelta(Protocol):\n    \"\"\"\n    Protocol to calculate analytical rate delta sensitivities of any *Leg* type.\n\n    \"\"\"\n\n    @property\n    def periods(self) -> Sequence[_BasePeriod]: ...\n\n    def local_analytic_delta(\n        self,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the analytic rate delta of a *Period* expressed in its local settlement currency.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        local_analytic_delta: DualTypes = sum(\n            _.try_local_analytic_delta(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx=fx,\n                fx_vol=fx_vol,\n                settlement=settlement,\n                forward=forward,\n            ).unwrap()\n            for _ in self.periods\n        )\n        return local_analytic_delta\n\n    def analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Calculate the analytic rate delta of a *Period* expressed in a base currency.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        base: str, optional\n            The currency to convert the *local settlement* NPV to.\n        local: bool, optional\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        local_analytic_delta: DualTypes = sum(\n            _.try_local_analytic_delta(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx=fx,\n                fx_vol=fx_vol,\n                settlement=settlement,\n                forward=forward,\n            ).unwrap()\n            for _ in self.periods\n        )\n        return _maybe_local(\n            value=local_analytic_delta,\n            local=local,\n            currency=self.periods[0].settlement_params.currency,\n            fx=fx,\n            base=base,\n            forward=forward,\n        )\n"
  },
  {
    "path": "python/rateslib/legs/protocols/analytic_fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame, concat\n\nfrom rateslib.enums.generics import NoInput\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurveOption_,\n        FXForwards_,\n        Sequence,\n        _BaseCurve_,\n        _BasePeriod,\n        _FXVolOption_,\n        datetime_,\n    )\n\n\nclass _WithAnalyticRateFixings(Protocol):\n    \"\"\"\n    Protocol to calculate analytical rate fixing sensitivities of any *Leg* type.\n\n    \"\"\"\n\n    @property\n    def periods(self) -> Sequence[_BasePeriod]: ...\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Return a DataFrame of financial sensitivity to published interest rate fixings,\n        expressed in local **settlement currency** of the *Period*.\n\n        If the *Period* has no sensitivity to rates fixings this *DataFrame* is empty.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        dfs = []\n        for period in self.periods:\n            dfs.append(\n                period.local_analytic_rate_fixings(\n                    rate_curve=rate_curve,\n                    index_curve=index_curve,\n                    disc_curve=disc_curve,\n                    fx=fx,\n                    fx_vol=fx_vol,\n                    settlement=settlement,\n                    forward=forward,\n                )\n            )\n\n        with warnings.catch_warnings():\n            # TODO: pandas 2.1.0 has a FutureWarning for concatenating DataFrames with Null entries\n            warnings.filterwarnings(\"ignore\", category=FutureWarning)\n            return concat(dfs)\n"
  },
  {
    "path": "python/rateslib/legs/protocols/cashflows.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame\n\nfrom rateslib.curves import index_left\nfrom rateslib.enums.generics import NoInput\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurveOption_,\n        FXForwards_,\n        Schedule,\n        Sequence,\n        _BaseCurve_,\n        _BasePeriod,\n        _FXVolOption_,\n        _IRVolOption_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithCashflows(Protocol):\n    \"\"\"\n    Protocol to generate cashflows of any *Leg* type.\n\n    \"\"\"\n\n    @property\n    def periods(self) -> Sequence[_BasePeriod]: ...\n\n    def cashflows(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Return aggregated cashflow data for the *Leg*.\n\n        .. warning::\n\n           This method is a convenience method to provide a visual representation of all\n           associated calculation data. Calling this method to extracting certain values\n           should be avoided. It is more efficent to source relevant parameters or calculations\n           from object attributes or other methods directly.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, :green:`optional`\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, :green:`optional`\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, :green:`optional`\n            Used to discount cashflows.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, :green:`optional`\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, :green:`optional`\n            The IR volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        base: str, :green:`optional`\n            The currency to convert relevant values into.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        seq = [\n            period.cashflows(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                index_curve=index_curve,\n                fx=fx,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n                base=base,\n                settlement=settlement,\n                forward=forward,\n            )\n            for period in self.periods\n        ]\n        return DataFrame.from_records(seq)\n\n\nclass _WithExDiv(Protocol):\n    \"\"\"\n    Protocol to determine if a *Leg* is ex-dividend on a given settlement.\n\n    \"\"\"\n\n    @property\n    def schedule(self) -> Schedule: ...\n\n    def _period_index(self, settlement: datetime) -> int:\n        \"\"\"\n        Get the period index for that which the settlement date fall within.\n        Uses adjusted dates.\n        \"\"\"\n        _: int = index_left(\n            self.schedule.aschedule,\n            len(self.schedule.aschedule),\n            settlement,\n        )\n        return _\n\n    def ex_div(self, settlement: datetime) -> bool:\n        \"\"\"\n        Return a boolean whether the security is ex-div at the given settlement.\n\n        Parameters\n        ----------\n        settlement : datetime\n            The settlement date to test.\n\n        Returns\n        -------\n        bool\n\n        Notes\n        -----\n        Uses the UK DMO convention of returning *False* if ``settlement``\n        **is on or before** the ex-div date for a regular coupon period.\n\n        This is evaluated by analysing the attribute ``pschedule3`` of the associated\n        :class:`~rateslib.scheduling.Schedule` object of the *Leg*.\n        \"\"\"\n        left_period_index = self._period_index(settlement)\n        ex_div_date = self.schedule.pschedule3[left_period_index + 1]\n        return settlement > ex_div_date\n"
  },
  {
    "path": "python/rateslib/legs/protocols/fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame, Series\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.legs.protocols.npv import _WithNPV\nfrom rateslib.periods.protocols.fixings import (\n    _replace_fixings_with_ad_variables,\n    _reset_fixings_data,\n    _structure_sensitivity_data,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurveOption_,\n        DualTypes,\n        FXForwards_,\n        Sequence,\n        _BaseCurve_,\n        _BasePeriod,\n        _FXVolOption_,\n        datetime_,\n        int_,\n    )\n\n\nclass _WithFixings(_WithNPV, Protocol):\n    \"\"\"\n    Protocol for determining fixing sensitivity for a *Period* with AD.\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithFixings.reset_fixings\n\n    \"\"\"\n\n    @property\n    def periods(self) -> Sequence[_BasePeriod]: ...\n\n    def reset_fixings(self, state: int_ = NoInput(0)) -> None:\n        \"\"\"\n        Resets any fixings values of the *Leg* derived using the given data state.\n\n        .. role:: green\n\n        Parameters\n        ----------\n        state: int, :green:`optional`\n            The *state id* of the data series that set the fixing. Only fixings determined by this\n            data will be reset. If not given resets all fixings.\n\n        Returns\n        -------\n        None\n        \"\"\"\n        for period in self.periods:\n            period.reset_fixings(state)\n\n    def local_fixings(\n        self,\n        identifiers: Sequence[tuple[str, Series]],\n        scalars: Sequence[float] | NoInput = NoInput(0),\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Calculate the sensitivity to fixings of the *Instrument*, expressed in local\n        settlement currency.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        indentifiers: Sequence of tuple[str, Series], :red:`required`\n            These are the series string identifiers and the data values that will be used in each\n            Series to determine the sensitivity against.\n        scalars: Sequence of floats, :green:`optional (each set as 1.0)`\n            A sequence of scalars to multiply the sensitivities by for each on of the\n            ``identifiers``.\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional (set as immediate date)\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional (set as ``settlement``)\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        original_data, index, state = _replace_fixings_with_ad_variables(identifiers)\n        # Extract sensitivity data\n        pv: dict[str, DualTypes] = {\n            self.settlement_params.currency: self.local_npv(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx=fx,\n                fx_vol=fx_vol,\n                settlement=settlement,\n                forward=forward,\n            )\n        }\n        df = _structure_sensitivity_data(pv, index, identifiers, scalars)\n        _reset_fixings_data(self, original_data, state, identifiers)\n        return df\n"
  },
  {
    "path": "python/rateslib/legs/protocols/npv.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.periods.parameters import _SettlementParams\nfrom rateslib.periods.utils import (\n    _maybe_local,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        CurveOption_,\n        DualTypes,\n        FXForwards_,\n        Sequence,\n        _BaseCurve_,\n        _BasePeriod,\n        _FXVolOption_,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithNPV(Protocol):\n    \"\"\"\n    Protocol to establish value of any *Leg* type.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n       ~_WithNPV.spread\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithNPV.local_npv\n       ~_WithNPV.npv\n\n    \"\"\"\n\n    @property\n    def periods(self) -> Sequence[_BasePeriod]:\n        ...\n        # \"\"\"List of *Periods* associated with the *Leg*.\"\"\"\n        # return self._periods\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` of the\n        first *Period* of the *Leg*.\"\"\"\n        return self.periods[0].settlement_params\n\n    def local_npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the NPV of the *Leg* expressed in local settlement currency.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n\n        \"\"\"\n        # a Leg only has cashflows in one single currency, so some up those values first\n        # then format for necessary dict output if required.\n        local_npv: DualTypes = sum(\n            _.local_npv(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx=fx,\n                fx_vol=fx_vol,\n                settlement=settlement,\n                forward=forward,\n            )\n            for _ in self.periods\n        )\n        return local_npv\n\n    def npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Calculate the NPV of the *Period* converted to any other *base* accounting currency.\n\n        .. hint::\n\n           If the cashflows are unspecified or incalculable due to missing information this method\n           will raise an exception. For a function that returns a `Result` indicating success or\n           failure use :meth:`~rateslib.periods._WithNPV.try_local_npv`.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        base: str, optional\n            The currency to convert the *local settlement* NPV to.\n        local: bool, optional\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable or dict of such indexed by string currency.\n\n        Notes\n        -----\n        If ``base`` is not provided then this function will return the value obtained from\n        :meth:`~rateslib.periods._WithNPV.try_local_npv`.\n\n        If ``base`` is provided this then an :class:`~rateslib.fx.FXForwards` object may be\n        required to perform conversions. An :class:`~rateslib.fx.FXRates` object is also allowed\n        for this conversion although best practice does not recommend it due to possible\n        settlement date conflicts.\n        \"\"\"\n        # a Leg only has cashflows in one single currency, so some up those values first\n        # then format for necessary dict output if required.\n        local_npv: DualTypes = sum(\n            _.local_npv(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx=fx,\n                fx_vol=fx_vol,\n                settlement=settlement,\n                forward=forward,\n            )\n            for _ in self.periods\n        )\n        return _maybe_local(\n            value=local_npv,\n            local=local,\n            currency=self.settlement_params.currency,\n            fx=fx,\n            base=base,\n            forward=forward,\n        )\n\n    def spread(\n        self,\n        *,\n        target_npv: DualTypes,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Calculate a spread metric which when applied to the *Leg* allows it to attain the target\n        value.\n\n        Parameters\n        ----------\n        target_npv: DualTypes, required\n            The target value of the *Leg* measured using all of the other given arguments.\n            Must be expressed in local settlement currency units.\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        raise NotImplementedError(f\"Method: `spread` is not available for {type(self).__name__}.\")\n"
  },
  {
    "path": "python/rateslib/local_types.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n# This module is reserved only for typing purposes.\n# It avoids all circular import by performing a TYPE_CHECKING check on any component.\n\nfrom collections.abc import Callable as Callable\nfrom collections.abc import Iterable as Iterable\nfrom collections.abc import Sequence as Sequence\nfrom datetime import datetime as datetime\nfrom typing import Any as Any\nfrom typing import NoReturn as NoReturn\nfrom typing import Protocol, TypeAlias\n\nimport numpy as np\nfrom pandas import DataFrame as DataFrame\nfrom pandas import Series as Series\n\nfrom rateslib.curves import RolledCurve as RolledCurve\nfrom rateslib.curves import ShiftedCurve as ShiftedCurve\nfrom rateslib.curves import TranslatedCurve as TranslatedCurve\nfrom rateslib.curves import _BaseCurve as _BaseCurve\nfrom rateslib.curves import _CurveMeta as _CurveMeta\nfrom rateslib.data.fixings import FloatRateIndex as FloatRateIndex\nfrom rateslib.data.fixings import FloatRateSeries as FloatRateSeries\nfrom rateslib.data.fixings import FXFixing as FXFixing\nfrom rateslib.data.fixings import FXIndex as FXIndex\nfrom rateslib.data.fixings import IBORFixing as IBORFixing\nfrom rateslib.data.fixings import IBORStubFixing as IBORStubFixing\nfrom rateslib.data.fixings import IndexFixing as IndexFixing\nfrom rateslib.data.fixings import IRSSeries as IRSSeries\nfrom rateslib.data.fixings import RFRFixing as RFRFixing\nfrom rateslib.data.loader import Fixings as Fixings\nfrom rateslib.data.loader import _BaseFixingsLoader as _BaseFixingsLoader\nfrom rateslib.default import PlotOutput as PlotOutput\nfrom rateslib.dual.variable import Variable as Variable\nfrom rateslib.enums.generics import NoInput as NoInput\nfrom rateslib.enums.generics import Result as Result\nfrom rateslib.enums.parameters import FloatFixingMethod as FloatFixingMethod\nfrom rateslib.enums.parameters import FXDeltaMethod as FXDeltaMethod\nfrom rateslib.enums.parameters import FXOptionMetric as FXOptionMetric\nfrom rateslib.enums.parameters import IndexMethod as IndexMethod\nfrom rateslib.enums.parameters import IROptionMetric as IROptionMetric\nfrom rateslib.enums.parameters import OptionPricingModel as OptionPricingModel\nfrom rateslib.enums.parameters import OptionType as OptionType\nfrom rateslib.enums.parameters import SpreadCompoundMethod as SpreadCompoundMethod\nfrom rateslib.enums.parameters import SwaptionSettlementMethod as SwaptionSettlementMethod\nfrom rateslib.fx import FXForwards as FXForwards\nfrom rateslib.fx import FXRates as FXRates\nfrom rateslib.instruments import CDS as CDS\nfrom rateslib.instruments import FRA as FRA\nfrom rateslib.instruments import IIRS as IIRS\nfrom rateslib.instruments import IRS as IRS\nfrom rateslib.instruments import SBS as SBS\nfrom rateslib.instruments import XCS as XCS\nfrom rateslib.instruments import ZCIS as ZCIS\nfrom rateslib.instruments import ZCS as ZCS\nfrom rateslib.instruments import Bill as Bill\nfrom rateslib.instruments import FixedRateBond as FixedRateBond\nfrom rateslib.instruments import FloatRateNote as FloatRateNote\nfrom rateslib.instruments import Fly as Fly\nfrom rateslib.instruments import FXBrokerFly as FXBrokerFly\nfrom rateslib.instruments import FXCall as FXCall\nfrom rateslib.instruments import FXPut as FXPut\nfrom rateslib.instruments import FXRiskReversal as FXRiskReversal\nfrom rateslib.instruments import FXStraddle as FXStraddle\nfrom rateslib.instruments import FXStrangle as FXStrangle\nfrom rateslib.instruments import FXSwap as FXSwap\nfrom rateslib.instruments import IndexFixedRateBond as IndexFixedRateBond\nfrom rateslib.instruments import Portfolio as Portfolio\nfrom rateslib.instruments import Spread as Spread\nfrom rateslib.instruments import STIRFuture as STIRFuture\nfrom rateslib.instruments import Value as Value\nfrom rateslib.instruments import _BaseInstrument as _BaseInstrument\nfrom rateslib.instruments.ir_options import _BaseIRSOption as _BaseIRSOption\nfrom rateslib.instruments.protocols.kwargs import _KWArgs as _KWArgs\nfrom rateslib.instruments.protocols.pricing import _Curves as _Curves\nfrom rateslib.instruments.protocols.pricing import _Vol as _Vol\nfrom rateslib.legs import CreditPremiumLeg as CreditPremiumLeg\nfrom rateslib.legs import CreditProtectionLeg as CreditProtectionLeg\nfrom rateslib.legs import FixedLeg as FixedLeg\nfrom rateslib.legs import FloatLeg as FloatLeg\nfrom rateslib.legs import ZeroFixedLeg as ZeroFixedLeg\nfrom rateslib.legs import ZeroFloatLeg as ZeroFloatLeg\nfrom rateslib.legs.protocols import _BaseLeg as _BaseLeg\nfrom rateslib.periods import Cashflow as Cashflow\nfrom rateslib.periods import CreditPremiumPeriod as CreditPremiumPeriod\nfrom rateslib.periods import CreditProtectionPeriod as CreditProtectionPeriod\nfrom rateslib.periods import FixedPeriod as FixedPeriod\nfrom rateslib.periods import FloatPeriod as FloatPeriod\nfrom rateslib.periods import FXCallPeriod as FXCallPeriod\nfrom rateslib.periods import FXPutPeriod as FXPutPeriod\nfrom rateslib.periods import ZeroFloatPeriod as ZeroFloatPeriod\nfrom rateslib.periods import _BaseFXOptionPeriod as _BaseFXOptionPeriod\nfrom rateslib.periods import _BaseIRSOptionPeriod as _BaseIRSOptionPeriod\nfrom rateslib.periods.parameters import _FloatRateParams as _FloatRateParams\nfrom rateslib.periods.parameters import _IndexParams as _IndexParams\nfrom rateslib.periods.parameters import _NonDeliverableParams as _NonDeliverableParams\nfrom rateslib.periods.parameters import _PeriodParams as _PeriodParams\nfrom rateslib.periods.parameters import _SettlementParams as _SettlementParams\nfrom rateslib.periods.protocols import _BasePeriod as _BasePeriod\nfrom rateslib.rs import Adjuster as Adjuster\nfrom rateslib.rs import (\n    FlatBackwardInterpolator,\n    FlatForwardInterpolator,\n    LinearInterpolator,\n    LinearZeroRateInterpolator,\n    LogLinearInterpolator,\n    NullInterpolator,\n)\nfrom rateslib.rs import StubInference as StubInference\nfrom rateslib.volatility import FXDeltaVolSmile as FXDeltaVolSmile\nfrom rateslib.volatility import FXDeltaVolSurface as FXDeltaVolSurface\nfrom rateslib.volatility import FXSabrSmile as FXSabrSmile\nfrom rateslib.volatility import FXSabrSurface as FXSabrSurface\nfrom rateslib.volatility import IRSabrCube as IRSabrCube\nfrom rateslib.volatility import IRSabrSmile as IRSabrSmile\nfrom rateslib.volatility import IRSplineCube as IRSplineCube\nfrom rateslib.volatility import IRSplineSmile as IRSplineSmile\nfrom rateslib.volatility import _BaseIRCube as _BaseIRCube\nfrom rateslib.volatility import _BaseIRSmile as _BaseIRSmile\nfrom rateslib.volatility import _IRVolPricingParams as _IRVolPricingParams\n\nCurveInterpolator: TypeAlias = \"FlatBackwardInterpolator | FlatForwardInterpolator | LinearInterpolator | LogLinearInterpolator | LinearZeroRateInterpolator | NullInterpolator\"\n\nfrom rateslib.rs import Cal as Cal\nfrom rateslib.rs import Convention as Convention\nfrom rateslib.rs import Dual as Dual\nfrom rateslib.rs import Dual2 as Dual2\nfrom rateslib.rs import Frequency as Frequency\nfrom rateslib.rs import LegIndexBase as LegIndexBase\nfrom rateslib.rs import NamedCal as NamedCal\nfrom rateslib.rs import PPSplineDual as PPSplineDual\nfrom rateslib.rs import PPSplineDual2 as PPSplineDual2\nfrom rateslib.rs import PPSplineF64 as PPSplineF64\nfrom rateslib.rs import RollDay as RollDay\nfrom rateslib.rs import UnionCal as UnionCal\nfrom rateslib.scheduling import Schedule as Schedule\nfrom rateslib.solver import Solver as Solver\n\nSolver_: TypeAlias = \"Solver | NoInput\"\n\nCalTypes: TypeAlias = \"Cal | UnionCal | NamedCal\"\nCalInput: TypeAlias = \"CalTypes | str | NoInput\"\nAdjuster_: TypeAlias = \"Adjuster | NoInput\"\nFXIndex_: TypeAlias = \"FXIndex | NoInput\"\n\nDualTypes: TypeAlias = \"float | Dual | Dual2 | Variable\"\nDualTypes_: TypeAlias = \"DualTypes | NoInput\"\n\nNumber: TypeAlias = \"float | Dual | Dual2\"\n\n# https://stackoverflow.com/questions/68916893/\nArr1dF64: TypeAlias = \"np.ndarray[tuple[int], np.dtype[np.float64]]\"\nArr2dF64: TypeAlias = \"np.ndarray[tuple[int, int], np.dtype[np.float64]]\"\nArr1dObj: TypeAlias = \"np.ndarray[tuple[int], np.dtype[np.object_]]\"\nArr2dObj: TypeAlias = \"np.ndarray[tuple[int, int], np.dtype[np.object_]]\"\nArr3dObj: TypeAlias = \"np.ndarray[tuple[int, int, int], np.dtype[np.object_]]\"\n\nPeriodFixings: TypeAlias = \"DualTypes | Series[DualTypes] | str | NoInput\"\nLegFixings: TypeAlias = \"PeriodFixings | list[PeriodFixings] | tuple[PeriodFixings, PeriodFixings]\"\n\nFixingsRates: TypeAlias = \"Series[DualTypes] | list[DualTypes | list[DualTypes] | Series[DualTypes] | NoInput] | tuple[DualTypes, Series[DualTypes]] | DualTypes\"\nFixingsRates_: TypeAlias = \"FixingsRates | NoInput\"\n\nFixingsFx: TypeAlias = (\n    \"DualTypes | list[DualTypes] | Series[DualTypes] | tuple[DualTypes, Series[DualTypes]]\"\n)\nFixingsFx_: TypeAlias = \"FixingsFx | NoInput\"\n\nstr_: TypeAlias = \"str | NoInput\"\nbool_: TypeAlias = \"bool | NoInput\"\nint_: TypeAlias = \"int | NoInput\"\ndatetime_: TypeAlias = \"datetime | NoInput\"\nfloat_: TypeAlias = \"float | NoInput\"\n\n# _BaseCurve is an ABC\n_BaseCurve_: TypeAlias = \"_BaseCurve | NoInput\"\n_BaseCurveOrId: TypeAlias = \"_BaseCurve | str\"  # used as best practice for Solver mappings\n_BaseCurveOrId_: TypeAlias = \"_BaseCurveOrId | NoInput\"\n_BaseCurveOrIdDict: TypeAlias = (\n    \"dict[str, _BaseCurve | str] | dict[str, _BaseCurve] | dict[str, str]\"\n)\n_BaseCurveDict: TypeAlias = \"dict[str, _BaseCurve]\"\n_BaseCurveOrDict: TypeAlias = \"_BaseCurve | _BaseCurveDict\"\n_BaseCurveOrIdOrIdDict: TypeAlias = \"_BaseCurveOrId | _BaseCurveOrIdDict\"\n_BaseCurveOrDict_: TypeAlias = \"_BaseCurve | _BaseCurveDict | NoInput\"\n_BaseCurveOrIdOrIdDict_: TypeAlias = \"_BaseCurveOrId | _BaseCurveOrIdDict | NoInput\"\nCurvesT: TypeAlias = \"_BaseCurveOrIdOrIdDict | Sequence[CurveOrId | CurveDict] | _Curves\"\nCurvesT_: TypeAlias = \"CurvesT | NoInput\"\n\n_FXVolObj: TypeAlias = \"FXDeltaVolSurface | FXDeltaVolSmile | FXSabrSmile | FXSabrSurface\"\n_FXVolOption: TypeAlias = \"_FXVolObj | DualTypes\"\n_FXVolOption_: TypeAlias = \"_FXVolOption | NoInput\"\n\nFXVol: TypeAlias = \"_FXVolOption | str\"\nFXVol_: TypeAlias = \"FXVol | NoInput\"\n\n_IRVolObj: TypeAlias = \"_BaseIRSmile | _BaseIRCube[Any]\"\n_IRVolOption: TypeAlias = \"_IRVolObj | DualTypes\"\n_IRVolOption_: TypeAlias = \"_IRVolOption | NoInput\"\n\nIRVol: TypeAlias = \"_IRVolOption | str\"\nIRVol_: TypeAlias = \"IRVol | NoInput\"\n\nVolT: TypeAlias = \"IRVol | FXVol | _Vol\"\nVolT_: TypeAlias = \"VolT | NoInput\"\n\nVolStrat_: TypeAlias = \"Sequence[VolStrat_] | VolT | NoInput\"\nSeqVolT_: TypeAlias = \"Sequence[VolT_]\"\n\nCurveDict: TypeAlias = \"dict[str, _BaseCurve | str] | dict[str, _BaseCurve] | dict[str, str]\"\nCurveOrId: TypeAlias = \"_BaseCurve | str\"\nCurveOrId_: TypeAlias = \"CurveOrId | NoInput\"\n\nCurveInput: TypeAlias = \"CurveOrId | CurveDict\"\nCurveInput_: TypeAlias = \"CurveInput | NoInput\"\n\nCurveOption: TypeAlias = \"_BaseCurve | dict[str, _BaseCurve]\"\nCurveOption_: TypeAlias = \"CurveOption | NoInput\"\n\nCurves: TypeAlias = \"CurveOrId | CurveDict | Sequence[CurveOrId | CurveDict]\"\nCurves_: TypeAlias = \"CurveOrId_ | CurveDict | Sequence[CurveOrId_ | CurveDict]\"\n\nCurves_Tuple: TypeAlias = \"tuple[CurveOption_, CurveOption_, CurveOption_, CurveOption_]\"\nCurves_DiscTuple: TypeAlias = \"tuple[CurveOption_, _BaseCurve_, CurveOption_, _BaseCurve_]\"\n\n# this is a type for a wrapped `rate_curve`, `disc_curve` and `index_curve`\nPeriodCurves: TypeAlias = \"tuple[CurveOption_, _BaseCurve_, _BaseCurve_]\"\n\nFX: TypeAlias = \"DualTypes | FXRates | FXForwards\"\nFX_: TypeAlias = \"FX | NoInput\"\nFXRevised_: TypeAlias = \"FXRates | FXForwards | NoInput\"\nFXForwards_: TypeAlias = \"FXForwards | NoInput\"\n\n# NPV: TypeAlias = \"DualTypes | dict[str, DualTypes]\"\n#\n\n# Leg: TypeAlias = \"FixedLeg | FloatLeg | ZeroFloatLeg | ZeroFixedLeg | ZeroIndexLeg | CreditPremiumLeg | CreditProtectionLeg\"\n# Period: TypeAlias = \"FixedPeriod | FloatPeriod | Cashflow | CreditPremiumPeriod | CreditProtectionPeriod\"\n#\n# Security: TypeAlias = \"FixedRateBond | FloatRateNote | Bill | IndexFixedRateBond\"\n# FXOptionTypes: TypeAlias = (\n#     \"FXCall | FXPut | FXRiskReversal | FXStraddle | FXStrangle | FXBrokerFly | FXOptionStrat\"\n# )\n# RatesDerivative: TypeAlias = \"IRS | SBS | FRA | ZCS | STIRFuture\"\n# IndexDerivative: TypeAlias = \"IIRS | ZCIS\"\n# CurrencyDerivative: TypeAlias = \"XCS | FXSwap | FXForward\"\n# Combinations: TypeAlias = \"Portfolio | Fly | Spread | Value | VolValue\"\n#\n# Instrument: TypeAlias = (\n#     \"Combinations | Security | FXOptionTypes | RatesDerivative | CDS | CurrencyDerivative\"\n# )\n\n\nclass SupportsSolverMutability(Protocol):\n    @property\n    def _n(self) -> int: ...\n    @property\n    def _ini_solve(self) -> int: ...\n    def _set_ad_order(self, ad: int) -> None: ...\n    def _set_node_vector(self, vector: Arr1dObj, ad: int) -> None: ...\n    def _get_node_vars(self) -> tuple[str, ...]: ...\n    def _get_node_vector(self) -> Arr1dObj: ...\n\n\nclass SupportsRate(Protocol):\n    def rate(self, *args: Any, **kwargs: Any) -> DualTypes: ...\n\n    @property\n    def rate_scalar(self) -> float: ...\n\n\nclass SupportsMetrics:\n    def rate(self, *args: Any, **kwargs: Any) -> DualTypes: ...  # type: ignore[empty-body]\n    def npv(self, *args: Any, **kwargs: Any) -> DualTypes | dict[str, DualTypes]: ...  # type: ignore[empty-body]\n    def delta(self, *args: Any, **kwargs: Any) -> DataFrame: ...  # type: ignore[empty-body]\n    def gamma(self, *args: Any, **kwargs: Any) -> DataFrame: ...  # type: ignore[empty-body]\n    def cashflows(self, *args: Any, **kwargs: Any) -> DataFrame: ...  # type: ignore[empty-body]\n    def cashflows_table(self, *args: Any, **kwargs: Any) -> DataFrame: ...  # type: ignore[empty-body]\n\n\nclass _SupportsFixedFloatLeg1(Protocol):\n    @property\n    def leg1(self) -> FixedLeg | FloatLeg: ...\n"
  },
  {
    "path": "python/rateslib/mutability/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport os\nfrom collections import OrderedDict\nfrom collections.abc import Callable\nfrom functools import wraps\nfrom typing import TYPE_CHECKING, Generic, ParamSpec, TypeVar\n\nfrom rateslib import defaults\n\nif TYPE_CHECKING:\n    pass\n\nP = ParamSpec(\"P\")\nR = TypeVar(\"R\")\n\n\ndef _no_interior_validation(func: Callable[P, R]) -> Callable[P, R]:\n    \"\"\"\n    Used with a Solver to provide a context to set a flag to prevent repetitive validation,\n    for example during iteration. After conclusion of the function re-activate validation.\n    \"\"\"\n\n    @wraps(func)\n    def wrapper_no_interior_validation(*args: P.args, **kwargs: P.kwargs) -> R:\n        self = args[0]\n        if self._do_not_validate:  # type: ignore[attr-defined]\n            # make no changes: handle recursive no interior validations.\n            result = func(*args, **kwargs)\n        else:\n            # set to no further validation and reset at end of method\n            self._do_not_validate = True  # type: ignore[attr-defined]\n            result = func(*args, **kwargs)\n            self._do_not_validate = False  # type: ignore[attr-defined]\n        return result\n\n    return wrapper_no_interior_validation\n\n\ndef _validate_states(func: Callable[P, R]) -> Callable[P, R]:\n    \"\"\"\n    Add a decorator to a class instance method to first validate the object state before performing\n    additional operations. If a change is detected the implemented `validate_state` function\n    is responsible for resetting the cache and updating any `state_id`s.\n    \"\"\"\n\n    @wraps(func)\n    def wrapper_validate_states(*args: P.args, **kwargs: P.kwargs) -> R:\n        self = args[0]\n        self._validate_state()  # type: ignore[attr-defined]\n        return func(*args, **kwargs)\n\n    return wrapper_validate_states\n\n\ndef _clear_cache_post(func: Callable[P, R]) -> Callable[P, R]:\n    \"\"\"\n    Add a decorator to a class instance method to clear the cache and set a new state\n    post performing the function.\n    \"\"\"\n\n    @wraps(func)\n    def wrapper_clear_cache(*args: P.args, **kwargs: P.kwargs) -> R:\n        self = args[0]\n        result = func(*args, **kwargs)\n        self._clear_cache()  # type: ignore[attr-defined]\n        return result\n\n    return wrapper_clear_cache\n\n\ndef _new_state_post(func: Callable[P, R]) -> Callable[P, R]:\n    \"\"\"\n    Add a decorator to a class instance method to clear the cache and set a new state\n    post performing the function.\n    \"\"\"\n\n    @wraps(func)\n    def wrapper_new_state(*args: P.args, **kwargs: P.kwargs) -> R:\n        self = args[0]\n        result = func(*args, **kwargs)\n        self._set_new_state()  # type: ignore[attr-defined]\n        return result\n\n    return wrapper_new_state\n\n\nclass _WithState:\n    \"\"\"\n    Record and manage the `state_id` of mutable classes.\n\n    Attributes\n    ----------\n    _state: int: This is the most recent recorded state reference of this object.\n    _mutable_by_association: bool: This is a rateslib definition of whether this object is\n        directly mutable and therefore generates its own state id, or whether its state is\n        derived from the most recently evaluated state of its associated objects.\n    \"\"\"\n\n    _state: int = 0\n    _mutable_by_association: bool = False\n    _do_not_validate: bool = False\n\n    def _set_new_state(self) -> None:\n        \"\"\"Set the state_id of a superclass. Some objects which are 'mutable by association'\n        will overload the `get_compoisted_state` method to derive a state from their\n        associated items.\"\"\"\n        if self._mutable_by_association:\n            self._state = self._get_composited_state()\n        else:\n            self._state = hash(os.urandom(8))  # 64-bit entropy\n\n    def _validate_state(self) -> None:\n        \"\"\"Used by 'mutable by association' objects to evaluate if their own record of\n        associated objects states matches the current state of those objects.\n\n        Mutable by update objects have no concept of state validation, they simply maintain\n        a *state* id.\n        \"\"\"\n        return None\n\n    def _get_composited_state(self) -> int:\n        \"\"\"Used by 'mutable by association' objects to record the state of their associated\n        objects and set this as the object's own state.\"\"\"\n        raise NotImplementedError(\"Must be implemented for 'mutable by association' types\")\n\n\nKT = TypeVar(\"KT\")\nVT = TypeVar(\"VT\")\n\n\nclass _WithCache(Generic[KT, VT]):\n    _cache: OrderedDict[KT, VT]\n    _cache_len: int\n\n    def _cached_value(self, key: KT, val: VT) -> VT:\n        \"\"\"Used to add a value to the cache and control memory size when returning some\n        parameter from an object using cache and state management.\"\"\"\n        if defaults.curve_caching and key not in self._cache:\n            if self._cache_len < defaults.curve_caching_max:\n                self._cache[key] = val\n                self._cache_len += 1\n            else:\n                self._cache.popitem(last=False)\n                self._cache[key] = val\n        return val\n\n    def _clear_cache(self) -> None:\n        \"\"\"Clear the cache of values on a object controlled by cache and state management.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n        This should be used if any modification has been made to the *Curve*.\n        Users are advised against making direct modification to *Curve* classes once\n        constructed to avoid the issue of un-cleared caches returning erroneous values.\n\n        Alternatively the curve caching as a feature can be set to *False* in ``defaults``.\n        \"\"\"\n        self._cache = OrderedDict()\n        self._cache_len = 0\n"
  },
  {
    "path": "python/rateslib/periods/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom rateslib.periods.cashflow import (\n    Cashflow,\n    MtmCashflow,\n    # IndexCashflow,\n    # NonDeliverableCashflow,\n    # NonDeliverableIndexCashflow,\n)\nfrom rateslib.periods.credit import CreditPremiumPeriod, CreditProtectionPeriod\nfrom rateslib.periods.fixed_period import (\n    FixedPeriod,\n    # IndexFixedPeriod,\n    # NonDeliverableFixedPeriod,\n    # NonDeliverableIndexFixedPeriod,\n    ZeroFixedPeriod,\n)\nfrom rateslib.periods.float_period import (\n    FloatPeriod,\n    # IndexFloatPeriod,\n    # NonDeliverableFloatPeriod,\n    # NonDeliverableIndexFloatPeriod,\n    ZeroFloatPeriod,\n)\nfrom rateslib.periods.fx_volatility import FXCallPeriod, FXPutPeriod, _BaseFXOptionPeriod\nfrom rateslib.periods.ir_volatility import IRSCallPeriod, IRSPutPeriod, _BaseIRSOptionPeriod\nfrom rateslib.periods.protocols import _BasePeriod, _BasePeriodStatic\n\n__all__ = [\n    \"FixedPeriod\",\n    \"FloatPeriod\",\n    \"ZeroFixedPeriod\",\n    \"ZeroFloatPeriod\",\n    \"Cashflow\",\n    \"MtmCashflow\",\n    \"CreditPremiumPeriod\",\n    \"CreditProtectionPeriod\",\n    \"FXCallPeriod\",\n    \"FXPutPeriod\",\n    \"IRSCallPeriod\",\n    \"IRSPutPeriod\",\n    \"_BasePeriod\",\n    \"_BasePeriodStatic\",\n    \"_BaseFXOptionPeriod\",\n    \"_BaseIRSOptionPeriod\",\n]\n"
  },
  {
    "path": "python/rateslib/periods/cashflow.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom pandas import DataFrame\n\nfrom rateslib import defaults\nfrom rateslib.data.fixings import _get_fx_index, _maybe_get_fx_index\nfrom rateslib.enums.generics import NoInput, Ok, _drb\nfrom rateslib.enums.parameters import IndexMethod\nfrom rateslib.periods.parameters import (\n    _init_MtmParams,\n    _init_or_none_IndexParams,\n    _init_or_none_NonDeliverableParams,\n    _init_SettlementParams_with_fx_pair,\n)\nfrom rateslib.periods.parameters.mtm import _MtmParams\nfrom rateslib.periods.protocols import (\n    _BasePeriodStatic,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        FXIndex,\n        Result,\n        Series,\n        _BaseCurve_,\n        _FXVolOption_,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass Cashflow(_BasePeriodStatic):\n    r\"\"\"\n    A *Period* defined by a specific amount.\n\n    The expected unindexed reference cashflow under the risk neutral distribution is defined as,\n\n    .. math::\n\n       \\mathbb{E^Q} [\\bar{C}_t] = -N\n\n    There is no *analytical delta* for this *Period* type and hence :math:`\\xi` is not defined.\n\n    Examples\n    --------\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import Cashflow\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       period = Cashflow(\n           payment=dt(2025, 10, 22),\n           ex_dividend=dt(2025, 10, 21),\n           currency=\"eur\",\n           notional=125000,\n       )\n       period.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency: str, :green:`optional (set by 'defaults')`\n        The physical *settlement currency* of the *Period*.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional amount of the *Period* expressed in ``notional currency``.\n    payment: datetime, :red:`required`\n        The payment date of the *Period* cashflow.\n    ex_dividend: datetime, :green:`optional (set as 'payment')`\n        The ex-dividend date of the *Period*. Settlements occurring **after** this date\n        are assumed to be non-receivable.\n\n        .. note::\n\n           The following parameters define **non-deliverability**. If the *Period* is directly\n           deliverable do not supply these parameters.\n\n    pair: FXIndex, str, :green:`optional`\n        The currency pair of the :class:`~rateslib.data.fixings.FXFixing` that determines\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing`. If a scalar is used directly.\n        If a string identifier will link to the central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    delivery: datetime, :green:`optional (set as 'payment')`\n        The settlement delivery date of the :class:`~rateslib.data.fixings.FXFixing`.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value set of the base index value.\n        If not given and ``index_fixings`` is a str fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The index value for the reference date.\n        If a scalar value this is used directly. If a string identifier will link to the\n        central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n    index_base_date: datetime, :green:`optional`\n        The reference date for determining the base index value. Not required if ``_index_base``\n        value is given directly.\n    index_reference_date: datetime, :green:`optional (set as 'end')`\n        The reference date for determining the index value. Not required if ``_index_fixings``\n        is given as a scalar value.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which determines non-payment of notional on supported *Periods*.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        # currency args:\n        payment: datetime,\n        notional: DualTypes,\n        currency: str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n        # non-deliverable args:\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        delivery: datetime_ = NoInput(0),\n        # index-args:\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        index_only: bool_ = NoInput(0),\n        index_base_date: datetime_ = NoInput(0),\n        index_reference_date: datetime_ = NoInput(0),\n    ):\n        self._settlement_params = _init_SettlementParams_with_fx_pair(\n            _notional=notional,\n            _payment=payment,\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _ex_dividend=_drb(payment, ex_dividend),\n            _fx_pair=_maybe_get_fx_index(pair),\n        )\n        self._non_deliverable_params = _init_or_none_NonDeliverableParams(\n            _currency=self.settlement_params.currency,\n            _fx_index=pair,\n            _fx_fixings=fx_fixings,\n            _delivery=_drb(self.settlement_params.payment, delivery),\n        )\n        self._index_params = _init_or_none_IndexParams(\n            _index_base=index_base,\n            _index_lag=index_lag,\n            _index_method=index_method,\n            _index_fixings=index_fixings,\n            _index_base_date=index_base_date,\n            _index_reference_date=_drb(self.settlement_params.payment, index_reference_date),\n            _index_only=index_only,\n        )\n\n    def unindexed_reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        **kwargs: Any,\n    ) -> DualTypes:\n        return -self.settlement_params.notional\n\n    def try_unindexed_reference_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        return Ok(0.0)\n\n    def try_unindexed_reference_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        return Ok(DataFrame())\n\n\nclass MtmCashflow(_BasePeriodStatic):\n    r\"\"\"\n    A *Period* defined by a specific amount calculated from the difference between two\n    :class:`~rateslib.data.fixings.FXFixing`.\n\n    This type does not permit non-deliverability, although its notional is expressed in a\n    notional currency which is different to the settlement currency.\n\n    The expected unindexed reference cashflow under the risk neutral distribution is defined as,\n\n    .. math::\n\n       \\mathbb{E^Q} [\\bar{C}_t] = -N ( f_{ref:loc}(m_{a.e}) - f_{ref:loc}(m_{a.s}) )\n\n    There is no *analytical delta* for this *Period* type and hence :math:`\\xi` is not defined.\n\n    Examples\n    --------\n\n    This *MTMCashflow* is the movement of the EURUSD FX rate from 1.1 to 1.2 on a notional of\n    125,000 EUR resulting in a cashflow of -12,500 USD.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import MtmCashflow\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       period = MtmCashflow(\n           payment=dt(2025, 10, 22),\n           start=dt(2025, 7, 22),\n           currency=\"usd\",\n           pair=\"eurusd\",\n           notional=125000,\n           fx_fixings_start=1.10,\n           fx_fixings_end=1.20,\n       )\n       period.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency: str, :green:`optional (set by 'defaults')`\n        The physical *settlement currency* of the *Period*.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional amount of the *Period* expressed in ``notional currency``.\n    payment: datetime, :red:`required`\n        The payment date of the *Period* cashflow.\n    ex_dividend: datetime, :green:`optional (set as 'payment')`\n        The ex-dividend date of the *Period*. Settlements occurring **after** this date\n        are assumed to be non-receivable.\n\n        .. note::\n\n           The following parameters define the specific **mtm** aspects of the *cashflow*.\n\n    pair: FXIndex, str, :red:`required`\n        The currency pair of the two :class:`~rateslib.data.fixings.FXFixing` that determines\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    start: datetime, :red:`required`\n        The delivery date of the first :class:`~rateslib.data.fixings.FXFixing` at the start of\n        the *Period*.\n    end: datetime, :green:`optional (set as 'payment')`\n        The delivery date of the second :class:`~rateslib.data.fixings.FXFixing` at the end of\n        the *Period*.\n    fx_fixings_start: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the first :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier will link to the central ``fixings`` object and\n        data loader. See :ref:`fixings <fixings-doc>`.\n    fx_fixings_end: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the second :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier will link to the central ``fixings`` object and\n        data loader. See :ref:`fixings <fixings-doc>`.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value set of the base index value.\n        If not given and ``index_fixings`` is a str fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The index value for the reference date.\n        If a scalar value this is used directly. If a string identifier will link to the\n        central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n    index_base_date: datetime, :green:`optional`\n        The reference date for determining the base index value. Not required if ``_index_base``\n        value is given directly.\n    index_reference_date: datetime, :green:`optional (set as 'end')`\n        The reference date for determining the index value. Not required if ``_index_fixings``\n        is given as a scalar value.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which determines non-payment of notional on supported *Periods*.\n\n    \"\"\"\n\n    @property\n    def mtm_params(self) -> _MtmParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._MtmParams` of the\n        *Period*.\"\"\"\n        return self._mtm_params\n\n    def __init__(\n        self,\n        *,\n        payment: datetime,\n        notional: DualTypes,\n        pair: FXIndex | str,\n        start: datetime,\n        end: datetime_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n        fx_fixings_start: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        fx_fixings_end: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        # index-args:\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        index_only: bool_ = NoInput(0),\n        index_base_date: datetime_ = NoInput(0),\n        index_reference_date: datetime_ = NoInput(0),\n    ):\n        fx_index = _get_fx_index(pair)\n        self._settlement_params = _init_SettlementParams_with_fx_pair(\n            _notional=notional,\n            _payment=payment,\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _ex_dividend=_drb(payment, ex_dividend),\n            _fx_pair=fx_index,\n        )\n        self._mtm_params = _init_MtmParams(\n            _fx_index=fx_index,\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _start=start,\n            _end=_drb(payment, end),\n            _fx_fixings_start=fx_fixings_start,\n            _fx_fixings_end=fx_fixings_end,\n        )\n        self._non_deliverable_params = None\n        self._index_params = _init_or_none_IndexParams(\n            _index_base=index_base,\n            _index_lag=index_lag,\n            _index_method=index_method,\n            _index_fixings=index_fixings,\n            _index_base_date=index_base_date,\n            _index_reference_date=_drb(self.settlement_params.payment, index_reference_date),\n            _index_only=index_only,\n        )\n\n    def unindexed_reference_cashflow(  # type: ignore[override]\n        self,\n        *,\n        fx: FXForwards_ = NoInput(0),\n        **kwargs: Any,\n    ) -> DualTypes:\n        fx0 = self.mtm_params.fx_fixing_start.try_value_or_forecast(fx).unwrap()\n        fx1 = self.mtm_params.fx_fixing_end.try_value_or_forecast(fx).unwrap()\n        if self.mtm_params.fx_reversed:\n            diff = 1.0 / fx1 - 1.0 / fx0\n        else:\n            diff = fx1 - fx0\n        return -self.settlement_params.notional * diff\n\n    def try_unindexed_reference_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        return Ok(0.0)\n\n    def try_unindexed_reference_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        return Ok(DataFrame())\n"
  },
  {
    "path": "python/rateslib/periods/credit.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import timedelta\nfrom typing import TYPE_CHECKING\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import Err, NoInput, Ok, Result, _drb\nfrom rateslib.periods.parameters import (\n    _CreditParams,\n    _FixedRateParams,\n    _PeriodParams,\n    _SettlementParams,\n)\nfrom rateslib.periods.protocols import _BasePeriod\nfrom rateslib.periods.protocols.npv import _screen_ex_div_and_forward\nfrom rateslib.periods.utils import _maybe_local, _try_validate_base_curve, _validate_credit_curves\nfrom rateslib.scheduling import Convention, Frequency, get_calendar\nfrom rateslib.scheduling.adjuster import _get_adjuster\nfrom rateslib.scheduling.convention import _get_convention\nfrom rateslib.scheduling.frequency import _get_frequency\n\nif TYPE_CHECKING:  # pragma: no cover\n    from rateslib.local_types import (\n        Adjuster,\n        CalInput,\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        FXRevised_,\n        RollDay,\n        _BaseCurve,\n        _BaseCurve_,\n        _FXVolOption_,\n        _IRVolOption_,\n        _IRVolPricingParams,\n        bool_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass CreditPremiumPeriod(_BasePeriod):\n    r\"\"\"\n    A *Period* defined by a fixed interest rate and contingent credit event.\n\n    The immediate expected valuation of the *Period* cashflow is defined as;\n\n    .. math::\n\n       \\mathbb{E^Q} [V(m_T)C_T] = -N S d (Q(m_{a.s}) v(m_t) + V_{I_{pa}} )\n\n    where,\n\n    .. math::\n\n       V_{I_{pa}} = C_t I_{pa} v(m_{a.e}) \\times \\left \\{ \\begin{matrix}  \\frac{1}{2} \\left ( Q(m_{a.s}) - Q(m_{a.e}) \\right ) & m_{a.s} >= m_{today} \\\\ \\frac{\\tilde{n}+r}{2\\tilde{n}} \\left ( 1 - Q(m_{a.e}) \\right ) & m_{a.s} < m_{today} \\\\ \\end{matrix} \\right .\n\n    For *analytic delta* purposes the :math:`\\xi=-S`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import CreditPremiumPeriod\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       cp = CreditPremiumPeriod(\n           start=dt(2000, 3, 20),\n           end=dt(2000, 6, 20),\n           payment=dt(2000, 6, 20),\n           frequency=\"Q\",\n           fixed_rate=1.00,\n       )\n       cp.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency: str, :green:`optional (set by 'defaults')`\n        The physical *settlement currency* of the *Period*.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional amount of the *Period* expressed in ``notional currency``.\n    payment: datetime, :red:`required`\n        The payment date of the *Period* cashflow.\n    ex_dividend: datetime, :green:`optional (set as 'payment')`\n        The ex-dividend date of the *Period*. Settlements occurring **after** this date\n        are assumed to be non-receivable.\n\n        .. note::\n\n           The following parameters are scheduling **period** parameters\n\n    start: datetime, :red:`required`\n        The identified start date of the *Period*.\n    end: datetime, :red:`required`\n        The identified end date of the *Period*.\n    frequency: Frequency, str, :red:`required`\n        The :class:`~rateslib.scheduling.Frequency` associated with the *Period*.\n    convention: Convention, str, :green:`optional` (set by 'defaults')\n        The day count :class:`~rateslib.scheduling.Convention` associated with the *Period*.\n    termination: datetime, :green:`optional`\n        The termination date of an external :class:`~rateslib.scheduling.Schedule`.\n    calendar: Calendar, :green:`optional`\n         The calendar associated with the *Period*.\n    stub: bool, str, :green:`optional (set as False)`\n        Whether the *Period* is defined as a stub according to some external\n        :class:`~rateslib.scheduling.Schedule`.\n    roll: RollDay, int, str, :green:`optional (set by 'frequency')`\n        The rollday associated with any monthly :class:`~rateslib.scheduling.Frequency`, if\n        not directly associated with that object.\n    adjuster: Adjuster, :green:`optional`\n        The date :class:`~rateslib.scheduling.Adjuster` applied to unadjusted dates in the\n        external :class:`~rateslib.scheduling.Schedule` to arrive at adjusted accrual dates.\n\n        .. note::\n\n           The following define **fixed rate** parameters.\n\n    fixed_rate: float, Dual, Dual2, Variable, :green:`optional`\n        The fixed rate to determine the *Period* cashflow.\n\n        .. note::\n\n           The following parameters define **credit specific** elements.\n\n    premium_accrued: bool, :green:`optional (set by 'defaults')`\n        Whether an accrued premium is paid on the event of mid-period credit default.\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def credit_params(self) -> _CreditParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._CreditParams` of the *Period*.\"\"\"\n        return self._credit_params\n\n    @property\n    def rate_params(self) -> _FixedRateParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._FixedRateParams` of the *Period*.\"\"\"\n        return self._rate_params\n\n    @property\n    def period_params(self) -> _PeriodParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._PeriodParams` of the *Period*.\"\"\"\n        return self._period_params\n\n    def __init__(\n        self,\n        *,\n        # currency args:\n        payment: datetime,\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n        # period params\n        start: datetime,\n        end: datetime,\n        frequency: Frequency | str,\n        convention: str_ = NoInput(0),\n        termination: datetime_ = NoInput(0),\n        stub: bool = False,\n        roll: RollDay | int | str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        adjuster: Adjuster | str_ = NoInput(0),\n        # specific params\n        fixed_rate: DualTypes_ = NoInput(0),\n        premium_accrued: bool_ = NoInput(0),\n    ) -> None:\n        self._settlement_params = _SettlementParams(\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _notional_currency=_drb(defaults.base_currency, currency).lower(),\n            _payment=payment,\n            _notional=_drb(defaults.notional, notional),\n            _ex_dividend=_drb(payment, ex_dividend),\n        )\n        self._rate_params = _FixedRateParams(\n            _fixed_rate=fixed_rate,\n        )\n        self._credit_params = _CreditParams(\n            _premium_accrued=_drb(defaults.cds_premium_accrued, premium_accrued),\n        )\n        self._period_params = _PeriodParams(\n            _start=start,\n            _end=end,\n            _frequency=_get_frequency(frequency, roll, calendar),\n            _calendar=get_calendar(calendar),\n            _adjuster=NoInput(0) if isinstance(adjuster, NoInput) else _get_adjuster(adjuster),\n            _stub=stub,\n            _convention=_get_convention(_drb(defaults.convention, convention)),\n            _termination=termination,\n        )\n\n    def immediate_local_npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> DualTypes:\n        rate_curve_, disc_curve_ = _validate_credit_curves(rate_curve, disc_curve).unwrap()\n\n        cf = self.cashflow()\n        return cf * self._probability_adjusted_df(rate_curve_, disc_curve_)\n\n    def try_immediate_local_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXRevised_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        c = 0.0001 * self.period_params.dcf * self.settlement_params.notional\n\n        c_res = _validate_credit_curves(rate_curve, disc_curve)\n        if isinstance(c_res, Err):\n            return c_res\n        else:\n            rate_curve_, disc_curve_ = c_res.unwrap()\n\n        return Ok(c * self._probability_adjusted_df(rate_curve_, disc_curve_))\n\n    def cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> DualTypes:\n        if isinstance(self.rate_params.fixed_rate, NoInput):\n            raise ValueError(err.VE_NEEDS_FIXEDRATE)\n        return (\n            -self.rate_params.fixed_rate\n            * 0.01\n            * self.period_params.dcf\n            * self.settlement_params.notional\n        )\n\n    def try_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNPVStatic.cashflow`\n        with lazy exception handling.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        try:\n            v = self.cashflow(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx_vol=fx_vol,\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n    def _probability_adjusted_df(self, rate_curve: _BaseCurve, disc_curve: _BaseCurve) -> DualTypes:\n        v_payment = disc_curve[self.settlement_params.payment]\n        q_end = rate_curve[self.period_params.end]\n        if self.credit_params.premium_accrued:\n            v_end = disc_curve[self.period_params.end]\n            n = _dual_float((self.period_params.end - self.period_params.start).days)\n\n            if self.period_params.start < disc_curve.nodes.initial:\n                # then mid-period valuation\n                r: float = _dual_float((disc_curve.nodes.initial - self.period_params.start).days)\n                q_start: DualTypes = 1.0\n                _v_start: DualTypes = 1.0\n            else:\n                r = 0.0\n                q_start = rate_curve[self.period_params.start]\n                _v_start = disc_curve[self.period_params.start]\n\n            # method 1:\n            accrued_: DualTypes = 0.5 * (1 + r / n)\n            accrued_ *= q_start - q_end\n            accrued_ *= v_end\n\n            # # method 4 EXACT\n            # _ = 0.0\n            # for i in range(1, int(s)):\n            #     m_i, m_i2 = m_today + timedelta(days=i-1), m_today + timedelta(days=i)\n            #     _ += (\n            #     (i + r) / n * disc_curve[m_today + timedelta(days=i)] * (curve[m_i] - curve[m_i2])\n            #     )\n        else:\n            accrued_ = 0.0\n        return q_end * v_payment + accrued_\n\n    def try_accrued(self, settlement: datetime) -> Result[DualTypes]:\n        \"\"\"\n        Calculate the amount of premium accrued until a specific date within the *Period*, with\n        lazy error raising.\n\n        Parameters\n        ----------\n        settlement: datetime\n            The date against which accrued is measured.\n\n        Returns\n        -------\n        Result[float]\n        \"\"\"\n        if isinstance(self.rate_params.fixed_rate, NoInput):\n            return Err(ValueError(err.VE_NEEDS_FIXEDRATE))\n\n        c = (\n            -self.rate_params.fixed_rate\n            * 0.01\n            * self.period_params.dcf\n            * self.settlement_params.notional\n        )\n        start, end = self.period_params.start, self.period_params.end\n        if settlement <= start or settlement >= end:\n            return Ok(0.0)\n        return Ok(c * (settlement - start).days / (end - start).days)\n\n    def accrued(self, settlement: datetime) -> DualTypes:\n        \"\"\"\n        Calculate the amount of premium accrued until a specific date within the *Period*.\n\n        Parameters\n        ----------\n        settlement: datetime\n            The date against which accrued is measured.\n\n        Returns\n        -------\n        float\n        \"\"\"\n        return self.try_accrued(settlement).unwrap()\n\n\nclass CreditProtectionPeriod(_BasePeriod):\n    r\"\"\"\n    A *Period* defined by a credit event and contingent notional payment.\n\n    The immediate expected valuation of the *Period* cashflow is defined as;\n\n    .. math::\n\n       \\mathbb{E^Q}[V(m_T)C_T] = -N(1-RR) \\int_{max(m_{a.s}, m_{today})}^{m_{a.e}} w_{loc:col}(m_s) Q(m_s) \\lambda(s) ds\n\n    where the integral is numerically determined.\n\n    There is no *analytical delta* for this *Period* type and hence :math:`\\xi` is not defined.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import CreditProtectionPeriod\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       cp = CreditProtectionPeriod(\n           start=dt(2000, 3, 20),\n           end=dt(2000, 6, 20),\n           payment=dt(2000, 6, 20),\n           frequency=\"Q\",\n       )\n       cp.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency: str, :green:`optional (set by 'defaults')`\n        The physical *settlement currency* of the *Period*.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional amount of the *Period* expressed in ``notional currency``.\n    payment: datetime, :red:`required`\n        The payment date of the *Period* cashflow.\n    ex_dividend: datetime, :green:`optional (set as 'payment')`\n        The ex-dividend date of the *Period*. Settlements occurring **after** this date\n        are assumed to be non-receivable.\n\n        .. note::\n\n           The following parameters are scheduling **period** parameters\n\n    start: datetime, :red:`required`\n        The identified start date of the *Period*.\n    end: datetime, :red:`required`\n        The identified end date of the *Period*.\n    frequency: Frequency, str, :red:`required`\n        The :class:`~rateslib.scheduling.Frequency` associated with the *Period*.\n    termination: datetime, :green:`optional`\n        The termination date of an external :class:`~rateslib.scheduling.Schedule`.\n    calendar: Calendar, :green:`optional`\n         The calendar associated with the *Period*.\n    stub: bool, str, :green:`optional (set as False)`\n        Whether the *Period* is defined as a stub according to some external\n        :class:`~rateslib.scheduling.Schedule`.\n    roll: RollDay, int, str, :green:`optional (set by 'frequency')`\n        The rollday associated with any monthly :class:`~rateslib.scheduling.Frequency`, if\n        not directly associated with that object.\n    adjuster: Adjuster, :green:`optional`\n        The date :class:`~rateslib.scheduling.Adjuster` applied to unadjusted dates in the\n        external :class:`~rateslib.scheduling.Schedule` to arrive at adjusted accrual dates.\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def credit_params(self) -> _CreditParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._CreditParams` of the *Period*.\"\"\"\n        return self._credit_params\n\n    @property\n    def period_params(self) -> _PeriodParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._PeriodParams` of the *Period*.\"\"\"\n        return self._period_params\n\n    def __init__(\n        self,\n        *,\n        # currency args:\n        payment: datetime,\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n        # period params\n        start: datetime,\n        end: datetime,\n        frequency: Frequency | str,\n        # convention: str_ = NoInput(0),\n        termination: datetime_ = NoInput(0),\n        stub: bool = False,\n        roll: RollDay | int | str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        adjuster: Adjuster | str_ = NoInput(0),\n    ) -> None:\n        self._settlement_params = _SettlementParams(\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _notional_currency=_drb(defaults.base_currency, currency).lower(),\n            _payment=payment,\n            _notional=_drb(defaults.notional, notional),\n            _ex_dividend=_drb(payment, ex_dividend),\n        )\n        self._credit_params = _CreditParams(\n            _premium_accrued=True\n        )  # arg irrelevant for Period type.\n        self._period_params = _PeriodParams(\n            _start=start,\n            _end=end,\n            _frequency=_get_frequency(frequency, roll, calendar),\n            _calendar=get_calendar(calendar),\n            _adjuster=NoInput(0) if isinstance(adjuster, NoInput) else _get_adjuster(adjuster),\n            _stub=stub,\n            _convention=Convention.One,  # _get_convention(_drb(defaults.convention, convention)),\n            _termination=termination,\n        )\n\n    def cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> DualTypes:\n        rate_curve_ = _try_validate_base_curve(rate_curve).unwrap()\n        return -self.settlement_params.notional * (1 - rate_curve_.meta.credit_recovery_rate)\n\n    def try_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNPVStatic.cashflow`\n        with lazy exception handling.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        try:\n            v = self.cashflow(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx_vol=fx_vol,\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n    def immediate_local_npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> DualTypes:\n        rate_curve_, disc_curve_ = _validate_credit_curves(rate_curve, disc_curve).unwrap()\n        quadrature = self._quadrature(rate_curve_, disc_curve_)\n        cf = self.cashflow(rate_curve=rate_curve)\n        return quadrature * cf\n\n    def _quadrature(\n        self,\n        rate_curve_: _BaseCurve,\n        disc_curve_: _BaseCurve,\n    ) -> DualTypes:\n        \"\"\"determine the integral component of the NPV function using discretised intervals\"\"\"\n        discretization = rate_curve_.meta.credit_discretization\n\n        if self.period_params.start < rate_curve_.nodes.initial:\n            s2 = rate_curve_.nodes.initial\n        else:\n            s2 = self.period_params.start\n\n        value: DualTypes = 0.0\n        q2: DualTypes = rate_curve_[s2]\n        v2: DualTypes = disc_curve_[s2]\n        while s2 < self.period_params.end:\n            q1, v1 = q2, v2\n            s2 = s2 + timedelta(days=discretization)\n            if s2 > self.period_params.end:\n                s2 = self.period_params.end\n            q2, v2 = rate_curve_[s2], disc_curve_[s2]\n            value += 0.5 * (v1 + v2) * (q1 - q2)\n            # value += v2 * (q1 - q2)\n\n        return value\n\n    def try_immediate_local_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXRevised_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        return Ok(0.0)\n\n    def analytic_rec_risk(\n        self,\n        rate_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the exposure of the NPV to a change in recovery rate.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve, :red:`required`\n            Used to forecast credit parameters, such as hazard rates and recovery rates.\n        disc_curve: _BaseCurve, :red:`required`\n            Used to discount cashflows.\n        fx: FXForwards, :green:`optional`\n            The :class:`~rateslib.fx.FXForwards` object used for currency conversion.\n        base: str, :green:`optional`\n            The currency to convert the *local settlement* value into.\n        settlement: datetime, :green:`optional`\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, :green:`optional`\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2\n        \"\"\"\n        rate_curve_, disc_curve_ = _validate_credit_curves(rate_curve, disc_curve).unwrap()\n        quadrature = self._quadrature(rate_curve_, disc_curve_)\n        local_immediate_value = quadrature * self.settlement_params.notional * 0.01\n\n        local_value = _screen_ex_div_and_forward(\n            local_value=Ok(local_immediate_value),\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            ex_dividend=self.settlement_params.ex_dividend,\n            settlement=settlement,\n            forward=forward,\n        )\n        ret: DualTypes = _maybe_local(  # type: ignore[assignment]  # local is False\n            value=local_value.unwrap(),\n            local=False,\n            currency=self.settlement_params.currency,\n            fx=fx,\n            base=base,\n            forward=forward,\n        )\n        return ret\n"
  },
  {
    "path": "python/rateslib/periods/fixed_period.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nfrom pandas import DataFrame\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.data.fixings import _maybe_get_fx_index\nfrom rateslib.enums.generics import Err, NoInput, Ok, _drb\nfrom rateslib.enums.parameters import IndexMethod\nfrom rateslib.periods.parameters import (\n    _FixedRateParams,\n    _init_or_none_IndexParams,\n    _init_or_none_NonDeliverableParams,\n    _init_SettlementParams_with_fx_pair,\n    _PeriodParams,\n)\nfrom rateslib.periods.protocols import _BasePeriodStatic\nfrom rateslib.scheduling import Adjuster, Frequency, dcf, get_calendar\nfrom rateslib.scheduling.adjuster import _get_adjuster\nfrom rateslib.scheduling.convention import _get_convention\nfrom rateslib.scheduling.frequency import _get_frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CalInput,\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        FXIndex,\n        Result,\n        RollDay,\n        Schedule,\n        Series,\n        _BaseCurve_,\n        _FXVolOption_,\n        _IRVolOption_,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass FixedPeriod(_BasePeriodStatic):\n    r\"\"\"\n    A *Period* defined by a fixed interest rate.\n\n    The expected unindexed reference cashflow under the risk neutral distribution is defined as,\n\n    .. math::\n\n       \\mathbb{E^Q} [\\bar{C}_t] = -N d R\n\n    For *analytic delta* purposes the :math:`\\xi=-R`.\n\n    .. role:: red\n\n    .. role:: green\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import FixedPeriod\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       period = FixedPeriod(\n           start=dt(2000, 1, 1),\n           end=dt(2001, 1, 1),\n           payment=dt(2001, 1, 1),\n           fixed_rate=5.0,\n           notional=1e6,\n           convention=\"ActActICMA\",\n           frequency=\"A\",\n       )\n       period.cashflows()\n\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency: str, :green:`optional (set by 'defaults')`\n        The physical *settlement currency* of the *Period*.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional amount of the *Period* expressed in ``notional currency``.\n    payment: datetime, :red:`required`\n        The payment date of the *Period* cashflow.\n    ex_dividend: datetime, :green:`optional (set as 'payment')`\n        The ex-dividend date of the *Period*. Settlements occurring **after** this date\n        are assumed to be non-receivable.\n\n        .. note::\n\n           The following parameters are scheduling **period** parameters\n\n    start: datetime, :red:`required`\n        The identified start date of the *Period*.\n    end: datetime, :red:`required`\n        The identified end date of the *Period*.\n    frequency: Frequency, str, :red:`required`\n        The :class:`~rateslib.scheduling.Frequency` associated with the *Period*.\n    convention: Convention, str, :green:`optional` (set by 'defaults')\n        The day count :class:`~rateslib.scheduling.Convention` associated with the *Period*.\n    termination: datetime, :green:`optional`\n        The termination date of an external :class:`~rateslib.scheduling.Schedule`.\n    calendar: Calendar, :green:`optional`\n         The calendar associated with the *Period*.\n    stub: bool, str, :green:`optional (set as False)`\n        Whether the *Period* is defined as a stub according to some external\n        :class:`~rateslib.scheduling.Schedule`.\n    roll: RollDay, int, str, :green:`optional (set by 'frequency')`\n        The rollday associated with any monthly :class:`~rateslib.scheduling.Frequency`, if\n        not directly associated with that object.\n    adjuster: Adjuster, :green:`optional`\n        The date :class:`~rateslib.scheduling.Adjuster` applied to unadjusted dates in the\n        external :class:`~rateslib.scheduling.Schedule` to arrive at adjusted accrual dates.\n\n        .. note::\n\n           The following define **fixed rate** parameters.\n\n    fixed_rate: float, Dual, Dual2, Variable, :green:`optional`\n        The fixed rate to determine the *Period* cashflow.\n\n        .. note::\n\n           The following parameters define **non-deliverability**. If the *Period* is directly\n           deliverable do not supply these parameters.\n\n    pair: str, :green:`optional`\n        The currency pair of the :class:`~rateslib.data.fixings.FXFixing` that determines\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing`. If a scalar is used directly.\n        If a string identifier will link to the central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    delivery: datetime, :green:`optional (set as 'payment')`\n        The settlement delivery date of the :class:`~rateslib.data.fixings.FXFixing`.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value set of the base index value.\n        If not given and ``index_fixings`` is a str fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The index value for the reference date.\n        If a scalar value this is used directly. If a string identifier will link to the\n        central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n    index_base_date: datetime, :green:`optional`\n        The reference date for determining the base index value. Not required if ``_index_base``\n        value is given directly.\n    index_reference_date: datetime, :green:`optional (set as 'end')`\n        The reference date for determining the index value. Not required if ``_index_fixings``\n        is given as a scalar value.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which determines non-payment of notional on supported *Periods*.\n\n\n    ..  Examples\n        --------\n\n        A typical RFR type :class:`~rateslib.periods.FloatPeriod`.\n\n        .. ipython:: python\n           :supress:\n\n           from rateslib.periods import FloatPeriod\n           from rateslib.data.fixings import FloatRateIndex\n           from datetime import datetime as dt\n\n        .. ipython:: python\n\n           period = FloatPeriod(\n               start=dt(2025, 9, 22),\n               end=dt(2025, 10, 20),\n               payment=dt(2025, 10, 22),\n               frequency=\"1M\",\n           )\n\n        A typical IBOR tenor type :class:`~rateslib.periods.FloatPeriod`.\n\n        .. ipython:: python\n\n           period = FloatPeriod(\n               start=dt(2025, 9, 22),\n               end=dt(2025, 10, 22),\n               payment=dt(2025, 10, 22),\n               frequency=\"1M\",\n               currency=\"eur\",\n               fixing_method=\"IBOR\",\n               fixing_series=\"eur_IBOR\",\n           )\n\n    \"\"\"\n\n    @property\n    def rate_params(self) -> _FixedRateParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._FixedRateParams` of the *Period*.\"\"\"\n        return self._rate_params\n\n    @property\n    def period_params(self) -> _PeriodParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._PeriodParams` of the *Period*.\"\"\"\n        return self._period_params\n\n    def __init__(\n        self,\n        *,\n        fixed_rate: DualTypes_ = NoInput(0),\n        # currency args:\n        payment: datetime,\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n        # period params\n        start: datetime,\n        end: datetime,\n        frequency: Frequency | str,\n        convention: str_ = NoInput(0),\n        termination: datetime_ = NoInput(0),\n        stub: bool = False,\n        roll: RollDay | int | str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        adjuster: Adjuster | str_ = NoInput(0),\n        # non-deliverable args:\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        delivery: datetime_ = NoInput(0),\n        # index-args:\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        index_only: bool_ = NoInput(0),\n        index_base_date: datetime_ = NoInput(0),\n        index_reference_date: datetime_ = NoInput(0),\n    ) -> None:\n        self._settlement_params = _init_SettlementParams_with_fx_pair(\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _payment=payment,\n            _notional=_drb(defaults.notional, notional),\n            _ex_dividend=_drb(payment, ex_dividend),\n            _fx_pair=_maybe_get_fx_index(pair),\n        )\n        self._non_deliverable_params = _init_or_none_NonDeliverableParams(\n            _currency=self.settlement_params.currency,\n            _fx_index=pair,\n            _delivery=_drb(self.settlement_params.payment, delivery),\n            _fx_fixings=fx_fixings,\n        )\n        self._period_params = _PeriodParams(\n            _start=start,\n            _end=end,\n            _frequency=_get_frequency(frequency, roll, calendar),\n            _calendar=get_calendar(calendar),\n            _adjuster=NoInput(0) if isinstance(adjuster, NoInput) else _get_adjuster(adjuster),\n            _stub=stub,\n            _convention=_get_convention(_drb(defaults.convention, convention)),\n            _termination=termination,\n        )\n        self._index_params = _init_or_none_IndexParams(\n            _index_base=index_base,\n            _index_lag=index_lag,\n            _index_method=index_method,\n            _index_fixings=index_fixings,\n            _index_only=index_only,\n            _index_base_date=index_base_date,\n            _index_reference_date=_drb(self.period_params.end, index_reference_date),\n        )\n        self._rate_params = _FixedRateParams(fixed_rate)\n\n    def unindexed_reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        **kwargs: Any,\n    ) -> DualTypes:\n        if isinstance(self.rate_params.fixed_rate, NoInput):\n            raise ValueError(err.VE_NEEDS_FIXEDRATE)\n        else:\n            return (\n                -self.settlement_params.notional\n                * self.rate_params.fixed_rate\n                * 0.01\n                * self.period_params.dcf\n            )\n\n    # def try_cashflow(\n    #     self,\n    #     *,\n    #     rate_curve: CurveOption_ = NoInput(0),\n    #     disc_curve: _BaseCurve_ = NoInput(0),\n    #     index_curve: _BaseCurve_ = NoInput(0),\n    #     fx: FXForwards_ = NoInput(0),\n    #     fx_vol: _FXVolOption_ = NoInput(0),\n    # ) -> Result[DualTypes]:\n    #     if self.index_params is None:\n    #         if self.non_deliverable_params is None:\n    #             return self.try_unindexed_reference_cashflow(\n    #                 rate_curve=rate_curve,\n    #                 disc_curve=disc_curve,\n    #                 index_curve=index_curve,\n    #                 fx=fx,\n    #                 fx_vol=fx_vol,\n    #             )\n    #         else:\n    #             return self.try_unindexed_cashflow(\n    #                 rate_curve=rate_curve,\n    #                 disc_curve=disc_curve,\n    #                 index_curve=index_curve,\n    #                 fx=fx,\n    #                 fx_vol=fx_vol,\n    #             )\n    #     else:\n    #         if self.non_deliverable_params is None:\n    #             return self.try_reference_cashflow(\n    #                 rate_curve=rate_curve,\n    #                 disc_curve=disc_curve,\n    #                 index_curve=index_curve,\n    #                 fx=fx,\n    #                 fx_vol=fx_vol,\n    #             )\n    #         else:\n    #             rc = self.try_reference_cashflow(\n    #                 rate_curve=rate_curve,\n    #                 index_curve=index_curve,\n    #                 disc_curve=disc_curve,\n    #                 fx=fx,\n    #                 fx_vol=fx_vol,\n    #             )\n    #             return self.try_convert_deliverable(value=rc, fx=fx)\n\n    def try_unindexed_reference_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        return Ok(self.settlement_params.notional * 0.0001 * self.period_params.dcf)\n\n    def try_unindexed_reference_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        return Ok(DataFrame())\n\n\nclass ZeroFixedPeriod(_BasePeriodStatic):\n    r\"\"\"\n    A *Period* defined by a fixed interest rate, as a representation of multiple compounded *Periods*.\n\n    The expected unindexed reference cashflow under the risk neutral distribution is defined as,\n\n    .. math::\n\n       \\mathbb{E^Q}[\\bar{C}_t] = - N \\left ( \\left ( 1 + \\frac{R}{f} \\right )^{df} - 1 \\right ), \\qquad d = \\sum_{i=1}^n d_i\n\n    For *analytic delta* purposes the :math:`\\xi=-R`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import ZeroFixedPeriod\n       from rateslib.legs import CustomLeg\n       from rateslib.scheduling import Schedule\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       period = ZeroFixedPeriod(\n           schedule=Schedule(dt(2000, 1, 1), \"5Y\", \"A\"),\n           fixed_rate=5.0,\n           convention=\"1\",\n       )\n       period.cashflows()\n\n    For more details of the individual compounded periods one can compose a\n    :class:`~rateslib.legs.CustomLeg` and view the pseudo-cashflows.\n\n    .. ipython:: python\n\n       CustomLeg(period.fixed_periods).cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency: str, :green:`optional (set by 'defaults')`\n        The physical *settlement currency* of the *Period*.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional amount of the *Period* expressed in ``notional currency``.\n\n        .. note::\n\n           The following parameters are scheduling **period** parameters\n\n    schedule: Schedule, :red:`required`\n        The :class:`~rateslib.scheduling.Schedule` defining the individual *Periods*, including\n        the *payment* and *ex-dividend* dates.\n\n        .. note::\n\n           The following define **fixed rate** parameters.\n\n    fixed_rate: float, Dual, Dual2, Variable, :green:`optional`\n        The fixed rate to determine the *Period* cashflow.\n\n        .. note::\n\n           The following parameters define **non-deliverability**. If the *Period* is directly\n           deliverable do not supply these parameters.\n\n    pair: str, :green:`optional`\n        The currency pair of the :class:`~rateslib.data.fixings.FXFixing` that determines\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing`. If a scalar is used directly.\n        If a string identifier will link to the central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    delivery: datetime, :green:`optional (set as 'payment')`\n        The settlement delivery date of the :class:`~rateslib.data.fixings.FXFixing`.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value set of the base index value.\n        If not given and ``index_fixings`` is a str fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The index value for the reference date.\n        If a scalar value this is used directly. If a string identifier will link to the\n        central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which determines non-payment of notional on supported *Periods*.\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def rate_params(self) -> _FixedRateParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._FixedRateParams` of the *Period*.\"\"\"\n        return self._rate_params\n\n    @property\n    def period_params(self) -> _PeriodParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._PeriodParams` of the *Period*.\"\"\"\n        return self._period_params\n\n    @property\n    def schedule(self) -> Schedule:\n        \"\"\"The :class:`~rateslib.scheduling.Schedule` object for this *Period*.\"\"\"\n        return self._schedule\n\n    @cached_property\n    def dcf(self) -> float:\n        \"\"\"An overload for the calculation of the DCF, replacing `period_params.dcf`.\"\"\"\n        return sum(\n            dcf(\n                start=self.schedule.aschedule[i],\n                end=self.schedule.aschedule[i + 1],\n                convention=self.period_params.convention,\n                termination=self.schedule.aschedule[-1],\n                frequency=self.schedule.frequency_obj,\n                stub=self.schedule._stubs[i],\n                roll=NoInput(0),  # taken from Frequency obj\n                calendar=self.schedule.calendar,\n                adjuster=self.schedule.modifier,\n            )\n            for i in range(self.schedule.n_periods)\n        )\n\n    @property\n    def fixed_periods(self) -> list[FixedPeriod]:\n        \"\"\"\n        The individual :class:`~rateslib.periods.FixedPeriod` that are\n        compounded.\n        \"\"\"\n        return self._fixed_periods\n\n    def __init__(\n        self,\n        *,\n        fixed_rate: DualTypes_ = NoInput(0),\n        schedule: Schedule,\n        # currency args:\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        # period params\n        convention: str_ = NoInput(0),\n        # non-deliverable args:\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        delivery: datetime_ = NoInput(0),\n        # index-args:\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        index_only: bool_ = NoInput(0),\n    ) -> None:\n        self._schedule = schedule\n        self._settlement_params = _init_SettlementParams_with_fx_pair(\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _payment=self.schedule.pschedule[-1],\n            _notional=_drb(defaults.notional, notional),\n            _ex_dividend=self.schedule.pschedule3[-1],\n            _fx_pair=_maybe_get_fx_index(pair),\n        )\n        self._non_deliverable_params = _init_or_none_NonDeliverableParams(\n            _currency=self.settlement_params.currency,\n            _fx_index=pair,\n            _delivery=_drb(self.settlement_params.payment, delivery),\n            _fx_fixings=fx_fixings,\n        )\n        self._period_params = _PeriodParams(\n            _start=self.schedule.aschedule[0],\n            _end=self.schedule.aschedule[-1],\n            _frequency=self.schedule.frequency_obj,\n            _calendar=self.schedule.calendar,\n            _adjuster=self.schedule.modifier,\n            _stub=True,\n            _convention=_get_convention(_drb(defaults.convention, convention)),\n            _termination=self.schedule.aschedule[-1],\n        )\n        self._index_params = _init_or_none_IndexParams(\n            _index_base=index_base,\n            _index_lag=index_lag,\n            _index_method=index_method,\n            _index_fixings=index_fixings,\n            _index_only=index_only,\n            _index_base_date=self.schedule.aschedule[0],\n            _index_reference_date=self.schedule.aschedule[-1],\n        )\n        self._rate_params = _FixedRateParams(fixed_rate)\n        self._fixed_periods: list[FixedPeriod] = [\n            FixedPeriod(\n                fixed_rate=fixed_rate,\n                # currency args:\n                payment=self.schedule.pschedule[i + 1],\n                notional=notional,\n                currency=currency,\n                ex_dividend=self.schedule.pschedule3[i + 1],\n                # period params\n                start=self.schedule.aschedule[i],\n                end=self.schedule.aschedule[i + 1],\n                frequency=self.schedule.frequency_obj,\n                convention=convention,\n                termination=self.schedule.aschedule[-1],\n                stub=self.schedule._stubs[i],\n                roll=NoInput(0),  # inferred from frequency obj\n                calendar=self.schedule.calendar,\n                adjuster=self.schedule.modifier,\n                # Each individual period is not genuine Period, only psuedo periods to derive the\n                # cashflow calculation so no 'non-deliverable' or 'index' params are required.\n            )\n            for i in range(self.schedule.n_periods)\n        ]\n\n    def unindexed_reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        **kwargs: Any,\n    ) -> DualTypes:\n        if isinstance(self.rate_params.fixed_rate, NoInput):\n            raise ValueError(err.VE_NEEDS_FIXEDRATE)\n        else:\n            f = self.schedule.periods_per_annum\n            return -self.settlement_params.notional * (\n                (1 + self.rate_params.fixed_rate / (f * 100)) ** (self.dcf * f) - 1\n            )\n\n    def try_unindexed_reference_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        if isinstance(self.rate_params.fixed_rate, NoInput):\n            return Err(ValueError(err.VE_NEEDS_FIXEDRATE))\n        else:\n            f = self.schedule.periods_per_annum\n            return Ok(\n                self.settlement_params.notional\n                * 0.0001\n                * self.dcf\n                * ((1 + self.rate_params.fixed_rate / (f * 100)) ** (self.dcf * f - 1))\n            )\n\n    def try_unindexed_reference_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        return Ok(DataFrame())\n\n    def cashflows(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> dict[str, Any]:\n        \"\"\"\n        Return aggregated cashflow data for the *Period*.\n\n        .. warning::\n\n           This method is a convenience method to provide a visual representation of all\n           associated calculation data. Calling this method to extracting certain values\n           should be avoided. It is more efficient to source relevant parameters or calculations\n           from object attributes or other methods directly.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        base: str, optional\n            The currency to convert the *local settlement* NPV to.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        dict[Any]\n        \"\"\"\n        d = super().cashflows(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            settlement=settlement,\n            forward=forward,\n            base=base,\n        )\n        d[defaults.headers[\"dcf\"]] = self.dcf  # reinsert the overload\n        return d\n"
  },
  {
    "path": "python/rateslib/periods/float_period.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\nfrom pandas import DataFrame, Index, MultiIndex, Series, concat, merge\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.curves import _BaseCurve\nfrom rateslib.curves.utils import average_rate\nfrom rateslib.data.fixings import (\n    FloatRateSeries,\n    _leg_fixings_to_list,\n    _maybe_get_fx_index,\n    _RFRRate,\n)\nfrom rateslib.data.loader import _find_neighbouring_tenors\nfrom rateslib.dual import Variable, gradient\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import Err, NoInput, Ok, _drb\nfrom rateslib.enums.parameters import FloatFixingMethod, IndexMethod, SpreadCompoundMethod\nfrom rateslib.periods.float_rate import (\n    try_rate_value,\n)\nfrom rateslib.periods.parameters import (\n    _init_FloatRateParams,\n    _init_or_none_IndexParams,\n    _init_or_none_NonDeliverableParams,\n    _init_SettlementParams_with_fx_pair,\n    _PeriodParams,\n)\nfrom rateslib.periods.protocols import _BasePeriodStatic\nfrom rateslib.periods.utils import _get_rfr_curve_from_dict\nfrom rateslib.scheduling import Adjuster, Frequency, dcf, get_calendar\nfrom rateslib.scheduling.adjuster import _get_adjuster\nfrom rateslib.scheduling.convention import _get_convention\nfrom rateslib.scheduling.frequency import _get_frequency, _get_tenor_from_frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        Arr1dObj,\n        CalInput,\n        Convention,\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        Frequency,\n        FXForwards_,\n        FXIndex,\n        Result,\n        RFRFixing,\n        RollDay,\n        Schedule,\n        Series,\n        _BaseCurve_,\n        _FloatRateParams,\n        _FXVolOption_,\n        _IRVolOption_,\n        bool_,\n        datetime,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass FloatPeriod(_BasePeriodStatic):\n    r\"\"\"\n    A *Period* defined by a floating interest rate.\n\n    The expected unindexed reference cashflow under the risk neutral distribution is defined as,\n\n    .. math::\n\n       \\mathbb{E^Q} [\\bar{C}_t] = -N d r(\\mathbf{C}, z, R_i)\n\n    For *analytic delta* purposes the :math:`\\xi=-z`.\n\n    .. role:: red\n\n    .. role:: green\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import FloatPeriod, Frequency, fixings, FloatRateSeries\n       from rateslib.enums import SpreadCompoundMethod, FloatFixingMethod\n       from datetime import datetime as dt\n       from pandas import Series\n\n    .. ipython:: python\n\n       fixings.add(\"MY_RATE_INDEX_6M\", Series(index=[dt(2000, 1, 1)], data=[2.66]))\n       period = FloatPeriod(\n           start=dt(2000, 1, 1),\n           end=dt(2000, 7, 1),\n           payment=dt(2000, 7, 1),\n           notional=1e6,\n           convention=\"Act360\",\n           frequency=\"S\",\n           fixing_method=\"ibor(0)\",\n           rate_fixings=\"MY_RATE_INDEX\"\n       )\n       period.cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"MY_RATE_INDEX_6M\")\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define generalised **settlement** parameters.\n\n    currency: str, :green:`optional (set by 'defaults')`\n        The physical *settlement currency* of the *Period*.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional amount of the *Period* expressed in ``notional currency``.\n    payment: datetime, :red:`required`\n        The payment date of the *Period* cashflow.\n    ex_dividend: datetime, :green:`optional (set as 'payment')`\n        The ex-dividend date of the *Period*. Settlements occurring **after** this date\n        are assumed to be non-receivable.\n\n        .. note::\n\n           The following parameters are scheduling **period** parameters\n\n    start: datetime, :red:`required`\n        The identified start date of the *Period*.\n    end: datetime, :red:`required`\n        The identified end date of the *Period*.\n    frequency: Frequency, str, :red:`required`\n        The :class:`~rateslib.scheduling.Frequency` associated with the *Period*.\n    convention: Convention, str, :green:`optional` (set by 'defaults')\n        The day count :class:`~rateslib.scheduling.Convention` associated with the *Period*.\n    termination: datetime, :green:`optional`\n        The termination date of an external :class:`~rateslib.scheduling.Schedule`.\n    calendar: Calendar, :green:`optional`\n         The calendar associated with the *Period*.\n    stub: bool, str, :green:`optional (set as False)`\n        Whether the *Period* is defined as a stub according to some external\n        :class:`~rateslib.scheduling.Schedule`.\n    roll: RollDay, int, str, :green:`optional (set by 'frequency')`\n        The rollday associated with any monthly :class:`~rateslib.scheduling.Frequency`, if\n        not directly associated with that object.\n    adjuster: Adjuster, :green:`optional`\n        The date :class:`~rateslib.scheduling.Adjuster` applied to unadjusted dates in the\n        external :class:`~rateslib.scheduling.Schedule` to arrive at adjusted accrual dates.\n\n        .. note::\n\n           The following define **floating rate** parameters.\n\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for the period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the period for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in the period rate determination. If not given is\n        set to zero.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with (non-averaging)\n        RFR type ``fixing_method``.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n\n        .. note::\n\n           The following parameters define **non-deliverability**. If the *Period* is directly\n           deliverable do not supply these parameters.\n\n    pair: str, :green:`optional`\n        The currency pair of the :class:`~rateslib.data.fixings.FXFixing` that determines\n        settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the :class:`~rateslib.data.fixings.FXFixing`. If a scalar is used directly.\n        If a string identifier will link to the central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    delivery: datetime, :green:`optional (set as 'payment')`\n        The settlement delivery date of the :class:`~rateslib.data.fixings.FXFixing`.\n\n        .. note::\n\n           The following parameters define **indexation**. The *Period* will be considered\n           indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n           are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n        The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n        The specific value set of the base index value.\n        If not given and ``index_fixings`` is a str fixings identifier that will be\n        used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The index value for the reference date.\n        If a scalar value this is used directly. If a string identifier will link to the\n        central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n    index_base_date: datetime, :green:`optional`\n        The reference date for determining the base index value. Not required if ``_index_base``\n        value is given directly.\n    index_reference_date: datetime, :green:`optional (set as 'end')`\n        The reference date for determining the index value. Not required if ``_index_fixings``\n        is given as a scalar value.\n    index_only: bool, :green:`optional (set as False)`\n        A flag which determines non-payment of notional on supported *Periods*.\n\n    Notes\n    -----\n\n    **Five** different classifications of *FloatPeriod* are possible to construct.\n\n    .. tabs::\n\n       .. tab:: RFR\n\n          A standard *RFR* period consists of multiple *'1B'* overnight fixings compounded\n          over the *Period* to determine the *rate*. It is specified by using any non-averaging\n          *RFR* ``fixing_method``. This variant constructs an\n          :class:`~rateslib.data.fixings.RFRFixing` as the object to coordinate *rate*\n          calculation. It will depend on ``spread_compound_method`` to incorporate a\n          ``float_spread`` into the calculation. The ``fixing_frequency`` is *'1B'* under this\n          method.\n\n          .. ipython:: python\n\n             fp = FloatPeriod(\n                 start=dt(2026, 1, 22),\n                 end=dt(2027, 1, 22),\n                 payment=dt(2027, 1, 25),\n                 frequency=Frequency.Months(12, None),  # <- or \"A\"\n                 fixing_method=FloatFixingMethod.RFRPaymentDelay(),  # <- or \"rfr_payment_delay\"\n                 float_spread=5.0,\n                 spread_compound_method=SpreadCompoundMethod.NoneSimple,  # <- or \"NoneSimple\"\n             )\n             fp.rate_params.rate_fixing\n\n       .. tab:: Average RFR\n\n          This type is the same as **RFR** but uses an averaging ``fixing_method`` variant.\n          ``spread_compound_method`` cannot be used and can only be *'NoneSimple'*.\n\n          .. ipython:: python\n\n             fp = FloatPeriod(\n                 start=dt(2026, 1, 22),\n                 end=dt(2027, 1, 22),\n                 payment=dt(2027, 1, 25),\n                 frequency=Frequency.Months(12, None),  # <- or \"A\"\n                 fixing_method=FloatFixingMethod.RFRPaymentDelayAverage(),  # <- or \"rfr_payment_delay_avg\"\n                 float_spread=5.0,\n             )\n             fp.rate_params.rate_fixing\n\n          .. warning::\n\n             The :meth:`~rateslib.periods.FloatPeriod.rate` method does **not** make any\n             *convexity adjustments* for an averaging type versus the numéraire compounding type\n             and determines a *rate* under the direct calculations from a provided *rate Curve*.\n\n       .. tab:: IBOR\n\n          This type, for legacy tenor **IBOR** rates, such as US-LIBOR, GBP-LIBOR, and existing\n          tenor rates such as EURIBOR, STIBOR, NIBOR, BB3M etc. uses a single fixing period. It is\n          specified by an *'ibor'* ``fixing_method``.\n\n          When the period is regular it will create an :class:`~rateslib.data.fixings.IBORFixing`\n          with a ``fixing_frequency`` that aligns with that of the *Period*.\n\n          .. ipython:: python\n\n             fp = FloatPeriod(\n                 start=dt(2026, 1, 22),\n                 end=dt(2026, 4, 22),\n                 payment=dt(2026, 4, 22),\n                 frequency=Frequency.Months(3, None),  # <- or \"Q\"\n                 fixing_method=FloatFixingMethod.IBOR(2),  # <- or \"ibor(2)\"\n                 float_spread=5.0,\n             )\n             fp.rate_params.rate_fixing\n\n       .. tab:: Misaligned IBOR\n\n          The ``fixing_frequency`` and ``fixing_series`` allow custom definitions of an IBOR\n          *FloatPeriod* to be created, such as using a 6M tenor with a 3M period, or using\n          mixed accrual calendars that do not align with the IBOR definition.\n\n          .. ipython:: python\n\n             fp = FloatPeriod(\n                 start=dt(2026, 2, 4),\n                 end=dt(2026, 5, 7),  # <- Tokyo holidays on 4th, 5th, 6th May\n                 payment=dt(2026, 5, 7),\n                 frequency=Frequency.Months(3, None),  # <- or \"Q\"\n                 fixing_method=FloatFixingMethod.IBOR(2),  # <- or \"ibor(2)\"\n                 calendar=\"tyo,nyc\",\n                 float_spread=5.0,\n                 fixing_series=\"usd_ibor\",  # <- or define your own FloatRateSeries\n                 fixing_frequency=Frequency.Months(6, None),  # <- or \"S\"\n             )\n             fp.rate_params.rate_fixing\n\n       .. tab:: IBOR Stubs\n\n          IBOR stub periods can also be created which utilise an\n          :class:`~rateslib.data.fixings.IBORStubFixing`, for *rate* determination. These\n          must be identified by the ``stub`` flag.\n          *IBOR* stubs depend upon the ``tenors`` definition with the ``fixing_series``\n          (or by ['1W', '1M', '3M', '6M', '12M'] when omitted)\n\n          When using these in combinations with ``fixings`` all necessary date timeseries must\n          be available under the appropriate ``identifier``, e.g. *'STIBOR_1M'* and *'STIBOR_2M'*.\n\n          .. ipython:: python\n\n             fixings.add(\"STIBOR_1M\", Series(data=[1.0], index=[dt(2026, 2, 2)]))\n             fixings.add(\"STIBOR_2M\", Series(data=[2.0], index=[dt(2026, 2, 2)]))\n             fp = FloatPeriod(\n                 start=dt(2026, 2, 4),\n                 end=dt(2026, 3, 12),\n                 payment=dt(2026, 3, 12),\n                 frequency=Frequency.Months(6, None),  # <- or \"S\"\n                 stub=True,\n                 fixing_method=FloatFixingMethod.IBOR(2),  # <- or \"ibor(2)\"\n                 calendar=\"stk\",\n                 float_spread=5.0,\n                 fixing_series=FloatRateSeries(\n                     lag=2, calendar=\"stk\", modifier=\"MF\", convention=\"act360\",\n                     eom=False, tenors=[\"2D\", \"1W\", \"1M\", \"2M\", \"3M\", \"6M\"],\n                 ),\n                 rate_fixings=\"STIBOR\",\n             )\n             fp.rate_params.rate_fixing\n             fp.rate_params.rate_fixing.value\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"STIBOR_1M\")\n       fixings.pop(\"STIBOR_2M\")\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def rate_params(self) -> _FloatRateParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._FloatRateParams` of the *Period*.\"\"\"\n        return self._rate_params\n\n    @property\n    def period_params(self) -> _PeriodParams:\n        return self._period_params\n\n    def __init__(\n        self,\n        *,\n        float_spread: DualTypes_ = NoInput(0),\n        rate_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        fixing_method: FloatFixingMethod | str_ = NoInput(0),\n        spread_compound_method: SpreadCompoundMethod | str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # currency args:\n        payment: datetime,\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n        # period params\n        start: datetime,\n        end: datetime,\n        frequency: Frequency | str,\n        convention: Convention | str_ = NoInput(0),\n        termination: datetime_ = NoInput(0),\n        stub: bool = False,\n        roll: RollDay | int | str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        adjuster: Adjuster | str_ = NoInput(0),\n        # non-deliverable args:\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        delivery: datetime_ = NoInput(0),\n        # index-args:\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        index_only: bool_ = NoInput(0),\n        index_base_date: datetime_ = NoInput(0),\n        index_reference_date: datetime_ = NoInput(0),\n    ) -> None:\n        self._settlement_params = _init_SettlementParams_with_fx_pair(\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _payment=payment,\n            _notional=_drb(defaults.notional, notional),\n            _ex_dividend=_drb(payment, ex_dividend),\n            _fx_pair=_maybe_get_fx_index(pair),\n        )\n        self._non_deliverable_params = _init_or_none_NonDeliverableParams(\n            _currency=self.settlement_params.currency,\n            _fx_index=pair,\n            _delivery=_drb(self.settlement_params.payment, delivery),\n            _fx_fixings=fx_fixings,\n        )\n        self._period_params = _PeriodParams(\n            _start=start,\n            _end=end,\n            _frequency=_get_frequency(frequency, roll, calendar),\n            _calendar=get_calendar(calendar),\n            _adjuster=NoInput(0) if isinstance(adjuster, NoInput) else _get_adjuster(adjuster),\n            _stub=stub,\n            _convention=_get_convention(_drb(defaults.convention, convention)),\n            _termination=termination,\n        )\n        self._index_params = _init_or_none_IndexParams(\n            _index_base=index_base,\n            _index_lag=index_lag,\n            _index_method=index_method,\n            _index_fixings=index_fixings,\n            _index_only=index_only,\n            _index_base_date=index_base_date,\n            _index_reference_date=_drb(self.period_params.end, index_reference_date),\n        )\n        self._rate_params = _init_FloatRateParams(\n            _float_spread=float_spread,\n            _spread_compound_method=spread_compound_method,\n            _fixing_method=fixing_method,\n            _fixing_series=fixing_series,\n            _fixing_frequency=fixing_frequency,\n            _rate_fixings=rate_fixings,\n            _accrual_start=self.period_params.start,\n            _accrual_end=self.period_params.end,\n            _period_calendar=self.period_params.calendar,\n            _period_convention=self.period_params.convention,\n            _period_adjuster=self.period_params.adjuster,\n            _period_frequency=self.period_params.frequency,\n            _period_stub=self.period_params.stub,\n        )\n        if self.rate_params.spread_compound_method in [\n            SpreadCompoundMethod.ISDACompounding,\n            SpreadCompoundMethod.ISDAFlatCompounding,\n        ] and type(self.rate_params.fixing_method) in [\n            FloatFixingMethod.IBOR,\n            FloatFixingMethod.RFRPaymentDelayAverage,\n            FloatFixingMethod.RFRLookbackAverage,\n            FloatFixingMethod.RFRLockoutAverage,\n            FloatFixingMethod.RFRObservationShiftAverage,\n        ]:\n            raise ValueError(\n                f\"The input for `spread_compound_method`: \"\n                f\"{self.rate_params.spread_compound_method} is not compatible with the \"\n                f\"`fixing_method`: {self.rate_params.fixing_method}.\"\n            )\n\n    def unindexed_reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        **kwargs: Any,\n    ) -> DualTypes:\n        r = self.rate(rate_curve)\n        return -self.settlement_params.notional * r * 0.01 * self.period_params.dcf\n\n    def try_unindexed_reference_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Calculate the analytic rate delta of a *Period* expressed in ``reference_currency``\n        without indexation.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        if (\n            self.rate_params.spread_compound_method == SpreadCompoundMethod.NoneSimple\n            or self.rate_params.float_spread == 0\n        ):\n            # then analytic_delta is not impacted by float_spread compounding\n            dr_dz: float = 1.0\n        else:\n            _ = self.rate_params.float_spread\n            self.rate_params.float_spread = Variable(_dual_float(_), [\"z_float_spread\"])\n            rate: Result[DualTypes] = self.try_rate(rate_curve)\n            if rate.is_err:\n                return rate\n            dr_dz = gradient(rate.unwrap(), [\"z_float_spread\"])[0] * 100\n            self.rate_params.float_spread = _\n\n        return Ok(self.settlement_params.notional * 0.0001 * dr_dz * self.period_params.dcf)\n\n    def try_unindexed_reference_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        if isinstance(rate_curve, NoInput):\n            return Err(ValueError(err.VE_NEEDS_RATE_CURVE))\n\n        if isinstance(self.rate_params.fixing_method, FloatFixingMethod.IBOR):\n            return _UnindexedReferenceCashflowFixingsSensitivity._ibor(\n                self=self, rate_curve=rate_curve\n            )\n        else:  # is RFR\n            if isinstance(rate_curve, dict):\n                rate_curve_: _BaseCurve = _get_rfr_curve_from_dict(rate_curve)\n            else:\n                rate_curve_ = rate_curve\n            return _UnindexedReferenceCashflowFixingsSensitivity._rfr(\n                self=self, rate_curve=rate_curve_\n            )\n\n    # def try_unindexed_reference_fixings_exposure(\n    #     self,\n    #     rate_curve: CurveOption_ = NoInput(0),\n    #     disc_curve: _BaseCurve_ = NoInput(0),\n    #     right: datetime_ = NoInput(0),\n    # ) -> Result[DataFrame]:\n    #     if self.rate_params.fixing_method == FloatFixingMethod.IBOR:\n    #         return _FixingsExposureCalculator.ibor(\n    #             p=self,\n    #             rate_curve=rate_curve,\n    #             disc_curve=disc_curve,\n    #             right=right,\n    #         )\n    #     else:\n    #         if isinstance(rate_curve, dict):\n    #             rate_curve_: _BaseCurve_ = _get_rfr_curve_from_dict(rate_curve)\n    #         else:\n    #             rate_curve_ = rate_curve\n    #         return _FixingsExposureCalculator.rfr(\n    #             p=self,\n    #             rate_curve=_validate_obj_not_no_input(rate_curve_, \"rate_curve\"),\n    #             disc_curve=disc_curve,\n    #             right=right,\n    #         )\n\n    def try_rate(self, rate_curve: CurveOption_) -> Result[DualTypes]:\n        \"\"\"\n        Calculate the period rate, with lazy e\n\n        Parameters\n        ----------\n        rate_curve: XXX\n            The curve used to forecast rates, if the period has no fixing.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        rate_fixing = self.rate_params.rate_fixing.value\n        if isinstance(rate_fixing, NoInput):\n            return try_rate_value(\n                start=self.rate_params.accrual_start,\n                end=self.rate_params.accrual_end,\n                rate_curve=NoInput(0) if rate_curve is None else rate_curve,\n                rate_fixings=self.rate_params.rate_fixing.identifier,\n                fixing_method=self.rate_params.fixing_method,\n                spread_compound_method=self.rate_params.spread_compound_method,\n                float_spread=self.rate_params.float_spread,\n                stub=self.period_params.stub,\n                frequency=self.rate_params.fixing_frequency,\n                rate_series=self.rate_params.fixing_series,\n            )\n        else:\n            # the fixing value is a scalar so a Curve should not be required for this calculation\n            return try_rate_value(\n                start=self.rate_params.accrual_start,\n                end=self.rate_params.accrual_end,\n                rate_curve=NoInput(0),\n                rate_fixings=rate_fixing,\n                fixing_method=self.rate_params.fixing_method,\n                spread_compound_method=self.rate_params.spread_compound_method,\n                float_spread=self.rate_params.float_spread,\n                stub=self.period_params.stub,\n                frequency=self.rate_params.fixing_frequency,\n                rate_series=self.rate_params.fixing_series,\n            )\n\n    def rate(self, rate_curve: CurveOption_) -> DualTypes:\n        \"\"\"\n        Calculate the period rate.\n\n        Parameters\n        ----------\n        rate_curve: XXX\n            The curve used to forecast rates, if the period has no fixing.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        return self.try_rate(rate_curve).unwrap()\n\n\nclass ZeroFloatPeriod(_BasePeriodStatic):\n    r\"\"\"\n    A *Period* defined by compounded floating rate *Periods*.\n\n    The expected unindexed reference cashflow under the risk neutral distribution is defined as,\n\n    .. math::\n\n      \\mathbb{E^Q}[\\bar{C}_t] = - N \\left ( \\prod_{i=1}^n \\left ( 1 + r_i(\\mathbf{C}, R_j, z) d_i \\right ) - 1 \\right )\n\n    For *analytic delta* purposes the :math:`\\xi=-z`.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n      :suppress:\n\n      from rateslib.periods import ZeroFloatPeriod\n      from rateslib.legs import CustomLeg\n      from rateslib.scheduling import Schedule\n      from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fixings.add(\"MY_RATE_INDEX_6M\", Series(\n           index=[dt(2000, 1, 1), dt(2000, 7, 1), dt(2001, 1, 1), dt(2001, 7, 1)],\n           data=[1.0, 2.0, 3.0, 4.0]\n       ))\n       period = ZeroFloatPeriod(\n           schedule=Schedule(dt(2000, 1, 1), \"2Y\", \"S\"),\n           fixing_method=\"IBOR(0)\",\n           rate_fixings=\"MY_RATE_INDEX\",\n           convention=\"Act360\",\n       )\n       period.cashflows()\n\n    For more details of the individual compounded periods one can compose a\n    :class:`~rateslib.legs.CustomLeg` and view the pseudo-cashflows.\n\n    .. ipython:: python\n\n       CustomLeg(period.float_periods).cashflows()\n\n    .. ipython:: python\n       :suppress:\n\n       fixings.pop(\"MY_RATE_INDEX_6M\")\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n       .. note::\n\n          The following parameters are scheduling **period** parameters\n\n    schedule: Schedule, :red:`required`\n       The :class:`~rateslib.scheduling.Schedule` defining the individual *Periods*, including\n       the *payment* and *ex-dividend* dates.\n    convention: Convention, str, :green:`optional (set by 'defaults')`\n       The day count :class:`~rateslib.scheduling.Convention` associated with the *Period*.\n\n       .. note::\n\n          The following define generalised **settlement** parameters.\n\n    currency: str, :green:`optional (set by 'defaults')`\n       The physical *settlement currency* of the *Period*.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n       The notional amount of the *Period* expressed in ``notional currency``.\n\n       .. note::\n\n          The following define **floating rate** parameters.\n\n    fixing_method: FloatFixingMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for the period.\n    fixing_frequency: Frequency, str, :green:`optional (set by 'frequency' or '1B')`\n        The :class:`~rateslib.scheduling.Frequency` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given is assumed to match the\n        frequency of the period for an IBOR type ``fixing_method`` or '1B' if RFR type.\n    fixing_series: FloatRateSeries, str, :green:`optional (implied by other parameters)`\n        The :class:`~rateslib.data.fixings.FloatRateSeries` as a component of the\n        :class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given\n        such as the ``calendar``, ``convention``, ``fixing_method`` etc.\n    float_spread: float, Dual, Dual2, Variable, :green:`optional (set as 0.0)`\n        The amount (in bps) added to the rate in the period rate determination. If not given is\n        set to zero.\n    spread_compound_method: SpreadCompoundMethod, str, :green:`optional (set by 'defaults')`\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the rate fixing. If a scalar, is used directly. If a string identifier, links\n        to the central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n\n        .. note::\n\n          The following parameters define **non-deliverability**. If the *Period* is directly\n          deliverable do not supply these parameters.\n\n    pair: str, :green:`optional`\n       The currency pair of the :class:`~rateslib.data.fixings.FXFixing` that determines\n       settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.\n    fx_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n       The value of the :class:`~rateslib.data.fixings.FXFixing`. If a scalar is used directly.\n       If a string identifier will link to the central ``fixings`` object and data loader.\n       See :ref:`fixings <fixings-doc>`.\n    delivery: datetime, :green:`optional (set as 'payment')`\n       The settlement delivery date of the :class:`~rateslib.data.fixings.FXFixing`.\n\n       .. note::\n\n          The following parameters define **indexation**. The *Period* will be considered\n          indexed if any of ``index_method``, ``index_lag``, ``index_base``, ``index_fixings``\n          are given.\n\n    index_method : IndexMethod, str, :green:`optional (set by 'defaults')`\n       The interpolation method, or otherwise, to determine index values from reference dates.\n    index_lag: int, :green:`optional (set by 'defaults')`\n       The indexation lag, in months, applied to the determination of index values.\n    index_base: float, Dual, Dual2, Variable, :green:`optional`\n       The specific value set of the base index value.\n       If not given and ``index_fixings`` is a str fixings identifier that will be\n       used to determine the base index value.\n    index_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n       The index value for the reference date.\n       If a scalar value this is used directly. If a string identifier will link to the\n       central ``fixings`` object and data loader. See :ref:`fixings <fixings-doc>`.\n    index_base_date: datetime, :green:`optional (set as aschedule[0])`\n        The reference date for determining the base index value. Not used if ``_index_base``\n        value is given directly.\n    index_reference_date: datetime, :green:`optional (set as aschedule[1])`\n        The reference date for determining the index value. Not used if ``_index_fixings``\n        is given as a scalar value.\n    index_only: bool, :green:`optional (set as False)`\n       A flag which determines non-payment of notional on supported *Periods*.\n\n       .. note::\n\n          The following are meta parameters\n\n    metric: str, :green:`optional (set as 'compounding')`\n       The type of calculation to use in the :meth:`~rateslib.periods.ZeroFloatPeriod.rate` method.\n\n    \"\"\"  # noqa: E501\n\n    @property\n    def rate_params(self) -> _FloatRateParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._FixedRateParams` of the *Period*.\"\"\"\n        return self.float_periods[0].rate_params\n\n    @property\n    def rate_metric(self) -> str:\n        \"\"\"The type of calculation to perform in :meth:`~rateslib.periods.ZeroFloatPeriod.rate`.\"\"\"\n        return self._rate_metric\n\n    @property\n    def period_params(self) -> _PeriodParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._PeriodParams` of the *Period*.\"\"\"\n        return self._period_params\n\n    @property\n    def schedule(self) -> Schedule:\n        \"\"\"The :class:`~rateslib.scheduling.Schedule` object for this *Period*.\"\"\"\n        return self._schedule\n\n    @cached_property\n    def dcf(self) -> float:\n        \"\"\"An overload for the calculation of the DCF, replacing `period_params.dcf`.\"\"\"\n        return sum(\n            dcf(\n                start=self.schedule.aschedule[i],\n                end=self.schedule.aschedule[i + 1],\n                convention=self.period_params.convention,\n                termination=self.schedule.aschedule[-1],\n                frequency=self.schedule.frequency_obj,\n                stub=self.schedule._stubs[i],\n                roll=NoInput(0),  # taken from Frequency obj\n                calendar=self.schedule.calendar,\n                adjuster=self.schedule.modifier,\n            )\n            for i in range(self.schedule.n_periods)\n        )\n\n    @property\n    def float_spread(self) -> DualTypes:\n        \"\"\"The float spread parameter of each :class:`~rateslib.periods.FloatPeriod`.\"\"\"\n        return self._float_periods[0].rate_params.float_spread\n\n    @float_spread.setter\n    def float_spread(self, value: DualTypes) -> None:\n        for period in self._float_periods:\n            period.rate_params.float_spread = value\n\n    @property\n    def float_periods(self) -> list[FloatPeriod]:\n        \"\"\"\n        The individual :class:`~rateslib.periods.FloatPeriod` that are\n        compounded.\n        \"\"\"\n        return self._float_periods\n\n    def __init__(\n        self,\n        schedule: Schedule,\n        *,\n        float_spread: DualTypes_ = NoInput(0),\n        rate_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        fixing_method: FloatFixingMethod | str_ = NoInput(0),\n        spread_compound_method: SpreadCompoundMethod | str_ = NoInput(0),\n        fixing_frequency: Frequency | str_ = NoInput(0),\n        fixing_series: FloatRateSeries | str_ = NoInput(0),\n        # currency args:\n        notional: DualTypes_ = NoInput(0),\n        currency: str_ = NoInput(0),\n        # period params\n        convention: str_ = NoInput(0),\n        # non-deliverable args:\n        pair: FXIndex | str_ = NoInput(0),\n        fx_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        delivery: datetime_ = NoInput(0),\n        # index-args:\n        index_base: DualTypes_ = NoInput(0),\n        index_lag: int_ = NoInput(0),\n        index_method: IndexMethod | str_ = NoInput(0),\n        index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        index_base_date: datetime_ = NoInput(0),\n        index_reference_date: datetime_ = NoInput(0),\n        index_only: bool_ = NoInput(0),\n        # meta-args:\n        metric: str_ = NoInput(0),\n    ) -> None:\n        self._rate_metric: str = _drb(\"compounding\", metric).lower()\n        self._schedule = schedule\n        self._settlement_params = _init_SettlementParams_with_fx_pair(\n            _currency=_drb(defaults.base_currency, currency).lower(),\n            _payment=self.schedule.pschedule[-1],\n            _notional=_drb(defaults.notional, notional),\n            _ex_dividend=self.schedule.pschedule3[-1],\n            _fx_pair=_maybe_get_fx_index(pair),\n        )\n        self._non_deliverable_params = _init_or_none_NonDeliverableParams(\n            _currency=self.settlement_params.currency,\n            _fx_index=pair,\n            _delivery=_drb(self.settlement_params.payment, delivery),\n            _fx_fixings=fx_fixings,\n        )\n        self._period_params = _PeriodParams(\n            _start=self.schedule.aschedule[0],\n            _end=self.schedule.aschedule[-1],\n            _frequency=self.schedule.frequency_obj,\n            _calendar=self.schedule.calendar,\n            _adjuster=self.schedule.modifier,\n            _stub=not self.schedule.frequency_obj.is_uregular(\n                self.schedule.uschedule[0], self.schedule.uschedule[-1]\n            ),\n            _convention=_get_convention(_drb(defaults.convention, convention)),\n            _termination=self.schedule.aschedule[-1],\n        )\n        self._index_params = _init_or_none_IndexParams(\n            _index_base=index_base,\n            _index_lag=index_lag,\n            _index_method=index_method,\n            _index_fixings=index_fixings,\n            _index_only=index_only,\n            _index_base_date=_drb(self.schedule.aschedule[0], index_base_date),\n            _index_reference_date=_drb(self.schedule.aschedule[-1], index_reference_date),\n        )\n        rate_fixings_ = _leg_fixings_to_list(rate_fixings, self.schedule.n_periods)\n        self._float_periods: list[FloatPeriod] = [\n            FloatPeriod(\n                float_spread=float_spread,\n                rate_fixings=rate_fixings_[i],\n                fixing_method=fixing_method,\n                spread_compound_method=spread_compound_method,\n                fixing_frequency=fixing_frequency,\n                fixing_series=fixing_series,\n                # currency args:\n                payment=self.schedule.pschedule[i + 1],\n                notional=notional,\n                currency=currency,\n                ex_dividend=self.schedule.pschedule3[i + 1],\n                # period params\n                start=self.schedule.aschedule[i],\n                end=self.schedule.aschedule[i + 1],\n                frequency=self.schedule.frequency_obj,\n                convention=convention,\n                termination=self.schedule.aschedule[-1],\n                stub=self.schedule._stubs[i],\n                roll=NoInput(0),  # inferred from frequency obj\n                calendar=self.schedule.calendar,\n                adjuster=self.schedule.modifier,\n                # Each individual period is not genuine Period, only psuedo periods to derive the\n                # cashflow calculation so no 'non-deliverable' or 'index' params are required.\n            )\n            for i in range(self.schedule.n_periods)\n        ]\n\n    def try_rate(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        **kwargs: Any,\n    ) -> Result[DualTypes]:\n        try:\n            r_i = [period.rate(rate_curve=rate_curve) for period in self.float_periods]\n            d_i = [period.period_params.dcf for period in self.float_periods]\n        except Exception as e:\n            return Err(e)\n\n        if self.rate_metric == \"compounding\":\n            f = self.schedule.periods_per_annum\n            r = np.prod(1.0 + np.array(r_i) * np.array(d_i) / 100.0)\n            r = r ** (1.0 / (self.dcf * f))\n            r = (r - 1) * f * 100.0\n        elif self.rate_metric == \"simple\":\n            r = np.prod(1.0 + np.array(r_i) * np.array(d_i) / 100.0)\n            r = (r - 1.0) * 100.0 / self.dcf\n        else:\n            return Err(ValueError(\"`rate_metric` must be 'simple' or 'compounding'.\"))\n        return Ok(r)\n\n    def rate(self, *, rate_curve: CurveOption_ = NoInput(0)) -> DualTypes:\n        r\"\"\"Calculate a single *rate* representation for the *Period's* cashflow.\n\n        The *rate* is determined from the compounded *Period* rates according to:\n\n        .. math::\n\n           \\left ( 1 + \\frac{r}{f} \\right )^{df} = \\prod_{i=1}^n \\left ( 1 + r_i(\\mathbf{C}, R_j, z) d_i \\right )\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n\n        Returns\n        -------\n        float, Dual, Dual2 or Variable\n        \"\"\"  # noqa: E501\n\n        return self.try_rate(rate_curve=rate_curve).unwrap()\n\n    def unindexed_reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        **kwargs: Any,\n    ) -> DualTypes:\n        # determine each rate from individual Periods\n        r_i = [period.rate(rate_curve=rate_curve) for period in self.float_periods]\n        d_i = [period.period_params.dcf for period in self.float_periods]\n        r: DualTypes = np.prod(1.0 + np.array(r_i) * np.array(d_i) / 100.0) - 1.0\n        return -self.settlement_params.notional * r\n\n    def try_unindexed_reference_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        try:\n            r_i = [period.rate(rate_curve=rate_curve) for period in self._float_periods]\n            d_i = [period.period_params.dcf for period in self._float_periods]\n            a_i = [\n                period.try_unindexed_reference_cashflow_analytic_delta(\n                    rate_curve=rate_curve, disc_curve=disc_curve\n                ).unwrap()\n                for period in self._float_periods\n            ]\n        except Exception as e:\n            return Err(e)\n\n        lhs = np.prod(1.0 + np.array(r_i) * np.array(d_i) / 100.0)\n        rhs = np.sum([a / (1 + r * d / 100.0) for (a, d, r) in zip(a_i, d_i, r_i, strict=False)])\n        return Ok(lhs * rhs)\n\n    def try_unindexed_reference_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        try:\n            r_i = [period.rate(rate_curve=rate_curve) for period in self.float_periods]\n            d_i = [period.period_params.dcf for period in self.float_periods]\n            dfs_i = [\n                period.try_unindexed_reference_cashflow_analytic_rate_fixings(\n                    rate_curve=rate_curve,\n                    disc_curve=disc_curve,\n                    fx=fx,\n                    fx_vol=fx_vol,\n                    index_curve=index_curve,\n                ).unwrap()\n                for period in self.float_periods\n            ]\n        except Exception as e:\n            return Err(e)\n\n        scalar = np.prod(1.0 + np.array(r_i) * np.array(d_i) / 100.0)\n        dfs = [\n            df * scalar / (1 + d * r / 100.0) for (df, d, r) in zip(dfs_i, d_i, r_i, strict=False)\n        ]\n        return Ok(concat(dfs))\n\n    def cashflows(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> dict[str, Any]:\n        d = super().cashflows(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            settlement=settlement,\n            forward=forward,\n            base=base,\n        )\n        d[defaults.headers[\"dcf\"]] = self.dcf  # reinsert the overload\n        return d\n\n\ndef _get_ibor_curve_from_dict(fixing_frequency: Frequency, d: dict[str, _BaseCurve]) -> _BaseCurve:\n    remapped = {k.upper(): v for k, v in d.items()}\n    try:\n        freq_str = _get_tenor_from_frequency(fixing_frequency)\n        return remapped[freq_str]\n    except KeyError:\n        raise ValueError(\n            \"If supplying `rate_curve` as dict must provide a tenor mapping key and curve for\"\n            f\"the frequency of the given Period. The missing mapping is '{freq_str}'.\"\n        )\n\n\ndef _get_ibor_curve_from_dict2(fixing_frequency: str, d: dict[str, _BaseCurve]) -> _BaseCurve:\n    remapped = {k.upper(): v for k, v in d.items()}\n    try:\n        return remapped[fixing_frequency.upper()]\n    except KeyError:\n        raise ValueError(\n            \"If supplying `rate_curve` as dict must provide a tenor mapping key and curve for\"\n            f\"the frequency of the given Period. The missing mapping is '{fixing_frequency}'.\"\n        )\n\n\nclass _UnindexedReferenceCashflowFixingsSensitivity:\n    @staticmethod\n    def _ibor(\n        self: FloatPeriod, rate_curve: _BaseCurve | dict[str, _BaseCurve]\n    ) -> Result[DataFrame]:\n        if self.period_params.stub:\n            if isinstance(rate_curve, dict):\n                rate_curve_: dict[str, _BaseCurve] = rate_curve\n            else:\n                rate_curve_ = {\n                    _get_tenor_from_frequency(self.rate_params.fixing_frequency): rate_curve\n                }\n            return _UnindexedReferenceCashflowFixingsSensitivity._ibor_stub(\n                self=self,\n                rate_curve=rate_curve_,\n                frequency_str=_get_tenor_from_frequency(self.rate_params.fixing_frequency),\n            )\n        else:\n            if isinstance(rate_curve, dict):\n                rate_curve__: _BaseCurve = _get_ibor_curve_from_dict(\n                    self.rate_params.fixing_frequency, rate_curve\n                )\n            else:\n                rate_curve__ = rate_curve\n            return _UnindexedReferenceCashflowFixingsSensitivity._ibor_regular(\n                self=self,\n                rate_curve=rate_curve__,\n                frequency_str=_get_tenor_from_frequency(self.rate_params.fixing_frequency),\n            )\n\n    @staticmethod\n    def _ibor_regular(\n        self: FloatPeriod,\n        rate_curve: _BaseCurve,\n        frequency_str: str,\n    ) -> Result[DataFrame]:\n        return Ok(\n            DataFrame(\n                index=Index(data=[self.rate_params.rate_fixing.date], name=\"obs_dates\"),\n                data=[\n                    -self.settlement_params.notional * self.period_params.dcf * 0.0001\n                    if isinstance(self.rate_params.rate_fixing.value, NoInput)\n                    else 0.0\n                ],\n                columns=MultiIndex.from_tuples(\n                    [\n                        (\n                            rate_curve.id,\n                            self.settlement_params.currency,\n                            self.settlement_params.notional_currency,\n                            frequency_str,\n                        )\n                    ],\n                    names=[\"identifier\", \"local_ccy\", \"display_ccy\", \"frequency\"],\n                ),\n            )\n        )\n\n    @staticmethod\n    def _ibor_stub(\n        self: FloatPeriod,\n        rate_curve: dict[str, _BaseCurve],\n        frequency_str: str,\n    ) -> Result[DataFrame]:\n        # get consistent curves for the tenors of the stub fixings\n        tenors, ends = _find_neighbouring_tenors(\n            end=self.rate_params.rate_fixing.accrual_end,\n            start=self.rate_params.rate_fixing.accrual_start,\n            tenors=[_ for _ in rate_curve if _.upper() != \"RFR\"],\n            rate_series=self.rate_params.rate_fixing.series,  # type: ignore[union-attr]\n        )\n        rate_curve_1: _BaseCurve = _get_ibor_curve_from_dict2(tenors[0], rate_curve)\n        df1_res = _UnindexedReferenceCashflowFixingsSensitivity._ibor_regular(\n            self=self,\n            rate_curve=rate_curve_1,\n            frequency_str=tenors[0],\n        )\n        if len(tenors) == 1 or tenors[0] == tenors[1]:\n            return df1_res  # then no multiple curves for the stub\n        else:\n            rate_curve_2: _BaseCurve = _get_ibor_curve_from_dict2(tenors[1], rate_curve)\n            df2_res = _UnindexedReferenceCashflowFixingsSensitivity._ibor_regular(\n                self=self,\n                rate_curve=rate_curve_2,\n                frequency_str=tenors[1],\n            )\n            alpha = (ends[1] - self.period_params.end) / (ends[1] - ends[0])\n            return Ok(\n                merge(\n                    left=df1_res.unwrap() * alpha,\n                    right=df2_res.unwrap() * (1 - alpha),\n                    left_index=True,\n                    right_index=True,\n                )\n            )\n\n    @staticmethod\n    def _rfr(\n        self: FloatPeriod,\n        rate_curve: _BaseCurve,\n    ) -> Result[DataFrame]:\n        rf: RFRFixing = self.rate_params.rate_fixing  # type: ignore[assignment]\n\n        if isinstance(rf.value, NoInput):\n            # then some sensitivity still exists\n            drdr = _UnindexedReferenceCashflowFixingsSensitivity._rfr_drdr_approximation(\n                self=self,\n                rate_curve=rate_curve,\n            )\n        else:\n            # all sensitivity is zero\n            drdr = np.array([0.0 for _ in range(len(rf.dates_obs) - 1)])\n\n        temp = Series(\n            index=rf.dates_obs[:-1],\n            data=-self.settlement_params.notional * self.period_params.dcf * 0.0001 * drdr,\n        )\n        temp[rf.populated.index] = 0.0\n\n        df1 = DataFrame(\n            index=Index(data=rf.dates_obs[:-1], name=\"obs_dates\"),\n            data=temp.to_list(),\n            columns=MultiIndex.from_tuples(\n                [\n                    (\n                        rate_curve.id,\n                        self.settlement_params.currency,\n                        self.settlement_params.notional_currency,\n                        \"1B\",\n                    )\n                ],\n                names=[\"identifier\", \"local_ccy\", \"display_ccy\", \"frequency\"],\n            ),\n        )\n        return Ok(df1)\n\n    @staticmethod\n    def _rfr_drdr_approximation(\n        self: FloatPeriod,\n        rate_curve: _BaseCurve,\n    ) -> Arr1dObj:\n        \"\"\"\n        Determine the value :math:`\\frac{\\\\partial r(r_i, z)}{\\\\partial r_j}` for rate\n        fixing sensitivity.\n\n        For NoneSimple spread compounding this formula is exact, which covers most cases.\n\n        For ISDAFlatCompounding this is approximated as the NoneSimple case so is an\n        approximation.\n\n        For ISDACompounding the geometric 1-day average rate is used as a component in the formula\n        meaning the result is approximate.\n\n        These values do **not** distinguish between published and unpublished fixings. This should\n        be adjusted post.\n\n        Returns\n        -------\n        ndarray\n        \"\"\"\n        rf: RFRFixing = self.rate_params.rate_fixing  # type: ignore[assignment]\n        d_hat_i = rf.dcfs_dcf\n        z = self.rate_params.float_spread\n        fixing_method = self.rate_params.fixing_method\n        spread_compound_method = self.rate_params.spread_compound_method\n        method_param = self.rate_params.fixing_method.method_param()\n\n        # approximate sensitivity to each fixing\n        z = z / 100.0\n\n        d = d_hat_i.sum()\n        if type(fixing_method) in [\n            FloatFixingMethod.RFRLockoutAverage,\n            FloatFixingMethod.RFRObservationShiftAverage,\n            FloatFixingMethod.RFRLookbackAverage,\n            FloatFixingMethod.RFRPaymentDelayAverage,\n        ]:\n            drdri: Arr1dObj = d_hat_i / d\n        else:\n            unpopulated = rf.unpopulated\n            populated = rf.populated\n            r_i = Series(index=rf.dates_obs[:-1], data=np.nan, dtype=object)\n            r_i.update(populated)  #  type: ignore[arg-type]\n            # determine the rate for the period, from the curve if necessary\n            if unpopulated.index[0] < rate_curve.nodes.initial:\n                raise ValueError(err.VE_BEFORE_INITIAL)\n            _RFRRate._forecast_fixing_rates_from_curve(\n                unpopulated=unpopulated,\n                populated=populated,\n                fixing_rates=r_i,  # type: ignore[arg-type]\n                rate_curve=rate_curve,\n                dates_obs=rf.dates_obs,\n                dcfs_obs=rf.dcfs_obs,\n            )\n            r_star = _RFRRate._inefficient_calculation(\n                fixing_rates=r_i,\n                fixing_dcfs=d_hat_i,\n                fixing_method=fixing_method,\n                spread_compound_method=spread_compound_method,\n                float_spread=self.rate_params.float_spread,\n            ).unwrap()\n            if spread_compound_method == SpreadCompoundMethod.ISDACompounding:\n                # this makes a number of approximations including reversing a compounded spread\n                # with a simple formula\n                r_bar, d_bar, n = average_rate(\n                    effective=self.period_params.start,\n                    termination=self.period_params.end,\n                    convention=self.rate_params.fixing_series.convention,\n                    rate=r_star - z,\n                    dcf=d,\n                )\n                drdri = d_hat_i / (1 + d_hat_i * (r_bar + z) / 100.0) * (1 + r_star / 100.0 * d) / d  # type: ignore[operator]\n            # elif spread_compound_method == SpreadCompoundMethod.ISDAFlatCompounding:\n            #     r_star = ((1 + d_bar * (r_bar + z) / 100.0) ** n - 1) * 100.0 / (n * d_bar)\n            #     drdri1 = di / (1 + di * (r_bar + z) / 100.0) * ((r_star / 100.0 * d) + 1) / d\n            #     drdri2 = di / (1 + di * r_bar / 100.0) * ((r_star0 / 100.0 * d) + 1) / d\n            #     drdri = (drdri1 + drdri2) / 2.0\n            else:  # spread_compound_method == SpreadCompoundMethod.NoneSimple:\n                r_i_ = r_i.to_numpy()\n                drdri = d_hat_i / (1 + d_hat_i * r_i_ / 100.0) * ((r_star - z) / 100.0 * d + 1) / d\n\n        if type(fixing_method) in [\n            FloatFixingMethod.RFRLockoutAverage,\n            FloatFixingMethod.RFRLockout,\n        ]:\n            for i in range(method_param):\n                drdri[-(method_param + 1)] += drdri[-(i + 1)]\n                drdri[-(i + 1)] = 0.0\n\n        return drdri\n"
  },
  {
    "path": "python/rateslib/periods/float_rate.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom pandas import Series\n\nfrom rateslib import NoInput\nfrom rateslib.data.fixings import _get_float_rate_series_or_blank, _IBORRate, _RFRRate\nfrom rateslib.enums.generics import Err, Ok, _drb\nfrom rateslib.enums.parameters import (\n    FloatFixingMethod,\n    SpreadCompoundMethod,\n    _get_float_fixing_method,\n    _get_spread_compound_method,\n)\nfrom rateslib.periods.utils import _get_rfr_curve_from_dict\nfrom rateslib.scheduling.frequency import _get_frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        FloatRateSeries,\n        Frequency,\n        Result,\n        Series,\n        _BaseCurve_,\n        datetime,\n        str_,\n    )\n\n\ndef rate_value(\n    start: datetime,\n    end: datetime,\n    rate_curve: CurveOption_ = NoInput(0),\n    *,\n    rate_fixings: DualTypes_ | str = NoInput(0),\n    frequency: Frequency | str_ = NoInput(0),\n    rate_series: FloatRateSeries | str_ = NoInput(0),\n    fixing_method: FloatFixingMethod | str = FloatFixingMethod.RFRPaymentDelay(),\n    spread_compound_method: SpreadCompoundMethod | str = SpreadCompoundMethod.NoneSimple,\n    float_spread: DualTypes = 0.0,\n    stub: bool = False,\n) -> DualTypes:\n    return try_rate_value(\n        start=start,\n        end=end,\n        rate_curve=rate_curve,\n        rate_series=rate_series,\n        frequency=frequency,\n        rate_fixings=rate_fixings,\n        fixing_method=fixing_method,\n        spread_compound_method=spread_compound_method,\n        float_spread=float_spread,\n        stub=stub,\n    ).unwrap()\n\n\ndef try_rate_value(\n    start: datetime,\n    end: datetime,\n    rate_curve: CurveOption_ = NoInput(0),\n    *,\n    rate_series: FloatRateSeries | str_ = NoInput(0),\n    frequency: Frequency | str_ = NoInput(0),\n    rate_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n    fixing_method: FloatFixingMethod | str = FloatFixingMethod.RFRPaymentDelay(),\n    spread_compound_method: SpreadCompoundMethod | str = SpreadCompoundMethod.NoneSimple,\n    float_spread: DualTypes = 0.0,\n    stub: bool = False,\n) -> Result[DualTypes]:\n    \"\"\"\n    Derive a floating rate value from a combination of market inputs.\n\n\n    \"\"\"\n    fm = _get_float_fixing_method(fixing_method)\n    scm = _get_spread_compound_method(spread_compound_method)\n    rs = _get_float_rate_series_or_blank(rate_series)\n    if type(fm) is FloatFixingMethod.IBOR:\n        return _IBORRate._rate(\n            start=start,\n            end=end,\n            rate_curve=rate_curve,\n            rate_fixings=rate_fixings,\n            float_spread=_drb(0.0, float_spread),\n            lag=fm.method_param(),\n            stub=stub,\n            rate_series=rs,\n            frequency=_get_frequency(frequency, NoInput(0), NoInput(0)),\n        )\n    else:  #  RFR based\n        if isinstance(rate_curve, dict):\n            rate_curve_: _BaseCurve_ = _get_rfr_curve_from_dict(rate_curve)\n        else:\n            rate_curve_ = rate_curve\n        r_result = _RFRRate._rate(\n            start=start,\n            end=end,\n            rate_curve=rate_curve_,\n            rate_fixings=rate_fixings,\n            fixing_method=fm,\n            spread_compound_method=scm,\n            float_spread=float_spread,\n            rate_series=rs,\n        )\n        if isinstance(r_result, Err):\n            return r_result\n        else:\n            return Ok(r_result.unwrap()[0])\n"
  },
  {
    "path": "python/rateslib/periods/fx_volatility.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom abc import ABCMeta, abstractmethod\nfrom datetime import timezone\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.curves._parsers import _validate_obj_not_no_input\nfrom rateslib.data.fixings import _get_fx_index\nfrom rateslib.dual import dual_exp, dual_log, dual_norm_cdf, dual_norm_pdf, newton_1dim\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, Ok, Result, _drb\nfrom rateslib.enums.parameters import (\n    FXDeltaMethod,\n    FXOptionMetric,\n    OptionType,\n    _get_fx_delta_type,\n    _get_fx_option_metric,\n)\nfrom rateslib.fx import FXForwards\nfrom rateslib.periods.parameters import (\n    _FXOptionParams,\n    _IndexParams,\n    _NonDeliverableParams,\n    _SettlementParams,\n)\nfrom rateslib.periods.protocols import _BasePeriodStatic, _WithAnalyticFXOptionGreeks\nfrom rateslib.periods.utils import (\n    _get_fx_vol_value_maybe_from_obj,\n    _get_vol_delta_type,\n    _get_vol_smile_or_raise,\n    _get_vol_smile_or_value,\n    _validate_fx_as_forwards,\n)\nfrom rateslib.volatility import (\n    FXDeltaVolSmile,\n    FXDeltaVolSurface,\n    FXSabrSmile,\n    FXSabrSurface,\n)\nfrom rateslib.volatility.fx import FXVolObj\nfrom rateslib.volatility.fx.delta_vol import (\n    _moneyness_from_atm_delta_one_dimensional,\n    _moneyness_from_atm_delta_two_dimensional,\n    _moneyness_from_delta_one_dimensional,\n    _moneyness_from_delta_two_dimensional,\n)\nfrom rateslib.volatility.fx.utils import (\n    _delta_type_constants,\n    _moneyness_from_atm_delta_closed_form,\n    _moneyness_from_delta_closed_form,\n)\nfrom rateslib.volatility.utils import (\n    _OptionModelBlack76,\n    _surface_index_left,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        Arr1dF64,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        FXIndex,\n        Number,\n        Series,\n        _BaseCurve,\n        _BaseCurve_,\n        _FXVolOption,\n        _FXVolOption_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\nUTC = timezone.utc\n\n\nclass _BaseFXOptionPeriod(_BasePeriodStatic, _WithAnalyticFXOptionGreeks, metaclass=ABCMeta):\n    r\"\"\"\n    Abstract base class for *FXOptionPeriods* types.\n\n    **See Also**: :class:`~rateslib.periods.FXCallPeriod`,\n    :class:`~rateslib.periods.FXPutPeriod`\n\n    \"\"\"\n\n    def analytic_greeks(\n        self,\n        rate_curve: _BaseCurve,\n        disc_curve: _BaseCurve,\n        fx: FXForwards,\n        fx_vol: _FXVolOption_ = NoInput(0),\n        premium: DualTypes_ = NoInput(0),  # expressed in the payment currency\n        premium_payment: datetime_ = NoInput(0),\n    ) -> dict[str, Any]:\n        return super()._base_analytic_greeks(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            premium=premium,\n            premium_payment=premium_payment,\n        )\n\n    @property\n    def period_params(self) -> None:\n        \"\"\"This *Period* type has no\n        :class:`~rateslib.periods.parameters._PeriodParams`.\"\"\"\n        return self._period_params\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams`\n        of the *Period*.\"\"\"\n        return self._settlement_params\n\n    @property\n    def index_params(self) -> _IndexParams | None:\n        \"\"\"The :class:`~rateslib.periods.parameters._IndexParams` of\n        the *Period*, if any.\"\"\"\n        return self._index_params\n\n    @property\n    def non_deliverable_params(self) -> _NonDeliverableParams | None:\n        \"\"\"The :class:`~rateslib.periods.parameters._NonDeliverableParams` of the\n        *Period*., if any.\"\"\"\n        return self._non_deliverable_params\n\n    @property\n    def rate_params(self) -> None:\n        \"\"\"This *Period* type has no rate parameters.\"\"\"\n        return self._rate_params\n\n    @property\n    def fx_option_params(self) -> _FXOptionParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._FXOptionParams` of the\n        *Period*.\"\"\"\n        return self._fx_option_params\n\n    @abstractmethod\n    def __init__(\n        self,\n        *,\n        # option params:\n        direction: OptionType,\n        delivery: datetime,  # otherwise termed the 'payment' of the period\n        pair: FXIndex | str,\n        expiry: datetime,\n        strike: DualTypes_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        delta_type: FXDeltaMethod | str_ = NoInput(0),\n        metric: FXOptionMetric | str_ = NoInput(0),\n        option_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        # currency args:\n        ex_dividend: datetime_ = NoInput(0),\n        # # non-deliverable args:\n        # nd_pair: str_ = NoInput(0),\n        # fx_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        # # index-args:\n        # index_base: DualTypes_ = NoInput(0),\n        # index_lag: int_ = NoInput(0),\n        # index_method: IndexMethod | str_ = NoInput(0),\n        # index_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  #type: ignore[type-var]\n        # index_only: bool_ = NoInput(0),\n        # index_base_date: datetime_ = NoInput(0),\n        # index_reference_date: datetime_ = NoInput(0),\n    ) -> None:\n        # self._index_params = _init_or_none_IndexParams(\n        #     _index_base=index_base,\n        #     _index_lag=index_lag,\n        #     _index_method=index_method,\n        #     _index_fixings=index_fixings,\n        #     _index_only=index_only,\n        #     _index_base_date=index_base_date,\n        #     _index_reference_date=_drb(delivery, index_reference_date),\n        # )\n        self._index_params = None\n        self._fx_option_params = _FXOptionParams(\n            _direction=direction,\n            _expiry=expiry,\n            _delivery=delivery,\n            _delta_type=_get_fx_delta_type(_drb(defaults.fx_delta_type, delta_type)),\n            _fx_index=_get_fx_index(pair),\n            _strike=strike,\n            _metric=_drb(defaults.fx_option_metric, metric),\n            _option_fixings=option_fixings,\n        )\n        self._rate_params = None\n        self._period_params = None\n\n        nd_pair = NoInput(0)\n        if isinstance(nd_pair, NoInput):\n            # then option is directly deliverable\n            self._non_deliverable_params: _NonDeliverableParams | None = None\n            self._settlement_params = _SettlementParams(\n                _notional=_drb(defaults.notional, notional),\n                _payment=delivery,\n                _currency=self.fx_option_params.fx_index.pair[3:],\n                _notional_currency=self.fx_option_params.fx_index.pair[:3],\n                _ex_dividend=ex_dividend,\n            )\n        else:\n            pass\n            # fx_ccy1, fx_ccy2 = self.fx_option_params.pair[:3], self.fx_option_params.pair[3:]\n            # nd_ccy1, nd_ccy2 = nd_pair.lower()[:3], nd_pair.lower()[3:]\n            #\n            # if nd_ccy1 != fx_ccy1 and nd_ccy1 != fx_ccy2:\n            #     raise ValueError(\n            #         err.VE_MISMATCHED_FX_PAIR_ND_PAIR.format(nd_ccy1, self.fx_option_params.pair)\n            #     )\n            # elif nd_ccy2 != fx_ccy1 and nd_ccy2 != fx_ccy2:\n            #     raise ValueError(\n            #         err.VE_MISMATCHED_FX_PAIR_ND_PAIR.format(nd_ccy2, self.fx_option_params.pair)\n            #     )\n            #\n            # self._non_deliverable_params = _NonDeliverableParams(\n            #     _currency=fx_ccy1,\n            #     _pair=nd_pair,\n            #     _delivery=delivery,\n            #     _fx_fixings=fx_fixings,\n            # )\n            # self._settlement_params = _SettlementParams(\n            #     _notional=_drb(defaults.notional, notional),\n            #     _payment=delivery,\n            #     _currency=fx_ccy1,\n            #     _notional_currency=fx_ccy1,\n            #     _ex_dividend=ex_dividend,\n            # )\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    def unindexed_reference_cashflow(  # type: ignore[override]\n        self,\n        *,\n        rate_curve: _BaseCurve_ = NoInput(0),  # w(.) variety\n        disc_curve: _BaseCurve_ = NoInput(0),  # v(.) variety\n        # index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        **kwargs: Any,\n    ) -> DualTypes:\n        # The unindexed_reference_cashflow does not require a discount curve.\n        # A curve may only be required to determine an evaluation date, which in turn is used to\n        # derive 'time_to_expiry'. The cashflow is expressed in reference currency on the delivery\n        # date of the FX forward, i.e. the 'forward FX date'.\n        if isinstance(self.fx_option_params.strike, NoInput):\n            raise ValueError(err.VE_NEEDS_STRIKE)\n        k = self.fx_option_params.strike\n\n        if not isinstance(self.fx_option_params.option_fixing.value, NoInput):\n            # then the cashflow amount is defined by a known fixing\n            fix: DualTypes = self.fx_option_params.option_fixing.value\n            phi: OptionType = self.fx_option_params.direction\n\n            if phi == OptionType.Call and k < fix:\n                return (fix - k) * self.settlement_params.notional\n            elif phi == OptionType.Put and k > fix:\n                return (k - fix) * self.settlement_params.notional\n            else:\n                return 0.0\n\n        else:\n            # value is expressed in reference currency (i.e. pair[3:])\n            fx_ = _validate_fx_as_forwards(fx)\n\n            vol_ = _get_fx_vol_value_maybe_from_obj(\n                fx_vol=fx_vol,\n                fx=fx_,\n                rate_curve=rate_curve,\n                strike=k,\n                pair=self.fx_option_params.pair,\n                delivery=self.fx_option_params.delivery,\n                expiry=self.fx_option_params.expiry,\n            )\n\n            # Get time to expiry from some object\n            if not isinstance(disc_curve, NoInput):\n                t_e = self.fx_option_params.time_to_expiry(disc_curve.nodes.initial)\n            elif isinstance(fx_vol, FXVolObj):\n                t_e = self.fx_option_params.time_to_expiry(fx_vol.meta.eval_date)\n            elif not isinstance(rate_curve, NoInput):\n                t_e = self.fx_option_params.time_to_expiry(rate_curve.nodes.initial)\n            else:\n                raise ValueError(\n                    \"Object required to define evaluation date and time to expiry.\\n\"\n                    \"Use one of `disc_curve`, `fx_vol`, or `rate_curve`.\"\n                )\n\n            expected = _OptionModelBlack76._value(\n                F=fx_.rate(self.fx_option_params.pair, self.fx_option_params.delivery),\n                K=k,\n                rate_shift=0.0,\n                t_e=t_e,\n                v2=1.0,  # disc_curve_[delivery] / disc_curve_[payment],\n                vol=vol_ / 100.0,\n                phi=self.fx_option_params.direction.value,  # controls calls or put price\n            )\n            return expected * self.settlement_params.notional\n\n    def try_rate(\n        self,\n        rate_curve: _BaseCurve,\n        disc_curve: _BaseCurve,\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        metric: FXOptionMetric | str_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Return the pricing metric of the *FXOption*, with lazy error handling.\n\n        See :meth:`~rateslib.periods._BaseFXOptionPeriod.rate`.\n        \"\"\"\n        if not isinstance(metric, NoInput):\n            metric_ = _get_fx_option_metric(metric)\n        else:  # use metric associated with self\n            metric_ = self.fx_option_params.metric\n\n        cash_res = self.try_unindexed_reference_cashflow(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n        )\n        if cash_res.is_err:\n            return cash_res\n        cash: DualTypes = cash_res.unwrap()\n\n        if metric_ == FXOptionMetric.Pips:\n            points_premium = cash / self.settlement_params.notional\n            if isinstance(forward, NoInput):\n                return Ok(points_premium * 10000.0)\n            else:\n                return Ok(\n                    points_premium\n                    * 10000.0\n                    * disc_curve[self.settlement_params.payment]\n                    / disc_curve[forward]\n                )\n        else:  # metric_ == FXOptionMetric.Percent:\n            fx_ = _validate_fx_as_forwards(fx)\n            currency_premium = cash / fx_.rate(\n                self.fx_option_params.pair, self.settlement_params.payment\n            )\n            if isinstance(forward, NoInput):\n                return Ok(currency_premium / self.settlement_params.notional * 100)\n            else:\n                currency_premium *= rate_curve[self.settlement_params.payment] / rate_curve[forward]\n                return Ok(currency_premium / self.settlement_params.notional * 100)\n\n    def rate(\n        self,\n        *,\n        rate_curve: _BaseCurve,\n        disc_curve: _BaseCurve,\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        metric: str_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return the pricing metric of the *FXOption*.\n\n        This is priced according to the ``payment`` date of the *OptionPeriod*.\n\n        Parameters\n        ----------\n        rate_curve: Curve\n            The discount *Curve* for the LHS currency. (Not used).\n        disc_curve: Curve\n            The discount *Curve* for the RHS currency.\n        fx: float, FXRates, FXForwards, optional\n            The object to project the currency pair FX rate at delivery.\n        base: str, optional\n            Not used by `rate`.\n        fx_vol: float, Dual, Dual2\n            The percentage log-normal volatility to price the option.\n        metric: str in {\"pips\", \"percent\"}\n            The metric to return. If \"pips\" assumes the premium is in foreign (rhs)\n            currency. If \"percent\", the premium is assumed to be domestic (lhs).\n        forward: datetime, optional (set as payment date of option)\n            The date to project the cashflow value to using the ``disc_curve`` if RHS (\"pips\") or\n            using ``rate_curve`` if LHS (\"percent\").\n\n        Returns\n        -------\n        float, Dual, Dual2 or dict of such.\n        \"\"\"\n        return self.try_rate(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            metric=metric,\n            forward=forward,\n        ).unwrap()\n\n    def implied_vol(\n        self,\n        rate_curve: _BaseCurve,\n        disc_curve: _BaseCurve,\n        fx: FXForwards,\n        premium: DualTypes,\n        metric: FXOptionMetric | str_ = NoInput(0),\n    ) -> Number:\n        \"\"\"\n        Calculate the implied volatility of the FX option.\n\n        Parameters\n        ----------\n        rate_curve: Curve\n            Not used by `implied_vol`.\n        disc_curve: Curve\n            The discount *Curve* for the RHS currency.\n        fx: FXForwards\n            The object to project the currency pair FX rate at delivery.\n        premium: float, Dual, Dual2\n            The premium value of the option paid at the appropriate payment date. Expressed\n            either in *'pips'* or *'percent'* of notional. Must align with ``metric``.\n        metric: str in {\"pips\", \"percent\"}, optional\n            The manner in which the premium is expressed.\n\n        Returns\n        -------\n        float, Dual or Dual2\n        \"\"\"\n        if isinstance(self.fx_option_params.strike, NoInput):\n            raise ValueError(err.VE_NEEDS_STRIKE)\n        k = self.fx_option_params.strike\n        phi = self.fx_option_params.direction\n        metric_ = _get_fx_option_metric(_drb(self.fx_option_params.metric, metric))\n\n        # This function uses newton_1d and is AD safe.\n\n        # convert the premium to a standardised immediate pips value.\n        if metric_ == FXOptionMetric.Percent:\n            # convert premium to pips form\n            premium = (\n                premium\n                * fx.rate(self.fx_option_params.pair, self.settlement_params.payment)\n                * 100.0\n            )\n        # convert to immediate pips form\n        imm_premium = premium * disc_curve[self.settlement_params.payment]\n\n        t_e = self.fx_option_params.time_to_expiry(disc_curve.nodes.initial)\n        v2 = disc_curve[self.fx_option_params.delivery]\n        f_d = fx.rate(self.fx_option_params.pair, self.fx_option_params.delivery)\n\n        def root(\n            vol: DualTypes, f_d: DualTypes, k: DualTypes, t_e: float, v2: DualTypes, phi: float\n        ) -> tuple[DualTypes, DualTypes]:\n            f0 = _OptionModelBlack76._value(f_d, k, 0.0, t_e, v2, vol, phi) * 10000.0 - imm_premium\n            sqrt_t = t_e**0.5\n            d_plus = _OptionModelBlack76._d_plus_min_u(k / f_d, vol * sqrt_t, 0.5)\n            f1 = v2 * dual_norm_pdf(phi * d_plus) * f_d * sqrt_t * 10000.0\n            return f0, f1\n\n        result = newton_1dim(root, 0.10, args=(f_d, k, t_e, v2, phi))\n        _: Number = result[\"g\"] * 100.0\n        return _\n\n    # Volatility determinations\n\n    def _index_vol_and_strike_from_atm(\n        self,\n        delta_type: FXDeltaMethod,\n        vol: _FXVolOption,\n        w_deli: DualTypes,\n        w_spot: DualTypes,\n        f: DualTypes | FXForwards,\n        t_e: DualTypes,\n    ) -> tuple[DualTypes | None, DualTypes, DualTypes]:\n        \"\"\"\n        This function returns strike and vol, where available, a delta index for an option period\n        defined by ATM delta.\n\n        Parameters\n        ----------\n        delta_type: FXDeltaMethod\n            The delta type of the option period.\n        vol: DualTypes | Smile | Surface\n            The volatility used, either specifici value or a Smile/Surface.\n        w_deli: DualTypes\n            The relevant discount factor at delivery.\n        w_spot: DualTypes\n            The relevant discount factor at spot.\n        f: DualTypes, FXForwards\n            The forward FX rate for delivery. FXForwards is used when a SabrSurface is present.\n        t_e: DualTypes\n            The time to expiry\n\n        Returns\n        -------\n        (delta_index, vol, strike)\n        \"\"\"\n        # TODO this method branches depending upon eta0 and eta1, but depending upon the\n        # type of vol these maybe automatically set equal to each other. Refactoring this would\n        # make eliminate repeated type checking for the vol argument.\n        vol_delta_type = _get_vol_delta_type(vol, delta_type)\n\n        z_w = w_deli / w_spot\n        eta_0, z_w_0, _ = _delta_type_constants(delta_type, z_w, 0.0)  # u: unused\n        eta_1, z_w_1, _ = _delta_type_constants(vol_delta_type, z_w, 0.0)  #  u: unused\n\n        if isinstance(vol, FXSabrSmile | FXSabrSurface):\n            return self._index_vol_and_strike_from_atm_sabr(f, eta_0, vol)\n        else:  # DualTypes | FXDeltaVolSmile | FXDeltaVolSurface\n            f_: DualTypes = f  # type: ignore[assignment]\n            vol_: DualTypes | FXDeltaVolSmile | FXDeltaVolSurface = vol\n            return self._index_vol_and_strike_from_atm_dv(\n                f_,\n                eta_0,\n                eta_1,\n                z_w_0,\n                z_w_1,\n                vol_,\n                t_e,\n                delta_type,\n                vol_delta_type,\n                z_w,\n            )\n\n    def _index_vol_and_strike_from_atm_sabr(\n        self,\n        f: DualTypes | FXForwards,\n        eta_0: float,\n        vol: FXSabrSmile | FXSabrSurface,\n    ) -> tuple[DualTypes | None, DualTypes, DualTypes]:\n        \"\"\"Get vol and strike from ATM delta specification under a SABR model.\"\"\"\n        t_e = (self.fx_option_params.expiry - vol.meta.eval_date).days / 365.0\n        if isinstance(f, FXForwards):\n            f_d: DualTypes = f.rate(self.fx_option_params.pair, self.fx_option_params.delivery)\n            # _ad = _set_ad_order_objects([0], [f])  # GH755\n        else:\n            f_d = f  # type: ignore[assignment]\n\n        def root1d(\n            k: DualTypes, f_d: DualTypes, fx: DualTypes | FXForwards, as_float: bool\n        ) -> tuple[DualTypes, DualTypes]:\n            # if not as_float and isinstance(fx, FXForwards):\n            #     _set_ad_order_objects(_ad, [fx])\n            dsigma_dk: Number\n            sigma, dsigma_dk = vol._d_sabr_d_k_or_f(  # type: ignore[assignment]\n                k=k, f=fx, expiry=self.fx_option_params.expiry, as_float=as_float, derivative=1\n            )\n            f0 = -dual_log(k / f_d) + eta_0 * sigma**2 * t_e\n            f1 = -1 / k + eta_0 * 2 * sigma * dsigma_dk * t_e\n            return f0, f1\n\n        if isinstance(vol, FXSabrSmile):\n            alpha = vol.nodes.alpha\n        else:  # FXSabrSurface\n            vol_: FXSabrSurface = vol\n            expiry_posix = self.fx_option_params.expiry.replace(tzinfo=UTC).timestamp()\n            e_idx, _ = _surface_index_left(vol_.meta.expiries_posix, expiry_posix)\n            alpha = vol_.smiles[e_idx].nodes.alpha\n\n        root_solver = newton_1dim(\n            root1d,\n            f_d * dual_exp(eta_0 * alpha**2 * t_e),\n            args=(f_d, f),\n            pre_args=(True,),  # solve `as_float` in iterations\n            final_args=(False,),  # capture AD in final iterations\n            raise_on_fail=True,\n        )\n\n        k: DualTypes = root_solver[\"g\"]\n        v_ = vol.get_from_strike(k, f, self.fx_option_params.expiry)[1]\n        return None, v_, k\n\n    def _index_vol_and_strike_from_atm_dv(  # DeltaVol type models\n        self,\n        f: DualTypes,\n        eta_0: float,\n        eta_1: float,\n        z_w_0: DualTypes,\n        z_w_1: DualTypes,\n        vol: DualTypes | FXDeltaVolSmile | FXDeltaVolSurface,\n        t_e: DualTypes,\n        delta_type: FXDeltaMethod,\n        vol_delta_type: FXDeltaMethod,\n        z_w: DualTypes,\n    ) -> tuple[DualTypes | None, DualTypes, DualTypes]:\n        \"\"\"Determine strike from ATM delta specification with DeltaVol models or fixed volatility\"\"\"\n        if eta_0 == 0.5:  # then delta type is unadjusted\n            if eta_1 == 0.5:  # then smile delta type matches: closed form eqn available\n                if isinstance(vol, FXDeltaVolSmile | FXDeltaVolSurface):\n                    d_i: DualTypes = z_w_1 / 2.0\n                    vol_value: DualTypes = _get_vol_smile_or_raise(\n                        vol, self.fx_option_params.expiry\n                    )[d_i]\n                    delta_idx: DualTypes | None = d_i\n                else:\n                    vol_value = _validate_obj_not_no_input(vol, \"vol\")  # type: ignore[assignment]\n                    delta_idx = None\n                u = _moneyness_from_atm_delta_closed_form(vol_value, t_e)\n                return delta_idx, vol_value, u * f\n            else:  # then smile delta type unmatched: 2-d solver required\n                delta: DualTypes = z_w_0 * self.fx_option_params.direction.value / 2.0\n                u, delta_idx = _moneyness_from_delta_two_dimensional(\n                    delta,\n                    delta_type,\n                    _get_vol_smile_or_raise(vol, self.fx_option_params.expiry),\n                    t_e,\n                    z_w,\n                    self.fx_option_params.direction.value,\n                )\n        else:  # then delta type is adjusted,\n            if eta_1 == -0.5:  # then smile type matches: use 1-d solver\n                u = _moneyness_from_atm_delta_one_dimensional(\n                    delta_type,\n                    vol_delta_type,\n                    _get_vol_smile_or_value(vol, self.fx_option_params.expiry),\n                    t_e,\n                    z_w,\n                    self.fx_option_params.direction,\n                )\n                delta_idx = z_w_1 * u * 0.5\n            else:  # smile delta type unmatched: 2-d solver required\n                u, delta_idx = _moneyness_from_atm_delta_two_dimensional(\n                    delta_type,\n                    _get_vol_smile_or_raise(vol, self.fx_option_params.expiry),\n                    t_e,\n                    z_w,\n                    self.fx_option_params.direction,\n                )\n\n        if isinstance(vol, FXDeltaVolSmile | FXDeltaVolSurface):\n            vol_value = _get_vol_smile_or_raise(vol, self.fx_option_params.expiry)[delta_idx]\n        else:\n            vol_value = _validate_obj_not_no_input(vol, \"vol\")  # type: ignore[assignment]\n\n        # u, delta_idx, delta =\n        # self._moneyness_from_delta_three_dimensional(delta_type, vol, t_e, z_w)\n        return delta_idx, vol_value, u * f\n\n    def _index_vol_and_strike_from_delta(\n        self,\n        delta: float,\n        delta_type: FXDeltaMethod,\n        vol: _FXVolOption,\n        w_deli: DualTypes,\n        w_spot: DualTypes,\n        f: DualTypes | FXForwards,\n        t_e: DualTypes,\n    ) -> tuple[DualTypes | None, DualTypes, DualTypes]:\n        \"\"\"\n        This function returns strike and, where available, a delta index for an option period\n        defined by a fixed delta percentage.\n\n        Parameters\n        ----------\n        delta: float\n           The delta percent, e.g 0.25.\n        delta_type: FXDeltaMethod\n           The delta type of the option period.\n        vol: DualTypes | Smile | Surface\n           The volatility used, either a specific value or a Smile/Surface.\n        w_deli: DualTypes\n           The relevant discount factor at delivery.\n        w_spot: DualTypes\n           The relevant discount factor at spot.\n        f: DualTypes | FXForwards\n           The forward FX rate for delivery. When using a *SabrSurface* this is required in\n           *FXForwards* form.\n        t_e: DualTypes\n           The time to expiry\n\n        Returns\n        -------\n        (DualTypes, DualTypes)\n        \"\"\"\n        vol_delta_type = _get_vol_delta_type(vol, delta_type)\n        z_w = w_deli / w_spot\n\n        if isinstance(vol, FXSabrSmile | FXSabrSurface):\n            return self._index_vol_and_strike_from_delta_sabr(delta, delta_type, vol, z_w, f)\n        else:  # DualTypes | FXDeltaVolSmile | FXDeltaVolSurface\n            f_: DualTypes = f  # type: ignore[assignment]\n            vol_: DualTypes | FXDeltaVolSmile = vol  # type: ignore[assignment]\n            return self._index_vol_and_strike_from_delta_dv(\n                f_,\n                delta,\n                vol_,\n                t_e,\n                delta_type,\n                vol_delta_type,\n                z_w,\n            )\n\n    def _index_vol_and_strike_from_delta_dv(\n        self,\n        f: DualTypes,\n        delta: float,\n        vol: DualTypes | FXDeltaVolSmile | FXDeltaVolSurface,\n        t_e: DualTypes,\n        delta_type: FXDeltaMethod,\n        vol_delta_type: FXDeltaMethod,\n        z_w: DualTypes,\n    ) -> tuple[DualTypes | None, DualTypes, DualTypes]:\n        \"\"\"Determine strike and delta index for an option by delta % for DeltaVol type models\n        or constant volatility\"\"\"\n        eta_0, z_w_0, _ = _delta_type_constants(delta_type, z_w, 0.0)  # u: unused\n        eta_1, z_w_1, _ = _delta_type_constants(vol_delta_type, z_w, 0.0)  # u: unused\n        # then delta types are both unadjusted, used closed form.\n        if eta_0 == eta_1 and eta_0 == 0.5:\n            if isinstance(vol, FXDeltaVolSmile | FXDeltaVolSurface):\n                d_i: DualTypes = (-z_w_1 / z_w_0) * (\n                    delta - 0.5 * z_w_0 * (self.fx_option_params.direction + 1.0)\n                )\n                vol_value: DualTypes = _get_vol_smile_or_raise(vol, self.fx_option_params.expiry)[\n                    d_i\n                ]\n                delta_idx: DualTypes | None = d_i\n            else:\n                vol_value = _validate_obj_not_no_input(vol, \"vol\")  # type: ignore[assignment]\n                delta_idx = None\n            u: DualTypes = _moneyness_from_delta_closed_form(\n                delta, vol_value, t_e, z_w_0, self.fx_option_params.direction\n            )\n            return delta_idx, vol_value, u * f\n        # then delta types are both adjusted, use 1-d solver.\n        elif eta_0 == eta_1 and eta_0 == -0.5:\n            u = _moneyness_from_delta_one_dimensional(\n                delta,\n                delta_type,\n                vol_delta_type,\n                _get_vol_smile_or_value(vol, self.fx_option_params.expiry),\n                t_e,\n                z_w,\n                self.fx_option_params.direction,\n            )\n            delta_idx = (-z_w_1 / z_w_0) * (\n                delta - z_w_0 * u * (self.fx_option_params.direction + 1.0) * 0.5\n            )\n        else:  # delta adjustment types are different, use 2-d solver.\n            u, delta_idx = _moneyness_from_delta_two_dimensional(\n                delta,\n                delta_type,\n                _get_vol_smile_or_raise(vol, self.fx_option_params.expiry),\n                t_e,\n                z_w,\n                self.fx_option_params.direction,\n            )\n\n        _1: DualTypes | None = delta_idx\n        _2: DualTypes = u * f\n        if isinstance(vol, FXDeltaVolSmile | FXDeltaVolSurface):\n            vol_value = _get_vol_smile_or_raise(vol, self.fx_option_params.expiry)[delta_idx]\n        else:\n            vol_value = _validate_obj_not_no_input(vol, \"vol\")  # type: ignore[assignment]\n        return _1, vol_value, _2\n\n    def _index_vol_and_strike_from_delta_sabr(\n        self,\n        delta: float,\n        delta_type: FXDeltaMethod,\n        vol: FXSabrSmile | FXSabrSurface,\n        z_w: DualTypes,\n        f: DualTypes | FXForwards,\n    ) -> tuple[DualTypes | None, DualTypes, DualTypes]:\n        eta_0, z_w_0, _ = _delta_type_constants(delta_type, z_w, 0.0)  # u: unused\n        t_e = (self.fx_option_params.expiry - vol.meta.eval_date).days / 365.0\n        sqrt_t = t_e**0.5\n        if isinstance(f, FXForwards):\n            f_d: DualTypes = f.rate(self.fx_option_params.pair, self.fx_option_params.delivery)\n            # _ad = _set_ad_order_objects([0], [f])  # GH755\n        else:\n            f_d = f  # type: ignore[assignment]\n\n        def root1d(\n            k: DualTypes,\n            f_d: DualTypes,\n            fx: FXForwards | DualTypes,\n            z_w_0: DualTypes,\n            delta: float,\n            as_float: bool,\n        ) -> tuple[DualTypes, DualTypes]:\n            # if not as_float and isinstance(fx, FXForwards):\n            #     _set_ad_order_objects(_ad, [fx])\n\n            sigma, dsigma_dk = vol._d_sabr_d_k_or_f(\n                k=k, f=fx, expiry=self.fx_option_params.expiry, as_float=as_float, derivative=1\n            )\n            dn0 = -dual_log(k / f_d) / (sigma * sqrt_t) + eta_0 * sigma * sqrt_t\n            Phi = dual_norm_cdf(self.fx_option_params.direction * dn0)\n\n            if eta_0 == -0.5:\n                z_u_0, dz_u_dk = k / f_d, 1 / f_d\n                d_1 = -dz_u_dk * z_w_0 * self.fx_option_params.direction * Phi\n            else:\n                z_u_0, dz_u_dk = 1.0, 0.0\n                d_1 = 0.0\n\n            ddn_dk = (dual_log(k / f_d) / (sigma**2 * sqrt_t) + eta_0 * sqrt_t) * dsigma_dk - 1 / (\n                k * sigma * sqrt_t\n            )\n            d_2 = -z_u_0 * z_w_0 * dual_norm_pdf(self.fx_option_params.direction * dn0) * ddn_dk\n\n            f0 = delta - z_w_0 * z_u_0 * self.fx_option_params.direction * Phi\n            f1 = d_1 + d_2\n            return f0, f1\n\n        g01 = delta if self.fx_option_params.direction > 0 else max(delta, -0.75)\n        if isinstance(vol, FXSabrSmile):\n            alpha = vol.nodes.alpha\n        else:  # FXSabrSurface\n            vol_: FXSabrSurface = vol\n            expiry_posix = self.fx_option_params.expiry.replace(tzinfo=UTC).timestamp()\n            e_idx, _ = _surface_index_left(vol_.meta.expiries_posix, expiry_posix)\n            alpha = vol_.smiles[e_idx].nodes.alpha\n\n        g0 = (\n            _moneyness_from_delta_closed_form(\n                g01, alpha * 100.0, t_e, z_w_0, self.fx_option_params.direction\n            )\n            * f_d\n        )\n\n        root_solver = newton_1dim(\n            root1d,\n            g0,\n            args=(f_d, f, z_w_0, delta),\n            pre_args=(True,),  # solve iterations `as_float`\n            final_args=(False,),  # solve final iteration with AD\n            raise_on_fail=True,\n        )\n\n        k: DualTypes = root_solver[\"g\"]\n        v_ = vol.get_from_strike(k, f, self.fx_option_params.expiry)[1]\n        return None, v_, k\n\n    def _payoff_at_expiry(\n        self, rng: tuple[float, float] | NoInput = NoInput(0)\n    ) -> tuple[Arr1dF64, Arr1dF64]:\n        # used by plotting methods\n        if isinstance(self.fx_option_params.strike, NoInput):\n            raise ValueError(\n                \"Cannot return payoff for option without a specified `strike`.\",\n            )  # pragma: no cover\n        if isinstance(rng, NoInput):\n            x = np.linspace(0, 20, 1001)\n        else:\n            x = np.linspace(rng[0], rng[1], 1001)\n        k: float = _dual_float(self.fx_option_params.strike)\n        _ = (x - k) * self.fx_option_params.direction\n        __ = np.zeros(1001)\n        if self.fx_option_params.direction > 0:  # call\n            y = np.where(x < k, __, _) * self.settlement_params.notional\n        else:  # put\n            y = np.where(x > k, __, _) * self.settlement_params.notional\n        return x, y\n\n\nclass FXCallPeriod(_BaseFXOptionPeriod):\n    r\"\"\"\n    A *Period* defined by a European FX call option.\n\n    The expected unindexed reference cashflow is given by,\n\n    .. math::\n\n       \\mathbb{E^Q}[\\bar{C}_t] = \\left \\{ \\begin{matrix} \\max(f_d - K, 0) & \\text{after expiry} \\\\ B76(f_d, K, t, \\sigma) & \\text{before expiry} \\end{matrix} \\right .\n\n    where :math:`B76(.)` is the Black-76 option pricing formula, using log-normal volatility\n    calculations with calendar day time reference.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import FXCallPeriod\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fxo = FXCallPeriod(\n           delivery=dt(2000, 3, 1),\n           pair=\"eurusd\",\n           expiry=dt(2000, 2, 28),\n           strike=1.10,\n           delta_type=\"forward\",\n       )\n       fxo.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define **fx option** and generalised **settlement** parameters.\n\n    delivery: datetime, :red:`required`\n        The settlement date of the underlying FX rate of the option. Also used as the implied\n        payment date of the cashflow valuation date.\n    pair: str, :red:`required`\n        The currency pair of the :class:`~rateslib.data.fixings.FXFixing` against which the option\n        will settle.\n    expiry: datetime, :red:`required`\n        The expiry date of the option, when the option fixing is determined.\n    strike: float, Dual, Dual2, Variable, :green:`optional`\n        The strike price of the option. Can be set after initialisation.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional of the option expressed in units of LHS currency of `pair`.\n    delta_type: FXDeltaMethod, str, :green:`optional (set by 'default')`\n        The definition of the delta for the option.\n    metric: FXOptionMetric, str, :green:`optional` (set by 'default')`\n        The metric used by default in the\n        :meth:`~rateslib.periods.fx_volatility._BaseFXOptionPeriod.rate` method.\n    option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the option :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    ex_dividend: datetime, :green:`optional (set as 'delivery')`\n        The ex-dividend date of the settled cashflow.\n\n        .. note::\n\n           This *Period* type has not implemented **indexation** or **non-deliverability**.\n\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        *,\n        # option params:\n        delivery: datetime,  # otherwise termed the 'payment' of the period\n        pair: str,\n        expiry: datetime,\n        strike: DualTypes_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        delta_type: FXDeltaMethod | str_ = NoInput(0),\n        metric: FXOptionMetric | str_ = NoInput(0),\n        option_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        # currency args:\n        ex_dividend: datetime_ = NoInput(0),\n    ) -> None:\n        super().__init__(\n            direction=OptionType.Call,\n            delivery=delivery,\n            pair=pair,\n            expiry=expiry,\n            strike=strike,\n            notional=notional,\n            delta_type=delta_type,\n            metric=metric,\n            option_fixings=option_fixings,\n            ex_dividend=ex_dividend,\n        )\n\n\nclass FXPutPeriod(_BaseFXOptionPeriod):\n    r\"\"\"\n    A *Period* defined by a European FX put option.\n\n    The expected unindexed reference cashflow is given by,\n\n    .. math::\n\n       \\mathbb{E^Q}[\\bar{C}_t] = \\left \\{ \\begin{matrix} \\max(K - f_d, 0) & \\text{after expiry} \\\\ B76(f_d, K, t, \\sigma) & \\text{before expiry} \\end{matrix} \\right .\n\n    where :math:`B76(.)` is the Black-76 option pricing formula, using log-normal volatility\n    calculations with calendar day time reference.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import FXPutPeriod\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fxo = FXPutPeriod(\n           delivery=dt(2000, 3, 1),\n           pair=\"eurusd\",\n           expiry=dt(2000, 2, 28),\n           strike=1.10,\n           delta_type=\"forward\",\n       )\n       fxo.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define **fx option** and generalised **settlement** parameters.\n\n    delivery: datetime, :red:`required`\n        The settlement date of the underlying FX rate of the option. Also used as the implied\n        payment date of the cashflow valuation date.\n    pair: str, :red:`required`\n        The currency pair of the :class:`~rateslib.data.fixings.FXFixing` against which the option\n        will settle.\n    expiry: datetime, :red:`required`\n        The expiry date of the option, when the option fixing is determined.\n    strike: float, Dual, Dual2, Variable, :green:`optional`\n        The strike price of the option. Can be set after initialisation.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional of the option expressed in units of LHS currency of `pair`.\n    delta_type: FXDeltaMethod, str, :green:`optional (set by 'default')`\n        The definition of the delta for the option.\n    metric: FXOptionMetric, str, :green:`optional` (set by 'default')`\n        The metric used by default in the\n        :meth:`~rateslib.periods.fx_volatility._BaseFXOptionPeriod.rate` method.\n    option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the option :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    ex_dividend: datetime, :green:`optional (set as 'delivery')`\n        The ex-dividend date of the settled cashflow.\n\n        .. note::\n\n           This *Period* type has not implemented **indexation** or **non-deliverability**.\n\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        *,\n        # option params:\n        delivery: datetime,  # otherwise termed the 'payment' of the period\n        pair: str,\n        expiry: datetime,\n        strike: DualTypes_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        delta_type: FXDeltaMethod | str_ = NoInput(0),\n        metric: FXOptionMetric | str_ = NoInput(0),\n        option_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        # currency args:\n        ex_dividend: datetime_ = NoInput(0),\n    ) -> None:\n        super().__init__(\n            direction=OptionType.Put,\n            delivery=delivery,\n            pair=pair,\n            expiry=expiry,\n            strike=strike,\n            notional=notional,\n            delta_type=delta_type,\n            metric=metric,\n            option_fixings=option_fixings,\n            ex_dividend=ex_dividend,\n        )\n"
  },
  {
    "path": "python/rateslib/periods/ir_volatility.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom abc import ABCMeta, abstractmethod\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.curves._parsers import (\n    _disc_required_maybe_from_curve,\n    _validate_curve_not_no_input,\n)\nfrom rateslib.data.fixings import _get_irs_series\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import Err, NoInput, Ok, _drb\nfrom rateslib.enums.parameters import (\n    IROptionMetric,\n    OptionPricingModel,\n    OptionType,\n    SwaptionSettlementMethod,\n    _get_ir_option_metric,\n)\nfrom rateslib.instruments.protocols.pricing import _Curves\nfrom rateslib.periods.parameters import (\n    _IndexParams,\n    _IROptionParams,\n    _NonDeliverableParams,\n    _SettlementParams,\n)\nfrom rateslib.periods.protocols import _BasePeriodStatic, _WithAnalyticIROptionGreeks\nfrom rateslib.periods.utils import (\n    _get_ir_vol_value_and_forward_maybe_from_obj,\n)\nfrom rateslib.volatility.ir.utils import _IRVolPricingParams\nfrom rateslib.volatility.utils import (\n    _OptionModelBachelier,\n    _OptionModelBlack76,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        Arr1dF64,\n        CurveOption,\n        CurveOption_,\n        DualTypes,\n        DualTypes_,\n        FXForwards_,\n        IRSSeries,\n        Result,\n        Series,\n        _BaseCurve,\n        _BaseCurve_,\n        _IRVolOption_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass _BaseIRSOptionPeriod(_BasePeriodStatic, _WithAnalyticIROptionGreeks, metaclass=ABCMeta):\n    r\"\"\"\n    Abstract base class for *IROptionPeriods* types.\n\n    **See Also**: :class:`~rateslib.periods.IRSCallPeriod`,\n    :class:`~rateslib.periods.IRSPutPeriod`\n\n    \"\"\"\n\n    def analytic_greeks(\n        self,\n        rate_curve: CurveOption,\n        disc_curve: _BaseCurve,\n        index_curve: _BaseCurve,\n        fx: FXForwards_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n        premium: DualTypes_ = NoInput(0),  # expressed in the payment currency\n        premium_payment: datetime_ = NoInput(0),\n    ) -> dict[str, Any]:\n        return super()._base_analytic_greeks(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            ir_vol=ir_vol,\n            fx=fx,\n            premium=premium,\n            premium_payment=premium_payment,\n        )\n\n    @property\n    def period_params(self) -> None:\n        \"\"\"This *Period* type has no\n        :class:`~rateslib.periods.parameters._PeriodParams`.\"\"\"\n        return self._period_params  # type: ignore[return-value]  # pragma: no cover\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams`\n        of the *Period*.\"\"\"\n        return self._settlement_params\n\n    @property\n    def index_params(self) -> _IndexParams | None:\n        \"\"\"The :class:`~rateslib.periods.parameters._IndexParams` of\n        the *Period*, if any.\"\"\"\n        return self._index_params\n\n    @property\n    def non_deliverable_params(self) -> _NonDeliverableParams | None:\n        \"\"\"The :class:`~rateslib.periods.parameters._NonDeliverableParams` of the\n        *Period*., if any.\"\"\"\n        return self._non_deliverable_params\n\n    @property\n    def rate_params(self) -> None:\n        \"\"\"This *Period* type has no rate parameters.\"\"\"\n        return self._rate_params  # type: ignore[return-value]  # pragma: no cover\n\n    @property\n    def ir_option_params(self) -> _IROptionParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._IROptionParams` of the\n        *Period*.\"\"\"\n        return self._ir_option_params\n\n    @abstractmethod\n    def __init__(\n        self,\n        *,\n        # option params:\n        direction: OptionType,\n        expiry: datetime,\n        tenor: datetime | str,\n        irs_series: IRSSeries | str,\n        strike: DualTypes_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        option_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        # currency args:\n        settlement_method: SwaptionSettlementMethod | str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n    ) -> None:\n        self._index_params = None\n        self._rate_params = None\n        self._period_params = None\n\n        self._ir_option_params = _IROptionParams(\n            _direction=direction,\n            _expiry=expiry,\n            _tenor=tenor,\n            _irs_series=_get_irs_series(irs_series),\n            _strike=strike,\n            _metric=_drb(defaults.ir_option_metric, metric),\n            _option_fixings=option_fixings,\n            _settlement_method=_drb(defaults.ir_option_settlement, settlement_method),\n        )\n\n        nd_pair = NoInput(0)\n        if isinstance(nd_pair, NoInput):\n            # then option is directly deliverable\n            self._non_deliverable_params: _NonDeliverableParams | None = None\n            self._settlement_params = _SettlementParams(\n                _notional=_drb(defaults.notional, notional),\n                _payment=self.ir_option_params.option_fixing.effective,\n                _currency=self.ir_option_params.option_fixing.irs_series.currency,\n                _notional_currency=self.ir_option_params.option_fixing.irs_series.currency,\n                _ex_dividend=ex_dividend,\n            )\n        else:\n            raise NotImplementedError(\"ND IR Options not implement\")  # pragma: no cover\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    def _unindexed_reference_cashflow_elements(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> tuple[DualTypes, DualTypes | None, _IRVolPricingParams | None]:\n        \"\"\"\n        Perform the unindexed_reference_cashflow calculations but return calculation\n        components.\n\n        Returns\n        -------\n        (cashflow, analytic_delta, pricing params)\n        \"\"\"\n        # The unindexed_reference_cashflow is the value of the IRS after expiry.\n        # This may be based on number numerous different settlement methods: physical / cash etc.\n        # Currently we only offer 1 form of valuation which is \"physical or physical simulation\".\n        if isinstance(self.ir_option_params.strike, NoInput):\n            raise ValueError(err.VE_NEEDS_STRIKE)\n        k = self.ir_option_params.strike\n        r = self.ir_option_params.option_fixing.value\n        if not isinstance(r, NoInput):\n            # the presence of fixing value here is used purely as an indicator of exercise status.\n\n            phi: OptionType = self.ir_option_params.direction\n\n            if (phi == OptionType.Call and k < r) or (phi == OptionType.Put and k > r):\n                if self.ir_option_params.settlement_method is SwaptionSettlementMethod.Physical:\n                    local_npv_pay_dt: DualTypes = self.ir_option_params.option_fixing.irs.npv(  # type: ignore[assignment]\n                        curves=_Curves(\n                            disc_curve=index_curve,\n                            leg2_rate_curve=rate_curve,\n                            leg2_disc_curve=index_curve,\n                        ),\n                        forward=self.settlement_params.payment,\n                        local=False,\n                    )\n                    value = (\n                        local_npv_pay_dt\n                        * self.settlement_params.notional\n                        / 1e6\n                        * self.ir_option_params.direction.value\n                    )\n                    return value, None, None\n                else:\n                    # in [\n                    #   SwaptionSettlementMethod.CashParTenor,\n                    #   SwaptionSettlementMethod.CashCollateralized\n                    # ]\n                    index_curve_ = _validate_curve_not_no_input(index_curve)\n                    del index_curve\n                    a_r = self.ir_option_params.option_fixing.annuity(\n                        settlement_method=self.ir_option_params.settlement_method,\n                        rate_curve=rate_curve,\n                        index_curve=index_curve_,\n                    )\n                    value = (\n                        (r - k)\n                        * 100.0\n                        * a_r\n                        * self.settlement_params.notional\n                        / 1e6\n                        * self.ir_option_params.direction.value\n                    )\n                    return value, None, None\n            else:\n                # no exercise\n                return 0.0, None, None\n\n        else:\n            disc_curve_ = _disc_required_maybe_from_curve(curve=rate_curve, disc_curve=disc_curve)\n            del disc_curve\n            index_curve_ = _validate_curve_not_no_input(index_curve)\n            del index_curve\n\n            pricing_ = _get_ir_vol_value_and_forward_maybe_from_obj(\n                ir_vol=ir_vol,\n                index_curve=index_curve_,\n                rate_curve=rate_curve,\n                strike=k,\n                irs=self.ir_option_params.option_fixing.irs,\n                expiry=self.ir_option_params.expiry,\n                tenor=self.ir_option_params.option_fixing.termination,\n                t_e=self.ir_option_params.time_to_expiry(disc_curve_.nodes.initial),\n            )\n\n            match pricing_.pricing_model:\n                case OptionPricingModel.Black76:\n                    expected = (\n                        _OptionModelBlack76._value(\n                            F=pricing_.f,\n                            K=pricing_.k,\n                            rate_shift=pricing_.rate_shift,\n                            t_e=pricing_.t_e,\n                            v2=1.0,  # not required\n                            vol=pricing_.vol / 100.0,\n                            phi=self.ir_option_params.direction.value,\n                        )\n                        * 100.0\n                    )  # bps\n                case OptionPricingModel.Bachelier:\n                    expected = (\n                        _OptionModelBachelier._value(\n                            F=pricing_.f,\n                            K=pricing_.k,\n                            t_e=pricing_.t_e,\n                            v2=1.0,  # not required\n                            vol=pricing_.vol / 100.0,\n                            phi=self.ir_option_params.direction.value,\n                        )\n                        * 100.0\n                    )\n                case _:  # pragma: no cover\n                    raise RuntimeError(\n                        f\"Option pricing model {pricing_.pricing_model} not implemented. \"\n                        f\"Please report this issue.\"\n                    )\n\n            a_r = self.ir_option_params.option_fixing.annuity(\n                settlement_method=self.ir_option_params.settlement_method,\n                rate_curve=rate_curve,\n                index_curve=index_curve_,\n            )\n            return (\n                expected * self.settlement_params.notional / 1e6 * a_r,\n                a_r,\n                pricing_,\n            )\n\n    def unindexed_reference_cashflow(  # type: ignore[override]\n        self,\n        *,\n        rate_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n        **kwargs: Any,\n    ) -> DualTypes:\n        return self._unindexed_reference_cashflow_elements(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            ir_vol=ir_vol,\n        )[0]\n\n    def try_rate(\n        self,\n        rate_curve: CurveOption_,\n        disc_curve: _BaseCurve,\n        index_curve: _BaseCurve,\n        fx: FXForwards_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Return the pricing metric of the *FXOption*, with lazy error handling.\n\n        See :meth:`~rateslib.periods.FXOptionPeriod.rate`.\n        \"\"\"\n        try:\n            return Ok(\n                self.rate(\n                    rate_curve=rate_curve,\n                    disc_curve=disc_curve,\n                    index_curve=index_curve,\n                    fx=fx,\n                    ir_vol=ir_vol,\n                    metric=metric,\n                    forward=forward,\n                )\n            )\n        except Exception as e:\n            return Err(e)\n\n    def rate(\n        self,\n        *,\n        rate_curve: CurveOption_,\n        disc_curve: _BaseCurve,\n        index_curve: _BaseCurve,\n        fx: FXForwards_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        \"\"\"\n        Return the pricing metric of the *IRSOption*.\n\n        This is priced according to the ``payment`` date of the *OptionPeriod*.\n\n        Parameters\n        ----------\n        rate_curve: Curve\n            The curve used for forecasting rates on the underlying\n            :class:`~rateslib.instruments.IRS`.\n        disc_curve: Curve\n            The discount *Curve* according to the collateral agreement of the option.\n        index_curve: Curve\n            The curve used for price alignment indexing according to the\n            :class:`~rateslib.enums.SwaptionSettlementMethod`. I.e. the discount curve used on the\n            underlying :class:`~rateslib.instruments.IRS`.\n        fx: float, FXRates, FXForwards, optional\n            The object to project the currency pair FX rate at delivery.\n        ir_vol: IRSabrSmile, float, Dual, Dual2\n            The volatility object to price the option. If given as numeric, it is assumed to be\n            Black (log-normal) volatility with zero shift.\n        metric: IROptionMetric,\n            The metric to return. See examples.\n        forward: datetime, optional (set as payment date of option)\n            Not currently used by IRSOptionPeriod.rate.\n\n        Returns\n        -------\n        float, Dual, Dual2 or dict of such.\n        \"\"\"\n        if not isinstance(metric, NoInput):\n            metric_ = _get_ir_option_metric(metric)\n        else:  # use metric associated with self\n            metric_ = self.ir_option_params.metric\n        del metric\n\n        cash, anal_delta, pricing = self._unindexed_reference_cashflow_elements(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            ir_vol=ir_vol,\n        )\n\n        if metric_ == IROptionMetric.Premium():\n            return cash\n        elif metric_ == IROptionMetric.PercentNotional():\n            return cash / self.settlement_params.notional * 100.0\n\n        disc_curve_ = _disc_required_maybe_from_curve(curve=rate_curve, disc_curve=disc_curve)\n        del disc_curve\n\n        if pricing is None:\n            pricing_ = _get_ir_vol_value_and_forward_maybe_from_obj(\n                ir_vol=ir_vol,\n                index_curve=index_curve,\n                rate_curve=rate_curve,\n                strike=self.ir_option_params.strike,  # type: ignore[arg-type]\n                irs=self.ir_option_params.option_fixing.irs,\n                expiry=self.ir_option_params.expiry,\n                tenor=self.ir_option_params.option_fixing.termination,\n                t_e=self.ir_option_params.time_to_expiry(disc_curve_.nodes.initial),\n            )\n        else:\n            pricing_ = pricing\n        del pricing\n\n        if metric_ == IROptionMetric.NormalVol():\n            match pricing_.pricing_model:\n                case OptionPricingModel.Bachelier:\n                    return pricing_.vol\n                case OptionPricingModel.Black76:\n                    return _OptionModelBlack76.convert_to_bachelier(\n                        f=pricing_.f,\n                        k=pricing_.k,\n                        shift=pricing_.shift,\n                        vol=pricing_.vol,\n                        t_e=pricing_.t_e,\n                    )\n                case _:  # pragma: no cover\n                    raise RuntimeError(\n                        f\"Pricing model `{pricing_.pricing_model}` not implemented. \"\n                        f\"Please report this issue.\"\n                    )\n        elif type(metric_) is IROptionMetric.BlackVolShift:\n            # might need to resolve a volatility value depending upon the required shift\n            # and the expected shift\n            required_shift = metric_.shift()\n            provided_shift = int(_dual_float(pricing_.shift))\n\n            match pricing_.pricing_model:\n                case OptionPricingModel.Bachelier:\n                    return _OptionModelBachelier.convert_to_black76(\n                        f=pricing_.f,\n                        k=pricing_.k,\n                        shift=required_shift,\n                        vol=pricing_.vol,\n                        t_e=pricing_.t_e,\n                    )\n                case OptionPricingModel.Black76:\n                    return _OptionModelBlack76.convert_to_new_shift(\n                        f=pricing_.f,\n                        k=pricing_.k,\n                        old_shift=provided_shift,\n                        target_shift=required_shift,\n                        vol=pricing_.vol,\n                        t_e=pricing_.t_e,\n                    )\n        else:\n            raise NotImplementedError(\"IROptionMetric` not implemented.\")  # pragma: no cover\n\n    def _payoff_at_expiry(\n        self, rng: tuple[float, float] | NoInput = NoInput(0)\n    ) -> tuple[Arr1dF64, Arr1dF64]:\n        # used by plotting methods\n        if isinstance(self.ir_option_params.strike, NoInput):\n            raise ValueError(\n                \"Cannot return payoff for option without a specified `strike`.\",\n            )  # pragma: no cover\n        if isinstance(rng, NoInput):\n            x = np.linspace(0, 20, 1001)\n        else:\n            x = np.linspace(rng[0], rng[1], 1001)\n        k: float = _dual_float(self.ir_option_params.strike)\n        _ = (x - k) * self.ir_option_params.direction\n        __ = np.zeros(1001)\n        if self.ir_option_params.direction > 0:  # call\n            y = np.where(x < k, __, _) * self.settlement_params.notional\n        else:  # put\n            y = np.where(x > k, __, _) * self.settlement_params.notional\n        return x, y\n\n\nclass IRSCallPeriod(_BaseIRSOptionPeriod):\n    r\"\"\"\n    A *Period* defined by a European call option on an IRS.\n\n    The expected unindexed reference cashflow is given by,\n\n    .. math::\n\n       \\mathbb{E^Q}[\\bar{C}_t] = \\left \\{ \\begin{matrix} \\max(f_d - K, 0) & \\text{after expiry} \\\\ B76(f_d, K, t, \\sigma) & \\text{before expiry} \\end{matrix} \\right .\n\n    where :math:`B76(.)` is the Black-76 option pricing formula, using log-normal volatility\n    calculations with calendar day time reference.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import FXCallPeriod\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fxo = FXCallPeriod(\n           delivery=dt(2000, 3, 1),\n           pair=\"eurusd\",\n           expiry=dt(2000, 2, 28),\n           strike=1.10,\n           delta_type=\"forward\",\n       )\n       fxo.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define **ir option** and generalised **settlement** parameters.\n\n    expiry: datetime, :red:`required`\n        The expiry date of the option, when the option fixing is determined.\n    irs_series: IRSSeries, str :red:`required`\n        This defines the conventions of the underlying :class:`~rateslib.instruments.IRS`.\n    tenor: datetime, str :red:`required`\n        The tenor of the underlying :class:`~rateslib.instruments.IRS`.\n    strike: float, Dual, Dual2, Variable, :green:`optional`\n        The strike fixed rate of the option. Can be set after initialisation.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional of the option expressed in reference currency.\n    metric: IROptionMetric, str, :green:`optional` (set by 'default')`\n        The metric used by default in the\n        :meth:`~rateslib.periods.ir_volatility._BaseIRSOptionPeriod.rate` method.\n    option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the option :class:`~rateslib.data.fixings.IRSFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    settlement_method: SwaptionSettlementMethod, str, :green:`optional` (set by 'default')`\n        The method for deriving the settlement cashflow or underlying value.\n    ex_dividend: datetime, :green:`optional (set as 'delivery')`\n        The ex-dividend date of the settled cashflow.\n\n        .. note::\n\n           This *Period* type has not implemented **indexation** or **non-deliverability**.\n\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        *,\n        # option params:\n        expiry: datetime,\n        tenor: datetime | str,\n        irs_series: IRSSeries | str,\n        strike: DualTypes_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        option_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        # currency args:\n        settlement_method: SwaptionSettlementMethod | str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n    ) -> None:\n        super().__init__(\n            direction=OptionType.Call,\n            tenor=tenor,\n            irs_series=irs_series,\n            expiry=expiry,\n            strike=strike,\n            notional=notional,\n            metric=metric,\n            option_fixings=option_fixings,\n            settlement_method=settlement_method,\n            ex_dividend=ex_dividend,\n        )\n\n\nclass IRSPutPeriod(_BaseIRSOptionPeriod):\n    r\"\"\"\n    A *Period* defined by a European FX put option.\n\n    The expected unindexed reference cashflow is given by,\n\n    .. math::\n\n       \\mathbb{E^Q}[\\bar{C}_t] = \\left \\{ \\begin{matrix} \\max(K - f_d, 0) & \\text{after expiry} \\\\ B76(f_d, K, t, \\sigma) & \\text{before expiry} \\end{matrix} \\right .\n\n    where :math:`B76(.)` is the Black-76 option pricing formula, using log-normal volatility\n    calculations with calendar day time reference.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib.periods import FXPutPeriod\n       from datetime import datetime as dt\n\n    .. ipython:: python\n\n       fxo = FXPutPeriod(\n           delivery=dt(2000, 3, 1),\n           pair=\"eurusd\",\n           expiry=dt(2000, 2, 28),\n           strike=1.10,\n           delta_type=\"forward\",\n       )\n       fxo.cashflows()\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    .\n        .. note::\n\n           The following define **fx option** and generalised **settlement** parameters.\n\n    delivery: datetime, :red:`required`\n        The settlement date of the underlying FX rate of the option. Also used as the implied\n        payment date of the cashflow valuation date.\n    pair: str, :red:`required`\n        The currency pair of the :class:`~rateslib.data.fixings.FXFixing` against which the option\n        will settle.\n    expiry: datetime, :red:`required`\n        The expiry date of the option, when the option fixing is determined.\n    strike: float, Dual, Dual2, Variable, :green:`optional`\n        The strike price of the option. Can be set after initialisation.\n    notional: float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')`\n        The notional of the option expressed in units of LHS currency of `pair`.\n    delta_type: FXDeltaMethod, str, :green:`optional (set by 'default')`\n        The definition of the delta for the option.\n    metric: FXDeltaMethod, str, :green:`optional` (set by 'default')`\n        The metric used by default in the\n        :meth:`~rateslib.periods.fx_volatility.FXOptionPeriod.rate` method.\n    option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`\n        The value of the option :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used\n        directly. If a string identifier, links to the central ``fixings`` object and data loader.\n        See :ref:`fixings <fixings-doc>`.\n    settlement_method: SwaptionSettlementMethod, str, :green:`optional` (set by 'default')`\n        The method for deriving the settlement cashflow or underlying value.\n    ex_dividend: datetime, :green:`optional (set as 'delivery')`\n        The ex-dividend date of the settled cashflow.\n\n        .. note::\n\n           This *Period* type has not implemented **indexation** or **non-deliverability**.\n\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        *,\n        # option params:\n        expiry: datetime,\n        tenor: datetime | str,\n        irs_series: IRSSeries | str,\n        strike: DualTypes_ = NoInput(0),\n        notional: DualTypes_ = NoInput(0),\n        metric: IROptionMetric | str_ = NoInput(0),\n        option_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n        # currency args:\n        settlement_method: SwaptionSettlementMethod | str_ = NoInput(0),\n        ex_dividend: datetime_ = NoInput(0),\n    ) -> None:\n        super().__init__(\n            direction=OptionType.Put,\n            tenor=tenor,\n            irs_series=irs_series,\n            expiry=expiry,\n            strike=strike,\n            notional=notional,\n            metric=metric,\n            option_fixings=option_fixings,\n            settlement_method=settlement_method,\n            ex_dividend=ex_dividend,\n        )\n"
  },
  {
    "path": "python/rateslib/periods/parameters/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.periods.parameters.credit import _CreditParams\nfrom rateslib.periods.parameters.fx_volatility import _FXOptionParams\nfrom rateslib.periods.parameters.index import (\n    _IndexParams,\n    _init_or_none_IndexParams,\n)\nfrom rateslib.periods.parameters.ir_volatility import _IROptionParams\nfrom rateslib.periods.parameters.mtm import (\n    _init_MtmParams,\n    _MtmParams,\n)\nfrom rateslib.periods.parameters.period import _PeriodParams\nfrom rateslib.periods.parameters.rate import (\n    _FixedRateParams,\n    _FloatRateParams,\n    _init_FloatRateParams,\n)\nfrom rateslib.periods.parameters.settlement import (\n    _init_or_none_NonDeliverableParams,\n    _init_SettlementParams_with_fx_pair,\n    _NonDeliverableParams,\n    _SettlementParams,\n)\n\n__all__ = [\n    \"_IndexParams\",\n    \"_init_or_none_IndexParams\",\n    \"_init_or_none_NonDeliverableParams\",\n    \"_init_SettlementParams_with_fx_pair\",\n    \"_init_FloatRateParams\",\n    \"_init_MtmParams\",\n    \"_SettlementParams\",\n    \"_PeriodParams\",\n    \"_FixedRateParams\",\n    \"_FloatRateParams\",\n    \"_NonDeliverableParams\",\n    \"_CreditParams\",\n    \"_MtmParams\",\n    \"_FXOptionParams\",\n    \"_IROptionParams\",\n]\n"
  },
  {
    "path": "python/rateslib/periods/parameters/credit.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\n\nclass _CreditParams:\n    \"\"\"\n    Parameters for *Period* cashflows associated with credit events.\n\n    \"\"\"\n\n    _premium_accrued: bool\n\n    def __init__(self, _premium_accrued: bool) -> None:\n        self._premium_accrued = _premium_accrued\n\n    @property\n    def premium_accrued(self) -> bool:\n        \"\"\"Whether the premium is accrued within the period to default.\"\"\"\n        return self._premium_accrued\n"
  },
  {
    "path": "python/rateslib/periods/parameters/fx_volatility.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom pandas import Series\n\nfrom rateslib.data.fixings import FXFixing\nfrom rateslib.enums.parameters import FXOptionMetric, _get_fx_option_metric\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        DualTypes,\n        DualTypes_,\n        FXDeltaMethod,\n        FXIndex,\n        OptionType,\n        datetime,\n        str_,\n    )\n\n\nclass _FXOptionParams:\n    \"\"\"\n    Parameters for *FX Option Period* cashflows.\n    \"\"\"\n\n    _expiry: datetime\n    _delivery: datetime\n    _fx_index: FXIndex\n    _delta_type: FXDeltaMethod\n    _metric: FXOptionMetric\n    _option_fixing: FXFixing\n    _strike: DualTypes_\n    _currency: str\n    _direction: OptionType\n\n    def __init__(\n        self,\n        _direction: OptionType,\n        _expiry: datetime,\n        _delivery: datetime,\n        _fx_index: FXIndex,\n        _delta_type: FXDeltaMethod,\n        _metric: str | FXOptionMetric,\n        _option_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n        _strike: DualTypes_,\n    ):\n        self._direction = _direction\n        self._expiry = _expiry\n        self._delivery = _delivery\n        self._fx_index = _fx_index\n        self._delta_type = _delta_type\n        self._metric = _get_fx_option_metric(_metric)\n        self._strike = _strike\n        if isinstance(_option_fixings, Series):\n            value = FXFixing._lookup(timeseries=_option_fixings, date=self.delivery)\n            self._option_fixing = FXFixing(\n                delivery=_delivery,\n                value=value,\n                fx_index=_fx_index,\n                publication=_expiry,\n            )\n        elif isinstance(_option_fixings, str):\n            self._option_fixing = FXFixing(\n                delivery=_delivery,\n                identifier=_option_fixings,\n                fx_index=_fx_index,\n                publication=_expiry,\n            )\n        else:\n            self._option_fixing = FXFixing(\n                delivery=_delivery,\n                value=_option_fixings,\n                fx_index=_fx_index,\n                publication=_expiry,\n            )\n\n    @property\n    def expiry(self) -> datetime:\n        \"\"\"The expiry date of the option.\"\"\"\n        return self._expiry\n\n    @property\n    def delivery(self) -> datetime:\n        \"\"\"The date of the FX rate exchange for the FX rate used for settlement of the option.\"\"\"\n        return self._delivery\n\n    @property\n    def fx_index(self) -> FXIndex:\n        \"\"\"The FX index defining the FX rate conventions\"\"\"\n        return self._fx_index\n\n    @property\n    def pair(self) -> str:\n        \"\"\"The currency pair for settlement of the option.\"\"\"\n        return self.fx_index.pair\n\n    @property\n    def direction(self) -> OptionType:\n        \"\"\"The direction of the option.\"\"\"\n        return self._direction\n\n    @property\n    def strike(self) -> DualTypes_:\n        \"\"\"The strike price of the option.\"\"\"\n        return self._strike\n\n    @strike.setter\n    def strike(self, val: DualTypes_) -> None:\n        self._strike = val\n\n    @property\n    def option_fixing(self) -> FXFixing:\n        \"\"\"The FX fixing related to settlement of the option.\"\"\"\n        return self._option_fixing\n\n    @property\n    def metric(self) -> FXOptionMetric:\n        \"\"\"The default pricing quoting of the option.\"\"\"\n        return self._metric\n\n    @property\n    def delta_type(self) -> FXDeltaMethod:\n        \"\"\"The delta type used by the option to define its delta.\"\"\"\n        return self._delta_type\n\n    def time_to_expiry(self, now: datetime) -> float:\n        \"\"\"The time to expiry of the option in years measured by calendar days from ``now``.\"\"\"\n        # TODO make this a dual, associated with theta\n        return (self.expiry - now).days / 365.0\n"
  },
  {
    "path": "python/rateslib/periods/parameters/index.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom typing import TYPE_CHECKING\n\nfrom pandas import Series\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.curves.curves import _try_index_value\nfrom rateslib.data.fixings import IndexFixing\nfrom rateslib.enums.generics import (\n    Err,\n    NoInput,\n    Ok,\n    _drb,\n)\nfrom rateslib.enums.parameters import (\n    IndexMethod,\n    _get_index_method,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Any,\n        DualTypes,\n        DualTypes_,\n        Result,\n        _BaseCurve_,\n        bool_,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nclass _IndexParams:\n    \"\"\"\n    Parameters for *Period* cashflows adjusted under some indexation.\n\n    Parameters\n    ----------\n    _index_method : IndexMethod\n        The interpolation method, or otherwise, to determine index values from reference dates.\n    _index_lag: int\n        The indexation lag, in months, applied to the determination of index values.\n    _index_base: float, Dual, Dual2, Variable, optional\n        The specific value set of the base index value.\n        If not given and ``index_fixings`` is a str fixings identifier that will be\n        used to determine the base index value.\n    _index_fixings: float, Dual, Dual2, Variable, Series, str, optional\n        The index value for the reference date.\n        If a scalar value this is used directly. If a string identifier will link to the\n        central ``fixings`` object and data loader.\n    _index_base_date: datetime, optional\n        The reference date for determining the base index value. Not required if ``_index_base``\n        value is given directly.\n    _index_reference_date: datetime, optional\n        The reference date for determining the index value. Not required if ``_index_fixings``\n        is given as a scalar value.\n    _index_only: bool, optional\n        A flag which determines non-payment of notional on supported *Periods*.\n\n    \"\"\"\n\n    _index_lag: int\n    _index_method: IndexMethod\n    _index_fixing: IndexFixing\n    _index_base: IndexFixing\n    _index_only: bool\n\n    def __init__(\n        self,\n        *,\n        _index_method: IndexMethod,\n        _index_lag: int,\n        _index_base: DualTypes_,\n        _index_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n        _index_base_date: datetime_,\n        _index_reference_date: datetime_,\n        _index_only: bool,\n    ) -> None:\n        self._index_method = _index_method\n        self._index_lag = _index_lag\n        self._index_only = _index_only\n\n        if isinstance(_index_fixings, Series):\n            warnings.warn(err.FW_FIXINGS_AS_SERIES, FutureWarning)\n\n        if isinstance(_index_base, NoInput) and isinstance(_index_fixings, Series):\n            _index_base_value = IndexFixing._lookup(\n                index_lag=self.index_lag,\n                index_method=self.index_method,\n                timeseries=_index_fixings,\n                date=_index_base_date,  # type: ignore[arg-type]  # argument combinations\n            )\n            self._index_base = IndexFixing(\n                date=_index_base_date,  # type: ignore[arg-type]  # argument combinations\n                index_lag=self.index_lag,\n                index_method=self.index_method,\n                value=_index_base_value,\n                identifier=NoInput(0),\n            )\n        else:\n            self._index_base = IndexFixing(\n                date=_index_base_date,  # type: ignore[arg-type]  # argument combinations\n                index_lag=self.index_lag,\n                index_method=self.index_method,\n                value=_index_base,\n                identifier=_index_fixings if isinstance(_index_fixings, str) else NoInput(0),\n            )\n\n        if isinstance(_index_fixings, Series):\n            _index_ref_value = IndexFixing._lookup(\n                index_lag=self.index_lag,\n                index_method=self.index_method,\n                timeseries=_index_fixings,\n                date=_index_reference_date,  # type: ignore[arg-type]  # argument combinations\n            )\n            self._index_fixing = IndexFixing(\n                date=_index_reference_date,  # type: ignore[arg-type]  # argument combinations\n                index_lag=self.index_lag,\n                index_method=self.index_method,\n                value=_index_ref_value,\n                identifier=NoInput(0),\n            )\n        else:\n            self._index_fixing = IndexFixing(\n                date=_index_reference_date,  # type: ignore[arg-type]  # argument combinations\n                index_lag=self.index_lag,\n                index_method=self.index_method,\n                value=_index_fixings if not isinstance(_index_fixings, str) else NoInput(0),\n                identifier=_index_fixings if isinstance(_index_fixings, str) else NoInput(0),\n            )\n\n    @property\n    def index_base(self) -> IndexFixing:\n        \"\"\"The :class:`~rateslib.data.fixings.IndexFixing` associated with the index base date.\"\"\"\n        return self._index_base\n\n    @index_base.setter\n    def index_base(self, value: Any) -> None:\n        raise ValueError(err.VE_ATTRIBUTE_IS_IMMUTABLE.format(\"index_base\"))\n\n    @property\n    def index_fixing(self) -> IndexFixing:\n        \"\"\"The :class:`~rateslib.data.fixings.IndexFixing` associated with the index\n        reference date.\"\"\"\n        return self._index_fixing\n\n    @index_fixing.setter\n    def index_fixing(self, value: Any) -> None:\n        raise ValueError(err.VE_ATTRIBUTE_IS_IMMUTABLE.format(\"index_fixing\"))\n\n    @property\n    def index_only(self) -> bool:\n        \"\"\"A flag which determines non-payment of notional on supported *Periods*.\"\"\"\n        return self._index_only\n\n    @property\n    def index_lag(self) -> int:\n        \"\"\"The indexation lag, in months, applied to the determination of index values.\"\"\"\n        return self._index_lag\n\n    @property\n    def index_method(self) -> IndexMethod:\n        \"\"\"The :class:`~rateslib.enums.parameters.IndexMethod` to determine index values\n        from reference dates.\"\"\"\n        return self._index_method\n\n    def try_index_value(\n        self,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Determine the index reference value from fixing or forecast curve, with lazy error raising.\n\n        Parameters\n        ----------\n        index_curve : _BaseCurve, optional\n            The curve from which index values are forecast if required.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        return _try_index_value(\n            index_fixings=self.index_fixing.value,\n            index_date=self.index_fixing.date,\n            index_curve=index_curve,\n            index_lag=self.index_lag,\n            index_method=self.index_method,\n        )\n\n    def try_index_base(\n        self,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Determine the index base value from fixing or forecast curve, with lazy error raising.\n\n        Parameters\n        ----------\n        index_curve : _BaseCurve, optional\n            The curve from which index values are forecast if required.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        return _try_index_value(\n            index_fixings=self.index_base.value,\n            index_date=self.index_base.date,\n            index_curve=index_curve,\n            index_lag=self.index_lag,\n            index_method=self.index_method,\n        )\n\n    def try_index_ratio(\n        self,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[tuple[DualTypes, DualTypes, DualTypes]]:\n        \"\"\"\n        Replicates :meth:`~rateslib.periods.parameters._IndexParams.index_ratio` with\n        lazy error raising.\n\n        Returns\n        -------\n        Result[tuple[float, Dual, Dual2, Variable]] for the ratio, numerator, denominator.\n        \"\"\"\n        denominator_ = self.try_index_base(index_curve=index_curve)\n        if isinstance(denominator_, Err):\n            return denominator_\n        numerator_ = self.try_index_value(index_curve=index_curve)\n        if isinstance(numerator_, Err):\n            return numerator_\n        n_, d_ = numerator_.unwrap(), denominator_.unwrap()\n        return Ok((n_ / d_, n_, d_))\n\n    def index_ratio(\n        self,\n        index_curve: _BaseCurve_ = NoInput(0),\n    ) -> tuple[DualTypes, DualTypes, DualTypes]:\n        \"\"\"\n        Calculate the index ratio for the *Period*, including the numerator and denominator.\n\n        .. math::\n\n           I(m) = \\\\frac{I_{val}(m)}{I_{base}}\n\n        Parameters\n        ----------\n        index_curve : _BaseCurve, optional\n            The curve from which index values are forecast if required.\n\n        Returns\n        -------\n        tuple[float, Dual, Dual2, Variable] for the ratio, numerator, denominator.\n        \"\"\"\n        return self.try_index_ratio(index_curve=index_curve).unwrap()\n\n\ndef _init_or_none_IndexParams(\n    _index_base: DualTypes_,\n    _index_lag: int_,\n    _index_method: IndexMethod | str_,\n    _index_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n    _index_only: bool_,\n    _index_base_date: datetime_,\n    _index_reference_date: datetime_,\n) -> _IndexParams | None:\n    if all(\n        isinstance(_, NoInput)\n        for _ in (\n            _index_base,\n            _index_lag,\n            _index_method,\n            _index_fixings,\n        )\n    ):\n        return None\n    else:\n        if isinstance(_index_base, str):\n            raise ValueError(err.VE_INDEX_BASE_NO_STR)\n        return _IndexParams(\n            _index_base=_index_base,\n            _index_lag=_drb(defaults.index_lag, _index_lag),\n            _index_method=_get_index_method(_drb(defaults.index_method, _index_method)),\n            _index_fixings=_index_fixings,\n            _index_base_date=_index_base_date,\n            _index_reference_date=_index_reference_date,\n            _index_only=_drb(False, _index_only),\n        )\n"
  },
  {
    "path": "python/rateslib/periods/parameters/ir_volatility.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom pandas import Series\n\nfrom rateslib.data.fixings import IRSFixing\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.enums.parameters import _get_ir_option_metric, _get_swaption_settlement_method\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        DualTypes,\n        DualTypes_,\n        IROptionMetric,\n        IRSSeries,\n        OptionType,\n        SwaptionSettlementMethod,\n        datetime,\n        str_,\n    )\n\n\nclass _IROptionParams:\n    \"\"\"\n    Parameters for *IR Option Period* cashflows.\n    \"\"\"\n\n    _expiry: datetime\n    _metric: IROptionMetric\n    _option_fixing: IRSFixing\n    _strike: DualTypes_\n    _direction: OptionType\n\n    def __init__(\n        self,\n        _direction: OptionType,\n        _expiry: datetime,\n        _tenor: str | datetime,\n        _irs_series: IRSSeries,\n        _metric: str | IROptionMetric,\n        _option_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n        _strike: DualTypes_,\n        _settlement_method: SwaptionSettlementMethod | str,\n    ):\n        self._direction = _direction\n        self._expiry = _expiry\n        self._metric = _get_ir_option_metric(_metric)\n        self._strike = _strike\n        self._settlement_method = _get_swaption_settlement_method(_settlement_method)\n\n        if isinstance(_option_fixings, Series):\n            value = IRSFixing._lookup(timeseries=_option_fixings, date=self.expiry)\n            self._option_fixing = IRSFixing(\n                tenor=_tenor,\n                value=value,\n                irs_series=_irs_series,\n                publication=_expiry,\n                identifier=NoInput(0),\n            )\n        elif isinstance(_option_fixings, str):\n            self._option_fixing = IRSFixing(\n                tenor=_tenor,\n                value=NoInput(0),\n                irs_series=_irs_series,\n                publication=_expiry,\n                identifier=_option_fixings,\n            )\n        else:\n            self._option_fixing = IRSFixing(\n                tenor=_tenor,\n                value=_option_fixings,\n                publication=_expiry,\n                irs_series=_irs_series,\n                identifier=NoInput(0),\n            )\n\n        self._option_fixing.irs.fixed_rate = self.strike\n\n    @property\n    def settlement_method(self) -> SwaptionSettlementMethod:\n        \"\"\"The settlement method of the option.\"\"\"\n        return self._settlement_method\n\n    @property\n    def expiry(self) -> datetime:\n        \"\"\"The expiry date of the option.\"\"\"\n        return self._expiry\n\n    @property\n    def direction(self) -> OptionType:\n        \"\"\"The direction of the option.\"\"\"\n        return self._direction\n\n    @property\n    def strike(self) -> DualTypes_:\n        \"\"\"The strike price of the option.\"\"\"\n        return self._strike\n\n    @strike.setter\n    def strike(self, val: DualTypes_) -> None:\n        self.option_fixing.irs.fixed_rate = val\n        self._strike = val\n\n    @property\n    def option_fixing(self) -> IRSFixing:\n        \"\"\"The :class:`~rateslib.data.fixings.IRSFixing` related to settlement of the option.\"\"\"\n        return self._option_fixing\n\n    @property\n    def metric(self) -> IROptionMetric:\n        \"\"\"The default :class:`~rateslib.enums.IROptionMetric` used for the rate of the option.\"\"\"\n        return self._metric\n\n    def time_to_expiry(self, now: datetime) -> float:\n        \"\"\"The time to expiry of the option in years measured by calendar days from ``now``.\"\"\"\n        # TODO make this a dual, associated with theta\n        return (self.expiry - now).days / 365.0\n"
  },
  {
    "path": "python/rateslib/periods/parameters/mtm.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nfrom pandas import Series\n\nfrom rateslib.enums import Err, Ok\nfrom rateslib.periods.parameters.settlement import _init_fx_fixing\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        DualTypes,\n        FXFixing,\n        FXForwards_,\n        FXIndex,\n        Result,\n        datetime,\n        str_,\n    )\n\n\nclass _MtmParams:\n    \"\"\"\n    Parameters for *Period* cashflows associated with multiple\n    :class:`~rateslib.data.fixings.FXFixing`.\n\n    Parameters\n    ----------\n    _fx_fixing_start: FXFixing\n        The :class:`~rateslib.data.fixings.FXFixing` that is determined at the start of the\n        *Period*.\n    _fx_fixing_end: FXFixing\n        The :class:`~rateslib.data.fixings.FXFixing` that is determined at the end of the *Period*.\n    _currency: str\n        The local *settlement currency* of the *Period*.\n    \"\"\"\n\n    _fx_fixing_start: FXFixing\n    _fx_fixing_end: FXFixing\n    _currency: str\n\n    def __init__(\n        self,\n        _fx_fixing_start: FXFixing,\n        _fx_fixing_end: FXFixing,\n        _currency: str,\n    ) -> None:\n        self._fx_fixing_start = _fx_fixing_start\n        self._fx_fixing_end = _fx_fixing_end\n        self._currency = _currency\n\n    @property\n    def fx_fixing_start(self) -> FXFixing:\n        \"\"\"The  :class:`~rateslib.data.fixings.FXFixing` measured at the start of the period.\"\"\"\n        return self._fx_fixing_start\n\n    @property\n    def fx_fixing_end(self) -> FXFixing:\n        \"\"\"The :class:`~rateslib.data.fixings.FXFixing` measured at the end of the period.\"\"\"\n        return self._fx_fixing_end\n\n    @property\n    def currency(self) -> str:\n        \"\"\"The settlement currency of the period.\"\"\"\n        return self._currency\n\n    @property\n    def pair(self) -> str:\n        \"\"\"The pair that defines each  :class:`~rateslib.data.fixings.FXFixing`.\"\"\"\n        return self.fx_fixing_start.pair\n\n    @property\n    def reference_currency(self) -> str:\n        \"\"\"The *reference currency* of the period.\"\"\"\n        ccy1, ccy2 = self.pair[:3], self.pair[3:]\n        return ccy1 if ccy2 == self.currency else ccy2\n\n    @cached_property\n    def fx_reversed(self) -> bool:\n        \"\"\"Whether the ``reference_currency`` and ``currency`` are reversed in the ``pair``.\"\"\"\n        return self.currency == self.pair[:3]\n\n    def try_fixing_change(self, fx: FXForwards_) -> Result[DualTypes]:\n        \"\"\"Calculate the change between the FX fixing at the start and end of the period.\"\"\"\n        fx0 = self.fx_fixing_start.try_value_or_forecast(fx=fx)\n        fx1 = self.fx_fixing_end.try_value_or_forecast(fx=fx)\n        if isinstance(fx0, Err):\n            return fx0\n        if isinstance(fx1, Err):\n            return fx1\n        else:\n            return Ok(fx1.unwrap() - fx0.unwrap())\n\n\ndef _init_MtmParams(\n    _fx_index: FXIndex,\n    _start: datetime,\n    _end: datetime,\n    _fx_fixings_start: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n    _fx_fixings_end: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n    _currency: str,\n) -> _MtmParams:\n    # FX fixing publication dates are derived under the ISDA conventions associated with FXIndex.\n    return _MtmParams(\n        _fx_fixing_start=_init_fx_fixing(\n            delivery=_start,\n            fx_index=_fx_index,\n            fixings=_fx_fixings_start,\n        ),\n        _fx_fixing_end=_init_fx_fixing(\n            delivery=_end,\n            fx_index=_fx_index,\n            fixings=_fx_fixings_end,\n        ),\n        _currency=_currency,\n    )\n"
  },
  {
    "path": "python/rateslib/periods/parameters/period.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.enums.generics import (\n    NoInput,\n)\nfrom rateslib.scheduling import Convention, Frequency, dcf\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Adjuster_,\n        CalTypes,\n        datetime,\n        datetime_,\n    )\n\n\nclass _PeriodParams:\n    \"\"\"Parameters of *Period* cashflows associated with some\n    :class:`~rateslib.scheduling.Schedule`.\n\n    Parameters\n    ----------\n    _start: datetime\n        The identified start date of the *Period*.\n    _end: datetime\n        The identified end date of the *Period*.\n    _frequency: Frequency\n        The :class:`~rateslib.scheduling.Frequency` associated with the *Period*.\n    _convention: Convention\n        The day count :class:`~rateslib.scheduling.Convention` associated with the *Period*.\n    _termination: datetime, optional\n        The termination date of an external :class:`~rateslib.scheduling.Schedule`.\n    _calendar: Calendar, optional\n         The calendar associated with the *Period*.\n    _stub: bool\n        Whether the *Period* is defined as a stub according to some external\n        :class:`~rateslib.scheduling.Schedule`.\n    _adjuster: Adjuster, optional\n        The date :class:`~rateslib.scheduling.Adjuster` applied to unadjusted dates in the\n        external :class:`~rateslib.scheduling.Schedule` to arrive at adjusted accrual dates.\n    \"\"\"\n\n    _start: datetime\n    _end: datetime\n    _frequency: Frequency\n    _convention: Convention\n    _termination: datetime_\n    _calendar: CalTypes\n    _stub: bool\n    _adjuster: Adjuster_\n\n    def __init__(\n        self,\n        _start: datetime,\n        _end: datetime,\n        _frequency: Frequency,\n        _convention: Convention,\n        _termination: datetime_,\n        _calendar: CalTypes,\n        _adjuster: Adjuster_,\n        _stub: bool,\n    ):\n        if _end < _start:\n            raise ValueError(\"`end` cannot be before `start`.\")\n\n        self._start = _start\n        self._end = _end\n        self._frequency = _frequency\n        self._convention = _convention\n        self._termination = _termination\n        self._calendar = _calendar\n        self._stub = _stub\n        self._adjuster = _adjuster\n\n    @property\n    def start(self) -> datetime:\n        \"\"\"The identified start date of the *Period*.\"\"\"\n        return self._start\n\n    @property\n    def end(self) -> datetime:\n        \"\"\"The identified end date of the *Period*.\"\"\"\n        return self._end\n\n    @property\n    def termination(self) -> datetime_:\n        \"\"\"The termination date of an external :class:`~rateslib.scheduling.Schedule`.\"\"\"\n        return self._termination\n\n    @property\n    def adjuster(self) -> Adjuster_:\n        \"\"\"The date :class:`~rateslib.scheduling.Adjuster` applied to unadjusted dates in the\n        external :class:`~rateslib.scheduling.Schedule` to arrive at adjusted accrual dates.\"\"\"\n        return self._adjuster\n\n    @property\n    def calendar(self) -> CalTypes:\n        \"\"\"The calendar associated with the *Period*.\"\"\"\n        return self._calendar\n\n    @property\n    def stub(self) -> bool:\n        \"\"\"Whether the *Period* is defined as a stub according to some external\n        :class:`~rateslib.scheduling.Schedule`\"\"\"\n        return self._stub\n\n    @property\n    def convention(self) -> Convention:\n        \"\"\"The day count :class:`~rateslib.scheduling.Convention` associated with the *Period*.\"\"\"\n        return self._convention\n\n    @property\n    def frequency(self) -> Frequency:\n        \"\"\"The :class:`~rateslib.scheduling.Frequency` associated with the *Period*.\"\"\"\n        return self._frequency\n\n    @cached_property\n    def dcf(self) -> float:\n        \"\"\"\n        The DCF of the *Period* determined under its given parameters.\n        \"\"\"\n        return dcf(\n            start=self.start,\n            end=self.end,\n            convention=self.convention,\n            termination=self.termination,\n            frequency=self.frequency,\n            stub=self.stub,\n            roll=NoInput(0),  # `frequency` is a Frequency.\n            calendar=self.calendar,\n            adjuster=self.adjuster,\n        )\n"
  },
  {
    "path": "python/rateslib/periods/parameters/rate.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\nfrom pandas import Series\n\nfrom rateslib import defaults\nfrom rateslib.data.fixings import (\n    FloatRateIndex,\n    FloatRateSeries,\n    IBORFixing,\n    IBORStubFixing,\n    RFRFixing,\n    _get_float_rate_series,\n    _RFRRate,\n)\nfrom rateslib.enums.generics import (\n    NoInput,\n    _drb,\n)\nfrom rateslib.enums.parameters import (\n    FloatFixingMethod,\n    SpreadCompoundMethod,\n    _get_float_fixing_method,\n    _get_spread_compound_method,\n)\nfrom rateslib.scheduling import Convention, Frequency, StubInference\nfrom rateslib.scheduling.adjuster import _convert_to_adjuster\nfrom rateslib.scheduling.frequency import _get_frequency, _get_tenor_from_frequency\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Adjuster,\n        Adjuster_,\n        CalTypes,\n        DualTypes,\n        DualTypes_,\n        PeriodFixings,\n        datetime,\n        str_,\n    )\n\n\ndef _init_FloatRateParams(\n    _float_spread: DualTypes_,\n    _rate_fixings: PeriodFixings,\n    _fixing_method: FloatFixingMethod | str_,\n    _spread_compound_method: SpreadCompoundMethod | str_,\n    _fixing_frequency: Frequency | str_,\n    _fixing_series: FloatRateSeries | str_,\n    _accrual_start: datetime,\n    _accrual_end: datetime,\n    _period_calendar: CalTypes,\n    _period_adjuster: Adjuster_,\n    _period_convention: Convention,\n    _period_frequency: Frequency,\n    _period_stub: bool,\n) -> _FloatRateParams:\n    fixing_method: FloatFixingMethod = _get_float_fixing_method(\n        _drb(defaults.fixing_method, _fixing_method)\n    )\n    spread_compound_method = _get_spread_compound_method(\n        _drb(defaults.spread_compound_method, _spread_compound_method)\n    )\n\n    fixing_series: FloatRateSeries = _init_float_rate_series(\n        fixing_series=_fixing_series,\n        calendar=_period_calendar,\n        convention=_period_convention,\n        adjuster=_period_adjuster,\n        fixing_method=fixing_method,\n    )\n\n    float_spread = _drb(0.0, _float_spread)\n    if isinstance(_fixing_frequency, NoInput):\n        if type(fixing_method) is FloatFixingMethod.IBOR:\n            fixing_frequency = _period_frequency\n        else:\n            fixing_frequency = Frequency.BusDays(1, fixing_series.calendar)\n    else:\n        fixing_frequency = _get_frequency(\n            frequency=_fixing_frequency, roll=NoInput(0), calendar=fixing_series.calendar\n        )\n\n    fixing_index = FloatRateIndex(\n        frequency=fixing_frequency,\n        series=fixing_series,\n    )\n\n    if type(fixing_method) is FloatFixingMethod.IBOR and not _period_stub:\n        if isinstance(_rate_fixings, Series):\n            result = IBORFixing._lookup(\n                timeseries=_rate_fixings,\n                date=fixing_index.calendar.lag_bus_days(_accrual_start, -fixing_index.lag, False),\n                bounds=None,\n            )\n            rate_fixing: IBORFixing | IBORStubFixing | RFRFixing = IBORFixing(\n                rate_index=fixing_index,\n                accrual_start=_accrual_start,\n                date=NoInput(0),\n                value=result,\n                identifier=NoInput(0),\n            )\n        else:\n            if isinstance(_rate_fixings, str):\n                identifier: str_ = _rate_fixings + \"_\" + _get_tenor_from_frequency(fixing_frequency)\n            else:\n                identifier = NoInput(0)\n            rate_fixing = IBORFixing(\n                rate_index=fixing_index,\n                accrual_start=_accrual_start,\n                date=NoInput(0),\n                value=_rate_fixings if not isinstance(_rate_fixings, str) else NoInput(0),\n                identifier=identifier,\n            )\n    elif type(fixing_method) is FloatFixingMethod.IBOR and _period_stub:\n        if isinstance(_rate_fixings, Series):\n            result = IBORFixing._lookup(\n                timeseries=_rate_fixings,\n                date=fixing_index.calendar.lag_bus_days(_accrual_start, -fixing_index.lag, False),\n                bounds=None,\n            )\n            rate_fixing = IBORStubFixing(\n                rate_series=fixing_series,\n                accrual_start=_accrual_start,\n                accrual_end=_accrual_end,\n                date=NoInput(0),\n                value=result,\n                identifier=NoInput(0),\n            )\n        else:\n            rate_fixing = IBORStubFixing(\n                rate_series=fixing_series,\n                accrual_start=_accrual_start,\n                accrual_end=_accrual_end,\n                date=NoInput(0),\n                value=_rate_fixings if not isinstance(_rate_fixings, str) else NoInput(0),\n                identifier=_rate_fixings if isinstance(_rate_fixings, str) else NoInput(0),\n            )\n    else:\n        if isinstance(_rate_fixings, Series):\n            dates_obs, dates_dcf = RFRFixing._get_date_bounds(\n                accrual_start=_accrual_start,\n                accrual_end=_accrual_end,\n                fixing_method=fixing_method,\n                fixing_calendar=fixing_index.calendar,\n            )\n            dcfs_dcf = _RFRRate._get_dcf_values(\n                dcf_dates=np.array(\n                    fixing_index.calendar.bus_date_range(dates_dcf[0], dates_dcf[1])\n                ),\n                fixing_convention=fixing_index.convention,\n                fixing_calendar=fixing_index.calendar,\n            )\n            result = RFRFixing._lookup(\n                timeseries=_rate_fixings,\n                fixing_method=fixing_method,\n                spread_compound_method=spread_compound_method,\n                float_spread=float_spread,\n                dates_obs=np.array(\n                    fixing_index.calendar.bus_date_range(dates_obs[0], dates_obs[1])\n                ),\n                dcfs_dcf=dcfs_dcf,\n            )[0]\n            rate_fixing = RFRFixing(\n                rate_index=fixing_index,\n                float_spread=float_spread,\n                spread_compound_method=spread_compound_method,\n                accrual_start=_accrual_start,\n                accrual_end=_accrual_end,\n                fixing_method=fixing_method,\n                value=result,\n                identifier=NoInput(0),\n            )\n        else:\n            if isinstance(_rate_fixings, str):\n                identifier = _rate_fixings + \"_\" + _get_tenor_from_frequency(fixing_index.frequency)\n            else:\n                identifier = NoInput(0)\n            rate_fixing = RFRFixing(\n                rate_index=fixing_index,\n                accrual_start=_accrual_start,\n                accrual_end=_accrual_end,\n                fixing_method=fixing_method,\n                float_spread=float_spread,\n                spread_compound_method=spread_compound_method,\n                value=_rate_fixings if not isinstance(_rate_fixings, str) else NoInput(0),\n                identifier=identifier,\n            )\n\n    return _FloatRateParams(\n        _float_spread=float_spread,\n        _spread_compound_method=spread_compound_method,\n        _fixing_series=fixing_series,\n        _fixing_frequency=fixing_frequency,\n        _fixing_method=fixing_method,\n        _rate_fixing=rate_fixing,\n    )\n\n\ndef _init_float_rate_series(\n    fixing_series: FloatRateSeries | str_,\n    calendar: CalTypes,\n    convention: Convention,\n    fixing_method: FloatFixingMethod,\n    adjuster: Adjuster | NoInput,\n) -> FloatRateSeries:\n    if not isinstance(fixing_series, NoInput):\n        fixing_series_ = _get_float_rate_series(fixing_series)\n        del fixing_series\n\n        if (\n            isinstance(fixing_method, FloatFixingMethod.IBOR)\n            and fixing_method.method_param() != fixing_series_.lag\n        ):\n            raise ValueError(\n                \"A `fixing_series` has been provided with a publication `lag` that does not \"\n                f\"match the `param` of the `fixing_method`.\\nGot {fixing_series_.lag} and \"\n                f\"{fixing_method.method_param()} respectively.\"\n            )\n        return fixing_series_\n\n    else:\n        # modifier is defaulted to days only type if RFR based\n        if type(fixing_method) is FloatFixingMethod.IBOR:\n            lag = fixing_method.method_param()\n        else:\n            lag = 0\n        return FloatRateSeries(\n            lag=lag,\n            calendar=calendar,\n            convention=convention,\n            modifier=_convert_to_adjuster(\n                modifier=_drb(defaults.modifier, adjuster),\n                settlement=False,\n                mod_days=not isinstance(fixing_method, FloatFixingMethod.IBOR),\n            ),\n            eom=defaults.eom,\n            zero_period_stub=StubInference.ShortBack,  # TODO:  hard coded default replaced?\n        )\n\n\nclass _CreditParams:\n    \"\"\"\n    Parameters associated with credit related *Periods*.\n\n    Parameters\n    ----------\n    _premium_accrued: bool\n        Whether premium *Periods* pay accrued in the event of mid-period default.\n    \"\"\"\n\n    _premium_accrued: bool\n\n    def __init__(self, _premium_accrued: bool):\n        self.__premium_accrued = _premium_accrued\n\n    @property\n    def premium_accrued(self) -> bool:\n        \"\"\"Whether premium *Periods* pay accrued in the event of mid-period default.\"\"\"\n        return self._premium_accrued\n\n\nclass _FixedRateParams:\n    \"\"\"\n    Parameters for a *Period* containing a fixed rate.\n\n    Parameters\n    ----------\n    _fixed_rate: float, Dual, Dual2, Variable, optional\n        The fixed rate defining the *Period* cashflow.\n    \"\"\"\n\n    def __init__(self, _fixed_rate: DualTypes_) -> None:\n        self._fixed_rate = _fixed_rate\n\n    @property\n    def fixed_rate(self) -> DualTypes | NoInput:\n        \"\"\"The fixed rate defining the *Period* cashflow.\"\"\"\n        return self._fixed_rate\n\n    @fixed_rate.setter\n    def fixed_rate(self, value: DualTypes_) -> None:\n        self._fixed_rate = value\n\n\nclass _FloatRateParams:\n    \"\"\"\n    Parameters for a *Period* containing a floating rate.\n\n    Parameters\n    ----------\n    _fixing_method: FloatFixingMethod\n        The :class:`~rateslib.enums.parameters.FloatFixingMethod` describing the determination\n        of the floating rate for the period.\n    _fixing_series: FloatRateSeries,\n        The :class:`~rateslib.enums.parameters.FloatRateSeries` of the\n        :class:`~rateslib.enums.parameters.FloatRateIndex` defining the specific interest\n        rate index and some of its calculation parameters.\n    _fixing_frequency: Frequency,\n        The :class:`~rateslib.scheduling.Frequency` of the\n        :class:`~rateslib.enums.parameters.FloatRateIndex`.\n    _float_spread: float, Dual, Dual2, Variable\n        The amount (in bps) added to the rate in the period rate determination.\n    _spread_compound_method: SpreadCompoundMethod\n        The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation\n        of the period rate when combining a ``_float_spread``. Used **only** with RFR type\n        ``fixing_method``.\n    _rate_fixing: IBORFixing, IBORStubFixing, RFRFixing\n        The appropriate rate fixing class that is used to determine if known, published\n        values are available for the *Period*.\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        _fixing_method: FloatFixingMethod,\n        _fixing_series: FloatRateSeries,\n        _fixing_frequency: Frequency,\n        _float_spread: DualTypes,\n        _spread_compound_method: SpreadCompoundMethod,\n        _rate_fixing: IBORFixing | IBORStubFixing | RFRFixing,\n    ) -> None:\n        self._fixing_method: FloatFixingMethod = _fixing_method\n        self._spread_compound_method: SpreadCompoundMethod = _spread_compound_method\n        self._fixing_series = _fixing_series\n        self._fixing_index = FloatRateIndex(\n            frequency=_fixing_frequency,\n            series=_fixing_series,\n        )\n        self._float_spread: DualTypes = _float_spread\n        self._rate_fixing: IBORFixing | IBORStubFixing | RFRFixing = _rate_fixing\n\n        self._validate_combinations_args()\n\n    @property\n    def fixing_series(self) -> FloatRateSeries:\n        \"\"\"The :class:`~rateslib.enums.parameters.FloatRateSeries` of the\n        :class:`~rateslib.enums.parameters.FloatRateIndex`.\"\"\"\n        return self._fixing_series\n\n    @property\n    def fixing_index(self) -> FloatRateIndex:\n        \"\"\"The :class:`~rateslib.enums.parameters.FloatRateIndex` assoociated with the\n        determination of the floating rate for the *Period*.\"\"\"\n        return self._fixing_index\n\n    @cached_property\n    def fixing_date(self) -> datetime:\n        \"\"\"The relevant date of the (first) rate fixing for the *Period*.\"\"\"\n        if type(self.fixing_method) in [\n            FloatFixingMethod.RFRPaymentDelay,\n            FloatFixingMethod.RFRPaymentDelayAverage,\n            FloatFixingMethod.RFRLockout,\n            FloatFixingMethod.RFRLockoutAverage,\n        ]:\n            return self.accrual_start\n        else:\n            return self.fixing_calendar.lag_bus_days(\n                date=self.accrual_start, days=-self.fixing_series.lag, settlement=False\n            )\n\n    @property\n    def fixing_convention(self) -> Convention:\n        \"\"\"The day count :class:`~rateslib.scheduling.Convention` of the\n        :class:`~rateslib.enums.parameters.FloatRateIndex`.\"\"\"\n        return self.fixing_index.convention\n\n    @property\n    def fixing_modifier(self) -> Adjuster:\n        \"\"\"The date :class:`~rateslib.scheduling.Adjuster` of the\n        :class:`~rateslib.enums.parameters.FloatRateIndex`.\"\"\"\n        return self.fixing_index.modifier\n\n    @property\n    def fixing_frequency(self) -> Frequency:\n        \"\"\"The date :class:`~rateslib.scheduling.Frequency` of the\n        :class:`~rateslib.enums.parameters.FloatRateIndex`.\"\"\"\n        return self.fixing_index.frequency\n\n    @property\n    def fixing_identifier(self) -> str_:\n        \"\"\"The string identifier provided to ``rate_fixings`` to construct a *Fixings* object.\"\"\"\n        if isinstance(self.rate_fixing, RFRFixing):\n            if isinstance(self.rate_fixing.identifier, str):\n                return self.rate_fixing.identifier[:-3]  # strip out \"_1B\"\n            return NoInput(0)\n        elif isinstance(self.rate_fixing, IBORFixing):\n            if isinstance(self.rate_fixing.identifier, str):\n                if self.rate_fixing.identifier[-3] == \"_\":\n                    return self.rate_fixing.identifier[:-3]\n                else:  # [-4] == \"_\"\n                    return self.rate_fixing.identifier[:-4]\n            else:\n                return NoInput(0)\n        else:  # IBORStubFixing\n            if isinstance(self.rate_fixing.identifier, str):\n                return self.rate_fixing.identifier  # no suffix\n            return NoInput(0)\n\n    @property\n    def accrual_start(self) -> datetime:\n        \"\"\"\n        The accrual start date for the *Period*.\n\n        Fixing dates will be measured relative to this date under appropriate calendars and\n        ``fixing_method``\n        \"\"\"\n        return self.rate_fixing.accrual_start\n\n    @property\n    def accrual_end(self) -> datetime:\n        \"\"\"The accrual end date for the *Period*.\n\n        Final fixing dates (or IBOR stub weights) will be measured relative to this date under\n        appropriate calendars and ``fixing_method``.\n        \"\"\"\n        return self.rate_fixing.accrual_end\n\n    @property\n    def fixing_calendar(self) -> CalTypes:\n        \"\"\"The calendar of the :class:`~rateslib.enums.parameters.FloatRateIndex`.\"\"\"\n        return self.fixing_index.calendar\n\n    @property\n    def fixing_method(self) -> FloatFixingMethod:\n        \"\"\"The :class:`~rateslib.enums.parameters.FloatFixingMethod` defining the determination of\n        the floating rate for the period.\"\"\"\n        return self._fixing_method\n\n    @property\n    def float_spread(self) -> DualTypes:\n        \"\"\"The amount (in bps) added to the rate in the period rate determination.\"\"\"\n        return self._float_spread\n\n    @float_spread.setter\n    def float_spread(self, value: DualTypes) -> None:\n        self._float_spread = value\n        self.rate_fixing.reset()\n\n    @property\n    def spread_compound_method(self) -> SpreadCompoundMethod:\n        \"\"\"The :class:`~rateslib.enums.parameters.SpreadCompoundMethod` used in the calculation.\"\"\"\n        return self._spread_compound_method\n\n    @property\n    def rate_fixing(self) -> IBORFixing | IBORStubFixing | RFRFixing:\n        \"\"\"The :class:`~rateslib.data.fixings.IBORFixing`,\n        :class:`~rateslib.data.fixings.IBORStubFixing`, or :class:`~rateslib.data.fixings.RFRFixing`\n        appropriate for the *Period*.\"\"\"\n        return self._rate_fixing\n\n    def _validate_combinations_args(self) -> None:\n        \"\"\"\n        Validate the argument input to float periods.\n\n        Returns\n        -------\n        tuple\n        \"\"\"\n        if (\n            type(self.fixing_method)\n            in [\n                FloatFixingMethod.RFRLockout,\n                FloatFixingMethod.RFRLockoutAverage,\n            ]\n            and self.fixing_method.method_param() < 1\n        ):\n            raise ValueError(\n                f'`method_param` must be >0 for \"RFRLockout\" type `fixing_method`, '\n                f\"got {self.fixing_method.method_param()}\",\n            )\n"
  },
  {
    "path": "python/rateslib/periods/parameters/settlement.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nfrom pandas import Series\n\nimport rateslib.errors as err\nfrom rateslib import defaults\nfrom rateslib.data.fixings import FXFixing, _FXFixingMajor, _get_fx_index\nfrom rateslib.enums.generics import (\n    NoInput,\n    _drb,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        DualTypes,\n        DualTypes_,\n        FXIndex,\n        FXIndex_,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\nclass _SettlementParams:\n    \"\"\"\n    Parameters for settlement of *Period* cashflows.\n\n    Parameters\n    ----------\n    _currency: str\n        The physical *settlement currency* of the *Period*.\n    _notional: float, Dual, Dual2, Variable\n        The notional amount of the *Period* expressed in ``notional currency``.\n    _notional_currency: str\n        The currency for the expresion of ``notional`` amount.\n    _payment: datetime\n        The payment date of the *Period* cashflow.\n    _ex_dividend: datetime, optional\n        The ex-dividend date of the *Period*. Settlements occurring **after** this date\n        are assumed to be non-receivable. If not given is assumed to be equal to ``payment``\n    \"\"\"\n\n    _currency: str\n    _notional: DualTypes\n    _notional_currency: str\n    _payment: datetime\n    _ex_dividend: datetime\n\n    def __init__(\n        self,\n        _currency: str,\n        _notional: DualTypes,\n        _notional_currency: str,\n        _payment: datetime,\n        _ex_dividend: datetime_ = NoInput(0),\n    ) -> None:\n        self._currency = _currency.lower()\n        self._notional = _notional\n        self._notional_currency = _notional_currency.lower()\n        self._payment = _payment\n        self._ex_dividend = _drb(self.payment, _ex_dividend)\n\n    @property\n    def currency(self) -> str:\n        \"\"\"The local settlement currency of the *Period* cashflow.\"\"\"\n        return self._currency\n\n    @property\n    def notional(self) -> DualTypes:\n        \"\"\"The notional amount of the *Period* expressed in units of ``notional_currency``.\"\"\"\n        return self._notional\n\n    @property\n    def notional_currency(self) -> str:\n        \"\"\"The currency for the expression of ``notional`` amount.\"\"\"\n        return self._notional_currency\n\n    @property\n    def payment(self) -> datetime:\n        \"\"\"The payment date of the *Period* cashflow.\"\"\"\n        return self._payment\n\n    @property\n    def ex_dividend(self) -> datetime:\n        \"\"\"The ex-dividend date for settlement of the *Period* cashflow.\"\"\"\n        return self._ex_dividend\n\n\nclass _NonDeliverableParams:\n    \"\"\"\n    Parameters for determination of non-deliverable *Period* cashflows.\n\n    Parameters\n    ----------\n    _currency: str\n        The physical *settlement currency* of the *Period*.\n    _fx_index: FXIndex,\n        The :class:`~rateslib.fixings.data.FXIndex` defining conventions of the currency pair\n        of the *FX* rate fixing that determines settlement, including its\n        settlement and quotation conventions. The\n        *reference currency* is implied from ``pair`` when it is not equal to ``currency``.\n    _delivery: datetime\n        The settlement delivery date of the *FX* rate fixing.\n    _fx_fixings: float, Dual, Dual2, Variable, Series, str, optional\n        The value of the :class:`~rateslib.data.fixings.FXFixing`. If a scalar is used directly.\n        If a string identifier will link to the central ``fixings`` object and data loader.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        _currency: str,\n        _fx_index: FXIndex,\n        _delivery: datetime,\n        _fx_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0),  # type: ignore[type-var]\n    ) -> None:\n        self._currency = _currency.lower()\n        self._fx_index = _fx_index\n        self._fx_fixing = _init_fx_fixing(\n            fx_index=_fx_index,\n            fixings=_fx_fixings,\n            delivery=_delivery,\n        )\n\n    @property\n    def currency(self) -> str:\n        \"\"\"The physical *settlement currency* of the *Period*.\"\"\"\n        return self._currency\n\n    @property\n    def reference_currency(self) -> str:\n        \"\"\"The *reference currency* of underlying *Period* cashflows.\"\"\"\n        ccy1, ccy2 = self.pair[0:3], self.pair[3:6]\n        return ccy1 if ccy1 != self.currency else ccy2\n\n    @property\n    def fx_index(self) -> FXIndex:\n        \"\"\"\n        The :class:`~rateslib.fixings.data.FXIndex` defining conventions of the FX fixing.\n        \"\"\"\n        return self._fx_index\n\n    @property\n    def pair(self) -> str:\n        \"\"\"The currency pair associated with the :class:`~rateslib.data.fixings.FXFixing`.\"\"\"\n        return self.fx_index.pair\n\n    @property\n    def fx_fixing(self) -> FXFixing:\n        \"\"\"The :class:`~rateslib.data.fixings.FXFixing` associated with the *Period* cashflow.\"\"\"\n        return self._fx_fixing\n\n    @fx_fixing.setter\n    def fx_fixing(self, val: Any) -> None:\n        raise ValueError(err.VE_ATTRIBUTE_IS_IMMUTABLE.format(\"fx_fixing\"))\n\n    @property\n    def delivery(self) -> datetime:\n        \"\"\"The settlement delivery date of the :class:`~rateslib.data.fixings.FXFixing`.\"\"\"\n        return self.fx_fixing.delivery\n\n    @property\n    def publication(self) -> datetime:\n        \"\"\"The publication date of the :class:`~rateslib.data.fixings.FXFixing`.\"\"\"\n        return self.fx_fixing.publication\n\n    @cached_property\n    def fx_reversed(self) -> bool:\n        \"\"\"Is *True* if the ``referency_currency`` is the RHS of ``pair``.\"\"\"\n        return self.pair[3:6] == self.reference_currency\n\n\ndef _init_or_none_NonDeliverableParams(\n    _currency: str,\n    _fx_index: str | FXIndex_,\n    _delivery: datetime,\n    _fx_fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n) -> _NonDeliverableParams | None:\n    if isinstance(_fx_index, NoInput):\n        return None\n    else:\n        return _NonDeliverableParams(\n            _currency=_currency,\n            _fx_index=_get_fx_index(_fx_index),\n            _delivery=_delivery,\n            _fx_fixings=_fx_fixings,\n        )\n\n\ndef _init_SettlementParams_with_fx_pair(\n    _currency: str_,\n    _payment: datetime,\n    _notional: DualTypes_,\n    _ex_dividend: datetime,\n    _fx_pair: FXIndex_,\n) -> _SettlementParams:\n    notional = _drb(defaults.notional, _notional)\n    ccy = _drb(defaults.base_currency, _currency).lower()\n    if isinstance(_fx_pair, NoInput):\n        return _SettlementParams(\n            _currency=ccy,\n            _notional_currency=ccy,\n            _payment=_payment,\n            _notional=notional,\n            _ex_dividend=_ex_dividend,\n        )\n    else:\n        c1, c2 = _fx_pair.pair[:3], _fx_pair.pair[3:]\n        # other parameters will also be determined.\n        if ccy != c1 and ccy != c2:\n            raise ValueError(err.VE_MISMATCHED_ND_PAIR.format(ccy, _fx_pair.pair))\n        return _SettlementParams(\n            _currency=ccy,\n            _notional_currency=c1 if c1 != ccy else c2,\n            _payment=_payment,\n            _notional=notional,\n            _ex_dividend=_ex_dividend,\n        )\n\n\ndef _init_fx_fixing(\n    delivery: datetime,\n    fx_index: FXIndex,\n    fixings: DualTypes | Series[DualTypes] | str_,  # type: ignore[type-var]\n) -> FXFixing:\n    # physical FX fixings do not set versus a screen therefore do not require cross methodology\n    if isinstance(fixings, Series):\n        publication_: datetime = fx_index.isda_fixing_date(delivery)\n        value = _FXFixingMajor._lookup(timeseries=fixings, date=publication_)\n        return FXFixing(delivery=delivery, value=value, fx_index=fx_index)\n    elif isinstance(fixings, str):\n        return FXFixing(delivery=delivery, identifier=fixings, fx_index=fx_index)\n    else:\n        return FXFixing(delivery=delivery, value=fixings, fx_index=fx_index)\n"
  },
  {
    "path": "python/rateslib/periods/protocols/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n# ruff: noqa: I001\n\nfrom abc import ABCMeta\n\nfrom rateslib.periods.protocols.npv import (\n    _WithIndexingStatic,\n    _WithNonDeliverableStatic,\n    _WithNPV,\n    _WithNPVStatic,\n)\nfrom rateslib.periods.protocols.analytic_delta import (\n    _WithAnalyticDelta,\n    _WithAnalyticDeltaStatic,\n)\nfrom rateslib.periods.protocols.analytic_fixings import (\n    _WithAnalyticRateFixings,\n    _WithAnalyticRateFixingsStatic,\n)\nfrom rateslib.periods.protocols.analytic_greeks import (\n    _WithAnalyticFXOptionGreeks,\n    _WithAnalyticIROptionGreeks,\n)\nfrom rateslib.periods.protocols.cashflows import (\n    _WithCashflows,\n    _WithCashflowsStatic,\n)\nfrom rateslib.periods.protocols.fixings import (\n    _WithFixings,\n)\n\n\nclass _BasePeriod(\n    _WithCashflows,\n    _WithAnalyticDelta,\n    _WithAnalyticRateFixings,\n    _WithFixings,\n    metaclass=ABCMeta,\n):\n    \"\"\"Abstract base class for *Period* types.\"\"\"\n\n    pass\n\n\nclass _BasePeriodStatic(\n    _WithCashflowsStatic,\n    _WithAnalyticDeltaStatic,\n    _WithAnalyticRateFixingsStatic,\n    _BasePeriod,\n    metaclass=ABCMeta,\n):\n    \"\"\"Abstract base class for *Static Period* types.\"\"\"\n\n    pass\n\n\n__all__ = [\n    \"_BasePeriod\",\n    \"_BasePeriodStatic\",\n    \"_WithNPV\",\n    \"_WithCashflows\",\n    \"_WithFixings\",\n    \"_WithAnalyticDelta\",\n    \"_WithAnalyticRateFixings\",\n    \"_WithAnalyticFXOptionGreeks\",\n    \"_WithAnalyticIROptionGreeks\",\n    \"_WithNPVStatic\",\n    \"_WithCashflowsStatic\",\n    \"_WithAnalyticDeltaStatic\",\n    \"_WithAnalyticRateFixingsStatic\",\n    \"_WithIndexingStatic\",\n    \"_WithNonDeliverableStatic\",\n]\n"
  },
  {
    "path": "python/rateslib/periods/protocols/analytic_delta.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.curves._parsers import (\n    _try_disc_required_maybe_from_curve,\n)\nfrom rateslib.enums.generics import Err, NoInput, Ok\nfrom rateslib.periods.parameters.settlement import _SettlementParams\nfrom rateslib.periods.protocols.npv import (\n    _screen_ex_div_and_forward,\n    _WithIndexingStatic,\n    _WithNonDeliverableStatic,\n)\nfrom rateslib.periods.utils import (\n    _maybe_local,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurveOption_,\n        DualTypes,\n        FXForwards_,\n        FXRevised_,\n        Result,\n        _BaseCurve,\n        _BaseCurve_,\n        _FXVolOption_,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithAnalyticDelta(Protocol):\n    r\"\"\"\n    Protocol to establish analytical sensitivity to rate type metrics.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n       ~_WithAnalyticDelta.try_immediate_local_analytic_delta\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithAnalyticDelta.try_local_analytic_delta\n       ~_WithAnalyticDelta.analytic_delta\n\n    Notes\n    -----\n    Since this is *analytical*, each *Period* type must define its unique referenced sensitivity\n    to interest rates. This protocol ultimately determines the quantity,\n\n    .. math::\n\n       A^{bas}(m_f, m_s) = \\frac{\\partial P^{bas}(m_f, m_s)}{\\partial \\xi}, \\quad \\text{for some quantity, } \\xi\n    \"\"\"  # noqa: E501\n\n    _settlement_params: _SettlementParams\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` of the\n        *Period*.\"\"\"\n        return self._settlement_params\n\n    def try_immediate_local_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Calculate the immediate, analytic rate delta of a *Period* expressed in local\n        settlement currency, with lazy error raising.\n\n        This method does **not** adjust for ex-dividend and is an immediate measure according to,\n\n        .. math::\n\n           A_0 = \\frac{\\partial P_0}{\\partial \\xi}, \\quad \\text{for some, } \\xi\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForward` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        pass\n\n    def try_local_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Calculate the analytic rate delta of a *Period* expressed in local settlement currency,\n        with lazy error raising.\n\n        This method adjusts the immediate NPV for ex-dividend and forward projected value,\n        according to,\n\n        .. math::\n\n           A(m_s, m_f) = \\mathbb{I}(m_s) \\frac{1}{v(m_f)} A_0,  \\qquad \\; \\mathbb{I}(m_s) = \\left \\{ \\begin{matrix} 0 & m_s > m_{ex} \\\\ 1 & m_s \\leq m_{ex} \\end{matrix} \\right .\n\n        for forward, :math:`m_f`, settlement, :math:`m_s`, and ex-dividend, :math:`m_{ex}`.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForward` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"  # noqa: E501\n        local_immediate_result = self.try_immediate_local_analytic_delta(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n        )\n        return _screen_ex_div_and_forward(\n            local_value=local_immediate_result,\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            ex_dividend=self.settlement_params.ex_dividend,\n            settlement=settlement,\n            forward=forward,\n        )\n\n    def analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Calculate the analytic rate delta of the *Period* converted to any other\n        *base* accounting currency.\n\n        This method converts a local settlement currency value to a base accounting currency\n        according to:\n\n        .. math::\n\n           A^{bas}(m_s, m_f) = f_{loc:bas}(m_f) A(m_s, m_f)\n\n        .. hint::\n\n           If the cashflows are unspecified or incalculable due to missing information this method\n           will raise an exception. For a function that returns a `Result` indicating success or\n           failure use\n           :meth:`~rateslib.periods._WithAnalyticDelta.try_local_analytic_delta`.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        base: str, optional\n            The currency to convert the *local settlement* NPV to.\n        local: bool, optional\n            An override flag to return a dict of values indexed by string currency.\n        settlement: datetime, optional, (set as immediate date)\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional, (set as ``settlement``)\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable or dict\n        \"\"\"\n        local_delta = self.try_local_analytic_delta(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            settlement=settlement,\n            forward=forward,\n        ).unwrap()\n        return _maybe_local(\n            value=local_delta,\n            local=local,\n            currency=self.settlement_params.currency,\n            fx=fx,\n            base=base,\n            forward=forward,\n        )\n\n\nclass _WithAnalyticDeltaStatic(\n    _WithAnalyticDelta, _WithIndexingStatic, _WithNonDeliverableStatic, Protocol\n):\n    r\"\"\"\n    Protocol to establish analytical sensitivity to rate type metrics for *Static Period* types.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n       ~_WithAnalyticDeltaStatic.try_unindexed_reference_cashflow_analytic_delta\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithAnalyticDeltaStatic.try_reference_cashflow_analytic_delta\n       ~_WithAnalyticDeltaStatic.try_unindexed_cashflow_analytic_delta\n       ~_WithAnalyticDeltaStatic.try_cashflow_analytic_delta\n       ~_WithAnalyticDeltaStatic.try_immediate_local_analytic_delta\n       ~_WithAnalyticDeltaStatic.try_local_analytic_delta\n       ~_WithAnalyticDeltaStatic.analytic_delta\n\n    Notes\n    -----\n    Since this is *analytical*, each *Period* type must define its unique referenced sensitivity\n    to interest rates. This protocol ultimately determines the quantity,\n\n    .. math::\n\n       A^{bas}(m_f, m_s) = \\frac{\\partial P^{bas}(m_f, m_s)}{\\partial \\xi}, \\quad \\text{for some quantity, } \\xi\n    \"\"\"  # noqa: E501\n\n    def try_unindexed_reference_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Calculate the cashflow analytic delta for the *Static Period* before settlement currency\n        adjustment and indexation, with lazy error raising.\n\n        .. math::\n\n           \\frac{\\partial \\mathbb{E^Q}[\\bar{C}_t]}{\\partial \\xi}\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        raise NotImplementedError(\n            f\"type {type(self).__name__} has not implemented \"\n            f\"`try_unindexed_reference_cashflow_analytic_delta`\"\n        )\n\n    def try_reference_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Calculate the cashflow analytic delta for the *Static Period* before settlement currency\n        adjustment but after indexation, with lazy error raising.\n\n        .. math::\n\n           I_r \\frac{\\partial \\mathbb{E^Q}[\\bar{C}_t]}{\\partial \\xi}\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        rrad = self.try_unindexed_reference_cashflow_analytic_delta(\n            rate_curve=rate_curve, disc_curve=disc_curve\n        )\n        return self.try_index_up(value=rrad, index_curve=index_curve)\n\n    def try_unindexed_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Calculate the cashflow analytic delta for the *Static Period* with settlement currency\n        adjustment but without indexation, with lazy error raising.\n\n        .. math::\n\n           f(m_d) \\frac{\\partial \\mathbb{E^Q}[\\bar{C}_t]}{\\partial \\xi}\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        rrad = self.try_unindexed_reference_cashflow_analytic_delta(\n            rate_curve=rate_curve, disc_curve=disc_curve\n        )\n        return self.try_convert_deliverable(value=rrad, fx=fx)\n\n    def try_cashflow_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXRevised_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Calculate the cashflow for the *Period* with settlement currency adjustment\n        and indexation.\n\n        .. math::\n\n           I_r f(m_d) \\frac{\\partial \\mathbb{E^Q}[\\bar{C}_t]}{\\partial \\xi}\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n\n        \"\"\"\n        rad = self.try_reference_cashflow_analytic_delta(\n            rate_curve=rate_curve, disc_curve=disc_curve, index_curve=index_curve\n        )\n        lad = self.try_convert_deliverable(value=rad, fx=fx)  # type: ignore[arg-type]\n        if lad.is_err:\n            return lad\n        return lad\n\n    def try_immediate_local_analytic_delta(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXRevised_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        dc_res = _try_disc_required_maybe_from_curve(curve=rate_curve, disc_curve=disc_curve)\n        if isinstance(dc_res, Err):\n            return dc_res\n        disc_curve_: _BaseCurve = dc_res.unwrap()\n\n        if self.settlement_params.payment < disc_curve_.nodes.initial:\n            # payment date is in the past\n            return Ok(0.0)\n\n        cad = self.try_cashflow_analytic_delta(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve_,\n            fx_vol=fx_vol,\n            fx=fx,\n        )\n        if cad.is_err:\n            return cad\n        return Ok(cad.unwrap() * disc_curve_[self.settlement_params.payment])\n"
  },
  {
    "path": "python/rateslib/periods/protocols/analytic_fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame, MultiIndex\n\nfrom rateslib.curves._parsers import (\n    _try_disc_required_maybe_from_curve,\n)\nfrom rateslib.enums.generics import Err, NoInput, Ok\nfrom rateslib.periods.parameters import _SettlementParams\nfrom rateslib.periods.protocols import _WithIndexingStatic, _WithNonDeliverableStatic\nfrom rateslib.periods.protocols.npv import _screen_ex_div_and_forward\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurveOption_,\n        FXForwards_,\n        Result,\n        _BaseCurve,\n        _BaseCurve_,\n        _FXVolOption_,\n        datetime_,\n    )\n\n\nclass _WithAnalyticRateFixings(Protocol):\n    \"\"\"\n    Protocol to derive a rate fixings sensitivity *DataFrame*.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n       ~_WithAnalyticRateFixings.try_immediate_analytic_rate_fixings\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithAnalyticRateFixings.local_analytic_rate_fixings\n\n    \"\"\"\n\n    @property\n    def settlement_params(self) -> _SettlementParams: ...\n\n    def try_immediate_analytic_rate_fixings(\n        self,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        \"\"\"\n        Return a DataFrame of financial sensitivity to published interest rate fixings,\n        expressed in local **settlement currency** of the *Period* with immediate value, with\n        lazy error raising.\n\n        If the *Period* has no sensitivity to rates fixings this *DataFrame* is empty.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        Result[DataFrame]\n        \"\"\"\n        return Ok(DataFrame())\n\n    def local_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Return a DataFrame of financial sensitivity to published interest rate fixings,\n        expressed in local **settlement currency** of the *Period*.\n\n        If the *Period* has no sensitivity to rates fixings this *DataFrame* is empty.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        rfs = self.try_immediate_analytic_rate_fixings(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n        )\n        return _screen_ex_div_and_forward(\n            local_value=rfs,  # type: ignore[arg-type]\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            ex_dividend=self.settlement_params.ex_dividend,\n            forward=forward,\n            settlement=settlement,\n        ).unwrap()  # type: ignore[return-value]\n\n\nclass _WithAnalyticRateFixingsStatic(\n    _WithAnalyticRateFixings, _WithIndexingStatic, _WithNonDeliverableStatic, Protocol\n):\n    \"\"\"\n    Protocol to derive an analytic rate fixings sensitivity *DataFrame* from pricing *Curves*.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n       ~_WithAnalyticRateFixingsStatic.try_unindexed_reference_cashflow_analytic_rate_fixings\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithAnalyticRateFixingsStatic.try_unindexed_cashflow_analytic_rate_fixings\n       ~_WithAnalyticRateFixingsStatic.try_reference_cashflow_analytic_rate_fixings\n       ~_WithAnalyticRateFixingsStatic.try_cashflow_analytic_rate_fixings\n       ~_WithAnalyticRateFixingsStatic.try_immediate_analytic_rate_fixings\n       ~_WithAnalyticRateFixingsStatic.local_analytic_rate_fixings\n\n    \"\"\"\n\n    def try_unindexed_reference_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        \"\"\"\n        Return a DataFrame of financial sensitivity to published interest rate fixings,\n        expressed in reference currency of the *Period*, unadjusted\n        by timing of the cashflow and by indexation.\n\n        If the *Period* has no sensitivity to rates fixings this *DataFrame* is empty.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        Result[DataFrame]\n        \"\"\"\n\n        raise NotImplementedError(\n            f\"Type: {type(self).__name__} has not implemented \"\n            f\"`try_unindexed_reference_cashflow_fixings_sensitivity`.\"\n        )\n\n    def try_unindexed_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        \"\"\"\n        Return a DataFrame of financial sensitivity to published interest rate fixings,\n        expressed in settlement currency of the *Period*, unadjusted\n        by timing of the cashflow and indexation.\n\n        If the *Period* has no sensitivity to rates fixings this *DataFrame* is empty.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n           Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n           Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n           Used to discount cashflows.\n        fx: FXForwards, optional\n           The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n           ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n           :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n           The FX volatility *Smile* or *Surface* object used for determining Black calendar\n           day implied volatility values.\n\n        Returns\n        -------\n        Result[DataFrame]\n        \"\"\"\n        urcfe = self.try_unindexed_reference_cashflow_analytic_rate_fixings(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n        )\n        if self.non_deliverable_params is None:\n            return urcfe  # no ND modifications required\n        if urcfe.is_err:\n            return urcfe\n\n        if urcfe.unwrap().empty:\n            return urcfe  # nothing to modify\n\n        nd_scalar = self.try_convert_deliverable(value=Ok(1.0), fx=fx)\n        if nd_scalar.is_err:\n            return nd_scalar  # type: ignore[return-value]\n\n        d = urcfe.unwrap() * nd_scalar.unwrap()\n        c = d.columns\n        d.columns = MultiIndex.from_tuples(\n            tuples=[\n                (c.values[0][0], c.values[0][1], self.settlement_params.currency, c.values[0][3])\n            ],\n            names=c.names,\n        )\n        return Ok(d)\n\n    def try_reference_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        \"\"\"\n        Return a DataFrame of financial sensitivity to published interest rate fixings,\n        expressed in reference currency of the *Period*,adjusted for indexation but unadjusted\n        by timing of the cashflow.\n\n        If the *Period* has no sensitivity to rates fixings this *DataFrame* is empty.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n           Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n           Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n           Used to discount cashflows.\n        fx: FXForwards, optional\n           The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n           ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n           :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n           The FX volatility *Smile* or *Surface* object used for determining Black calendar\n           day implied volatility values.\n\n        Returns\n        -------\n        Result[DataFrame]\n        \"\"\"\n        urcfe = self.try_unindexed_reference_cashflow_analytic_rate_fixings(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n        )\n        if urcfe.is_err:\n            return urcfe\n        index_scalar = self.try_index_up(value=Ok(1.0), index_curve=index_curve)\n        if index_scalar.is_err:\n            return index_scalar  # type: ignore[return-value]\n        return Ok(urcfe.unwrap() * index_scalar.unwrap())\n\n    def try_cashflow_analytic_rate_fixings(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        \"\"\"\n        Return a DataFrame of financial sensitivity to published interest rate fixings,\n        expressed in settlement currency of the *Period*, adjusted for indexation but unadjusted\n        by timing of the cashflow.\n\n        If the *Period* has no sensitivity to rates fixings this *DataFrame* is empty.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n          Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n          Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n          Used to discount cashflows.\n        fx: FXForwards, optional\n          The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n          ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n          :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n          The FX volatility *Smile* or *Surface* object used for determining Black calendar\n          day implied volatility values.\n\n        Returns\n        -------\n        Result[DataFrame]\n        \"\"\"\n        ucfe = self.try_unindexed_cashflow_analytic_rate_fixings(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n        )\n        if ucfe.is_err:\n            return ucfe\n        index_scalar = self.try_index_up(value=Ok(1.0), index_curve=index_curve)\n        if index_scalar.is_err:\n            return index_scalar  # type: ignore[return-value]\n        return Ok(ucfe.unwrap() * index_scalar.unwrap())\n\n    def try_immediate_analytic_rate_fixings(\n        self,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n    ) -> Result[DataFrame]:\n        dc_res = _try_disc_required_maybe_from_curve(curve=rate_curve, disc_curve=disc_curve)\n        if isinstance(dc_res, Err):\n            return dc_res\n        disc_curve_: _BaseCurve = dc_res.unwrap()\n\n        cfe = self.try_cashflow_analytic_rate_fixings(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n        )\n        if cfe.is_err:\n            return cfe\n\n        if self.settlement_params.payment < disc_curve_.nodes.initial:\n            # payment date is in the past\n            return Ok(cfe.unwrap() * 0.0)\n\n        return Ok(cfe.unwrap() * disc_curve_[self.settlement_params.payment])\n"
  },
  {
    "path": "python/rateslib/periods/protocols/analytic_greeks.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.dual import dual_log, dual_norm_cdf, dual_norm_pdf\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import FXDeltaMethod, OptionPricingModel\nfrom rateslib.periods.parameters.fx_volatility import _FXOptionParams\nfrom rateslib.periods.parameters.ir_volatility import _IROptionParams\nfrom rateslib.periods.parameters.settlement import _SettlementParams\nfrom rateslib.periods.utils import _get_ir_vol_value_and_forward_maybe_from_obj\nfrom rateslib.splines import evaluate\nfrom rateslib.volatility import (\n    FXDeltaVolSmile,\n    FXDeltaVolSurface,\n    FXSabrSmile,\n    FXSabrSurface,\n    _BaseIRCube,\n    _BaseIRSmile,\n    _IRVolPricingParams,\n)\nfrom rateslib.volatility.fx.utils import (\n    _delta_type_constants,\n)\nfrom rateslib.volatility.utils import (\n    _OptionModelBachelier,\n    _OptionModelBlack76,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CurveOption,\n        DualTypes,\n        DualTypes_,\n        FXForwards,\n        FXForwards_,\n        _BaseCurve,\n        _FXVolOption,\n        _FXVolOption_,\n        _IRVolOption_,\n        datetime,\n        datetime_,\n    )\n\n\nclass _WithAnalyticFXOptionGreeks(Protocol):\n    \"\"\"\n    Protocol to derive analytic *FXOption* greeks.\n    \"\"\"\n\n    @property\n    def fx_option_params(self) -> _FXOptionParams: ...\n\n    @property\n    def settlement_params(self) -> _SettlementParams: ...\n\n    # def try_unindexed_reference_analytic_greeks(\n    #     self,\n    #     *,\n    #     rate_curve: _BaseCurve,\n    #     disc_curve: _BaseCurve,\n    #     fx: FXForwards,\n    #     index_curve: _BaseCurve_ = NoInput(0),\n    #     fx_vol: _FXVolOption_ = NoInput(0),\n    # ) -> dict[str, Any]:\n    #     return self.__base_analytic_greeks(\n    #         rate_curve=rate_curve,\n    #         disc_curve=disc_curve,\n    #         fx=fx,\n    #         fx_vol=fx_vol,\n    #         premium=NoInput(0),\n    #         _reduced=False,\n    #     )\n\n    def analytic_greeks(\n        self,\n        rate_curve: _BaseCurve,\n        disc_curve: _BaseCurve,\n        fx: FXForwards,\n        fx_vol: _FXVolOption_ = NoInput(0),\n        premium: DualTypes_ = NoInput(0),  # expressed in the payment currency\n        premium_payment: datetime_ = NoInput(0),\n    ) -> dict[str, Any]:\n        r\"\"\"\n        Return the different greeks for the *FX Option*.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve\n            The discount *Curve* for the LHS currency of ``pair``.\n        disc_curve: _BaseCurve\n            The discount *Curve* for the RHS currency of ``pair``.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForward` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        premium: float, Dual, Dual2, optional\n            The premium value of the option paid at the appropriate payment date.\n            Premium should be expressed in domestic currency.\n            If not given calculates and assumes a mid-market premium.\n        premium_payment: datetime, optional\n            The date that the premium is paid. If not given is assumed to be equal to the\n            *payment* associated with the option period *settlement_params*.\n\n        Returns\n        -------\n        dict\n\n        Notes\n        -----\n        **Delta** :math:`\\Delta`\n\n        This is the percentage value of the domestic notional in either the *forward* or *spot*\n        FX rate. The choice of which is defined by the option's ``delta_type``.\n\n        Delta is also expressed in nominal domestic currency amount.\n\n        **Gamma** :math:`\\Gamma`\n\n        This defines by how much *delta* will change for a 1.0 increase in either the *forward*\n        or *spot* FX rate. Which rate is determined by the option's ``delta_type``.\n\n        Gamma is also expressed in nominal domestic currency amount for a +1% change in FX rates.\n\n        **Vanna** :math:`\\Delta_{\\nu}`\n\n        This defines by how much *delta* will change for a 1.0 increase (i.e. 100 log-vols) in\n        volatility. The additional\n\n        **Vega** :math:`\\nu`\n\n        This defines by how much the PnL of the option will change for a 1.0 increase in\n        volatility for a nominal of 1 unit of domestic currency.\n\n        Vega is also expressed in foreign currency for a 0.01 (i.e. 1 log-vol) move higher in vol.\n\n        **Vomma (Volga)** :math:`\\nu_{\\nu}`\n\n        This defines by how much *vega* will change for a 1.0 increase in volatility.\n\n        These values can be used to estimate PnL for a change in the *forward* or\n        *spot* FX rate and the volatility according to,\n\n        .. math::\n\n           \\delta P \\approx v_{deli} N^{dom} \\left ( \\Delta \\delta f + \\frac{1}{2} \\Gamma \\delta f^2 + \\Delta_{\\nu} \\delta f \\delta \\sigma \\right ) + N^{dom} \\left ( \\nu \\delta \\sigma + \\frac{1}{2} \\nu_{\\nu} \\delta \\sigma^2 \\right )\n\n        where :math:`v_{deli}` is the date of FX settlement for *forward* or *spot* rate.\n\n        **Kappa** :math:`\\kappa`\n\n        This defines by how much the PnL of the option will change for a 1.0 increase in\n        strike for a nominal of 1 unit of domestic currency.\n\n        **Kega** :math:`\\left . \\frac{dK}{d\\sigma} \\right|_{\\Delta}`\n\n        This defines the rate of change of strike with respect to volatility for a constant delta.\n\n        Raises\n        ------\n        ValueError: if the ``strike`` is not set on the *Option*.\n        \"\"\"  # noqa: E501\n        raise NotImplementedError(\n            \"Type {type(self).__name__} has not implmented `anlaytic_greeks`.\"\n        )\n\n    def _base_analytic_greeks(\n        self,\n        rate_curve: _BaseCurve,  #  w(.)\n        disc_curve: _BaseCurve,  # v(.)\n        fx: FXForwards,\n        fx_vol: _FXVolOption_ = NoInput(0),\n        premium: DualTypes_ = NoInput(0),  # expressed in the payment currency\n        premium_payment: datetime_ = NoInput(0),\n        _reduced: bool = False,\n    ) -> dict[str, Any]:\n        \"\"\"Calculates `analytic_greeks`, if _reduced only calculates those necessary for\n        Strange single_vol calculation.\n\n        _reduced calculates:\n\n        __vol, vega, __bs76, _kappa, _kega, _delta_index, gamma, __strike, __forward, __sqrt_t\n        \"\"\"\n        premium_payment_ = _drb(self.settlement_params.payment, premium_payment)\n        if isinstance(self.fx_option_params.strike, NoInput):\n            raise ValueError(\"`strike` must be set to value FXOption.\")\n\n        spot = fx.pairs_settlement[self.fx_option_params.pair]\n        w_spot = rate_curve[spot]\n        w_deli = rate_curve[self.fx_option_params.delivery]\n        if self.fx_option_params.delivery != premium_payment_:\n            w_payment = rate_curve[premium_payment_]\n        else:\n            w_payment = w_deli\n        v_deli = disc_curve[self.fx_option_params.delivery]\n        v_spot = disc_curve[spot]\n        f_d = fx.rate(self.fx_option_params.pair, self.fx_option_params.delivery)\n        f_t = fx.rate(self.fx_option_params.pair, spot)\n        u = self.fx_option_params.strike / f_d\n        sqrt_t = self.fx_option_params.time_to_expiry(rate_curve.nodes.initial) ** 0.5\n\n        eta_0, z_w_0, z_u_0 = _delta_type_constants(\n            self.fx_option_params.delta_type, w_deli / w_spot, u\n        )\n\n        if isinstance(fx_vol, NoInput):\n            raise ValueError(\"`fx_vol` must be a number quantity or Smile or Surface.\")\n        elif isinstance(fx_vol, FXDeltaVolSmile | FXDeltaVolSurface):\n            eta_1, z_w_1, __ = _delta_type_constants(fx_vol.meta.delta_type, w_deli / w_spot, u)\n            res: tuple[DualTypes, DualTypes, DualTypes] = fx_vol.get_from_strike(\n                k=self.fx_option_params.strike,\n                f=f_d,\n                expiry=self.fx_option_params.expiry,\n                z_w=w_deli / w_spot,\n            )\n            delta_idx: DualTypes | None = res[0]\n            fx_vol_: DualTypes = res[1]\n        elif isinstance(fx_vol, FXSabrSmile):\n            eta_1, z_w_1 = eta_0, z_w_0\n            res = fx_vol.get_from_strike(\n                k=self.fx_option_params.strike, f=f_d, expiry=self.fx_option_params.expiry\n            )\n            delta_idx = None\n            fx_vol_ = res[1]\n        elif isinstance(fx_vol, FXSabrSurface):\n            eta_1, z_w_1 = eta_0, z_w_0\n            # SabrSurface uses FXForwards to derive multiple rates\n            res = fx_vol.get_from_strike(\n                k=self.fx_option_params.strike, f=fx, expiry=self.fx_option_params.expiry\n            )\n            delta_idx = None\n            fx_vol_ = res[1]\n        else:\n            eta_1, z_w_1 = eta_0, z_w_0\n            delta_idx = None\n            fx_vol_ = fx_vol\n        fx_vol_ /= 100.0\n        vol_sqrt_t = fx_vol_ * sqrt_t\n\n        _is_spot = self.fx_option_params.delta_type in [\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.Spot,\n        ]\n        if _is_spot:\n            z_v_0 = v_deli / v_spot\n        else:\n            z_v_0 = 1.0\n        d_eta_0 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_0)\n        d_plus = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, 0.5)\n        d_min = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, -0.5)\n\n        _: dict[str, Any] = dict()\n\n        _[\"gamma\"] = self._analytic_gamma(\n            _is_spot,\n            v_deli,\n            v_spot,\n            z_w_0,\n            self.fx_option_params.direction,\n            d_plus,\n            f_d,\n            vol_sqrt_t,\n        )\n        _[\"vega\"] = self._analytic_vega(\n            v_deli, f_d, sqrt_t, self.fx_option_params.direction, d_plus\n        )\n        _[\"_kega\"] = self._analytic_kega(\n            z_u_0,\n            z_w_0,\n            eta_0,\n            fx_vol_,\n            sqrt_t,\n            f_d,\n            self.fx_option_params.direction,\n            self.fx_option_params.strike,\n            d_eta_0,\n        )\n        _[\"_kappa\"] = self._analytic_kappa(v_deli, self.fx_option_params.direction, d_min)\n        _[\"_delta_index\"] = delta_idx\n        _[\"__delta_type\"] = self.fx_option_params.delta_type\n        _[\"__vol\"] = fx_vol_\n        _[\"__strike\"] = self.fx_option_params.strike\n        _[\"__forward\"] = f_d\n        _[\"__sqrt_t\"] = sqrt_t\n        _[\"__bs76\"] = self._analytic_bs76(\n            self.fx_option_params.direction,\n            v_deli,\n            f_d,\n            d_plus,\n            self.fx_option_params.strike,\n            d_min,\n        )\n        _[\"__notional\"] = self.settlement_params.notional\n        if self.fx_option_params.direction > 0:\n            _[\"__class\"] = \"FXCallPeriod\"\n        else:\n            _[\"__class\"] = \"FXPutPeriod\"\n\n        if not _reduced:\n            _[\"delta\"] = self._analytic_delta(\n                premium,\n                self.fx_option_params.delta_type\n                in [FXDeltaMethod.SpotPremiumAdjusted, FXDeltaMethod.ForwardPremiumAdjusted],\n                z_u_0,\n                z_w_0,\n                d_eta_0,\n                self.fx_option_params.direction,\n                d_plus,\n                w_payment,\n                w_spot,\n                self.settlement_params.notional,\n            )\n            _[f\"delta_{self.fx_option_params.pair[:3]}\"] = (\n                abs(self.settlement_params.notional) * _[\"delta\"]\n            )\n\n            _[f\"gamma_{self.fx_option_params.pair[:3]}_1%\"] = (\n                _[\"gamma\"]\n                * abs(self.settlement_params.notional)\n                * (f_t if _is_spot else f_d)\n                * 0.01\n            )\n\n            _[f\"vega_{self.fx_option_params.pair[3:]}\"] = (\n                _[\"vega\"] * abs(self.settlement_params.notional) * 0.01\n            )\n\n            _[\"delta_sticky\"] = self._analytic_sticky_delta(\n                _[\"delta\"],\n                _[\"vega\"],\n                v_deli,\n                fx_vol,\n                sqrt_t,\n                fx_vol_,\n                self.fx_option_params.expiry,\n                f_d,\n                delta_idx,\n                u,\n                z_v_0,\n                z_w_0,\n                z_w_1,\n                eta_1,\n                d_plus,\n                self.fx_option_params.strike,\n                fx,\n            )\n            _[\"vomma\"] = self._analytic_vomma(_[\"vega\"], d_plus, d_min, fx_vol_)\n            _[\"vanna\"] = self._analytic_vanna(\n                z_w_0, self.fx_option_params.direction, d_plus, d_min, fx_vol_\n            )\n            # _[\"vanna\"] = self._analytic_vanna(_[\"vega\"], _is_spot, f_t, f_d, d_plus, vol_sqrt_t)\n\n        return _\n\n    @staticmethod\n    def _analytic_vega(\n        v_deli: DualTypes, f_d: DualTypes, sqrt_t: DualTypes, phi: float, d_plus: DualTypes\n    ) -> DualTypes:\n        return v_deli * f_d * sqrt_t * dual_norm_pdf(phi * d_plus)\n\n    @staticmethod\n    def _analytic_vomma(\n        vega: DualTypes,\n        d_plus: DualTypes,\n        d_min: DualTypes,\n        vol: DualTypes,\n    ) -> DualTypes:\n        return vega * d_plus * d_min / vol\n\n    @staticmethod\n    def _analytic_gamma(\n        spot: DualTypes,\n        v_deli: DualTypes,\n        v_spot: DualTypes,\n        z_w: DualTypes,\n        phi: float,\n        d_plus: DualTypes,\n        f_d: DualTypes,\n        vol_sqrt_t: DualTypes,\n    ) -> DualTypes:\n        ret = z_w * dual_norm_pdf(phi * d_plus) / (f_d * vol_sqrt_t)\n        if spot:\n            return ret * z_w * v_spot / v_deli\n        return ret\n\n    @staticmethod\n    def _analytic_delta(\n        premium: DualTypes | NoInput,\n        adjusted: bool,\n        z_u: DualTypes,\n        z_w: DualTypes,\n        d_eta: DualTypes,\n        phi: float,\n        d_plus: DualTypes,\n        w_payment: DualTypes,\n        w_spot: DualTypes,\n        N_dom: DualTypes,\n    ) -> DualTypes:\n        if not adjusted or isinstance(premium, NoInput):\n            # returns unadjusted delta or mid-market premium adjusted delta\n            return z_u * z_w * phi * dual_norm_cdf(phi * d_eta)\n        else:\n            # returns adjusted delta with set premium in domestic (LHS) currency.\n            # ASSUMES: if premium adjusted the premium is expressed in LHS currency.\n            return z_w * phi * dual_norm_cdf(phi * d_plus) - w_payment / w_spot * premium / N_dom\n\n    @staticmethod\n    def _analytic_sticky_delta(\n        delta: DualTypes,\n        vega: DualTypes,\n        v_deli: DualTypes,\n        vol: _FXVolOption,\n        sqrt_t: DualTypes,\n        vol_: DualTypes,\n        expiry: datetime,\n        f_d: DualTypes,\n        delta_idx: DualTypes | None,\n        u: DualTypes,\n        z_v_0: DualTypes,\n        z_w_0: DualTypes,\n        z_w_1: DualTypes,\n        eta_1: float,\n        d_plus: DualTypes,\n        k: DualTypes,\n        fxf: FXForwards,\n    ) -> DualTypes:\n        dvol_df: DualTypes\n        if isinstance(vol, FXSabrSmile):\n            _, dvol_df = vol._d_sabr_d_k_or_f(  # type: ignore[assignment]\n                k=k,\n                f=f_d,\n                expiry=expiry,\n                as_float=False,\n                derivative=2,  # with respect to f\n            )\n        elif isinstance(vol, FXSabrSurface):\n            _, dvol_df = vol._d_sabr_d_k_or_f(  # type: ignore[assignment]\n                k=k,\n                f=fxf,  # use FXForwards to derive multiple rates\n                expiry=expiry,\n                as_float=False,\n                derivative=2,  # with respect to f\n            )\n        elif isinstance(vol, FXDeltaVolSmile | FXDeltaVolSurface):\n            if isinstance(vol, FXDeltaVolSurface):\n                smile: FXDeltaVolSmile = vol.get_smile(expiry)\n            else:\n                smile = vol\n            # d sigma / d delta_idx\n            _B = evaluate(smile.nodes.spline.spline, delta_idx, 1) / 100.0  # type: ignore[arg-type]\n\n            if vol.meta.delta_type in [\n                FXDeltaMethod.ForwardPremiumAdjusted,\n                FXDeltaMethod.SpotPremiumAdjusted,\n            ]:\n                # then smile is adjusted:\n                ddelta_idx_df_d: DualTypes = -delta_idx / f_d  # type: ignore[operator]\n            else:\n                ddelta_idx_df_d = 0.0\n            _A = z_w_1 * dual_norm_pdf(-d_plus)\n            ddelta_idx_df_d -= _A / (f_d * vol_ * sqrt_t)\n            ddelta_idx_df_d /= 1 + _A * ((dual_log(u) / (vol_**2 * sqrt_t) + eta_1 * sqrt_t) * _B)\n\n            dvol_df = _B * z_w_0 / z_v_0 * ddelta_idx_df_d\n\n        else:\n            dvol_df = 0.0\n\n        return delta + vega / v_deli * z_v_0 * dvol_df\n\n    @staticmethod\n    def _analytic_vanna(\n        z_w: DualTypes,\n        phi: float,\n        d_plus: DualTypes,\n        d_min: DualTypes,\n        vol: DualTypes,\n    ) -> DualTypes:\n        return -z_w * dual_norm_pdf(phi * d_plus) * d_min / vol\n\n    # @staticmethod\n    # def _analytic_vanna(vega, spot, f_t, f_d, d_plus, vol_sqrt_t):  # Alternative monetary def.\n    #     if spot:\n    #         return vega / f_t * (1 - d_plus / vol_sqrt_t)\n    #     else:\n    #         return vega / f_d * (1 - d_plus / vol_sqrt_t)\n\n    @staticmethod\n    def _analytic_kega(\n        z_u: DualTypes,\n        z_w: DualTypes,\n        eta: float,\n        vol: DualTypes,\n        sqrt_t: float,\n        f_d: DualTypes,\n        phi: float,\n        k: DualTypes,\n        d_eta: DualTypes,\n    ) -> DualTypes:\n        if eta < 0:\n            # dz_u_du = 1.0\n            x = vol * phi * dual_norm_cdf(phi * d_eta) / (f_d * z_u * dual_norm_pdf(phi * d_eta))\n        else:\n            x = 0.0\n\n        ret = (d_eta - 2.0 * eta * sqrt_t * vol) / (-1 / (k * sqrt_t) + x)\n        return ret\n\n    @staticmethod\n    def _analytic_kappa(v_deli: DualTypes, phi: float, d_min: DualTypes) -> DualTypes:\n        return -v_deli * phi * dual_norm_cdf(phi * d_min)\n\n    @staticmethod\n    def _analytic_bs76(\n        phi: float,\n        v_deli: DualTypes,\n        f_d: DualTypes,\n        d_plus: DualTypes,\n        k: DualTypes,\n        d_min: DualTypes,\n    ) -> DualTypes:\n        return phi * v_deli * (f_d * dual_norm_cdf(phi * d_plus) - k * dual_norm_cdf(phi * d_min))\n\n\nclass _WithAnalyticIROptionGreeks(Protocol):\n    \"\"\"\n    Protocol to derive analytic *IROption* greeks.\n    \"\"\"\n\n    @property\n    def ir_option_params(self) -> _IROptionParams: ...\n\n    @property\n    def settlement_params(self) -> _SettlementParams: ...\n\n    def analytic_greeks(\n        self,\n        rate_curve: CurveOption,\n        disc_curve: _BaseCurve,\n        index_curve: _BaseCurve,\n        fx: FXForwards,\n        ir_vol: _IRVolOption_ = NoInput(0),\n        premium: DualTypes_ = NoInput(0),  # expressed in the payment currency\n        premium_payment: datetime_ = NoInput(0),\n    ) -> dict[str, Any]:\n        r\"\"\"\n        Return the different greeks for the *IR Option*.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve\n            The discount *Curve* for the LHS currency of ``pair``.\n        disc_curve: _BaseCurve\n            The discount *Curve* for the RHS currency of ``pair``.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForward` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary.\n        ir_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        premium: float, Dual, Dual2, optional\n            The premium value of the option paid at the appropriate payment date.\n            Premium should be expressed in domestic currency.\n            If not given calculates and assumes a mid-market premium.\n        premium_payment: datetime, optional\n            The date that the premium is paid. If not given is assumed to be equal to the\n            *payment* associated with the option period *settlement_params*.\n\n        Returns\n        -------\n        dict\n\n        Notes\n        -----\n        **Delta** :math:`\\Delta`\n\n        This is the percentage value of the domestic notional in either the *forward* or *spot*\n        FX rate. The choice of which is defined by the option's ``delta_type``.\n\n        Delta is also expressed in nominal domestic currency amount.\n\n        **Gamma** :math:`\\Gamma`\n\n        This defines by how much *delta* will change for a 1.0 increase in either the *forward*\n        or *spot* FX rate. Which rate is determined by the option's ``delta_type``.\n\n        Gamma is also expressed in nominal domestic currency amount for a +1% change in FX rates.\n\n        **Vanna** :math:`\\Delta_{\\nu}`\n\n        This defines by how much *delta* will change for a 1.0 increase (i.e. 100 log-vols) in\n        volatility. The additional\n\n        **Vega** :math:`\\nu`\n\n        This defines by how much the PnL of the option will change for a 1.0 increase in\n        volatility for a nominal of 1 unit of domestic currency.\n\n        Vega is also expressed in foreign currency for a 0.01 (i.e. 1 log-vol) move higher in vol.\n\n        **Vomma (Volga)** :math:`\\nu_{\\nu}`\n\n        This defines by how much *vega* will change for a 1.0 increase in volatility.\n\n        These values can be used to estimate PnL for a change in the *forward* or\n        *spot* FX rate and the volatility according to,\n\n        .. math::\n\n           \\delta P \\approx v_{deli} N^{dom} \\left ( \\Delta \\delta f + \\frac{1}{2} \\Gamma \\delta f^2 + \\Delta_{\\nu} \\delta f \\delta \\sigma \\right ) + N^{dom} \\left ( \\nu \\delta \\sigma + \\frac{1}{2} \\nu_{\\nu} \\delta \\sigma^2 \\right )\n\n        where :math:`v_{deli}` is the date of FX settlement for *forward* or *spot* rate.\n\n        **Kappa** :math:`\\kappa`\n\n        This defines by how much the PnL of the option will change for a 1.0 increase in\n        strike for a nominal of 1 unit of domestic currency.\n\n        **Kega** :math:`\\left . \\frac{dK}{d\\sigma} \\right|_{\\Delta}`\n\n        This defines the rate of change of strike with respect to volatility for a constant delta.\n\n        Raises\n        ------\n        ValueError: if the ``strike`` is not set on the *Option*.\n        \"\"\"  # noqa: E501\n        raise NotImplementedError(\n            \"Type {type(self).__name__} has not implemented `analytic_greeks`.\"\n        )\n\n    def _base_analytic_greeks(\n        self,\n        rate_curve: CurveOption,\n        disc_curve: _BaseCurve,\n        index_curve: _BaseCurve,\n        fx: FXForwards_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n        premium: DualTypes_ = NoInput(0),  # expressed in the payment currency\n        premium_payment: datetime_ = NoInput(0),\n        _reduced: bool = False,\n    ) -> dict[str, Any]:\n        \"\"\"Calculates `analytic_greeks`, if _reduced only calculates those necessary for\n        Strange single_vol calculation.\n\n        _reduced calculates:\n\n        __vol, vega, __bs76, _kappa, _kega, _delta_index, gamma, __strike, __forward, __sqrt_t\n        \"\"\"\n        _drb(self.settlement_params.payment, premium_payment)\n\n        if isinstance(self.ir_option_params.strike, NoInput):\n            raise ValueError(\"`strike` must be set to value IROption.\")\n\n        # v_deli = rate_curve[self.ir_option_params.option_fixing.effective]\n        sqrt_t = self.ir_option_params.time_to_expiry(disc_curve.nodes.initial) ** 0.5\n        pricing_ = _get_ir_vol_value_and_forward_maybe_from_obj(\n            ir_vol=ir_vol,\n            index_curve=index_curve,\n            rate_curve=rate_curve,\n            strike=self.ir_option_params.strike,\n            irs=self.ir_option_params.option_fixing.irs,\n            expiry=self.ir_option_params.expiry,\n            tenor=self.ir_option_params.option_fixing.termination,\n            t_e=sqrt_t**2,\n        )\n        vol_sqrt_t = pricing_.vol / 100.0 * sqrt_t\n        a_r = self.ir_option_params.option_fixing.annuity(\n            settlement_method=self.ir_option_params.settlement_method,\n            index_curve=index_curve,\n            rate_curve=rate_curve,\n        )\n        v_p = disc_curve[self.settlement_params.payment]\n\n        _: dict[str, Any] = dict()\n\n        match pricing_.pricing_model:\n            case OptionPricingModel.Black76:\n                d_plus = _OptionModelBlack76._d_plus_min_u(\n                    shifted_u=(pricing_.k + pricing_.rate_shift)\n                    / (pricing_.f + pricing_.rate_shift),\n                    vol_sqrt_t=vol_sqrt_t,\n                    eta=0.5,\n                )\n                _[\"__bs76\"] = _OptionModelBlack76._value(\n                    F=pricing_.f,\n                    K=pricing_.k,\n                    rate_shift=pricing_.rate_shift,\n                    t_e=pricing_.t_e,\n                    v2=1.0,\n                    vol=pricing_.vol / 100.0,\n                    phi=self.ir_option_params.direction,\n                )\n                # d_min = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, -0.5)\n            case OptionPricingModel.Bachelier:\n                d_plus = (pricing_.f - pricing_.k) / vol_sqrt_t\n                _[\"__bachelier\"] = _OptionModelBachelier._value(\n                    F=pricing_.f,\n                    K=pricing_.k,\n                    t_e=pricing_.t_e,\n                    v2=1.0,\n                    vol=pricing_.vol / 100.0,\n                    phi=self.ir_option_params.direction,\n                )\n\n        _[\"__forward\"] = pricing_.f\n        _[\"__sqrt_t\"] = sqrt_t\n        _[\"__vol\"] = pricing_.vol / 100.0\n        _[\"__strike\"] = pricing_.k\n        _[\"delta\"] = self._analytic_delta(\n            self.ir_option_params.direction, d_plus, pricing_.pricing_model\n        )\n        _[f\"delta_{self.settlement_params.currency}\"] = (\n            abs(self.settlement_params.notional) * _[\"delta\"] * a_r * v_p * 1e-6\n        )\n        _[\"gamma\"] = self._analytic_gamma(\n            self.ir_option_params.direction,\n            d_plus,\n            pricing_.pricing_model,\n            pricing_.f,\n            vol_sqrt_t,\n        )\n        _[f\"gamma_{self.settlement_params.currency}\"] = (\n            _[\"gamma\"] * abs(self.settlement_params.notional) * 1e-8 * a_r * v_p\n        )\n\n        _[\"vanna\"] = self._analytic_vanna(\n            self.ir_option_params.direction,\n            d_plus,\n            pricing_.pricing_model,\n            vol_sqrt_t,\n            pricing_.vol / 100.0,\n        )\n        _[f\"vanna_{self.settlement_params.currency}\"] = (\n            _[\"vanna\"] * abs(self.settlement_params.notional) * 1e-8 * a_r * v_p\n        )\n\n        _[\"vega\"] = self._analytic_vega(\n            self.ir_option_params.direction,\n            d_plus,\n            pricing_.pricing_model,\n            pricing_.f,\n            _[\"__sqrt_t\"],\n        )\n        _[f\"vega_{self.settlement_params.currency}\"] = (\n            _[\"vega\"] * abs(self.settlement_params.notional) * 1e-6 * a_r * v_p\n        )\n        _[\"vomma\"] = self._analytic_vomma(\n            self.ir_option_params.direction,\n            d_plus,\n            pricing_.pricing_model,\n            _[\"vega\"],\n            vol_sqrt_t,\n            pricing_.vol / 100.0,\n        )\n        _[f\"vomma_{self.settlement_params.currency}\"] = (\n            _[\"vomma\"] * abs(self.settlement_params.notional) * 1e-8 * a_r * v_p\n        )\n\n        _[\"delta_sticky\"] = self._analytic_sticky_delta(\n            delta=_[\"delta\"],\n            vega=_[\"vega\"],\n            ir_vol=ir_vol,\n            f=pricing_.f,\n            k=pricing_.k,\n            expiry=self.ir_option_params.expiry,\n            tenor=self.ir_option_params.option_fixing.termination,\n        )\n        _[f\"delta_sticky_{self.settlement_params.currency}\"] = (\n            abs(self.settlement_params.notional) * _[\"delta_sticky\"] * a_r * v_p * 1e-6\n        )\n\n        _[\"__notional\"] = self.settlement_params.notional\n        if self.ir_option_params.direction > 0:\n            _[\"__class\"] = \"IRSCallPeriod\"\n        else:\n            _[\"__class\"] = \"IRSPutPeriod\"\n\n        return _\n\n    @staticmethod\n    def _analytic_vega(\n        phi: float,\n        d_plus: DualTypes,\n        model: OptionPricingModel,\n        f: DualTypes,\n        sqrt_t: DualTypes,\n    ) -> DualTypes:\n        match model:\n            case OptionPricingModel.Black76:\n                return f * sqrt_t * dual_norm_pdf(phi * d_plus)\n            case OptionPricingModel.Bachelier:\n                return sqrt_t * dual_norm_pdf(d_plus)\n\n    @staticmethod\n    def _analytic_vomma(\n        phi: float,\n        d_plus: DualTypes,\n        model: OptionPricingModel,\n        vega: DualTypes,\n        vol_sqrt_t: DualTypes,\n        vol: DualTypes,\n    ) -> DualTypes:\n        match model:\n            case OptionPricingModel.Black76:\n                return vega * d_plus * (d_plus - vol_sqrt_t) / vol\n            case OptionPricingModel.Bachelier:\n                return vega * d_plus * d_plus / vol\n\n    @staticmethod\n    def _analytic_gamma(\n        phi: float,\n        d_plus: DualTypes,\n        model: OptionPricingModel,\n        f_d: DualTypes,\n        vol_sqrt_t: DualTypes,\n    ) -> DualTypes:\n        ret = dual_norm_pdf(phi * d_plus) / vol_sqrt_t\n        match model:\n            case OptionPricingModel.Black76:\n                return ret / f_d\n            case OptionPricingModel.Bachelier:\n                return ret\n\n    @staticmethod\n    def _analytic_delta(\n        phi: float,\n        d_plus: DualTypes,\n        model: OptionPricingModel = OptionPricingModel.Black76,\n    ) -> DualTypes:\n        match model:\n            case OptionPricingModel.Black76:\n                return phi * dual_norm_cdf(phi * d_plus)\n            case OptionPricingModel.Bachelier:\n                return phi * dual_norm_cdf(phi * d_plus)\n\n    @staticmethod\n    def _analytic_vanna(\n        phi: float,\n        d_plus: DualTypes,\n        model: OptionPricingModel,\n        vol_sqrt_t: DualTypes,\n        vol: DualTypes,\n    ) -> DualTypes:\n        match model:\n            case OptionPricingModel.Black76:\n                return -dual_norm_pdf(phi * d_plus) * (d_plus - vol_sqrt_t) / vol\n            case OptionPricingModel.Bachelier:\n                return -dual_norm_pdf(phi * d_plus) * d_plus / vol\n\n    @staticmethod\n    def _analytic_sticky_delta(\n        delta: DualTypes,\n        vega: DualTypes,\n        ir_vol: _IRVolOption_ | _IRVolPricingParams,\n        f: DualTypes,\n        k: DualTypes,\n        expiry: str | datetime,\n        tenor: str | datetime,\n    ) -> DualTypes:\n        dvol_df: DualTypes\n        if isinstance(ir_vol, _BaseIRSmile):\n            dvol_df = ir_vol._d_sigma_d_f(k=k, f=f)\n        elif isinstance(ir_vol, _BaseIRCube):\n            smile = ir_vol.get_smile(expiry, tenor)\n            dvol_df = smile._d_sigma_d_f(k=k, f=f)\n        elif isinstance(ir_vol, _IRVolPricingParams):\n            raise NotImplementedError(\n                \"Cannot calculate sticky delta from pricing params without object\"\n            )\n        else:\n            dvol_df = 0.0\n        return delta + vega * dvol_df\n"
  },
  {
    "path": "python/rateslib/periods/protocols/cashflows.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib import defaults\nfrom rateslib.curves._parsers import (\n    _try_disc_required_maybe_from_curve,\n)\nfrom rateslib.dual.utils import _dual_float, _float_or_none\nfrom rateslib.enums.generics import Err, NoInput\nfrom rateslib.periods.parameters import (\n    _CreditParams,\n    _FixedRateParams,\n    _FloatRateParams,\n    _IndexParams,\n    _MtmParams,\n    _NonDeliverableParams,\n    _PeriodParams,\n)\nfrom rateslib.periods.protocols.npv import _WithNPV, _WithNPVStatic\nfrom rateslib.periods.utils import (\n    _get_immediate_fx_scalar_and_base,\n    _try_validate_base_curve,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        CurveOption_,\n        DualTypes,\n        FXForwards_,\n        Result,\n        _BaseCurve_,\n        _FXVolOption_,\n        _IRVolOption_,\n        datetime_,\n        str_,\n    )\n\n\nclass _WithCashflows(_WithNPV, Protocol):\n    \"\"\"\n    Protocol for parameter and calculation display for the *Period*.\n\n    .. warning::\n\n       The direct methods of this class are for display convenience.\n       Calling these to extract certain values should be avoided. It is more efficient to\n       source relevant parameters or calculations from object attributes or other methods directly.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n       ~_WithCashflows.try_cashflow\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithCashflows.cashflows\n\n    \"\"\"\n\n    def try_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        \"\"\"\n        Calculate the cashflow for the *Period* with any non-deliverable currency adjustment\n        **and** indexation.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        Result of float, Dual, Dual2, Variable\n        \"\"\"\n        return Err(\n            NotImplementedError(\n                f\"`cashflow` is not explicitly implemented for period type: {type(self).__name__}\"\n            )\n        )\n\n    def cashflows(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> dict[str, Any]:\n        \"\"\"\n        Return aggregated cashflow data for the *Period*.\n\n        .. warning::\n\n           This method is a convenience method to provide a visual representation of all\n           associated calculation data. Calling this method to extracting certain values\n           should be avoided. It is more efficient to source relevant parameters or calculations\n           from object attributes or other methods directly.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        base: str, optional\n            The currency to convert the *local settlement* NPV to.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        dict[Any]\n        \"\"\"\n        standard_elements = _standard_elements(self=self)\n        period_elements = _period_elements(self=self)\n        cashflow_elements = _cashflow_elements(\n            self=self,\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            base=base,\n            forward=forward,\n            settlement=settlement,\n        )\n        rate_elements = _rate_elements(self=self, rate_curve=rate_curve)\n        credit_elements = _credit_elements(self=self, rate_curve=rate_curve)\n        return {\n            **standard_elements,\n            **period_elements,\n            **rate_elements,\n            **cashflow_elements,\n            **credit_elements,\n        }\n\n\nclass _WithCashflowsStatic(_WithNPVStatic, Protocol):\n    \"\"\"\n    Protocol for parameter and calculation display for the *Static Period*.\n\n    .. warning::\n\n       The direct methods of this class are for display convenience.\n       Calling these to extract certain values should be avoided. It is more efficient to\n       source relevant parameters or calculations from object attributes or other methods directly.\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithCashflowsStatic.cashflows\n\n    \"\"\"\n\n    def _index_elements(\n        self,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> dict[str, Any]:\n        # indexing parameters\n        index_elements: dict[str, Any] = {}\n        if hasattr(self, \"index_params\") and isinstance(self.index_params, _IndexParams):\n            assert isinstance(self.index_params, _IndexParams)  # noqa: S101\n            iv = self.index_params.try_index_value(index_curve=index_curve)\n            ib = self.index_params.try_index_base(index_curve=index_curve)\n            if not isinstance(iv, Err) and not isinstance(ib, Err):\n                ir = iv.unwrap() / ib.unwrap()\n            else:\n                ir = None\n\n            uc = self.try_unindexed_cashflow(\n                rate_curve=rate_curve,\n                disc_curve=disc_curve,\n                fx=fx,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n            )\n\n            index_elements = {\n                defaults.headers[\"index_base\"]: _float_or_none(ib),\n                defaults.headers[\"index_value\"]: _float_or_none(iv),\n                defaults.headers[\"index_ratio\"]: _float_or_none(ir),\n                defaults.headers[\"index_fix_date\"]: self.index_params.index_fixing.date,\n                defaults.headers[\"unindexed_cashflow\"]: _float_or_none(uc),\n            }\n        return index_elements\n\n    def _non_deliverable_elements(self, fx: FXForwards_) -> dict[str, Any]:\n        # non-deliverable parameters\n        non_deliverable_elements: dict[str, Any] = {}\n        if hasattr(self, \"non_deliverable_params\") and isinstance(\n            self.non_deliverable_params, _NonDeliverableParams\n        ):\n            fx_fixing_res: Result[DualTypes] = (\n                self.non_deliverable_params.fx_fixing.try_value_or_forecast(fx)\n            )\n            non_deliverable_elements.update(\n                {\n                    defaults.headers[\"fx_fixing\"]: _float_or_none(fx_fixing_res),\n                    defaults.headers[\"fx_fixing_date\"]: self.non_deliverable_params.fx_fixing.date,\n                    defaults.headers[\n                        \"reference_currency\"\n                    ]: self.non_deliverable_params.reference_currency.upper(),\n                }\n            )\n        return non_deliverable_elements\n\n    def _mtm_elements(self, fx: FXForwards_) -> dict[str, Any]:\n        mtm_elements: dict[str, Any] = {}\n        if hasattr(self, \"mtm_params\") and isinstance(self.mtm_params, _MtmParams):\n            # mtm_elements overwrite non_deliverable elements as these are exclusive params.\n            fx_fixing_res = self.mtm_params.fx_fixing_end.try_value_or_forecast(fx)\n            mtm_elements = {\n                defaults.headers[\"fx_fixing\"]: _float_or_none(fx_fixing_res),\n                defaults.headers[\"fx_fixing_date\"]: self.mtm_params.fx_fixing_end.date,\n                defaults.headers[\"reference_currency\"]: self.mtm_params.reference_currency.upper(),\n            }\n        return mtm_elements\n\n    def cashflows(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n        base: str_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> dict[str, Any]:\n        \"\"\"\n        Return aggregated cashflow data for the *Period*.\n\n        .. warning::\n\n           This method is a convenience method to provide a visual representation of all\n           associated calculation data. Calling this method to extracting certain values\n           should be avoided. It is more efficient to source relevant parameters or calculations\n           from object attributes or other methods directly.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        base: str, optional\n            The currency to convert the *local settlement* NPV to.\n        settlement: datetime, optional\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        dict[Any]\n        \"\"\"\n        standard_elements = _standard_elements(self=self)\n        period_elements = _period_elements(self=self)\n        cashflow_elements = _cashflow_elements(\n            self=self,\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            ir_vol=ir_vol,\n            base=base,\n            forward=forward,\n            settlement=settlement,\n        )\n        rate_elements = _rate_elements(self=self, rate_curve=rate_curve)\n        credit_elements = _credit_elements(self=self, rate_curve=rate_curve)\n        index_elements = self._index_elements(index_curve=index_curve)\n        non_deliverable_elements = self._non_deliverable_elements(fx=fx)\n        mtm_elements = self._mtm_elements(fx=fx)\n        return {\n            **standard_elements,\n            **period_elements,\n            **cashflow_elements,\n            **rate_elements,\n            **credit_elements,\n            **index_elements,\n            **non_deliverable_elements,\n            **mtm_elements,\n        }\n\n\ndef _standard_elements(self: _WithCashflows | _WithCashflowsStatic) -> dict[str, Any]:\n    \"\"\"Typical cashflow attributes for any constructed *Period*\"\"\"\n    # standard parameters\n    standard_elements: dict[str, Any] = {}\n    standard_elements.update(\n        {\n            defaults.headers[\"type\"]: type(self).__name__,\n            defaults.headers[\"currency\"]: self.settlement_params.currency.upper(),\n            defaults.headers[\"payment\"]: self.settlement_params.payment,\n            defaults.headers[\"notional\"]: _dual_float(self.settlement_params.notional),\n        }\n    )\n    return standard_elements\n\n\ndef _period_elements(self: _WithCashflows | _WithCashflowsStatic) -> dict[str, Any]:\n    \"\"\"\n    Typical date-like attributes for any constructed *Period* with `period_params`.\n    \"\"\"\n    # period parameters\n    period_elements: dict[str, Any] = {}\n    if hasattr(self, \"period_params\") and isinstance(self.period_params, _PeriodParams):\n        period_elements.update(\n            {\n                defaults.headers[\"stub_type\"]: \"Stub\" if self.period_params.stub else \"Regular\",\n                defaults.headers[\"convention\"]: str(self.period_params.convention),\n                defaults.headers[\"dcf\"]: self.period_params.dcf,\n                defaults.headers[\"a_acc_start\"]: self.period_params.start,\n                defaults.headers[\"a_acc_end\"]: self.period_params.end,\n            }\n        )\n    return period_elements\n\n\ndef _rate_elements(\n    self: _WithCashflows | _WithCashflowsStatic,\n    rate_curve: CurveOption_,\n) -> dict[str, Any]:\n    \"\"\"\n    Typical rate-like attributes for any constructed *Period* with `rate_params`.\n    \"\"\"\n    # rate parameters\n    rate_elements: dict[str, Any] = {}\n    if hasattr(self, \"rate_params\"):\n        if isinstance(self.rate_params, _FixedRateParams):\n            rate_elements.update(\n                {\n                    defaults.headers[\"rate\"]: _float_or_none(self.rate_params.fixed_rate),\n                    defaults.headers[\"spread\"]: None,\n                }\n            )\n        elif isinstance(self.rate_params, _FloatRateParams):\n            rate_elements.update(\n                {\n                    # try_rate is guaranteed by having FloatRateParams but this is poor typing.\n                    defaults.headers[\"rate\"]: _float_or_none(self.try_rate(rate_curve=rate_curve)),  # type: ignore[attr-defined]\n                    defaults.headers[\"spread\"]: _float_or_none(self.rate_params.float_spread),\n                }\n            )\n    return rate_elements\n\n\ndef _credit_elements(\n    self: _WithCashflows | _WithCashflowsStatic,\n    rate_curve: CurveOption_,\n) -> dict[str, Any]:\n    \"\"\"\n    Typical credit-like attributes for any constructed *Period* with `credit_params`.\n    \"\"\"\n    credit_elements: dict[str, Any] = {}\n    if hasattr(self, \"credit_params\") and isinstance(self.credit_params, _CreditParams):\n        if hasattr(self, \"period_params\") and isinstance(self.period_params, _PeriodParams):\n            rc_res = _try_validate_base_curve(rate_curve)\n            if not isinstance(rc_res, Err):\n                credit_elements.update(\n                    {\n                        defaults.headers[\"survival\"]: _dual_float(\n                            rc_res.unwrap()[self.period_params.end]\n                        ),\n                        defaults.headers[\"recovery\"]: _dual_float(\n                            rc_res.unwrap().meta.credit_recovery_rate\n                        ),\n                    }\n                )\n            else:\n                credit_elements.update(\n                    {defaults.headers[\"survival\"]: None, defaults.headers[\"recovery\"]: None}\n                )\n        else:\n            pass\n    return credit_elements\n\n\ndef _cashflow_elements(\n    self: _WithCashflows | _WithCashflowsStatic,\n    *,\n    rate_curve: CurveOption_ = NoInput(0),\n    disc_curve: _BaseCurve_ = NoInput(0),\n    index_curve: _BaseCurve_ = NoInput(0),\n    fx: FXForwards_ = NoInput(0),\n    fx_vol: _FXVolOption_ = NoInput(0),\n    ir_vol: _IRVolOption_ = NoInput(0),\n    base: str_ = NoInput(0),\n    settlement: datetime_ = NoInput(0),\n    forward: datetime_ = NoInput(0),\n) -> dict[str, Any]:\n    # cashflow valuation based parameters\n    c = self.try_cashflow(\n        rate_curve=rate_curve,\n        disc_curve=disc_curve,\n        index_curve=index_curve,\n        fx=fx,\n        fx_vol=fx_vol,\n        ir_vol=ir_vol,\n    )\n\n    disc_curve_result = _try_disc_required_maybe_from_curve(curve=rate_curve, disc_curve=disc_curve)\n    if disc_curve_result.is_err:\n        # then NPV is impossible\n        v, collateral = None, None\n    else:\n        v = disc_curve_result.unwrap()[self.settlement_params.payment]\n        collateral = disc_curve_result.unwrap().meta.collateral\n\n    # Since `cashflows` in not a performance critical function this call duplicates\n    # cashflow calculations. A more efficient calculation is possible but the code branching\n    # is ugly.\n    local_npv_result = self.try_local_npv(\n        rate_curve=rate_curve,\n        index_curve=index_curve,\n        disc_curve=disc_curve,\n        fx=fx,\n        fx_vol=fx_vol,\n        ir_vol=ir_vol,\n        settlement=settlement,\n        forward=forward,\n    )\n\n    fx_, base_ = _get_immediate_fx_scalar_and_base(self.settlement_params.currency, fx, base)\n    if local_npv_result.is_err:\n        npv_fx = None\n    else:\n        npv_fx = local_npv_result.unwrap() * fx_\n\n    return {\n        defaults.headers[\"df\"]: _float_or_none(v),\n        defaults.headers[\"cashflow\"]: _float_or_none(c),\n        defaults.headers[\"npv\"]: _float_or_none(local_npv_result),\n        defaults.headers[\"fx\"]: _dual_float(fx_),\n        defaults.headers[\"base\"]: base_.upper(),\n        defaults.headers[\"npv_fx\"]: _float_or_none(npv_fx),\n        defaults.headers[\"collateral\"]: collateral,\n    }\n"
  },
  {
    "path": "python/rateslib/periods/protocols/fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport os\nfrom itertools import product\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom pandas import DataFrame, DatetimeIndex, MultiIndex, Series, isna\n\nfrom rateslib import fixings\nfrom rateslib.dual import Variable, gradient\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.periods.parameters import (\n    _FloatRateParams,\n    _FXOptionParams,\n    _IndexParams,\n    _MtmParams,\n    _NonDeliverableParams,\n)\nfrom rateslib.periods.protocols.npv import _WithNPV\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CurveOption_,\n        DualTypes,\n        FXForwards_,\n        Sequence,\n        _BaseCurve_,\n        _FXVolOption_,\n        datetime_,\n        int_,\n    )\n\n\nclass _WithFixings(_WithNPV, Protocol):\n    \"\"\"\n    Protocol for determining fixing sensitivity for a *Period* with AD.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n       ~_WithFixings.reset_fixings\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithFixings.reset_fixings\n\n    \"\"\"\n\n    # def local_npv(\n    #     self,\n    #     *,\n    #     rate_curve: CurveOption_ = NoInput(0),\n    #     index_curve: _BaseCurve_ = NoInput(0),\n    #     disc_curve: _BaseCurve_ = NoInput(0),\n    #     fx: FXForwards_ = NoInput(0),\n    #     fx_vol: _FXVolOption_ = NoInput(0),\n    #     settlement: datetime_ = NoInput(0),\n    #     forward: datetime_ = NoInput(0),\n    # ) -> DualTypes: ...\n\n    # @property\n    # def settlement_param(self) -> _SettlementParams: ...\n\n    def reset_fixings(self, state: int_ = NoInput(0)) -> None:\n        \"\"\"\n        Resets any fixings values of the *Period* derived using the given data state.\n\n        .. rubric:: Examples\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import fixings, dt, NoInput, FloatPeriod\n           from pandas import Series\n\n        .. ipython:: python\n\n           fp = FloatPeriod(\n               start=dt(2026, 1, 12),\n               end=dt(2026, 1, 16),\n               payment=dt(2026, 1, 16),\n               frequency=\"M\",\n               fixing_method=\"rfr_payment_delay\",\n               rate_fixings=\"sofr\"\n           )\n           fixings.add(\n               name=\"sofr_1B\",\n               series=Series(\n                   index=[dt(2026, 1, 12), dt(2026, 1, 13), dt(2026, 1, 14), dt(2026, 1, 15)],\n                   data=[3.1, 3.2, 3.3, 3.4]\n               )\n           )\n           # value is populated from given data\n           assert 3.245 < fp.rate_params.rate_fixing.value < 3.255\n           fp.reset_fixings()\n           # private data related to fixing is removed and requires new data lookup\n           fp.rate_params.rate_fixing._value\n           fp.rate_params.rate_fixing._populated\n\n        .. role:: green\n\n        Parameters\n        ----------\n        state: int, :green:`optional`\n            The *state id* of the data series that set the fixing. Only fixings determined by this\n            data will be reset. If not given resets all fixings.\n        \"\"\"\n        if isinstance(getattr(self, \"index_params\", None), _IndexParams):\n            self.index_params.index_base.reset(state)  # type: ignore[attr-defined]\n            self.index_params.index_fixing.reset(state)  # type: ignore[attr-defined]\n        if isinstance(getattr(self, \"rate_params\", None), _FloatRateParams):\n            self.rate_params.rate_fixing.reset(state)  # type: ignore[attr-defined]\n        if isinstance(getattr(self, \"mtm_params\", None), _MtmParams):\n            self.mtm_params.fx_fixing_start.reset(state)  # type: ignore[attr-defined]\n            self.mtm_params.fx_fixing_end.reset(state)  # type: ignore[attr-defined]\n        if isinstance(getattr(self, \"non_deliverable_params\", None), _NonDeliverableParams):\n            self.non_deliverable_params.fx_fixing.reset(state)  # type: ignore[attr-defined]\n        from rateslib.periods.float_period import ZeroFloatPeriod\n\n        if isinstance(self, ZeroFloatPeriod):\n            for float_period in self.float_periods:\n                float_period.reset_fixings(state)\n        if isinstance(getattr(self, \"fx_option_params\", None), _FXOptionParams):\n            self.fx_option_params.option_fixing.reset(state)  # type: ignore[attr-defined]\n\n    def local_fixings(\n        self,\n        identifiers: Sequence[tuple[str, Series]],\n        scalars: Sequence[float] | NoInput = NoInput(0),\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Calculate the sensitivity to fixings of the *Instrument*, expressed in local\n        settlement currency.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        ----------\n        indentifiers: Sequence of tuple[str, Series], :red:`required`\n            These are the series string identifiers and the data values that will be used in each\n            Series to determine the sensitivity against.\n        scalars: Sequence of floats, :green:`optional (each set as 1.0)`\n            A sequence of scalars to multiply the sensitivities by for each on of the\n            ``identifiers``.\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional (set as immediate date)\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional (set as ``settlement``)\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        original_data, index, state = _replace_fixings_with_ad_variables(identifiers)\n        # Extract sensitivity data\n        pv: dict[str, DualTypes] = {\n            self.settlement_params.currency: self.local_npv(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx=fx,\n                fx_vol=fx_vol,\n                settlement=settlement,\n                forward=forward,\n            )\n        }\n        df = _structure_sensitivity_data(pv, index, identifiers, scalars)\n        _reset_fixings_data(self, original_data, state, identifiers)\n        return df\n\n\ndef _replace_fixings_with_ad_variables(\n    identifiers: Sequence[tuple[str, Series]],\n) -> tuple[dict[str, tuple[int, Series]], DatetimeIndex, int]:\n    \"\"\"\n    For a set of identifiers (which must already exist in the `fixings` object) extend those\n    with the given data as new fixings expressed as a Variable which will capture sensitivity.\n\n    Parameters\n    ----------\n    identifiers\n\n    Returns\n    -------\n    tuple: the original data that will be reset later, the DatetimeIndex of relevant dates\n    and the state id used for the added series\n    \"\"\"\n\n    # for each identifier, replace the existing fixing Series with a new one with AD Variables.\n    state = hash(os.urandom(64))\n    original_data: dict[str, tuple[int, Series]] = {}\n    index = DatetimeIndex(data=[])\n    for identifier in identifiers:\n        original_data[identifier[0]] = (fixings[identifier[0]][0], fixings[identifier[0]][1])\n        ad_series = Series(\n            index=identifier[1].index,\n            data=[  # type: ignore[arg-type]\n                Variable(_dual_float(v), [f\"{identifier[0]}_{d.strftime('%Y%m%d')}\"])  # type: ignore[attr-defined]\n                for d, v in identifier[1].items()\n            ],\n        )\n        index = index.union(other=ad_series.index, sort=None)  # type: ignore[arg-type]  # will sort\n        fixings.pop(name=identifier[0])\n        fixings.add(\n            name=identifier[0],\n            series=ad_series.combine(original_data[identifier[0]][1], _s2_before_s1),\n            state=state,\n        )\n\n    return original_data, index, state\n\n\ndef _structure_sensitivity_data(\n    pv: dict[str, DualTypes],\n    index: DatetimeIndex,\n    identifiers: Sequence[tuple[str, Series]],\n    scalars: Sequence[float] | NoInput,\n) -> DataFrame:\n    if isinstance(scalars, NoInput):\n        scalars_: Sequence[float] = [1.0] * len(identifiers)\n    elif len(scalars) != len(identifiers):\n        raise ValueError(\"If given, ``scalars`` must be same length as ``identifiers``.\")\n    else:\n        scalars_ = scalars\n\n    date_str = [_.strftime(\"%Y%m%d\") for _ in index]\n\n    # Construct DataFrame\n    df = DataFrame(\n        columns=MultiIndex.from_tuples(\n            product(pv.keys(), [i[0] for i in identifiers]), names=[\"local_ccy\", \"identifier\"]\n        ),\n        # index=date_list,\n        index=index,\n        data=[],\n        dtype=float,\n    )\n    for ccy, v in pv.items():\n        for j, identifier in enumerate(identifiers):\n            df[(ccy, identifier[0])] = (\n                gradient(v, vars=[identifier[0] + \"_\" + date for date in date_str]) * scalars_[j]\n            )\n\n    return df\n\n\nclass _SupportsResetFixings(Protocol):\n    def reset_fixings(self, state: int_ = NoInput(0)) -> None: ...\n\n\ndef _reset_fixings_data(\n    obj: _SupportsResetFixings,\n    original_data: dict[str, tuple[int, Series]],\n    state: int,\n    identifiers: Sequence[tuple[str, Series]],\n) -> None:\n    # reset all data to original values.\n    obj.reset_fixings(state=state)\n    for identifier in identifiers:\n        fixings.pop(name=identifier[0])\n        fixings.add(\n            name=identifier[0],\n            series=original_data[identifier[0]][1],\n            state=original_data[identifier[0]][0],\n        )\n\n\ndef _s2_before_s1(v1: DualTypes, v2: DualTypes | None) -> DualTypes:\n    if v2 is None or isna(v2):  # type: ignore[arg-type]\n        return v1\n    else:\n        return v2\n"
  },
  {
    "path": "python/rateslib/periods/protocols/npv.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom rateslib.curves import _BaseCurve\nfrom rateslib.curves._parsers import (\n    _disc_required_maybe_from_curve,\n    _try_disc_required_maybe_from_curve,\n)\nfrom rateslib.enums.generics import Err, NoInput, Ok\nfrom rateslib.periods.parameters import (\n    _IndexParams,\n    _SettlementParams,\n)\nfrom rateslib.periods.parameters.settlement import _NonDeliverableParams\nfrom rateslib.periods.utils import (\n    _maybe_local,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        FX_,\n        CurveOption_,\n        DualTypes,\n        FXForwards_,\n        Result,\n        _BaseCurve_,\n        _FXVolOption_,\n        _IRVolOption_,\n        _IRVolPricingParams,\n        datetime,\n        datetime_,\n        str_,\n    )\n\n\ndef _screen_ex_div_and_forward(\n    local_value: Result[DualTypes],\n    rate_curve: CurveOption_,\n    disc_curve: _BaseCurve_,\n    ex_dividend: datetime,\n    forward: datetime_ = NoInput(0),\n    settlement: datetime_ = NoInput(0),\n) -> Result[DualTypes]:\n    \"\"\"\n    Remap an immediate, local currency value to account for a forward valuation and settlement.\n\n    Parameters\n    ----------\n    local_value: Result[float, Dual, Dual2, Variable]\n        The value measured with immediate effect expressed in local currency.\n    rate_curve: _BaseCurve or NoInput\n        The rate curve which might be used in place of the ``disc_curve`` if that not given.\n    disc_curve: _BaseCurve or NoInput\n        The discount curve used to discount units of local currency at an appropriate\n        collateral rate.\n    ex_dividend: datetime\n        The ex-dividend date which, combined with ``settlement``, determines if this value\n        is set to zero.\n    settlement: datetime\n        The settlement date to compare against an ex-dividend date to imply a cashflow.\n    forward: datetime\n        The projected forward valuation of the PV obtained via the discount curve\n\n    Returns\n    -------\n    Float, Dual, Dual2, Variable\n    \"\"\"\n    if local_value.is_err:\n        return local_value\n\n    # determine forward_ and settlement_ if not given\n    is_settlement = not isinstance(settlement, NoInput)\n    is_forward = not isinstance(forward, NoInput)\n\n    if not is_settlement and not is_forward:\n        return local_value  # immediate value is returned unadjusted\n\n    dc_res = _try_disc_required_maybe_from_curve(curve=rate_curve, disc_curve=disc_curve)\n    if isinstance(dc_res, Err):\n        return dc_res\n    disc_curve_: _BaseCurve = dc_res.unwrap()\n\n    if not is_settlement:\n        # ex-div is assumed to always after a blank settlement\n        return Ok(local_value.unwrap() / disc_curve_[forward])  # type: ignore[index]\n    else:\n        if settlement > ex_dividend:  # type: ignore[operator]\n            return Ok(local_value.unwrap() * 0.0)  # TODO: profile this multiplication\n            # in the case of Dualtypes this would be faster to just return 0.0\n            # but the multiplication is used to handle DataFrame (FixingsSensitivity)\n        if not is_forward:\n            # forward is assumed to be immediate value if not given.\n            # # forward is assumed to be equal to settlement\n            return local_value  # / disc_curve_[settlement])  # type: ignore[index]\n        else:\n            return Ok(local_value.unwrap() / disc_curve_[forward])  # type: ignore[index]\n\n\nclass _WithNPV(Protocol):\n    r\"\"\"\n    Protocol to define value of any *Period* type.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n      ~_WithNPV.immediate_local_npv\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n      ~_WithNPV.local_npv\n      ~_WithNPV.npv\n      ~_WithNPV.try_immediate_local_npv\n      ~_WithNPV.try_local_npv\n\n\n    Notes\n    -----\n    Each *Period* type is required to implement the immediate expectation of value of\n    its cashflow under the risk neutral measure, expressed in its local settlement currency.\n\n    .. math::\n\n       P_0 = \\mathbb{E^Q}[V(m_T) C_T]\n    \"\"\"\n\n    _settlement_params: _SettlementParams\n\n    @property\n    def settlement_params(self) -> _SettlementParams:\n        \"\"\"The :class:`~rateslib.periods.parameters._SettlementParams` of the\n        *Period*.\"\"\"\n        return self._settlement_params\n\n    def __repr__(self) -> str:\n        return f\"<rl.{type(self).__name__} at {hex(id(self))}>\"\n\n    def immediate_local_npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> DualTypes:\n        r\"\"\"\n        Calculate the immediate NPV of the *Period* in local settlement currency.\n\n        This method does **not** adjust for ex-dividend and is an immediate measure according to,\n\n        .. math::\n\n           P_0 = \\mathbb{E^Q} [V(m_T) C(m_T)]\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, optional\n            The IR volatility *smile* or *Cube* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"  # noqa: E501\n        raise NotImplementedError(  # pragma: no cover\n            f\"Period type '{type(self).__name__}' must implement `immediate_local_npv`\"\n        )\n\n    def try_immediate_local_npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNPV.immediate_local_npv` with\n        lazy exception handling.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        try:\n            v = self.immediate_local_npv(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n        pass\n\n    def local_npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes:\n        r\"\"\"\n        Calculate the NPV of the *Period* in local settlement currency.\n\n        This method adjusts the immediate NPV for ex-dividend, settlement and forward projected value,\n        according to,\n\n        .. math::\n\n           P(m_s, m_f) = \\mathbb{I}(m_s) \\frac{1}{v(m_f)} P_0,  \\qquad \\; \\mathbb{I}(m_s) = \\left \\{ \\begin{matrix} 0 & m_s > m_{ex} \\\\ 1 & m_s \\leq m_{ex} \\end{matrix} \\right .\n\n        for forward, :math:`m_f`, settlement, :math:`m_s`, and ex-dividend, :math:`m_{ex}`.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, optional\n            The IR volatility *Smile* or *Cube* object used for determining Black calendar\n            day implied volatility values.\n        settlement: datetime, optional (set as immediate date)\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional (set as ``settlement``)\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"  # noqa: E501\n        local_immediate_npv = self.immediate_local_npv(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            ir_vol=ir_vol,\n        )\n        return _screen_ex_div_and_forward(\n            local_value=Ok(local_immediate_npv),\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            ex_dividend=self.settlement_params.ex_dividend,\n            settlement=settlement,\n            forward=forward,\n        ).unwrap()\n\n    def try_local_npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNPV.local_npv` with lazy\n        exception handling.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        try:\n            v = self.local_npv(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                settlement=settlement,\n                forward=forward,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n    def npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n        base: str_ = NoInput(0),\n        local: bool = False,\n        settlement: datetime_ = NoInput(0),\n        forward: datetime_ = NoInput(0),\n    ) -> DualTypes | dict[str, DualTypes]:\n        \"\"\"\n        Calculate the NPV of the *Period* converted to any other *base* accounting currency.\n\n        This method converts a local settlement currency value to a base accounting currency\n        according to:\n\n        .. math::\n\n           P^{bas}(m_s, m_f) = f_{loc:bas}(m_f) P(m_s, m_f)\n\n        .. hint::\n\n           If the cashflows are unspecified or incalculable due to missing information this method\n           will raise an exception. For a function that returns a `Result` indicating success or\n           failure use :meth:`~rateslib.periods.protocols._WithNPV.try_local_npv`.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, optional\n            The IR volatility *smile* or *Cube* object used for determining Black calendar\n            day implied volatility values.\n        base: str, optional\n            The currency to convert the *local settlement* NPV to.\n        local: bool, optional\n            An override flag to return a dict of NPV values indexed by string currency.\n        settlement: datetime, optional, (set as immediate date)\n            The assumed settlement date of the *PV* determination. Used only to evaluate\n            *ex-dividend* status.\n        forward: datetime, optional, (set as ``settlement``)\n            The future date to project the *PV* to using the ``disc_curve``.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable or dict of such indexed by string currency.\n\n        Notes\n        -----\n        If ``base`` is not provided then this function will return the value obtained from\n        :meth:`~rateslib.periods.protocols._WithNPV.local_npv`.\n\n        If ``base`` is provided this then an :class:`~rateslib.fx.FXForwards` object may be\n        required to perform conversions. An :class:`~rateslib.fx.FXRates` object is also allowed\n        for this conversion although best practice does not recommend it due to possible\n        settlement date conflicts.\n        \"\"\"\n        local_npv = self.local_npv(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            ir_vol=ir_vol,\n            settlement=settlement,\n            forward=forward,\n        )\n        return _maybe_local(\n            value=local_npv,\n            local=local,\n            currency=self.settlement_params.currency,\n            fx=fx,\n            base=base,\n            forward=forward,\n        )\n\n\nclass _WithIndexingStatic(Protocol):\n    \"\"\"\n    Protocol to provide indexation for *Static Period* types.\n    \"\"\"\n\n    _index_params: _IndexParams | None\n\n    @property\n    def index_params(self) -> _IndexParams | None:\n        \"\"\"\n        The :class:`~rateslib.periods.parameters._IndexParams` of the *Period*,\n        if any.\n        \"\"\"\n        return self._index_params\n\n    @property\n    def is_indexed(self) -> bool:\n        \"\"\"\n        Check whether the *Period* has indexation applied, which means it has ``index_params``.\n        \"\"\"\n        return self.index_params is not None\n\n    def index_up(self, value: DualTypes, index_curve: _BaseCurve_) -> DualTypes:\n        \"\"\"\n        Apply indexation to a *Static Period* value using its ``index_params``.\n\n        Parameters\n        ----------\n        value: float, Dual, Dual2, Variable\n            The possible value to apply indexation to.\n        index_curve: _BaseCurve, optional\n            The index curve used to forecast index values, if necessary.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        if self.index_params is None:\n            # then no indexation of the cashflow will occur.\n            return value\n        else:\n            ir = self.index_params.try_index_ratio(index_curve).unwrap()[0]\n            if self.index_params.index_only:\n                return value * (ir - 1)\n            else:\n                return value * ir\n\n    def try_index_up(self, value: Result[DualTypes], index_curve: _BaseCurve_) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithIndexingStatic.index_up`\n        with lazy exception handling.\n\n        Parameters\n        ----------\n        value: Result[float, Dual, Dual2, Variable]\n            The possible value to apply indexation to.\n        index_curve: _BaseCurve, optional\n            The index curve used to forecast index values, if necessary.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        try:\n            v = self.index_up(\n                value=value.unwrap(),\n                index_curve=index_curve,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n\nclass _WithNonDeliverableStatic(Protocol):\n    \"\"\"\n    Protocol to provide non-deliverable conversion for *Static Period* types.\n\n\n\n    \"\"\"\n\n    _non_deliverable_params: _NonDeliverableParams | None\n\n    @property\n    def non_deliverable_params(self) -> _NonDeliverableParams | None:\n        \"\"\"The :class:`~rateslib.periods.parameters._NonDeliverableParams` of the\n        *Period*., if any.\"\"\"\n        return self._non_deliverable_params\n\n    @property\n    def is_non_deliverable(self) -> bool:\n        \"\"\"\n        Check whether the *Period* is non-deliverable,\n        which means it has ``non_deliverable_params``.\n        \"\"\"\n        return self.non_deliverable_params is not None\n\n    def convert_deliverable(self, value: DualTypes, fx: FXForwards_) -> DualTypes:\n        \"\"\"\n        Apply settlement currency conversion to a *Static Period* using its\n        ``non_deliverable_params``.\n\n        Parameters\n        ----------\n        value: float, Dual, Dual2, Variable\n            The possible value to apply settlement currency conversion to.\n        fx: FXForwards, optional\n            The object used to forecast forward FX rates, if necessary.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        if self.non_deliverable_params is None:\n            # then cashflow is directly deliverable\n            return value\n        else:\n            fx_fix = self.non_deliverable_params.fx_fixing.try_value_or_forecast(fx).unwrap()\n            c = value * (fx_fix if not self.non_deliverable_params.fx_reversed else (1.0 / fx_fix))\n            return c\n\n    def try_convert_deliverable(\n        self, value: Result[DualTypes], fx: FXForwards_\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNonDeliverableStatic.convert_deliverable`\n        with lazy exception handling.\n\n        Parameters\n        ----------\n        value: Result[float, Dual, Dual2, Variable]\n            The possible value to apply settlement currency conversion to.\n        fx: FXForwards, optional\n            The object used to forecast forward FX rates, if necessary.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"  # noqa: E501\n        try:\n            v = self.convert_deliverable(\n                value=value.unwrap(),\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n\nclass _WithNPVStatic(_WithNPV, _WithIndexingStatic, _WithNonDeliverableStatic, Protocol):\n    r\"\"\"\n    Protocol to establish value of any *Static Period* type.\n\n    .. rubric:: Required methods\n\n    .. autosummary::\n\n       ~_WithNPVStatic.unindexed_reference_cashflow\n\n    .. rubric:: Provided methods\n\n    .. autosummary::\n\n       ~_WithNPVStatic.reference_cashflow\n       ~_WithNPVStatic.unindexed_cashflow\n       ~_WithNPVStatic.cashflow\n       ~_WithNPVStatic.immediate_local_npv\n       ~_WithNPVStatic.local_npv\n       ~_WithNPVStatic.npv\n       ~_WithNPVStatic.try_unindexed_reference_cashflow\n       ~_WithNPVStatic.try_reference_cashflow\n       ~_WithNPVStatic.try_unindexed_cashflow\n       ~_WithNPVStatic.try_cashflow\n       ~_WithNPVStatic.try_immediate_local_npv\n       ~_WithNPVStatic.try_local_npv\n\n    Notes\n    -----\n    A *Static Period* type is one with a defined, non-random cashflow date, and for which\n    indexation and non-deliverability components are independent and can be taken outside of\n    the expectation of value.\n\n    Each *Static Period* is required to implement the expectation of its unindexed reference\n    currency cashflow under the risk neutral measure, paid at the known payment date,\n    :math:`m_t`.\n\n    .. math::\n\n       \\mathbb{E^Q}[\\bar{C}_t]\n\n    \"\"\"\n\n    # required by each Static Period...\n    def unindexed_reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FX_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> DualTypes:\n        r\"\"\"\n        Calculate the cashflow for the *Static Period* before settlement currency and\n        indexation adjustments.\n\n        .. math::\n\n           \\mathbb{E^Q}[\\bar{C}_t]\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows, if necessary.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, optional\n            The IR volatility *smile* or *Cube* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        raise NotImplementedError(  # pragma: no cover\n            f\"Period type '{type(self).__name__}' must implement `unindexed_reference_cashflow`\"\n        )\n\n    # automatically provided for each Static Period...\n\n    def try_unindexed_reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FX_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNPVStatic.unindexed_reference_cashflow`\n        with lazy exception handling.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"  # noqa: E501\n        try:\n            v = self.unindexed_reference_cashflow(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n    def reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FX_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> DualTypes:\n        r\"\"\"\n        Calculate the cashflow for the *Static Period* before settlement currency adjustment\n        but after indexation.\n\n        .. math::\n\n           I_r\\mathbb{E^Q}[\\bar{C}_t]\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows, if necessary.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, optional\n            The IR volatility *smile* or *Cube* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        urc = self.unindexed_reference_cashflow(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            ir_vol=ir_vol,\n        )\n        return self.index_up(value=urc, index_curve=index_curve)\n\n    def try_reference_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FX_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNPVStatic.reference_cashflow`\n        with lazy exception handling.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        try:\n            v = self.reference_cashflow(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n    def unindexed_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> DualTypes:\n        r\"\"\"\n        Calculate the cashflow for the *Static Period* with settlement currency adjustment\n        but without indexation.\n\n        .. math::\n\n           f(m_d)\\mathbb{E^Q}[\\bar{C}_t]\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows, if necessary.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, optional\n            The IR volatility *smile* or *Cube* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        urc = self.unindexed_reference_cashflow(\n            rate_curve=rate_curve,\n            disc_curve=disc_curve,\n            index_curve=index_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            ir_vol=ir_vol,\n        )\n        return self.convert_deliverable(value=urc, fx=fx)\n\n    def try_unindexed_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNPVStatic.unindexed_cashflow`\n        with lazy exception handling.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        try:\n            v = self.unindexed_cashflow(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n    def cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> DualTypes:\n        r\"\"\"\n        Calculate the cashflow for the *Period* with settlement currency adjustment\n        and indexation.\n\n        .. math::\n\n           I_r f(m_d)\\mathbb{E^Q}[\\bar{C}_t]\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows, if necessary.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            :class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, optional\n            The IR volatility *smile* or *Cube* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        rc = self.reference_cashflow(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve,\n            fx=fx,\n            fx_vol=fx_vol,\n            ir_vol=ir_vol,\n        )\n        return self.convert_deliverable(value=rc, fx=fx)\n\n    def try_cashflow(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ = NoInput(0),\n    ) -> Result[DualTypes]:\n        r\"\"\"\n        Replicate :meth:`~rateslib.periods.protocols._WithNPVStatic.cashflow`\n        with lazy exception handling.\n\n        Returns\n        -------\n        Result[float, Dual, Dual2, Variable]\n        \"\"\"\n        try:\n            v = self.cashflow(\n                rate_curve=rate_curve,\n                index_curve=index_curve,\n                disc_curve=disc_curve,\n                fx_vol=fx_vol,\n                ir_vol=ir_vol,\n                fx=fx,\n            )\n        except Exception as e:\n            return Err(e)\n        else:\n            return Ok(v)\n\n    def immediate_local_npv(\n        self,\n        *,\n        rate_curve: CurveOption_ = NoInput(0),\n        index_curve: _BaseCurve_ = NoInput(0),\n        disc_curve: _BaseCurve_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        fx_vol: _FXVolOption_ = NoInput(0),\n        ir_vol: _IRVolOption_ | _IRVolPricingParams = NoInput(0),\n    ) -> DualTypes:\n        r\"\"\"\n        Calculate the NPV of the *Period* in local settlement currency.\n\n        This method does **not** adjust for ex-dividend and is an immediate measure according to,\n\n        .. math::\n\n           P_0 = v(m_t) I_r f(m_d) \\mathbb{E^Q} [\\bar{C}_t]\n\n        for non-deliverable delivery, :math:`m_d`, and index ratio, :math:`I_r`.\n\n        Parameters\n        ----------\n        rate_curve: _BaseCurve or dict of such indexed by string tenor, optional\n            Used to forecast floating period rates, if necessary.\n        index_curve: _BaseCurve, optional\n            Used to forecast index values for indexation, if necessary.\n        disc_curve: _BaseCurve, optional\n            Used to discount cashflows.\n        fx: FXForwards, optional\n            The :class:`~rateslib.fx.FXForwards` object used for forecasting the\n            ``fx_fixing`` for deliverable cashflows, if necessary. Or, an\n            class:`~rateslib.fx.FXRates` object purely for immediate currency conversion.\n        fx_vol: FXDeltaVolSmile, FXSabrSmile, FXDeltaVolSurface, FXSabrSurface, optional\n            The FX volatility *Smile* or *Surface* object used for determining Black calendar\n            day implied volatility values.\n        ir_vol: IRSabrSmile, optional\n            The IR volatility *smile* or *Cube* object used for determining Black calendar\n            day implied volatility values.\n\n        Returns\n        -------\n        float, Dual, Dual2, Variable\n        \"\"\"\n        # dc_res = _try_disc_required_maybe_from_curve(curve=rate_curve, disc_curve=disc_curve)\n        # if isinstance(dc_res, Err):\n        #     return dc_res\n        # disc_curve_: _BaseCurve = dc_res.unwrap()\n\n        disc_curve_ = _disc_required_maybe_from_curve(curve=rate_curve, disc_curve=disc_curve)\n        if self.settlement_params.payment < disc_curve_.nodes.initial:\n            # payment date is in the past\n            return 0.0\n\n        c = self.cashflow(\n            rate_curve=rate_curve,\n            index_curve=index_curve,\n            disc_curve=disc_curve_,\n            fx_vol=fx_vol,\n            ir_vol=ir_vol,\n            fx=fx,\n        )\n        return c * disc_curve_[self.settlement_params.payment]\n"
  },
  {
    "path": "python/rateslib/periods/utils.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nimport rateslib.errors as err\nfrom rateslib.curves._parsers import _validate_obj_not_no_input\nfrom rateslib.curves.curves import _BaseCurve\nfrom rateslib.enums.generics import Err, NoInput, Ok, Result\nfrom rateslib.enums.parameters import FXDeltaMethod, OptionPricingModel\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.instruments.protocols.pricing import _Curves\nfrom rateslib.volatility import (\n    FXDeltaVolSmile,\n    FXDeltaVolSurface,\n    FXSabrSmile,\n    FXSabrSurface,\n    _BaseIRCube,\n    _BaseIRSmile,\n)\nfrom rateslib.volatility.ir.utils import _IRVolPricingParams\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        FX_,\n        IRS,\n        Any,\n        CurveOption_,\n        DualTypes,\n        FXForwards_,\n        _BaseCurve_,\n        _FXVolOption_,\n        _IRVolOption_,\n        datetime_,\n        str_,\n    )\n\n\ndef _maybe_local(\n    value: DualTypes,\n    local: bool,\n    currency: str,\n    fx: FXForwards_,\n    base: str_,\n    forward: datetime_,\n) -> dict[str, DualTypes] | DualTypes:\n    \"\"\"\n    Return NPVs in scalar form or dict form.\n    \"\"\"\n    if local:\n        return {currency: value}\n    else:\n        return _maybe_fx_converted(\n            value=value, currency=currency, fx=fx, base=base, forward=forward\n        )\n\n\ndef _maybe_fx_converted(\n    value: DualTypes,\n    currency: str,\n    fx: FXForwards_,\n    base: str_,\n    forward: datetime_,\n) -> DualTypes:\n    \"\"\"Take an input Value and maybe FX convert it depending on the inputs\"\"\"\n    fx_, base = _get_immediate_fx_scalar_and_base(currency=currency, fx=fx, base=base)\n    if isinstance(forward, datetime) and base != currency:\n        fx_ = fx.rate(f\"{currency}{base}\", settlement=forward)  # type: ignore[union-attr]\n    return value * fx_\n\n\ndef _get_immediate_fx_scalar_and_base(\n    currency: str,\n    fx: FXForwards_,\n    base: str_,\n) -> tuple[DualTypes, str]:\n    \"\"\"\n    From a local currency and potentially FX Objects determine the conversion rate between\n    `currency` and `base`. If `base` is not given it is set as `currency` and the returned\n    FX rate is 1.0\n    \"\"\"\n    if isinstance(base, NoInput) or base is None:\n        if isinstance(fx, NoInput | FXRates | FXForwards):\n            return 1.0, currency\n        else:  # fx is DualTypes\n            if abs(fx - 1.0) < 1e-10:  # type: ignore[operator]\n                return fx, currency  # type: ignore[return-value]  # base is assumed\n            else:\n                warnings.warn(\n                    \"It is not best practice to provide `fx` as numeric since this can \"\n                    \"cause errors of output when dealing with multi-currency derivatives,\\n\"\n                    \"and it also fails to preserve FX rate sensitivity in calculations.\\n\"\n                    \"Instead, supply a 'base' currency and use an \"\n                    \"FXRates or, for best practice, an FXForwards object.\\n\"\n                    f\"Reformulate: [fx={fx}, base=None] -> \"\n                    f\"[fx=FXRates({{'{currency}bas': {fx}}}), base='bas'].\",\n                    UserWarning,\n                )\n            return fx, \"Unspecified\"  # type: ignore[return-value]  # base is unknown\n    else:  # base is str\n        if isinstance(fx, NoInput):\n            if base != currency:\n                raise ValueError(\n                    f\"`base` ({base}) cannot be requested without supplying `fx` as a \"\n                    \"valid FXRates or FXForwards object to convert from \"\n                    f\"currency ({currency}).\\n\"\n                    \"If you are using a `Solver` with multi-currency instruments have you \"\n                    \"forgotten to attach the FXForwards in the solver's `fx` argument?\",\n                )\n            return 1.0, currency\n        elif isinstance(fx, FXRates | FXForwards):\n            if base == currency:\n                return 1.0, currency\n            else:\n                return fx.rate(pair=f\"{currency}{base}\"), base\n        else:  # FX is DualTypes\n            if abs(fx - 1.0) < 1e-10:  # type: ignore[operator]\n                pass  # no warning when fx == 1.0\n            elif base == currency:\n                raise ValueError(\n                    \"`fx` is given as numeric when `base` and `currency` are the same but the value\"\n                    \"is not equal to 1.0, which it must be by definition.\"\n                )\n            else:\n                warnings.warn(\n                    f\"Supplying `fx` as numeric is ambiguous, particularly with \"\n                    f\"multi-currency Instruments, and may lead to forced errors. `base` ({base}) \"\n                    f\"will also be ignored.\\n\"\n                    f\"Future versions will likely remove this ability altogether.\\n\"\n                    f\"Best practice is to supply `fx` as an FXRates (or FXForwards) object.\\n\"\n                    f\"Reformulate the arguments directly: [fx={fx}, base='{base}'] -> \"\n                    f\"[fx=FXRates({{'{currency}{base}': {fx}}}), base='{base}'].\",\n                    DeprecationWarning,\n                )\n            return fx, base  # type: ignore[return-value]\n\n\ndef _get_ir_vol_value_and_forward_maybe_from_obj(\n    ir_vol: _IRVolOption_ | _IRVolPricingParams,\n    rate_curve: CurveOption_,\n    index_curve: _BaseCurve_,\n    strike: DualTypes | str,\n    irs: IRS,\n    expiry: datetime,\n    tenor: datetime,\n    t_e: DualTypes,\n) -> _IRVolPricingParams:\n    \"\"\"\n    Return the following pring requirements:\n\n    Returns\n    -------\n    output: tuple[DualTypes, DualTypes, DualTypes]\n        The forward IRS rate exc. shift, the Black shifted vol, the shift to add to `f` and `k`.\n    \"\"\"\n    if isinstance(ir_vol, _IRVolPricingParams):\n        return ir_vol\n    # IROption can have a `strike` that is NoInput, however this internal function should\n    # only be performed after a `strike` has been set to number, temporarily or otherwise.\n    f_ = irs.rate(\n        curves=_Curves(\n            disc_curve=index_curve, leg2_rate_curve=rate_curve, leg2_disc_curve=index_curve\n        )\n    )\n\n    if isinstance(strike, NoInput):\n        k_: DualTypes = f_\n    elif isinstance(strike, str):\n        if strike.lower() == \"atm\":\n            k_ = f_\n        elif \"bps\" in strike:\n            k_ = f_ + float(strike[:-3]) / 100.0\n        else:\n            raise ValueError(\"`strike` as string must be either 'atm' or '{}bps'.\")\n    else:\n        k_ = strike\n\n    if isinstance(ir_vol, _BaseIRSmile | _BaseIRCube):\n        # ir_vol is a Vol object\n        return ir_vol.get_from_strike(k=k_, f=f_, expiry=expiry, tenor=tenor)\n    elif isinstance(ir_vol, NoInput):\n        raise ValueError(\"`ir_vol` cannot be NoInput when provided to pricing function.\")\n    else:\n        # vol given as scalar interpolated as Black Vol Zero shifted\n        return _IRVolPricingParams(\n            vol=ir_vol, f=f_, k=k_, shift=0.0, pricing_model=OptionPricingModel.Black76, t_e=t_e\n        )\n\n\ndef _get_fx_vol_value_maybe_from_obj(\n    fx_vol: _FXVolOption_,\n    fx: FXForwards,\n    rate_curve: _BaseCurve_,\n    strike: DualTypes,\n    pair: str,\n    delivery: datetime,\n    expiry: datetime,\n) -> DualTypes:\n    \"\"\"Return a volatility for the option from a given FX Vol object.\n\n    ``rate_curve`` is used as the curve on the LHS rate_curve to convert between spot and delivery\n    delta. This is not a 'discount curve' because it is not used to discount cashflows.\n    \"\"\"\n    # FXOption can have a `strike` that is NoInput, however this internal function should\n    # only be performed after a `strike` has been set to number, temporarily or otherwise.\n\n    if isinstance(fx_vol, FXDeltaVolSmile | FXDeltaVolSurface):\n        # fx_vol is a Vol object\n        rate_curve_: _BaseCurve = _validate_base_curve(rate_curve)\n        spot = fx.pairs_settlement[pair]\n        f = fx.rate(pair, delivery)\n        _: tuple[Any, DualTypes, Any] = fx_vol.get_from_strike(\n            k=strike,\n            f=f,\n            z_w=rate_curve_[delivery] / rate_curve_[spot],\n            expiry=expiry,\n        )\n        vol_: DualTypes = _[1]\n    elif isinstance(fx_vol, FXSabrSmile | FXSabrSurface):\n        # fx_vol is a Vol object\n        f = fx.rate(pair, delivery)\n        _ = fx_vol.get_from_strike(\n            k=strike,\n            f=f,\n            expiry=expiry,\n        )\n        vol_ = _[1]\n    elif isinstance(fx_vol, NoInput):\n        raise ValueError(\"`fx_vol` cannot be NoInput when provided to pricing function.\")\n    else:\n        # fx_vol is a given scalar\n        vol_ = fx_vol\n\n    return vol_\n\n\ndef _get_vol_smile_or_value(vol: _FXVolOption_, expiry: datetime) -> FXDeltaVolSmile | DualTypes:\n    if isinstance(vol, FXDeltaVolSurface):\n        return vol.get_smile(expiry)\n    else:\n        return _validate_obj_not_no_input(vol, \"vol\")  # type: ignore[return-value]\n\n\ndef _get_vol_smile_or_raise(vol: _FXVolOption_, expiry: datetime) -> FXDeltaVolSmile:\n    if isinstance(vol, FXDeltaVolSurface):\n        return vol.get_smile(expiry)\n    elif isinstance(vol, FXDeltaVolSmile):\n        return vol\n    else:\n        raise ValueError(\"Must supply FXDeltaVolSmile/Surface as `vol` not numeric value.\")\n\n\ndef _get_vol_delta_type(vol: _FXVolOption_, default_delta_type: FXDeltaMethod) -> FXDeltaMethod:\n    if not isinstance(vol, FXDeltaVolSmile | FXDeltaVolSurface):\n        return default_delta_type\n    else:\n        return vol.meta.delta_type\n\n\ndef _validate_fx_as_forwards(fx: FX_) -> FXForwards:\n    return _try_validate_fx_as_forwards(fx).unwrap()\n\n\ndef _try_validate_fx_as_forwards(fx: FX_) -> Result[FXForwards]:\n    if isinstance(fx, NoInput):\n        return Err(ValueError(err.VE_NEEDS_FX_FORWARDS))\n    elif not isinstance(fx, FXForwards):\n        raise ValueError(err.VE_NEEDS_FX_FORWARDS_BAD_TYPE.format(type(fx).__name__))\n    else:\n        return Ok(fx)\n\n\ndef _try_validate_base_curve(curve: CurveOption_) -> Result[_BaseCurve]:\n    if not isinstance(curve, _BaseCurve):\n        return Err(\n            TypeError(\n                \"`curves` have not been supplied correctly.\\n\"\n                f\"A _BaseCurve type object is required. Got: {type(curve).__name__}\"\n            )\n        )\n    return Ok(curve)\n\n\ndef _validate_base_curve(curve: CurveOption_) -> _BaseCurve:\n    if not isinstance(curve, _BaseCurve):\n        raise TypeError(\n            \"`curves` have not been supplied correctly.\\n\"\n            f\"A _BaseCurve type object is required. Got: {type(curve).__name__}\"\n        )\n    return curve\n\n\ndef _validate_credit_curves(\n    rate_curve: CurveOption_, disc_curve: CurveOption_\n) -> Result[tuple[_BaseCurve, _BaseCurve]]:\n    # used by Credit type Periods to narrow inputs\n    if not isinstance(rate_curve, _BaseCurve):\n        return Err(\n            TypeError(\n                \"`curves` have not been supplied correctly.\\n\"\n                \"`curve`for a CreditPremiumPeriod must be supplied as a Curve type.\"\n            )\n        )\n    if not isinstance(disc_curve, _BaseCurve):\n        return Err(\n            TypeError(\n                \"`curves` have not been supplied correctly.\\n\"\n                \"`disc_curve` for a CreditPremiumPeriod must be supplied as a Curve type.\"\n            )\n        )\n    return Ok((rate_curve, disc_curve))\n\n\ndef _get_rfr_curve_from_dict(d: dict[str, _BaseCurve]) -> _BaseCurve:\n    for s in [\"rfr\", \"RFR\", \"Rfr\"]:\n        try:\n            ret: _BaseCurve = d[s]\n        except KeyError:\n            continue\n        else:\n            return ret\n    raise ValueError(\n        \"A `rate_curve` supplied as dict to an RFR based calculation must contain a key \"\n        \"entry 'rfr'.\"\n    )\n"
  },
  {
    "path": "python/rateslib/py.typed",
    "content": ""
  },
  {
    "path": "python/rateslib/rs.pyi",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom collections.abc import Sequence\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Any\n\nfrom typing_extensions import Self\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Arr1dF64,\n        Arr2dF64,\n        CalTypes,\n        CurveInterpolator,\n        DualTypes,\n        Number,\n    )\n\nclass ADOrder:\n    Zero: ADOrder\n    One: ADOrder\n    Two: ADOrder\n\nclass LegIndexBase:\n    Initial: LegIndexBase\n    PeriodOnPeriod: LegIndexBase\n\nclass Imm:\n    Wed3_HMUZ: Imm\n    Wed3: Imm\n    Day20_HMUZ: Imm\n    Day20_HU: Imm\n    Day20_MZ: Imm\n    Day20: Imm\n    Fri2_HMUZ: Imm\n    Fri2: Imm\n    Wed1_Post9: Imm\n    Wed1_Post9_HMUZ: Imm\n    Eom: Imm\n    Leap: Imm\n    Som: Imm\n\n    def next(self, date: datetime) -> datetime: ...\n    def validate(self, date: datetime) -> bool: ...\n    def get(self, year: int, month: int) -> datetime: ...\n    def to_json(self) -> str: ...\n\nclass _Scheduling:\n    def unext(self, udate: datetime) -> datetime: ...\n    def next(self, date: datetime) -> datetime: ...\n    def uprevious(self, udate: datetime) -> datetime: ...\n    def previous(self, date: datetime) -> datetime: ...\n    def uregular(self, ueffective: datetime, utermination: datetime) -> list[datetime]: ...\n    def infer_ustub(\n        self, ueffective: datetime, utermination: datetime, short: bool, front: bool\n    ) -> datetime: ...\n    def periods_per_annum(self) -> float: ...\n\nclass _FrequencyMixins:\n    def string(self) -> str: ...\n    def is_stub(self, ustart: datetime, uend: datetime, front: bool) -> bool: ...\n    def is_uregular(self, ueffective: datetime, utermination: datetime) -> bool: ...\n\nclass Frequency(_Scheduling, _FrequencyMixins):\n    class CalDays(Frequency):\n        number: int\n        def __init__(self, number: int) -> None: ...\n\n    class BusDays(Frequency):\n        number: int\n        calendar: CalTypes\n        def __init__(self, number: int, calendar: CalTypes) -> None: ...\n\n    class Months(Frequency):\n        number: int\n        roll: RollDay | None\n        def __init__(self, number: int, roll: RollDay | None) -> None: ...\n\n    class Zero(Frequency): ...\n\n    def to_json(self) -> str: ...\n\nclass StubInference:\n    ShortFront: StubInference\n    LongFront: StubInference\n    ShortBack: StubInference\n    LongBack: StubInference\n    NeitherSide: StubInference\n\n    def to_json(self) -> str: ...\n\nclass Schedule:\n    ueffective: datetime\n    utermination: datetime\n    ufront_stub: datetime | None\n    uback_stub: datetime | None\n    frequency: Frequency\n    calendar: CalTypes\n    accrual_adjuster: Adjuster\n    payment_adjuster: Adjuster\n    payment_adjuster2: Adjuster\n    payment_adjuster3: Adjuster\n    uschedule: list[datetime]\n    aschedule: list[datetime]\n    pschedule: list[datetime]\n    pschedule2: list[datetime]\n    pschedule3: list[datetime]\n\n    def __init__(\n        self,\n        effective: datetime,\n        termination: datetime,\n        frequency: Frequency,\n        calendar: CalTypes,\n        accrual_adjuster: Adjuster,\n        payment_adjuster: Adjuster,\n        payment_adjuster2: Adjuster,\n        payment_adjuster3: Adjuster | None,\n        front_stub: datetime | None,\n        back_stub: datetime | None,\n        eom: bool,\n        stub_inference: StubInference | None,\n    ) -> None: ...\n    def is_regular(self) -> bool: ...\n    def to_json(self) -> str: ...\n\nclass Convention:\n    Act365F: Convention\n    Act360: Convention\n    Act364: Convention\n    Act365_25: Convention\n    Thirty360: Convention\n    ThirtyU360: Convention\n    ThirtyE360: Convention\n    ThirtyE360ISDA: Convention\n    YearsAct365F: Convention\n    YearsAct360: Convention\n    YearsMonths: Convention\n    One: Convention\n    ActActISDA: Convention\n    ActActICMA: Convention\n    Bus252: Convention\n    ActActICMAStubAct365F: Convention\n\n    def dcf(\n        self,\n        start: datetime,\n        end: datetime,\n        termination: datetime | None,\n        frequency: Frequency | None,\n        stub: bool | None,\n        calendar: CalTypes | None,\n        adjuster: Adjuster | None,\n    ) -> float: ...\n    def to_json(self) -> str: ...\n\nclass Modifier:\n    P: Modifier\n    F: Modifier\n    ModP: Modifier\n    ModF: Modifier\n    Act: Modifier\n\nclass RollDay:\n    class Day(RollDay):\n        _0: int\n        def __init__(self, val: int) -> None: ...\n\n    class IMM(RollDay): ...\n\n    def to_json(self) -> str: ...\n\nclass _Adjustment:\n    def adjust(self, date: datetime, calendar: CalTypes) -> datetime: ...\n    def reverse(self, date: datetime, calendar: CalTypes) -> list[datetime]: ...\n    def adjusts(self, udates: list[datetime], calendars: CalTypes) -> list[datetime]: ...\n\nclass Adjuster(_Adjustment):\n    class Actual(Adjuster): ...\n    class Following(Adjuster): ...\n    class ModifiedFollowing(Adjuster): ...\n    class FollowingSettle(Adjuster): ...\n    class ModifiedFollowingSettle(Adjuster): ...\n    class Previous(Adjuster): ...\n    class ModifiedPrevious(Adjuster): ...\n    class PreviousSettle(Adjuster): ...\n    class ModifiedPreviousSettle(Adjuster): ...\n    class FollowingExLast(Adjuster): ...\n    class FollowingExLastSettle(Adjuster): ...\n\n    class BusDaysLagSettleInAdvance(Adjuster):\n        number: int\n\n        def __init__(self, number: int) -> None: ...\n\n    class BusDaysLagSettle(Adjuster):\n        number: int\n        def __init__(self, number: int) -> None: ...\n\n    class CalDaysLagSettle(Adjuster):\n        number: int\n        def __init__(self, number: int) -> None: ...\n\n    def to_json(self) -> str: ...\n\nclass _MethodParam:\n    def method_param(self) -> int: ...\n\nclass FloatFixingMethod(_MethodParam):\n    class RFRPaymentDelay(FloatFixingMethod): ...\n\n    class RFRObservationShift(FloatFixingMethod):\n        param: int\n        def __init__(self, param: int) -> None: ...\n\n    class RFRLockout(FloatFixingMethod):\n        param: int\n        def __init__(self, param: int) -> None: ...\n\n    class RFRLookback(FloatFixingMethod):\n        param: int\n        def __init__(self, param: int) -> None: ...\n\n    class RFRPaymentDelayAverage(FloatFixingMethod): ...\n\n    class RFRObservationShiftAverage(FloatFixingMethod):\n        param: int\n        def __init__(self, param: int) -> None: ...\n\n    class RFRLockoutAverage(FloatFixingMethod):\n        param: int\n        def __init__(self, param: int) -> None: ...\n\n    class RFRLookbackAverage(FloatFixingMethod):\n        param: int\n        def __init__(self, param: int) -> None: ...\n\n    class IBOR(FloatFixingMethod):\n        param: int\n        def __init__(self, param: int) -> None: ...\n\n    def to_json(self) -> str: ...\n\nclass _Shift:\n    def shift(self) -> int: ...\n\nclass IROptionMetric(_Shift):\n    class NormalVol(IROptionMetric): ...\n    class PercentNotional(IROptionMetric): ...\n    class Premium(IROptionMetric): ...\n\n    class BlackVolShift(IROptionMetric):\n        param: int\n        def __init__(self, param: int) -> None: ...\n\n    def to_json(self) -> str: ...\n\nclass CalendarManager:\n    def add(self, name: str, calendar: Cal) -> None: ...\n    def pop(self, name: str) -> Cal | UnionCal: ...\n    def get(self, name: str) -> NamedCal: ...\n    def keys(self) -> list[str]: ...\n\nclass _DateRoll:\n    def add_bus_days(self, date: datetime, days: int, settlement: bool) -> datetime: ...\n    def add_cal_days(self, date: datetime, days: int, adjuster: Adjuster) -> datetime: ...\n    def add_months(\n        self,\n        date: datetime,\n        months: int,\n        adjuster: Adjuster,\n        roll: RollDay | None,\n    ) -> datetime: ...\n    def bus_date_range(self, start: datetime, end: datetime) -> list[datetime]: ...\n    def cal_date_range(self, start: datetime, end: datetime) -> list[datetime]: ...\n    def is_bus_day(self, date: datetime) -> bool: ...\n    def is_non_bus_day(self, date: datetime) -> bool: ...\n    def is_settlement(self, date: datetime) -> bool: ...\n    def lag_bus_days(self, date: datetime, days: int, settlement: bool) -> datetime: ...\n    def to_json(self) -> str: ...\n    def print(self, year: int, month: int | None = None) -> str: ...\n    def print_compare(self, comparator: Cal | UnionCal | NamedCal, year: int) -> str: ...\n\nclass _CalendarAdjustment:\n    def adjust(self, date: datetime, adjuster: Adjuster) -> datetime: ...\n    def adjusts(self, dates: list[datetime], adjuster: Adjuster) -> list[datetime]: ...\n\nclass Cal(_DateRoll, _CalendarAdjustment):\n    def __init__(self, rules: list[datetime], week_mask: list[int]) -> None: ...\n    @classmethod\n    def from_name(cls, name: str) -> Cal: ...\n\nclass UnionCal(_DateRoll, _CalendarAdjustment):\n    calendars: list[Cal] = ...\n    settlement_calendars: list[Cal] = ...\n    @classmethod\n    def from_name(cls, name: str) -> UnionCal: ...\n    def __init__(\n        self,\n        calendars: list[Cal],\n        settlement_calendars: list[Cal] | None,\n    ) -> None: ...\n\nclass NamedCal(_DateRoll, _CalendarAdjustment):\n    inner: UnionCal | Cal = ...\n    name: str = ...\n    def __init__(self, name: str) -> None: ...\n    def inner_ptr_eq(self, other: NamedCal) -> bool: ...\n\nclass Ccy:\n    def __init__(self, name: str) -> None: ...\n    name: str = ...\n\nclass FXRate:\n    def __init__(\n        self, lhs: str, rhs: str, rate: DualTypes, settlement: datetime | None\n    ) -> None: ...\n    rate: DualTypes = ...\n    ad: int = ...\n    settlement: datetime = ...\n    pair: str = ...\n    def __repr__(self) -> str: ...\n    def __eq__(self, other: FXRate) -> bool: ...  # type: ignore[override]\n\nclass FXRates:\n    def __init__(self, fx_rates: list[FXRate], base: Ccy | None) -> None: ...\n    def __copy__(self) -> FXRates: ...\n    fx_rates: list[FXRate] = ...\n    currencies: list[Ccy] = ...\n    ad: int = ...\n    base: Ccy = ...\n    fx_vector: list[DualTypes] = ...\n    fx_array: list[list[DualTypes]] = ...\n    def get_ccy_index(self, currency: Ccy) -> int | None: ...\n    def rate(self, lhs: Ccy, rhs: Ccy) -> DualTypes | None: ...\n    def update(self, fx_rates: list[FXRate]) -> None: ...\n    def set_ad_order(self, ad: ADOrder) -> None: ...\n    def to_json(self) -> str: ...\n\nclass _DualOps:\n    def __eq__(self, other: Number) -> bool: ...  # type: ignore[override]\n    def __lt__(self, other: Number) -> bool: ...\n    def __le__(self, other: Number) -> bool: ...\n    def __gt__(self, other: Number) -> bool: ...\n    def __ge__(self, other: Number) -> bool: ...\n    def __neg__(self) -> Self: ...\n    def __add__(self, other: Number) -> Self: ...\n    def __radd__(self, other: Number) -> Self: ...\n    def __sub__(self, other: Number) -> Self: ...\n    def __rsub__(self, other: Number) -> Self: ...\n    def __mul__(self, other: Number) -> Self: ...\n    def __rmul__(self, other: Number) -> Self: ...\n    def __truediv__(self, other: Number) -> Self: ...\n    def __rtruediv__(self, other: Number) -> Self: ...\n    def __pow__(self, power: Number, modulo: int | None = None) -> Self: ...\n    def __exp__(self) -> Self: ...\n    def __abs__(self) -> float: ...\n    def __log__(self) -> Self: ...\n    def __norm_cdf__(self) -> Self: ...\n    def __norm_inv_cdf__(self) -> Self: ...\n    def __float__(self) -> float: ...\n    def to_json(self) -> str: ...\n    def ptr_eq(self, other: Self) -> bool: ...\n    def __repr__(self) -> str: ...\n    def grad1(self, vars: Sequence[str]) -> Arr1dF64: ...  # noqa: A002\n    def grad2(self, vars: Sequence[str]) -> Arr2dF64: ...  # noqa: A002\n\nclass Dual(_DualOps):\n    def __init__(self, real: float, vars: Sequence[str], dual: Sequence[float] | Arr1dF64): ...  # noqa: A002\n    real: float = ...\n    vars: list[str] = ...\n    dual: Arr1dF64 = ...\n    @classmethod\n    def vars_from(\n        cls,\n        other: Dual,\n        real: float,\n        vars: Sequence[str],  # noqa: A002\n        dual: Sequence[float] | Arr1dF64,\n    ) -> Dual: ...\n    def to_dual2(self) -> Dual2: ...\n\nclass Dual2(_DualOps):\n    def __init__(\n        self,\n        real: float,\n        vars: Sequence[str],  # noqa: A002\n        dual: Sequence[float] | Arr1dF64,\n        dual2: Sequence[float],\n    ): ...\n    real: float = ...\n    vars: list[str] = ...\n    dual: Arr1dF64 = ...\n    dual2: Arr2dF64 = ...\n    @classmethod\n    def vars_from(\n        cls,\n        other: Dual2,\n        real: float,\n        vars: list[str],  # noqa: A002\n        dual: list[float] | Arr1dF64,\n        dual2: list[float] | Arr1dF64,\n    ) -> Dual2: ...\n    def grad1_manifold(self, vars: Sequence[str]) -> list[Dual2]: ...  # noqa: A002\n    def to_dual(self) -> Dual: ...\n\ndef _dsolve1(a: list[Any], b: list[Any], allow_lsq: bool) -> list[Dual]: ...\ndef _dsolve2(a: list[Any], b: list[Any], allow_lsq: bool) -> list[Dual2]: ...\ndef _fdsolve1(a: Arr2dF64, b: list[Any], allow_lsq: bool) -> list[Dual]: ...\ndef _fdsolve2(a: Arr2dF64, b: list[Any], allow_lsq: bool) -> list[Dual2]: ...\n\nclass PPSplineF64:\n    n: int = ...\n    k: int = ...\n    t: list[float] = ...\n    c: list[float] | None = ...\n    def __init__(self, k: int, t: list[float], c: list[float] | None) -> None: ...\n    def csolve(\n        self, tau: list[float], y: list[float], left_n: int, right_n: int, allow_lsq: bool\n    ) -> None: ...\n    def ppev_single(self, x: Number) -> float: ...\n    def ppev_single_dual(self, x: Number) -> Dual: ...\n    def ppev_single_dual2(self, x: Number) -> Dual2: ...\n    def ppev(self, x: list[float]) -> list[float]: ...\n    def ppdnev_single(self, x: Number, m: int) -> float: ...\n    def ppdnev_single_dual(self, x: Number, m: int) -> Dual: ...\n    def ppdnev_single_dual2(self, x: Number, m: int) -> Dual2: ...\n    def ppdnev(self, x: list[float], m: int) -> list[float]: ...\n    def bsplev(self, x: list[float], i: int) -> list[float]: ...\n    def bspldnev(self, x: list[float], i: int, m: int) -> list[float]: ...\n    def bsplmatrix(self, tau: list[float], left_n: int, right_n: int) -> Arr2dF64: ...\n    def __eq__(self, other: PPSplineF64) -> bool: ...  # type: ignore[override]\n    def __copy__(self) -> PPSplineF64: ...\n    def to_json(self) -> str: ...\n\nclass PPSplineDual:\n    n: int = ...\n    k: int = ...\n    t: list[float] = ...\n    c: list[Dual] | None = ...\n    def __init__(self, k: int, t: list[float], c: list[Dual] | None) -> None: ...\n    def csolve(\n        self, tau: list[float], y: list[Dual], left_n: int, right_n: int, allow_lsq: bool\n    ) -> None: ...\n    def ppev_single(self, x: Number) -> Dual: ...\n    def ppev_single_dual(self, x: Number) -> Dual: ...\n    def ppev_single_dual2(self, x: Number) -> Dual2: ...\n    def ppev(self, x: list[float]) -> list[Dual]: ...\n    def ppdnev_single(self, x: Number, m: int) -> Dual: ...\n    def ppdnev_single_dual(self, x: Number, m: int) -> Dual: ...\n    def ppdnev_single_dual2(self, x: Number, m: int) -> Dual2: ...\n    def ppdnev(self, x: list[float], m: int) -> list[Dual]: ...\n    def bsplev(self, x: list[float], i: int) -> list[Dual]: ...\n    def bspldnev(self, x: list[float], i: int, m: int) -> list[Dual]: ...\n    def bsplmatrix(self, tau: list[float], left_n: int, right_n: int) -> Arr2dF64: ...\n    def __eq__(self, other: PPSplineDual) -> bool: ...  # type: ignore[override]\n    def __copy__(self) -> PPSplineDual: ...\n    def to_json(self) -> str: ...\n\nclass PPSplineDual2:\n    n: int = ...\n    k: int = ...\n    t: list[float] = ...\n    c: list[Dual2] | None = ...\n    def __init__(self, k: int, t: list[float], c: list[Dual2] | None) -> None: ...\n    def csolve(\n        self, tau: list[float], y: list[Dual2], left_n: int, right_n: int, allow_lsq: bool\n    ) -> None: ...\n    def ppev_single(self, x: Number) -> Dual2: ...\n    def ppev_single_dual(self, x: Number) -> Dual: ...\n    def ppev_single_dual2(self, x: Number) -> Dual2: ...\n    def ppev(self, x: list[float]) -> list[Dual2]: ...\n    def ppdnev_single(self, x: Number, m: int) -> Dual2: ...\n    def ppdnev_single_dual(self, x: Number, m: int) -> Dual: ...\n    def ppdnev_single_dual2(self, x: Number, m: int) -> Dual2: ...\n    def ppdnev(self, x: list[float], m: int) -> list[Dual2]: ...\n    def bsplev(self, x: list[float], i: int) -> list[Dual2]: ...\n    def bspldnev(self, x: list[float], i: int, m: int) -> list[Dual2]: ...\n    def bsplmatrix(self, tau: list[float], left_n: int, right_n: int) -> Arr2dF64: ...\n    def __eq__(self, other: PPSplineDual2) -> bool: ...  # type: ignore[override]\n    def __copy__(self) -> PPSplineDual2: ...\n    def to_json(self) -> str: ...\n\ndef bsplev_single(x: float, i: int, k: int, t: list[float], org_k: int | None) -> float: ...\ndef bspldnev_single(\n    x: float, i: int, k: int, t: list[float], m: int, org_k: int | None\n) -> float: ...\ndef from_json(json: str) -> Any: ...\n\nclass FlatBackwardInterpolator:\n    def __init__(self) -> None: ...\n\nclass FlatForwardInterpolator:\n    def __init__(self) -> None: ...\n\nclass LinearInterpolator:\n    def __init__(self) -> None: ...\n\nclass LogLinearInterpolator:\n    def __init__(self) -> None: ...\n\nclass LinearZeroRateInterpolator:\n    def __init__(self) -> None: ...\n\nclass NullInterpolator:\n    def __init__(self) -> None: ...\n\nclass Curve:\n    modifier: Modifier = ...\n    convention: Convention = ...\n    interpolation: str = ...\n    ad: ADOrder = ...\n    id: str = ...\n    nodes: dict[datetime, Number] = ...\n    def __init__(\n        self,\n        nodes: dict[datetime, Number],\n        interpolator: CurveInterpolator,\n        ad: ADOrder,\n        id: str,  # noqa: A002\n        convention: Convention,\n        modifier: Modifier,\n        calendar: CalTypes,\n        index_base: float | None,\n    ) -> None: ...\n    def to_json(self) -> str: ...\n    def __eq__(self, other: Curve) -> bool: ...  # type: ignore[override]\n    def __getitem__(self, date: datetime) -> Number: ...\n    def set_ad_order(self, ad: ADOrder) -> None: ...\n    def index_value(self, date: datetime) -> Number: ...\n\ndef _get_convention_str(convention: Convention) -> str: ...\ndef _get_modifier_str(modifier: Modifier) -> str: ...\ndef index_left_f64(list_input: list[float], value: float, left_count: int | None = None) -> int: ...\ndef _sabr_x0(\n    k: Number,\n    f: Number,\n    t: Number,\n    a: Number,\n    b: Number,\n    p: Number,\n    v: Number,\n    derivative: int = 0,\n) -> tuple[Number, Number | None]: ...\ndef _sabr_x1(\n    k: Number,\n    f: Number,\n    t: Number,\n    a: Number,\n    b: Number,\n    p: Number,\n    v: Number,\n    derivative: int = 0,\n) -> tuple[Number, Number | None]: ...\ndef _sabr_x2(\n    k: Number,\n    f: Number,\n    t: Number,\n    a: Number,\n    b: Number,\n    p: Number,\n    v: Number,\n    derivative: int = 0,\n) -> tuple[Number, Number | None]: ...\n"
  },
  {
    "path": "python/rateslib/scheduling/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom rateslib.rs import (\n    Adjuster,\n    Cal,\n    CalendarManager,\n    Frequency,\n    Imm,\n    NamedCal,\n    RollDay,\n    StubInference,\n    UnionCal,\n)\nfrom rateslib.scheduling.calendars import get_calendar\nfrom rateslib.scheduling.convention import Convention\nfrom rateslib.scheduling.dcfs import dcf\nfrom rateslib.scheduling.frequency import add_tenor\nfrom rateslib.scheduling.imm import get_imm, next_imm\nfrom rateslib.scheduling.schedule import Schedule\n\nImm.__doc__ = \"\"\"\nEnumerable type for International Money-Market (IMM) date definitions.\n\nFor further information on these descriptors see the Rust low level docs\nfor :rust:`Imm <scheduling>`.\n\"\"\"\n\nStubInference.__doc__ = \"\"\"\nEnumerable type for :class:`~rateslib.scheduling.Schedule` stub inference.\n\"\"\"\n\nAdjuster.__doc__ = \"\"\"\nEnumerable type for date adjustment rules.\n\n.. rubric:: Variants\n\n.. ipython:: python\n   :suppress:\n\n   from rateslib.rs import Adjuster\n   variants = [item for item in Adjuster.__dict__ if \\\\\n       \"__\" != item[:2] and \\\\\n       item not in ['adjust', 'adjusts', 'to_json', 'reverse'] \\\n   ]\n\n.. ipython:: python\n\n   variants\n\n\"\"\"\n\nRollDay.__doc__ = \"\"\"\nEnumerable type for roll days.\n\n.. rubric:: Variants\n\n.. ipython:: python\n   :suppress:\n\n   from rateslib.rs import RollDay\n   variants = [\"Day(int)\", \"IMM()\"]\n\n.. ipython:: python\n\n   variants\n\n\"\"\"\n\nFrequency.__doc__ = \"\"\"\nEnumerable type for a scheduling frequency.\n\n.. rubric:: Variants\n\n.. ipython:: python\n   :suppress:\n\n   from rateslib.rs import Frequency\n   variants = [\"BusDays(int, calendar)\", \"CalDays(int)\", \"Months(int, rollday | None)\", \"Zero()\"]\n\n.. ipython:: python\n\n   variants\n\n\"\"\"\n\nCal.__doc__ = \"\"\"\nA business day calendar defined by weekends and a holiday list.\n\nParameters\n----------\nholidays: list[datetime]\n    A list of specific non-business days.\nweek_mask: list[int]\n    A list of days defined as weekends, e.g. [5,6] for Saturday and Sunday.\n\"\"\"\n\nUnionCal.__doc__ = \"\"\"\nA calendar defined by a business day intersection of multiple :class:`~rateslib.scheduling.Cal`\nobjects.\n\nParameters\n----------\ncalendars: list[Cal]\n    A list of :class:`~rateslib.scheduling.Cal` objects whose combination will define the\n    business and non-business days.\nsettlement_calendars: list[Cal]\n    A list of :class:`~rateslib.scheduling.Cal` objects whose combination will define the\n    settleable and non-settleable days.\n\"\"\"\n\nNamedCal.__doc__ = \"\"\"\nA wrapped :class:`~rateslib.scheduling.Cal` or\n:class:`~rateslib.scheduling.UnionCal` constructed with a string parsing syntax.\n\nThis instance can only be constructed from named :class:`~rateslib.scheduling.Cal` objects that\nhave already been populated to the ``calendars`` :class:`~rateslib.scheduling.CalendarManager`.\nEach *NamedCal* uses data shared in memory and does **not** reconstruct or copy the entire\nlist of holidays for every instantiation of this class.\n\nParameters\n----------\nname: str\n    The names of the calendars to populate the ``calendars`` and ``settlement_calendars``\n    arguments of a :class:`~rateslib.scheduling.UnionCal`. The individual calendar names must\n    pre-exist in the :class:`~rateslib.scheduling.CalendarManager`. The pipe operator\n    separates the two fields.\n\nExamples\n--------\n.. ipython:: python\n   :suppress:\n\n   from rateslib.scheduling import NamedCal, UnionCal\n\n.. ipython:: python\n\n   named_cal = NamedCal(\"ldn,tgt|fed\")\n   assert isinstance(named_cal.inner, UnionCal)\n   assert len(named_cal.inner.calendars) == 2\n   assert len(named_cal.inner.settlement_calendars) == 1\n\"\"\"\n\n__all__ = (\n    \"Schedule\",\n    \"Cal\",\n    \"NamedCal\",\n    \"UnionCal\",\n    \"CalendarManager\",\n    \"Adjuster\",\n    \"Convention\",\n    \"Frequency\",\n    \"Imm\",\n    \"RollDay\",\n    \"StubInference\",\n    \"add_tenor\",\n    \"get_calendar\",\n    \"get_imm\",\n    \"next_imm\",\n    \"dcf\",\n)\n"
  },
  {
    "path": "python/rateslib/scheduling/adjuster.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.rs import Adjuster\n\nif TYPE_CHECKING:\n    from rateslib.local_types import str_\n\n_A = {  # Provides the map of all available string to Adjuster conversions.\n    \"NONESETTLE\": Adjuster.Actual(),\n    \"NONE\": Adjuster.Actual(),\n    \"F\": Adjuster.Following(),\n    \"P\": Adjuster.Previous(),\n    \"MF\": Adjuster.ModifiedFollowing(),\n    \"MP\": Adjuster.ModifiedPrevious(),\n    \"FSETTLE\": Adjuster.FollowingSettle(),\n    \"PSETTLE\": Adjuster.PreviousSettle(),\n    \"MFSETTLE\": Adjuster.ModifiedFollowingSettle(),\n    \"MPSETTLE\": Adjuster.ModifiedPreviousSettle(),\n    \"FEX\": Adjuster.FollowingExLast(),\n    \"FEXSETTLE\": Adjuster.FollowingExLastSettle(),\n}\n\n\ndef _get_adjuster_none(adjuster: Adjuster | int | str_) -> Adjuster | None:\n    if isinstance(adjuster, NoInput):\n        return None\n    else:\n        return _get_adjuster(adjuster)\n\n\ndef _get_adjuster(adjuster: int | str | Adjuster) -> Adjuster:\n    \"\"\"Convert a str such as 'F', 'MF' or '2B' or '5D' to an Adjuster.\"\"\"\n    if isinstance(adjuster, Adjuster):\n        return adjuster\n    elif isinstance(adjuster, int):\n        # convert to business days\n        adjuster = f\"{adjuster}B\"\n\n    adjuster = adjuster.upper()\n    if adjuster[-1] == \"B\":\n        return Adjuster.BusDaysLagSettle(int(adjuster[:-1]))\n    elif adjuster[-1] == \"D\":\n        return Adjuster.CalDaysLagSettle(int(adjuster[:-1]))\n    else:\n        return _A[adjuster]\n\n\ndef _convert_to_adjuster(modifier: str | Adjuster, settlement: bool, mod_days: bool) -> Adjuster:\n    \"\"\"Convert a legacy `modifier` to an Adjuster with additional options.\n    If `modify days` is disallowed then MF -> F\n    \"\"\"\n    if isinstance(modifier, Adjuster):\n        return modifier\n    modifier = modifier.upper()\n    if not mod_days and modifier[0] == \"M\":\n        modifier = modifier[1:]\n    if settlement:\n        modifier = modifier + \"SETTLE\"\n    return _get_adjuster(modifier)\n"
  },
  {
    "path": "python/rateslib/scheduling/calendars.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import calendars\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.scheduling.adjuster import _convert_to_adjuster\n\nif TYPE_CHECKING:\n    from rateslib.local_types import CalInput, CalTypes, datetime\n\n\ndef get_calendar(\n    calendar: CalInput,\n) -> CalTypes:\n    \"\"\"\n    Returns a calendar object, possible constructed by the\n    :class:`~rateslib.scheduling.CalendarManager`.\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    calendar : str, Cal, UnionCal, NamedCal, :red:`required`\n        If `str`, then the calendar is returned from pre-calculated values.\n        If a specific user defined calendar this is returned without modification.\n\n    Returns\n    -------\n    NamedCal, Cal, UnionCal\n\n    Notes\n    -----\n\n    Please see the :ref:`defaults <defaults-doc>` section of the documentation to discover\n    which named calendars are base implemented to *rateslib*.\n\n    Combined calendars can be created with comma separated input, e.g. *\"tgt,nyc\"*. This would\n    be the typical calendar assigned to a cross-currency derivative such as a EUR/USD\n    cross-currency swap.\n\n    For short-dated, FX instrument date calculations a concept known as an\n    **associated settlement calendars** is introduced. This uses a secondary calendar to determine\n    if a calculated date is a valid settlement day, but it is not used in the determination\n    of tenor dates. For a EURUSD FX instrument the appropriate calendar combination is *\"tgt|nyc\"*.\n    For a GBPEUR FX instrument the appropriate calendar combination is *\"ldn,tgt|nyc\"*.\n\n    Examples\n    --------\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import get_calendar, dt\n\n    .. ipython:: python\n\n       tgt_cal = get_calendar(\"tgt\")\n       print(tgt_cal.print(2023, 5))\n       tgt_cal.add_bus_days(dt(2023, 1, 3), 5, True)\n       type(tgt_cal)\n\n    Calendars can be combined from the pre-existing names using comma separation.\n\n    .. ipython:: python\n\n       tgt_and_nyc_cal = get_calendar(\"tgt,nyc\")\n       print(tgt_and_nyc_cal.print(2023, 5))\n       type(tgt_and_nyc_cal)\n\n    \"\"\"\n    if isinstance(calendar, str):\n        return calendars.get(calendar)\n    elif isinstance(calendar, NoInput):\n        return calendars.get(\"all\")\n    else:  # calendar is a Calendar object type\n        return calendar\n\n\ndef _get_years_and_months(d1: datetime, d2: datetime) -> tuple[int, int]:\n    \"\"\"\n    Get the whole number of years and months between two dates\n    \"\"\"\n    years: int = d2.year - d1.year\n    if (d2.month == d1.month and d2.day < d1.day) or (d2.month < d1.month):\n        years -= 1\n\n    months: int = (d2.month - d1.month) % 12\n    return years, months\n\n\ndef _adjust_date(\n    date: datetime,\n    modifier: str,\n    calendar: CalInput,\n    settlement: bool = True,\n) -> datetime:\n    \"\"\"\n    Modify a date under specific rule.\n\n    Parameters\n    ----------\n    date : datetime\n        The date to be adjusted.\n    modifier : str\n        The modification rule, in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}. If *'NONE'* returns date.\n    calendar : calendar, optional\n        The holiday calendar object to use. Required only if `modifier` is not *'NONE'*.\n        If not given a calendar is created where every day including weekends is valid.\n    settlement : bool\n        Whether to also enforce the associated settlement calendar.\n\n    Returns\n    -------\n    datetime\n    \"\"\"\n    cal_ = get_calendar(calendar)\n    return _convert_to_adjuster(modifier, settlement, True).adjust(date, cal_)\n\n\ndef _is_day_type_tenor(tenor: str) -> bool:\n    tenor_ = tenor.upper()\n    return \"D\" in tenor_ or \"B\" in tenor_ or \"W\" in tenor_\n"
  },
  {
    "path": "python/rateslib/scheduling/convention.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.rs import Convention\n\nif TYPE_CHECKING:\n    pass\n\n_CONVENTIONS_MAP: dict[str, Convention] = {\n    \"ACT365F\": Convention.Act365F,\n    \"ACT365\": Convention.Act365F,\n    \"ACT360\": Convention.Act360,\n    \"ACT365_25\": Convention.Act365_25,\n    \"ACT364\": Convention.Act364,\n    ###\n    \"30360\": Convention.Thirty360,\n    \"THIRTY360\": Convention.Thirty360,\n    \"360360\": Convention.Thirty360,\n    \"BONDBASIS\": Convention.Thirty360,\n    \"30E360\": Convention.ThirtyE360,\n    \"THIRTYE360\": Convention.ThirtyE360,\n    \"EUROBONDBASIS\": Convention.ThirtyE360,\n    \"30E360ISDA\": Convention.ThirtyE360ISDA,\n    \"THIRTYE360ISDA\": Convention.ThirtyE360ISDA,\n    \"30U360\": Convention.ThirtyU360,\n    \"THIRTYU360\": Convention.ThirtyU360,\n    ###\n    \"ACT365F+\": Convention.YearsAct365F,\n    \"YEARSACT365F\": Convention.YearsAct365F,\n    \"ACT360+\": Convention.YearsAct360,\n    \"YEARSACT360\": Convention.YearsAct360,\n    \"1+\": Convention.YearsMonths,\n    \"YEARSMONTHS\": Convention.YearsMonths,\n    ###\n    \"1\": Convention.One,\n    \"ONE\": Convention.One,\n    ###\n    \"ACTACTISDA\": Convention.ActActISDA,\n    \"ACTACTICMA\": Convention.ActActICMA,\n    \"ACTACTISMA\": Convention.ActActICMA,\n    \"ACTACTBOND\": Convention.ActActICMA,\n    ###\n    \"BUS252\": Convention.Bus252,\n    ###\n    \"ACTACTICMA_STUB365F\": Convention.ActActICMAStubAct365F,\n    \"ACTACTICMASTUBACT365F\": Convention.ActActICMAStubAct365F,\n}\n\n\ndef _get_convention(convention: Convention | str) -> Convention:\n    \"\"\"Convert a user str input into a Convention enum.\"\"\"\n    if isinstance(convention, Convention):\n        return convention\n    else:\n        try:\n            return _CONVENTIONS_MAP[convention.upper()]\n        except KeyError:\n            if convention.upper() == \"ACTACT\":\n                raise ValueError(\n                    \"`ActAct` must be directly specified as `ActActICMA` (most common for bonds) \"\n                    \"or `ActActISDA` (rarely used).\"\n                )\n            raise ValueError(f\"`convention`: {convention}, is not valid.\")\n\n\n__all__ = [\"Convention\"]\n"
  },
  {
    "path": "python/rateslib/scheduling/dcfs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom datetime import datetime\nfrom functools import partial\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.scheduling import Adjuster, Convention, Frequency, RollDay\nfrom rateslib.scheduling.adjuster import _get_adjuster\nfrom rateslib.scheduling.calendars import get_calendar\nfrom rateslib.scheduling.convention import _get_convention\nfrom rateslib.scheduling.frequency import _get_frequency_none\n\nif TYPE_CHECKING:\n    from rateslib.local_types import Any, CalInput, Callable, bool_, datetime_, int_, str_\n\n\ndef dcf(\n    start: datetime,\n    end: datetime,\n    convention: Convention | str,\n    termination: datetime_ = NoInput(0),  # required for 30E360ISDA and ActActICMA\n    frequency: Frequency | str_ = NoInput(0),  # req. ActActICMA = ActActISMA = ActActBond\n    stub: bool_ = NoInput(0),  # required for ActActICMA = ActActISMA = ActActBond\n    roll: RollDay | str | int_ = NoInput(0),  # required also for ActACtICMA = ...\n    calendar: CalInput = NoInput(0),  # required for ActACtICMA = ActActISMA = ActActBond\n    adjuster: Adjuster | str_ = NoInput(0),\n) -> float:\n    \"\"\"\n    Calculate the day count fraction of a period.\n\n    Parameters\n    ----------\n    start : datetime\n        The adjusted start date of the calculation period.\n    end : datetime\n        The adjusted end date of the calculation period.\n    convention : Convention, str\n        The day count convention of the calculation period accrual. See notes.\n    termination : datetime, optional\n        The adjusted termination date of the leg. Required only for some ``convention``.\n    frequency : Frequency, str, optional\n        The frequency of the period. Required only for some ``convention``.\n    stub : bool, optional\n        Indicates whether the period is a stub or not. Required only for some ``convention``.\n    roll : str, int, optional\n        Used only if ``frequency`` is given in string form. Required only for some ``convention``.\n    calendar: str, Calendar, optional\n        Used only of ``frequency`` is given in string form. Required only for some ``convention``.\n    adjuster: Adjuster, str, optional\n        The :class:`~rateslib.scheduling.Adjuster` used to convert unadjusted dates to\n        adjusted accrual dates on the period. Required only for some ``convention``.\n\n    Returns\n    --------\n    float\n\n    Notes\n    -----\n    See :class:`~rateslib.scheduling.Convention` for permissible values and for argument\n    related specifics.\n\n    Further information can be found in the\n    :download:`2006 ISDA definitions <https://www.rbccm.com/assets/rbccm/docs/legal/doddfrank/Documents/ISDALibrary/2006%20ISDA%20Definitions.pdf>` and\n    :download:`2006 ISDA 30360 example <_static/30360isda_2006_example.xls>`, and also in the lower\n    level Rust documentation at :rust:`rateslib-rs: Scheduling <scheduling>`.\n\n    Examples\n    --------\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import dcf\n\n    .. ipython:: python\n\n       dcf(dt(2000, 1, 1), dt(2000, 4, 3), \"Act360\")\n       dcf(dt(2000, 1, 1), dt(2000, 4, 3), \"Act365f\")\n       dcf(dt(2000, 1, 1), dt(2000, 4, 3), \"ActActICMA\", dt(2010, 1, 1), \"Q\", False)\n       dcf(dt(2000, 1, 1), dt(2000, 4, 3), \"ActActICMA\", dt(2010, 1, 1), \"Q\", True)\n\n    \"\"\"  # noqa: E501\n    convention_ = _get_convention(convention)\n    if isinstance(adjuster, NoInput):\n        adjuster = Adjuster.Actual()\n    frequency_: Frequency | None = _get_frequency_none(frequency, roll, calendar)\n\n    if isinstance(frequency_, Frequency.Zero) and convention_ in [\n        Convention.ActActICMA,\n        Convention.ActActICMAStubAct365F,\n    ]:\n        warnings.warn(\n            \"`frequency` cannot be 'Zero' variant in combination with 'ActActICMA' type\"\n            \"conventions. Internally this will be converted to 'Frequency.Months(12, ...)'\",\n            UserWarning,\n        )\n\n    # delegate simple calculations to Python only for performance gains, otherwise use Rust.\n    if convention_ in PERFORMANCE:\n        try:\n            return PERFORMANCE[convention_](start, end, frequency=frequency_, stub=stub)\n        except NotImplementedError:\n            pass\n\n    return convention_.dcf(\n        start=start,\n        end=end,\n        termination=_drb(None, termination),\n        frequency=frequency_,\n        stub=_drb(None, stub),\n        calendar=get_calendar(calendar),\n        adjuster=_get_adjuster(_drb(Adjuster.Actual(), adjuster)),\n    )\n\n\ndef _dcf_numeric(start: datetime, end: datetime, denominator: float, **kwargs: Any) -> float:\n    \"\"\"Calculate the day count fraction of a period using the fixed denominator rule.\"\"\"\n    return (end - start).days / denominator\n\n\ndef _dcf_actacticma_nonstub(\n    start: datetime, end: datetime, frequency: Frequency, stub: bool, **kwargs: Any\n) -> float:\n    \"\"\"Calculate just the regular frequency part of the dcf for ActActICMA.\"\"\"\n    if not stub:\n        return 1.0 / frequency.periods_per_annum()\n    else:\n        raise NotImplementedError(\"`stub` must be `False` for `ActActICMA` performance short cut.\")\n\n\nPERFORMANCE: dict[Convention, Callable[..., float]] = {\n    Convention.Act365F: partial(_dcf_numeric, denominator=365.0),\n    Convention.Act360: partial(_dcf_numeric, denominator=360.0),\n    Convention.ActActICMA: _dcf_actacticma_nonstub,\n    Convention.ActActICMAStubAct365F: _dcf_actacticma_nonstub,\n}\n\n\n# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International\n# Commercial use of this code, and/or copying and redistribution is prohibited.\n# Contact rateslib at gmail.com if this code is observed outside its intended sphere.\n"
  },
  {
    "path": "python/rateslib/scheduling/frequency.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nimport rateslib.errors as err\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.rs import Adjuster, Frequency, Imm, RollDay\nfrom rateslib.scheduling.adjuster import _convert_to_adjuster\nfrom rateslib.scheduling.calendars import get_calendar\nfrom rateslib.scheduling.rollday import _get_rollday\nfrom rateslib.utils.calendars import _get_first_bus_day\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\ndef _get_frequency(\n    frequency: str_ | Frequency, roll: str | RollDay | int_, calendar: CalInput\n) -> Frequency:\n    \"\"\"\n    Get a :class:`~rateslib.scheduling.Frequency` object from legacy UI inputs.\n\n    Parameters\n    ----------\n    frequency: str or Frequency\n        If string, is combined with the ``roll`` and ``calendar`` parameters to derive the\n        output.\n    roll: str, int or RollDay, optional\n        The roll-day to be associated with a *Frequency.Months* variant, if given.\n    calendar: calendar, str, optional\n        The calendar to be associated with a *Frequency.BusDay* variant, if given.\n\n    Returns\n    -------\n    Frequency\n    \"\"\"\n    if isinstance(frequency, Frequency):\n        if getattr(frequency, \"roll\", \"no default\") is None:\n            return Frequency.Months(frequency.number, _get_rollday(roll))  # type: ignore[attr-defined]\n        return frequency\n\n    if isinstance(frequency, NoInput):\n        raise ValueError(err.VE_NEEDS_FREQUENCY)\n\n    frequency_: str = frequency.upper()[-1]\n    if frequency_ == \"D\":\n        n_: int = int(frequency[:-1])\n        return Frequency.CalDays(n_)\n    elif frequency_ == \"B\":\n        n_ = int(frequency[:-1])\n        return Frequency.BusDays(n_, get_calendar(calendar))\n    elif frequency_ == \"W\":\n        n_ = int(frequency[:-1])\n        return Frequency.CalDays(n_ * 7)\n    elif frequency_ == \"M\":\n        # handles the dual case of 'xM' for x-months or 'M' or monthly, i.e. 1-month\n        if len(frequency) == 1:\n            return Frequency.Months(1, _get_rollday(roll))\n        else:\n            n_ = int(frequency[:-1])\n            return Frequency.Months(n_, _get_rollday(roll))\n    elif frequency_ == \"Q\":\n        return Frequency.Months(3, _get_rollday(roll))\n    elif frequency_ == \"S\":\n        return Frequency.Months(6, _get_rollday(roll))\n    elif frequency_ == \"A\":\n        return Frequency.Months(12, _get_rollday(roll))\n    elif frequency_ == \"Y\":\n        n_ = int(frequency[:-1])\n        return Frequency.Months(12 * n_, _get_rollday(roll))\n    elif frequency_ == \"Z\":\n        return Frequency.Zero()\n    else:\n        raise ValueError(f\"Frequency can not be determined from `frequency` input: '{frequency}'.\")\n\n\ndef _get_frequency_none(\n    frequency: str | Frequency | NoInput, roll: str | RollDay | int_, calendar: CalInput\n) -> Frequency | None:\n    if isinstance(frequency, NoInput):\n        return None\n    else:\n        return _get_frequency(frequency, roll, calendar)\n\n\ndef _get_tenor_from_frequency(frequency: Frequency) -> str:\n    if isinstance(frequency, Frequency.Months):\n        return f\"{frequency.number}M\"\n    elif isinstance(frequency, Frequency.CalDays):\n        if frequency.number % 7 == 0:\n            return f\"{int(frequency.number / 7)}W\"\n        else:\n            return f\"{frequency.number}D\"\n    elif isinstance(frequency, Frequency.BusDays):\n        return f\"{frequency.number}B\"\n    elif isinstance(frequency, Frequency.Zero):\n        raise ValueError(\"Cannot determine regular tenor from Frequency.Zero\")\n    raise ValueError(\"Cannot determine regular tenor from Frequency\")\n\n\ndef add_tenor(\n    start: datetime,\n    tenor: str | Frequency,\n    modifier: str | Adjuster,\n    calendar: CalInput = NoInput(0),\n    roll: str | int_ | RollDay = NoInput(0),\n    settlement: bool = False,\n    mod_days: bool = False,\n) -> datetime:\n    r\"\"\"\n    Add a tenor to a given date under specific modification rules and holiday calendar.\n\n    .. warning::\n\n       Note this function does not validate the ``roll`` input, but expects it to be correct.\n       That is this function acts on ``start`` as an *unchecked* date. See notes.\n\n    Parameters\n    ----------\n    start : datetime\n        The date to which to add the tenor.\n    tenor : str | Frequency\n        The tenor to add, identified by calendar days, `\"D\"`, months, `\"M\"`,\n        years, `\"Y\"` or business days, `\"B\"`, for example `\"10Y\"` or `\"5B\"`.\n    modifier : str, optional in {\"NONE\", \"MF\", \"F\", \"MP\", \"P\"} | Adjuster\n        The modification rule to apply if the tenor is calendar days, months or years.\n    calendar : CustomBusinessDay or str, optional\n        The calendar for use with business day adjustment and modification.\n    roll : str, int, RollDay, optional\n        This is only required if the tenor is given in months or years. Ensures the tenor period\n        associates with a schedule's roll day.\n    settlement : bool, optional\n        If ``modifier`` is string this determines whether to enforce the settlement\n        with an associated settlement calendar, if provided.\n    mod_days : bool, optional\n        If ``modifier`` is string and ``tenor`` is a day variant setting this to *False*\n        will convert \"MF\" to \"F\" and \"MP\" to \"P\".\n\n    Returns\n    -------\n    datetime\n\n    Notes\n    ------\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import add_tenor, get_calendar\n\n       from datetime import datetime as dt\n       import pandas as pd\n       from pandas import date_range, Series, DataFrame\n       pd.set_option(\"display.float_format\", lambda x: '%.2f' % x)\n       pd.set_option(\"display.max_columns\", None)\n       pd.set_option(\"display.width\", 500)\n\n    This method is a convenience function for coordinating a :class:`~rateslib.scheduling.Frequency`\n    date manipulation and an :class:`~rateslib.scheduling.Adjuster`, from simple UI inputs.\n    For example the following:\n\n    .. ipython:: python\n\n       add_tenor(dt(2023, 9, 29), \"-6m\", \"MF\", NamedCal(\"bus\"), 31)\n\n    is internally mapped to the following, where :meth:`~rateslib.scheduling.Frequency.next`\n    performs an *unchecked* date period determination:\n\n    .. ipython:: python\n\n       unadjusted_date = Frequency.Months(-6, RollDay.Day(31)).next(dt(2023, 9, 29))\n       Adjuster.ModifiedFollowing().adjust(unadjusted_date, NamedCal(\"bus\"))\n\n    The allowed string inputs *{'B', 'D', 'W', 'M', 'Y'}* for **b**\\ usiness days, calendar\n    **d**\\ ays, **w**\\ eeks, **m**\\ onths and **y**\\ ears are mapped to an appropriate\n    :class:`~rateslib.scheduling.Frequency` variant (potentially also mapping a\n    :class:`~rateslib.scheduling.RollDay`), and an appropriate\n    :class:`~rateslib.scheduling.Adjuster` is derived from the combination of the ``modifier``,\n    ``settlement`` and ``mod_days`` arguments.\n\n    Read more about the ``settlement`` argument in the :ref:`calendar user guide <cal-doc>`.\n\n    The ``mod_days`` argument is provided to avoid having to reconfigure *Instrument*\n    specifications when a *termination* may differ between months or years, and days or weeks.\n    Most *Instruments* will be defined by the typical modified following (*\"MF\"*) ``modifier``,\n    but this would prefer not to always apply.\n\n    .. ipython:: python\n\n       add_tenor(dt(2021, 1, 29), \"1M\", \"MF\", \"bus\")\n\n    while, the following will by default roll into a new month,\n\n    .. ipython:: python\n\n       add_tenor(dt(2021, 1, 22), \"8d\", \"MF\", \"bus\")\n\n    unless day type frequencies are also specifically modified,\n\n    .. ipython:: python\n\n       add_tenor(dt(2021, 1, 22), \"8d\", \"MF\", \"bus\", mod_days=True)\n\n    Examples\n    --------\n\n    .. ipython:: python\n\n       add_tenor(dt(2022, 2, 28), \"3M\", \"NONE\")\n       add_tenor(dt(2022, 12, 28), \"4b\", \"F\", get_calendar(\"ldn\"))\n       add_tenor(dt(2022, 12, 28), \"4d\", \"F\", get_calendar(\"ldn\"))\n    \"\"\"  # noqa: E501\n    cal_ = get_calendar(calendar)\n    if isinstance(tenor, Frequency):\n        frequency: Frequency = tenor\n    else:\n        tenor = tenor.upper()\n        if \"D\" in tenor:\n            frequency = Frequency.CalDays(int(tenor[:-1]))\n        elif \"W\" in tenor:\n            frequency = Frequency.CalDays(int(tenor[:-1]) * 7)\n        elif \"B\" in tenor:\n            frequency = Frequency.BusDays(int(tenor[:-1]), cal_)\n        elif \"Y\" in tenor:\n            roll_ = _get_rollday(roll)\n            roll__ = RollDay.Day(start.day) if roll_ is None else roll_\n            frequency = Frequency.Months(int(float(tenor[:-1]) * 12), roll__)\n        elif \"M\" in tenor:\n            roll_ = _get_rollday(roll)\n            roll__ = RollDay.Day(start.day) if roll_ is None else roll_\n            frequency = Frequency.Months(int(float(tenor[:-1])), roll__)\n        else:\n            raise ValueError(\n                \"`tenor` must identify frequency in {'B', 'D', 'W', 'M', 'Y'} e.g. '1Y'\"\n            )\n\n    if isinstance(frequency, Frequency.Months | Frequency.Zero):\n        mod_days = True\n\n    next_date = frequency.next(start)\n    adjuster = _convert_to_adjuster(modifier, settlement, mod_days)\n    return adjuster.adjust(next_date, cal_)\n\n\ndef _get_fx_expiry_and_delivery_and_payment(\n    eval_date: datetime_,\n    expiry: str | datetime,\n    delivery_lag: Adjuster | int | datetime,\n    calendar: CalInput,\n    modifier: str,\n    eom: bool,\n    payment_lag: int | datetime,\n) -> tuple[datetime, datetime, datetime]:\n    \"\"\"\n    Determines the expiry and delivery date of an FX option using the following rules:\n\n    See Foreign Exchange Option Pricing by Iain Clark\n\n    Parameters\n    ----------\n    eval_date: datetime\n        The evaluation date, which is today (if required)\n    expiry: str, datetime\n        The expiry date\n    delivery_lag: Adjuster, int, datetime\n        Number of days, e.g. spot = 2, or a specified datetime for FX settlement after expiry.\n    calendar: CalInput\n        The calendar used for date rolling. This function makes use of the `settlement` option\n        within calendars.\n    modifier: str\n        Date rule, expected to be \"MF\" for most FX rate tenors.\n    eom: bool\n        Whether end-of-month is preserved in tenor date determination.\n    payment_lag: Adjuster, int, datetime\n        Number of business days to lag payment by after expiry.\n\n    Returns\n    -------\n    tuple of datetime\n    \"\"\"\n    calendar_ = get_calendar(calendar)\n    del calendar\n\n    if isinstance(delivery_lag, int):\n        delivery_lag_: datetime | Adjuster = Adjuster.BusDaysLagSettle(delivery_lag)\n    else:\n        delivery_lag_ = delivery_lag\n    del delivery_lag\n\n    if isinstance(payment_lag, int):\n        payment_lag_: datetime | Adjuster = Adjuster.BusDaysLagSettle(payment_lag)\n    else:\n        payment_lag_ = payment_lag\n    del payment_lag\n\n    if isinstance(expiry, str):\n        # then use the objects to derive the expiry\n\n        if isinstance(eval_date, NoInput):\n            raise ValueError(\"`expiry` as string tenor requires `eval_date`.\")\n        # then the expiry will be implied\n        e = expiry.upper()\n        if \"M\" in e or \"Y\" in e:\n            # method\n            if isinstance(delivery_lag_, datetime):\n                raise ValueError(\n                    \"Cannot determine FXOption expiry and delivery with given parameters.\\n\"\n                    \"Supply a `delivery_lag` as integer business days and not a datetime, when \"\n                    \"using a string tenor `expiry`.\",\n                )\n            else:\n                spot = delivery_lag_.adjust(eval_date, calendar_)\n                roll = \"eom\" if (eom and Imm.Eom.validate(spot)) else spot.day\n                delivery_: datetime = add_tenor(spot, expiry, modifier, calendar_, roll, True)\n                expiry_ = _get_first_bus_day(delivery_lag_.reverse(delivery_, calendar_), calendar_)\n            # else:\n            #     spot = calendar_.lag_bus_days(eval_date, delivery_lag, True)\n            #     roll = \"eom\" if (eom and Imm.Eom.validate(spot)) else spot.day\n            #     delivery_: datetime = add_tenor(spot, expiry, modifier, calendar_, roll, True)\n            #     expiry_ = calendar_.add_bus_days(delivery_, -delivery_lag, False)\n        else:\n            expiry_ = add_tenor(eval_date, expiry, \"F\", calendar_, NoInput(0), False)\n    else:\n        expiry_ = expiry\n\n    if isinstance(delivery_lag_, datetime):\n        delivery_ = delivery_lag_\n    else:\n        delivery_ = delivery_lag_.adjust(expiry_, calendar_)\n        # delivery_ = calendar_.lag_bus_days(expiry_, delivery_lag, True)\n\n    if isinstance(payment_lag_, datetime):\n        payment_ = payment_lag_\n    else:\n        payment_ = payment_lag_.adjust(expiry_, calendar_)\n        # payment_ = calendar_.lag_bus_days(expiry_, payment_lag, True)\n\n    return expiry_, delivery_, payment_\n"
  },
  {
    "path": "python/rateslib/scheduling/imm.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.rs import Imm\n\nif TYPE_CHECKING:\n    from rateslib.local_types import int_, str_\n\n\n_Imm: dict[str, Imm] = {\n    \"imm\": Imm.Wed3_HMUZ,\n    \"serial_imm\": Imm.Wed3,\n    \"credit_imm\": Imm.Day20_HMUZ,\n    \"credit_imm_hu\": Imm.Day20_HU,\n    \"credit_imm_mz\": Imm.Day20_MZ,\n    \"wed3_hmuz\": Imm.Wed3_HMUZ,\n    \"wed3\": Imm.Wed3,\n    \"day20_hmuz\": Imm.Day20_HMUZ,\n    \"day20\": Imm.Day20,\n    \"day20_mz\": Imm.Day20_MZ,\n    \"day20_hu\": Imm.Day20_HU,\n    \"fri2_hmuz\": Imm.Fri2_HMUZ,\n    \"fri2\": Imm.Fri2,\n    \"wed1_post9\": Imm.Wed1_Post9,\n    \"wed1_post9_hmuz\": Imm.Wed1_Post9_HMUZ,\n    \"eom\": Imm.Eom,\n    \"leap\": Imm.Leap,\n    \"som\": Imm.Som,\n}\n\n\ndef next_imm(start: datetime, definition: str | Imm = Imm.Wed3_HMUZ) -> datetime:\n    \"\"\"Return the next IMM date *after* the given start date.\n\n    Parameters\n    ----------\n    start : datetime\n        The date from which to determine the next IMM.\n    definition : Imm, str\n        The IMM definition to return the date for. This is entered as either an\n        :class:`~rateslib.scheduling.Imm` enum, or that enum variant name as sting, e.g. *\"Wed3\"*.\n\n    Returns\n    -------\n    datetime\n\n    Examples\n    --------\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import next_imm, Imm, dt\n\n    Get the next quarterly SOFR or ESTR futures date, defined by CME, EUREX, or ICE:\n\n    .. ipython:: python\n\n       next_imm(dt(2000, 1, 1), Imm.Wed3_HMUZ)\n\n    Get the next serial futures contract for a NZD bank bill defined by ASX:\n\n    .. ipython:: python\n\n       next_imm(dt(2000, 1, 1), \"Wed1_Post9\")\n\n    \"\"\"\n    if isinstance(definition, str):\n        d_ = definition.lower()\n        if d_ in [\"imm\", \"serial_imm\", \"credit_imm\", \"credit_imm_hu\", \"credit_imm_mz\"]:\n            warnings.warn(\n                f\"The given string entry '{d_}' is deprecated and will be removed in \"\n                f\"future releases. Please change the equivalent version in {{'Wed3', 'Wed3_HMUZ', \"\n                f\"'Day20', 'Day20_HMUZ', 'Day20_HU', 'Day20_MZ'}}\",\n                DeprecationWarning,\n            )\n        imm_: Imm = _Imm[d_]\n    else:\n        imm_ = definition\n    return imm_.next(start)\n\n\nMONTHS = {\n    \"F\": 1,\n    \"G\": 2,\n    \"H\": 3,\n    \"J\": 4,\n    \"K\": 5,\n    \"M\": 6,\n    \"N\": 7,\n    \"Q\": 8,\n    \"U\": 9,\n    \"V\": 10,\n    \"X\": 11,\n    \"Z\": 12,\n}\n\n\ndef get_imm(\n    month: int_ = NoInput(0),\n    year: int_ = NoInput(0),\n    code: str_ = NoInput(0),\n    definition: str | Imm = Imm.Wed3,\n) -> datetime:\n    \"\"\"\n    Return an IMM date for a specified month.\n\n    Parameters\n    ----------\n    month: int\n        The month of the year in which the IMM date falls.\n    year: int\n        The year in which the IMM date falls.\n    code: str\n        Identifier in the form of a one digit month code and 21st century year, e.g. \"U29\".\n        If code is given ``month`` and ``year`` are unused.\n    definition: Imm, str\n        The IMM definition to return the date for. This is entered as either an\n        :class:`~rateslib.scheduling.Imm` enum, or that enum variant name as sting, e.g. *\"Wed3\"*.\n\n    Returns\n    -------\n    datetime\n\n    Examples\n    --------\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import get_imm, Imm, dt\n\n    Get the quarterly SOFR or ESTR futures date, defined by CME, EUREX, or ICE:\n\n    .. ipython:: python\n\n       get_imm(3, 2022, definition=Imm.Wed3_HMUZ)\n       get_imm(code=\"H22\", definition=\"Wed3\")\n\n    Get a serial futures contract for a NZD bank bill defined by ASX:\n\n    .. ipython:: python\n\n       get_imm(1, 2023, definition=\"Wed1_Post9\")\n    \"\"\"\n    if isinstance(code, str):\n        year = int(code[1:]) + 2000\n        month = MONTHS[code[0].upper()]\n    elif isinstance(month, NoInput) or isinstance(year, NoInput):\n        raise ValueError(\"`month` and `year` must each be valid integers if `code`not given.\")\n\n    if isinstance(definition, str):\n        d_ = definition.lower()\n        if d_ in [\"imm\", \"serial_imm\", \"credit_imm\", \"credit_imm_hu\", \"credit_imm_mz\"]:\n            warnings.warn(\n                f\"The given string entry '{d_}' is deprecated and will be removed in \"\n                f\"future releases. Please change the equivalent version in {{'Wed3', 'Wed3_HMUZ', \"\n                f\"'Day20', 'Day20_HMUZ', 'Day20_HU', 'Day20_MZ'}}\",\n                DeprecationWarning,\n            )\n        imm_: Imm = _Imm[d_]\n    else:\n        imm_ = definition\n    return imm_.get(year, month)\n"
  },
  {
    "path": "python/rateslib/scheduling/rollday.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.rs import Adjuster, Imm, RollDay\n\nif TYPE_CHECKING:\n    from rateslib.local_types import CalTypes, int_\n\n\ndef _get_rollday(roll: RollDay | str | int_) -> RollDay | None:\n    \"\"\"Convert a user str or int into a RollDay enum object.\"\"\"\n    if isinstance(roll, RollDay):\n        return roll\n    elif isinstance(roll, str):\n        return {\n            \"EOM\": RollDay.Day(31),\n            \"SOM\": RollDay.Day(1),\n            \"IMM\": RollDay.IMM(),\n        }[roll.upper()]\n    elif isinstance(roll, int):\n        return RollDay.Day(roll)\n    return None\n\n\ndef _is_eom_cal(date: datetime, cal: CalTypes) -> bool:\n    \"\"\"Test whether a given date is end of month under a specific calendar\"\"\"\n    eom_unadjusted = Imm.Eom.get(date.year, date.month)\n    eom = Adjuster.Previous().adjust(eom_unadjusted, cal)\n    return date == eom\n    # end_day = calendar_mod.monthrange(date.year, date.month)[1]\n    # eom = datetime(date.year, date.month, end_day)\n    # adj_eom = _adjust_date(eom, \"P\", cal)\n    # return date == adj_eom\n"
  },
  {
    "path": "python/rateslib/scheduling/schedule.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nfrom pandas import DataFrame\n\nfrom rateslib import defaults\nfrom rateslib.default import _make_py_json\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.rs import Adjuster, Frequency, RollDay, StubInference\nfrom rateslib.rs import Schedule as Schedule_rs\nfrom rateslib.scheduling.adjuster import _convert_to_adjuster, _get_adjuster, _get_adjuster_none\nfrom rateslib.scheduling.calendars import _is_day_type_tenor, get_calendar\nfrom rateslib.scheduling.frequency import _get_frequency, add_tenor\nfrom rateslib.scheduling.rollday import _is_eom_cal\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (\n        Adjuster_,\n        Any,\n        CalInput,\n        CalTypes,\n        bool_,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\ndef _get_stub_inference(\n    stub: str | StubInference, front_stub: datetime_, back_stub: datetime_\n) -> StubInference:\n    \"\"\"\n    Perform two tasks:\n    - Convert `stub` as string to a `StubInference` enum.\n    - Convert a StubInference to NeitherSide if a specific stud date has been provided that\n      cannot be inferred.\n\n    Parameters\n    ----------\n    stub: str\n        The intention of the schedule for inferred stubs\n    front_stub: datetime, optional\n        If given StubInference will never contain any front elements.\n    back_stub: datetime, optional\n        If given StubInference will never contain any back elements.\n\n    Returns\n    -------\n    StubInference\n    \"\"\"\n    if isinstance(stub, StubInference):\n        if stub is StubInference.NeitherSide:\n            stub_: str = \"NEITHER_SIDE\"\n        elif stub is StubInference.ShortFront:\n            stub_ = \"SHORT_FRONT\"\n        elif stub is StubInference.LongFront:\n            stub_ = \"LONG_FRONT\"\n        elif stub is StubInference.ShortBack:\n            stub_ = \"SHORT_BACK\"\n        else:  #  StubInference.LongBack:\n            stub_ = \"LONG_BACK\"\n    elif stub is None:\n        stub_ = \"NONE\"\n    else:\n        stub_ = stub.upper()\n    del stub\n\n    _map: dict[str, StubInference] = {\n        \"SHORTFRONT\": StubInference.ShortFront,\n        \"LONGFRONT\": StubInference.LongFront,\n        \"SHORTBACK\": StubInference.ShortBack,\n        \"LONGBACK\": StubInference.LongBack,\n        \"NONE\": StubInference.NeitherSide,\n        \"NEITHERSIDE\": StubInference.NeitherSide,\n        \"SHORT_FRONT\": StubInference.ShortFront,\n        \"LONG_FRONT\": StubInference.LongFront,\n        \"SHORT_BACK\": StubInference.ShortBack,\n        \"LONG_BACK\": StubInference.LongBack,\n        \"NEITHER_SIDE\": StubInference.NeitherSide,\n    }\n\n    possibles: dict[str, StubInference] = {v: _map[v] for v in _map if v in stub_}\n    if not isinstance(front_stub, NoInput):\n        # cannot infer front stubs, since it is explicitly provided\n        possibles.pop(\"SHORTFRONT\", None)\n        possibles.pop(\"SHORT_FRONT\", None)\n        possibles.pop(\"LONGFRONT\", None)\n        possibles.pop(\"LONG_FRONT\", None)\n    if not isinstance(back_stub, NoInput):\n        # cannot infer back stubs, since it is explicitly provided\n        possibles.pop(\"SHORTBACK\", None)\n        possibles.pop(\"SHORT_BACK\", None)\n        possibles.pop(\"LONGBACK\", None)\n        possibles.pop(\"LONG_BACK\", None)\n\n    if len(possibles) == 0:\n        return StubInference.NeitherSide  # the stub inference is negated by a provided value\n    elif len(possibles) > 1:\n        raise ValueError(\n            \"Must supply at least one stub date for dual sided inference.\\n\"\n            f\"You have likely supplied to many sides to be inferred for `stub`. Got '{stub_}'.\"\n        )\n    else:\n        return list(possibles.values())[0]\n\n\ndef _get_adjuster_from_modifier(modifier: Adjuster | str_, mod_days: bool) -> Adjuster:\n    if isinstance(modifier, Adjuster):\n        return modifier  # use the adjuster as provided\n    modifier_: str = _drb(defaults.modifier, modifier).upper()\n    return _convert_to_adjuster(modifier_, settlement=False, mod_days=mod_days)\n\n\ndef _should_mod_days(tenor: datetime | str) -> bool:\n    \"\"\"Return whether a specified tenor should be subject to a `modifier`'s modification rule.\"\"\"\n    if isinstance(tenor, str):\n        return not _is_day_type_tenor(tenor)\n    else:\n        # cannot infer any data to issue an overwrite\n        return True\n\n\ndef _get_adjuster_from_lag_drb(lag: Adjuster | int_, default: str) -> Adjuster:\n    if isinstance(lag, Adjuster):\n        return lag\n    else:\n        lag_: int = _drb(getattr(defaults, default), lag)\n        return _get_adjuster(f\"{lag_}B\")\n\n\nclass Schedule:\n    \"\"\"\n    Generate a schedule of dates according to a regular pattern and calendar inference.\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import Schedule, RollDay, Frequency, StubInference, Adjuster, NamedCal, dt\n\n    .. tabs::\n\n       .. tab:: Original Inputs\n\n          The **original inputs** allow for a more UI friendly input for the most common schedules.\n\n          .. ipython:: python\n\n             s = Schedule(\n                 effective=dt(2024, 1, 3),\n                 termination=dt(2024, 11, 29),\n                 frequency=\"Q\",\n                 stub=\"ShortFront\",\n                 modifier=\"MF\",\n                 payment_lag=2,\n                 calendar=\"tgt\",\n                 eom=True,\n             )\n             print(s)\n\n       .. tab:: Core Inputs\n\n          The **core inputs** utilise the Rust objects directly and may provide more flexibility.\n\n          .. ipython:: python\n\n             s = Schedule(\n                 effective=dt(2024, 1, 3),\n                 termination=dt(2024, 11, 29),\n                 frequency=Frequency.Months(3, None),\n                 stub=StubInference.ShortFront,\n                 modifier=Adjuster.ModifiedFollowing(),\n                 payment_lag=Adjuster.BusDaysLagSettle(2),\n                 calendar=NamedCal(\"tgt\"),\n                 eom=True,\n             )\n             print(s)\n\n    .. role:: red\n\n    .. role:: green\n\n    Parameters\n    ----------\n    effective : datetime, str, :red:`required`\n        The unadjusted effective date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``eval_date`` and ``eval_mode``.\n    termination : datetime, str, :red:`required`\n        The unadjusted termination date. If given as adjusted, unadjusted alternatives may be\n        inferred. If given as string tenor will be calculated from ``effective``.\n    frequency : Frequency, str in {\"M\", \"Q\", \"S\", \"A\", \"Z\", \"_D\", \"_B\", \"_W\", \"_M\", \"_Y\"}, :red:`required`\n        The frequency of the schedule.\n        If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with:\n        monthly (\"M\"), quarterly (\"Q\"), semi-annually (\"S\"), annually(\"A\") or zero-coupon (\"Z\"), or\n        a set number of calendar or business days (\"_D\", \"_B\"), weeks (\"_W\"), months (\"_M\") or\n        years (\"_Y\").\n        Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll``\n        and business day calendar as per ``calendar``.\n    stub : StubInference, str in {\"ShortFront\", \"LongFront\", \"ShortBack\", \"LongBack\"}, :green:`optional (set by defaults)`\n        The stub type used if stub inference is required. If given as string will derive a\n        :class:`~rateslib.scheduling.StubInference`.\n    front_stub : datetime, :green:`optional`\n        The unadjusted date for the start stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n    back_stub : datetime, :green:`optional`\n        The unadjusted date for the back stub period. If given as adjusted, unadjusted\n        alternatives may be inferred.\n        See notes for combining ``stub``, ``front_stub`` and ``back_stub``\n        and any automatic stub inference.\n    roll : RollDay, int in [1, 31], str in {\"eom\", \"imm\", \"som\"}, :green:`optional`\n        The roll day of the schedule. If not given or not available in ``frequency`` will be\n        inferred for monthly frequency variants.\n    eom : bool, :green:`optional (set by defaults)`\n        Use an end of month preference rather than regular rolls for ``roll`` inference. Set by\n        default. Not required if ``roll`` is defined.\n    modifier : Adjuster, str in {\"NONE\", \"F\", \"MF\", \"P\", \"MP\"}, :green:`optional (set by defaults)`\n        The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates\n        into adjusted dates. If given as string must define simple date rolling rules.\n    calendar : calendar, str, :green:`optional (set as 'all')`\n        The business day calendar object to use. If string will call\n        :meth:`~rateslib.scheduling.get_calendar`.\n    payment_lag: Adjuster, int, :green:`optional (set by defaults)`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        a payment date. If given as integer will define the number of business days to\n        lag payments by.\n    payment_lag_exchange: Adjuster, int, :green:`optional (set by defaults)`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional payment date. If given as integer will define the number of business days to\n        lag payments by.\n    extra_lag: Adjuster, int, :green:`optional`\n        The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into\n        additional dates, which may be used, for example by fixings schedules. If given as integer\n        will define the number of business days to lag dates by.\n    eval_date: datetime, :green:`optional`\n        Only required if ``effective`` is given as a string tenor, to provide a point of reference.\n    eval_mode: str in {\"swaps_align\", \"swaptions_align\"}, :green:`optional (set by defaults)`\n        The method for determining the ``effective`` and ``termination`` dates if both are provided\n        as string tenors. See notes.\n\n    Notes\n    -----\n    Detailed information is provided within :ref:`the scheduling user guide <schedule-doc>`.\n\n    \"\"\"  # noqa: E501\n\n    _obj: Schedule_rs\n\n    @property\n    def obj(self) -> Schedule_rs:\n        \"\"\"A wrapped instance of the Rust implemented :rust:`Schedule <scheduling>`.\"\"\"\n        return self._obj\n\n    def __init__(\n        self,\n        effective: datetime | str,\n        termination: datetime | str,\n        frequency: str | Frequency,\n        *,\n        stub: StubInference | str_ = NoInput(0),\n        front_stub: datetime_ = NoInput(0),\n        back_stub: datetime_ = NoInput(0),\n        roll: str | RollDay | int_ = NoInput(0),\n        eom: bool_ = NoInput(0),\n        modifier: Adjuster | str_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        payment_lag: Adjuster | int_ = NoInput(0),\n        payment_lag_exchange: Adjuster | int_ = NoInput(0),\n        extra_lag: Adjuster | int | str_ = NoInput(0),\n        eval_date: datetime_ = NoInput(0),\n        eval_mode: str_ = NoInput(0),\n    ) -> None:\n        eom_: bool = _drb(defaults.eom, eom)\n        stub_: str | StubInference = _drb(defaults.stub, stub)\n        eval_mode_: str = _drb(defaults.eval_mode, eval_mode).lower()\n        calendar_: CalTypes = get_calendar(calendar)\n        frequency_: Frequency = _get_frequency(frequency, roll, calendar_)\n        accrual_adjuster = _get_adjuster_from_modifier(modifier, _should_mod_days(termination))\n        payment_adjuster = _get_adjuster_from_lag_drb(payment_lag, \"payment_lag\")\n        payment_adjuster2 = _get_adjuster_from_lag_drb(payment_lag_exchange, \"payment_lag_exchange\")\n        payment_adjuster3 = _get_adjuster_none(extra_lag)\n\n        effective_: datetime = _validate_effective(\n            effective,\n            eval_mode_,\n            eval_date,\n            accrual_adjuster,\n            calendar_,\n            roll,\n        )\n        termination_: datetime = _validate_termination(\n            termination,\n            effective_,\n            accrual_adjuster,\n            calendar_,\n            roll,\n            eom_,\n        )\n        stub_inference_ = _get_stub_inference(stub_, front_stub, back_stub)\n\n        try:\n            self._obj = Schedule_rs(\n                effective=effective_,\n                termination=termination_,\n                frequency=frequency_,\n                calendar=calendar_,\n                accrual_adjuster=accrual_adjuster,\n                payment_adjuster=payment_adjuster,\n                payment_adjuster2=payment_adjuster2,\n                payment_adjuster3=payment_adjuster3,\n                front_stub=_drb(None, front_stub),\n                back_stub=_drb(None, back_stub),\n                eom=eom_,\n                stub_inference=stub_inference_,\n            )\n        except ValueError:\n            raise ValueError(\n                \"A Schedule could not be generated from the parameter combinations:\\n\"\n                f\"effective: {effective}\\n\"\n                f\"front stub: {front_stub}\\n\"\n                f\"back stub: {back_stub}\\n\"\n                f\"termination: {termination}\\n\"\n                f\"frequency: {frequency_}\\n\"\n                f\"stub inference: {stub_inference_}\\n\"\n                f\"accrual adjuster: {accrual_adjuster}\\n\"\n                f\"calendar: {calendar_}\\n\"\n            )\n\n    @classmethod\n    def __init_from_obj__(cls, obj: Schedule_rs) -> Schedule:\n        \"\"\"Construct the class instance from a given rust object which is wrapped.\"\"\"\n        # create a default instance and overwrite it\n        new = cls(datetime(2000, 1, 1), datetime(2000, 2, 1), \"M\")\n        new._obj = obj\n        return new\n\n    def __getnewargs__(\n        self,\n    ) -> tuple[\n        datetime,\n        datetime,\n        Frequency,\n        StubInference,\n        datetime_,\n        datetime_,\n        NoInput,\n        NoInput,\n        Adjuster,\n        CalInput,\n        Adjuster,\n        Adjuster_,\n        Adjuster_,\n        NoInput,\n        NoInput,\n    ]:\n        return (\n            self.ueffective,\n            self.utermination,\n            self.frequency_obj,\n            StubInference.NeitherSide,\n            NoInput(0) if self.ufront_stub is None else self.ufront_stub,\n            NoInput(0) if self.uback_stub is None else self.uback_stub,\n            NoInput(0),\n            NoInput(0),\n            self.accrual_adjuster,\n            self.calendar,\n            self.payment_adjuster,\n            NoInput(0) if self.payment_adjuster2 is None else self.payment_adjuster2,\n            NoInput(0) if self.payment_adjuster3 is None else self.payment_adjuster3,\n            NoInput(0),\n            NoInput(0),\n        )\n\n    def __eq__(self, other: Any) -> bool:\n        if isinstance(other, self.__class__):\n            return self._obj == other._obj\n        else:\n            return False\n\n    @cached_property\n    def uschedule(self) -> list[datetime]:\n        \"\"\"A list of the *unadjusted* schedule dates.\"\"\"\n        return self.obj.uschedule\n\n    @cached_property\n    def aschedule(self) -> list[datetime]:\n        \"\"\"\n        A list of the *adjusted accrual* dates.\n\n        These are determined by applying the ``accrual_adjuster`` to ``uschedule``.\n        \"\"\"\n        return self.obj.aschedule\n\n    @cached_property\n    def pschedule(self) -> list[datetime]:\n        \"\"\"\n        A list of the cashflow *payment* dates.\n\n        These are determined by applying the ``payment_adjuster`` to ``aschedule``.\n        \"\"\"\n        return self.obj.pschedule\n\n    @cached_property\n    def pschedule2(self) -> list[datetime]:\n        \"\"\"\n        A list of accrual adjusted dates.\n\n        These are determined by applying the ``payment_adjuster2`` to ``aschedule``.\n        \"\"\"\n        return self.obj.pschedule2\n\n    @cached_property\n    def pschedule3(self) -> list[datetime]:\n        \"\"\"\n        A list of accrual adjusted dates.\n\n        These are determined by applying the ``payment_adjuster3`` to ``aschedule``.\n        \"\"\"\n        return self.obj.pschedule3\n\n    @cached_property\n    def frequency(self) -> str:\n        \"\"\"Original string representation of the :class:`~rateslib.scheduling.Frequency`.\"\"\"\n        return self.obj.frequency.string()\n\n    @cached_property\n    def periods_per_annum(self) -> float:\n        \"\"\"\n        Average number of coupons per annum. See\n        :meth:`~rateslib.scheduling.Frequency.periods_per_annum`.\n        \"\"\"\n        return self.obj.frequency.periods_per_annum()\n\n    @cached_property\n    def frequency_obj(self) -> Frequency:\n        \"\"\"The :class:`~rateslib.scheduling.Frequency` object determining the periods.\"\"\"\n        return self.obj.frequency\n\n    @property\n    def modifier(self) -> Adjuster:\n        \"\"\"Alias for the ``accrual_adjuster``.\"\"\"\n        return self.accrual_adjuster\n\n    @cached_property\n    def calendar(self) -> CalTypes:\n        \"\"\"\n        The calendar used for date adjustment by the ``accrual_adjuster`` and\n         ``payment_adjuster``.\n        \"\"\"\n        return self.obj.calendar\n\n    @cached_property\n    def accrual_adjuster(self) -> Adjuster:\n        \"\"\"The :class:`~rateslib.scheduling.Adjuster` object used for accrual date adjustment.\"\"\"\n        return self.obj.accrual_adjuster\n\n    @cached_property\n    def payment_adjuster(self) -> Adjuster:\n        \"\"\"The :class:`~rateslib.scheduling.Adjuster` object used for payment date adjustment.\"\"\"\n        return self.obj.payment_adjuster\n\n    @cached_property\n    def payment_adjuster2(self) -> Adjuster:\n        \"\"\"The :class:`~rateslib.scheduling.Adjuster` object used for additional date adjustment.\"\"\"\n        return self.obj.payment_adjuster2\n\n    @cached_property\n    def payment_adjuster3(self) -> Adjuster | None:\n        \"\"\"The :class:`~rateslib.scheduling.Adjuster` object used for additional date adjustment.\"\"\"\n        return self.obj.payment_adjuster3\n\n    @cached_property\n    def termination(self) -> datetime:\n        \"\"\"The *adjusted* termination date of the schedule.\"\"\"\n        return self.obj.aschedule[-1]\n\n    @cached_property\n    def effective(self) -> datetime:\n        \"\"\"The *adjusted* effective date of the schedule.\"\"\"\n        return self.obj.aschedule[0]\n\n    @cached_property\n    def utermination(self) -> datetime:\n        \"\"\"The *unadjusted* termination date of the schedule.\"\"\"\n        return self.obj.uschedule[-1]\n\n    @cached_property\n    def ueffective(self) -> datetime:\n        \"\"\"The *unadjusted* effective date of the schedule.\"\"\"\n        return self.obj.uschedule[0]\n\n    @cached_property\n    def ufront_stub(self) -> datetime | None:\n        \"\"\"The *unadjusted* front stub date of the schedule.\"\"\"\n        return self.obj.ufront_stub\n\n    @cached_property\n    def uback_stub(self) -> datetime | None:\n        \"\"\"The *unadjusted* back stub date of the schedule.\"\"\"\n        return self.obj.uback_stub\n\n    @cached_property\n    def roll(self) -> str | int | NoInput:\n        \"\"\"\n        The :class:`~rateslib.scheduling.RollDay` object associated\n        with :class:`~rateslib.scheduling.Frequency`, if available.\n        \"\"\"\n        if isinstance(self.obj.frequency, Frequency.Months):\n            # Frequency.Months on a valid Schedule will always have Some(RollDay).\n            if isinstance(self.obj.frequency.roll, RollDay.Day):\n                return self.obj.frequency.roll._0\n            else:\n                return self.obj.frequency.roll.__str__()\n        else:\n            return NoInput(0)\n\n    @cached_property\n    def table(self) -> DataFrame:\n        \"\"\"\n        A `DataFrame` of schedule dates and classification.\n        \"\"\"\n        df = DataFrame(\n            {\n                defaults.headers[\"stub_type\"]: [\n                    \"Stub\" if stub else \"Regular\" for stub in self._stubs\n                ],\n                defaults.headers[\"u_acc_start\"]: self.uschedule[:-1],\n                defaults.headers[\"u_acc_end\"]: self.uschedule[1:],\n                defaults.headers[\"a_acc_start\"]: self.aschedule[:-1],\n                defaults.headers[\"a_acc_end\"]: self.aschedule[1:],\n                defaults.headers[\"payment\"]: self.pschedule[1:],\n            },\n        )\n        return df\n\n    @cached_property\n    def _stubs(self) -> list[bool]:\n        \"\"\"A list of boolean flags indication whether periods are stubs (True) or regular (False)\"\"\"\n        front_stub = self.obj.frequency.is_stub(self.uschedule[0], self.uschedule[1], True)\n        back_stub = self.obj.frequency.is_stub(self.uschedule[-2], self.uschedule[-1], False)\n        if len(self.uschedule) == 2:  # single period\n            return [front_stub or back_stub]\n        else:\n            return [front_stub] + [False] * (len(self.uschedule) - 3) + [back_stub]\n\n    @cached_property\n    def n_periods(self) -> int:\n        \"\"\"The number of periods contained in the schedule.\"\"\"\n        return len(self.obj.uschedule) - 1\n\n    def __repr__(self) -> str:\n        return f\"<rl.Schedule at {hex(id(self))}>\"\n\n    def __str__(self) -> str:\n        f: str = self.frequency_obj.__str__()\n        a: str = self.accrual_adjuster.__str__()\n        p: str = self.payment_adjuster.__str__()\n        str_: str = f\"freq: {f}, accrual adjuster: {a}, payment adjuster: {p},\\n\"\n        ret: str = str_ + self.table.__repr__()\n        return ret\n\n    def is_regular(self) -> bool:\n        \"\"\"Returns whether the schedule is composed only of regular periods (no stubs).\"\"\"\n        return self.obj.is_regular()\n\n    def to_json(self) -> str:\n        \"\"\"Return a JSON representation of the object.\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return _make_py_json(self._obj.to_json(), \"Schedule\")\n\n\ndef _validate_effective(\n    effective: datetime | str,\n    eval_mode: str,\n    eval_date: datetime | NoInput,\n    modifier: str | Adjuster,\n    calendar: CalTypes,\n    roll: int | str | RollDay | NoInput,\n) -> datetime:\n    \"\"\"\n    Determine the effective date of a schedule if it is given in string form from\n    other parameters such as the eval date and the eval mode.\n    \"\"\"\n    if isinstance(effective, str):\n        if isinstance(eval_date, NoInput):\n            raise ValueError(\n                \"For `effective` given as string tenor, must also supply a base `eval_date`.\",\n            )\n        if eval_mode == \"swaps_align\":\n            # effective date is calculated as unadjusted\n            return add_tenor(\n                eval_date,\n                effective,\n                \"NONE\",\n                NoInput(0),\n                roll,\n            )\n        else:  # eval_mode == \"swaptions_align\":\n            return add_tenor(\n                eval_date,\n                effective,\n                modifier,\n                calendar,\n                roll,\n            )\n    else:\n        return effective\n\n\ndef _validate_termination(\n    termination: datetime | str,\n    effective: datetime,\n    modifier: str | Adjuster,\n    calendar: CalTypes,\n    roll: int | str | NoInput | RollDay,\n    eom: bool,\n) -> datetime:\n    \"\"\"\n    Determine the termination date of a schedule if it is given in string form from\n    \"\"\"\n    if isinstance(termination, str):\n        if _is_day_type_tenor(termination):\n            termination_: datetime = add_tenor(\n                start=effective,\n                tenor=termination,\n                modifier=modifier,\n                calendar=calendar,\n                roll=NoInput(0),\n                settlement=False,\n                mod_days=False,\n            )\n        else:\n            # if termination is string the end date is calculated as unadjusted, which will\n            # be used later according to roll inference rules, for monthly and yearly tenors.\n            if eom and isinstance(roll, NoInput) and _is_eom_cal(effective, calendar):\n                roll_: str | int | NoInput | RollDay = 31\n            else:\n                roll_ = roll\n            termination_ = add_tenor(\n                effective,\n                termination,\n                \"NONE\",\n                calendar,  # calendar is unused for NONE type modifier\n                roll_,\n            )\n    else:\n        termination_ = termination\n\n    if termination_ <= effective:\n        raise ValueError(\"Schedule `termination` must be after `effective`.\")\n    return termination_\n"
  },
  {
    "path": "python/rateslib/serialization/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom rateslib.serialization.json import from_json\n\n__all__ = [\"from_json\"]\n"
  },
  {
    "path": "python/rateslib/serialization/json.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom json import dumps, loads\n\n# globals namespace\nfrom typing import TYPE_CHECKING, Any\n\nfrom rateslib.curves import Curve, LineCurve\nfrom rateslib.curves.rs import CurveRs\nfrom rateslib.curves.utils import _CurveInterpolator, _CurveMeta, _CurveNodes, _CurveSpline\nfrom rateslib.dual import Variable\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.fx import FXRates\nfrom rateslib.rs import from_json as from_json_rs\nfrom rateslib.scheduling import Schedule\n\nif TYPE_CHECKING:\n    pass  # pragma: no cover\n\nNAMES_RsPy: dict[str, Any] = {  # this is a mapping of native Rust obj names to Py obj names\n    \"FXRates\": FXRates,\n    \"Curve\": CurveRs,\n    \"Schedule\": Schedule,\n}\n\n\nNAMES_Py: dict[str, Any] = {  # a mapping of native Python classes with a _from_json() method\n    \"_CurveMeta\": _CurveMeta,\n    \"_CurveSpline\": _CurveSpline,\n    \"_CurveInterpolator\": _CurveInterpolator,\n    \"_CurveNodes\": _CurveNodes,\n    \"Curve\": Curve,\n    \"LineCurve\": LineCurve,\n    \"Variable\": Variable,\n}\n\n\nENUMS_Py: dict[str, Any] = {\n    \"NoInput\": NoInput,\n}\n\n\ndef _pynative_from_json(name: str, json: dict[str, Any] | str) -> Any:\n    if name in NAMES_Py:\n        return NAMES_Py[name]._from_json(json)\n    else:\n        # is an Enum\n        return ENUMS_Py[name](json)\n\n\ndef from_json(json: str) -> Any:\n    \"\"\"\n    Create an object from JSON string.\n\n    Parameters\n    ----------\n    json: str\n        JSON string in appropriate format to construct the class.\n\n    Returns\n    -------\n    Object\n    \"\"\"\n    obj = loads(json)\n    if isinstance(obj, dict):\n        if \"PyWrapped\" in obj:\n            # then object is a Rust struct wrapped by a Python class.\n            # determine the Python class name and reconstruct the Python class from the Rust struct.\n            class_name = next(iter(obj[\"PyWrapped\"].keys()))\n            restructured_json = dumps(obj[\"PyWrapped\"])\n            # objs = globals()\n            class_obj = NAMES_RsPy[class_name]\n            return class_obj.__init_from_obj__(obj=from_json_rs(restructured_json))\n        elif \"PyNative\" in obj:\n            # PyNative are objects that are constructed only in Python but do not serialize directly\n            # and so are tagged with a serialization flag.\n            class_name = next(iter(obj[\"PyNative\"].keys()))\n            return _pynative_from_json(name=class_name, json=obj[\"PyNative\"][class_name])\n        else:\n            # the dict may have been a native Rust object, try loading directly\n            # this will raise if all combination exhausted\n            return from_json_rs(json)\n    else:\n        # object is a native Python element\n        return obj\n"
  },
  {
    "path": "python/rateslib/serialization/utils.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom enum import Enum\nfrom json import dumps\nfrom typing import TYPE_CHECKING\n\nfrom rateslib.dual import Dual, Dual2\nfrom rateslib.dual.utils import _to_number\nfrom rateslib.enums.generics import NoInput\n\nif TYPE_CHECKING:\n    from rateslib.local_types import Any, DualTypes, Number  # pragma: no cover\n\n\n# Dualtypes handles case of rust wrapped Dual/Dual2 datatype intermixed with float.\n\n\ndef _dualtypes_to_json(val: DualTypes) -> str:\n    val_: Number = _to_number(val)\n    if isinstance(val_, Dual | Dual2):\n        return val_.to_json()\n    else:\n        return dumps(val_)\n\n\ndef _enum_to_json(val: Enum) -> str:\n    return f'{{\"PyNative\":{{\"{type(val).__name__}\":{val.value}}}}}'\n\n\ndef _obj_to_json(val: Any) -> str:\n    if isinstance(val, NoInput):\n        return _enum_to_json(val)\n    else:\n        try:\n            return val.to_json()  # type: ignore[no-any-return]\n        except AttributeError:\n            return dumps(val)\n"
  },
  {
    "path": "python/rateslib/solver.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport warnings\nfrom itertools import combinations\nfrom math import log\nfrom time import time\nfrom typing import TYPE_CHECKING, ParamSpec\nfrom uuid import uuid4\n\nimport numpy as np\nfrom pandas import DataFrame, MultiIndex, Series, concat\nfrom pandas.errors import PerformanceWarning\n\nfrom rateslib import defaults\nfrom rateslib.curves import (\n    CompositeCurve,\n    Curve,\n    MultiCsaCurve,\n    ProxyCurve,\n    RolledCurve,\n    ShiftedCurve,\n    TranslatedCurve,\n    _BaseCurve,\n)\nfrom rateslib.dual import Dual, Dual2, dual_solve, gradient\nfrom rateslib.dual.newton import _solver_result\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.mutability import (\n    _new_state_post,\n    _no_interior_validation,\n    _validate_states,\n    _WithState,\n)\nfrom rateslib.volatility.fx import FXVols\nfrom rateslib.volatility.ir import IRVols, _BaseIRCube, _BaseIRSmile\n\nP = ParamSpec(\"P\")\n\nif TYPE_CHECKING:\n    from numpy import float64 as Nf64  # noqa: N812\n    from numpy import object_ as Nobject  # noqa: N812\n    from numpy.typing import NDArray\n\n    from rateslib.local_types import (\n        FX_,\n        Any,\n        Callable,\n        DualTypes,\n        FXForwards_,\n        Sequence,\n        SupportsRate,\n        SupportsSolverMutability,\n        Variable,\n        str_,\n    )\n\n\nclass Gradients:\n    \"\"\"\n    A catalogue of all the gradients used in optimisation routines and risk\n    sensitivties.\n    \"\"\"\n\n    _grad_s_vT_method: str = \"_grad_s_vT_final_iteration_analytical\"\n    _grad_s_vT_final_iteration_algo: str = \"gauss_newton_final\"\n\n    _J: NDArray[Nf64] | None\n    _J_pre: NDArray[Nf64] | None\n    _J2: NDArray[Nf64] | None\n    _J2_pre: NDArray[Nf64] | None\n    _grad_v_g: NDArray[Nf64] | None\n    _grad_s_vT: NDArray[Nf64] | None\n    _grad_s_vT_pre: NDArray[Nf64] | None\n    _grad_s_s_vT: NDArray[Nf64] | None\n    _grad_s_s_vT_pre: NDArray[Nf64] | None\n\n    _reset_properties_: Callable[..., None]\n    _update_step_: Callable[[str], NDArray[Nobject]]\n    _set_ad_order: Callable[[int], None]\n    iterate: Callable[..., None]\n\n    func_tol: float\n    conv_tol: float\n    pre_solvers: tuple[Solver, ...]\n    r: NDArray[Nobject]  # instrument rates at iterate\n    r_pre: NDArray[Nobject]  # instrument rates at iterate including pre_\n    s: NDArray[Nobject]  # target instrument rates\n    m: int  # number of instruments\n    pre_m: int  # number of instruments including pre_\n    n: int  # number of parameters/variables\n    pre_n: int  # number of parameters/variables in all solvers including pre_\n    g: Dual | Dual2  # solver objective function value\n    variables: tuple[str, ...]  # string tags for AD coordination\n    pre_variables: tuple[str, ...]  # string tags for AD coordination\n    pre_rate_scalars: list[float]  # scalars for the rate attribute of instruments\n    _ad: int  # ad order\n    instruments: tuple[tuple[SupportsRate, dict[str, Any]], ...]  # calibrators\n\n    @property\n    def J(self) -> NDArray[Nf64]:\n        \"\"\"\n        2d Jacobian array of calibrating instrument rates with respect to curve\n        variables, of size (n, m);\n\n        .. math::\n\n           [J]_{i,j} = [\\\\nabla_\\\\mathbf{v} \\\\mathbf{r^T}]_{i,j} = \\\\frac{\\\\partial r_j}{\\\\partial v_i}\n\n        Depends on ``self.r``.\n        \"\"\"  # noqa: E501\n        if self._J is None:\n            self._J = np.array([gradient(rate, self.variables) for rate in self.r]).T\n        return self._J\n\n    @property\n    def grad_v_rT(self) -> NDArray[Nf64]:\n        \"\"\"\n        Alias of ``J``.\n        \"\"\"\n        return self.J\n\n    @property\n    def J2(self) -> NDArray[Nf64]:\n        \"\"\"\n        3d array of second derivatives of calibrating instrument rates with\n        respect to curve variables, of size (n, n, m);\n\n        .. math::\n\n           [J2]_{i,j,k} = [\\\\nabla_\\\\mathbf{v} \\\\nabla_\\\\mathbf{v} \\\\mathbf{r^T}]_{i,j,k} = \\\\frac{\\\\partial^2 r_k}{\\\\partial v_i \\\\partial v_j}\n\n        Depends on ``self.r``.\n        \"\"\"  # noqa: E501\n        if self._J2 is None:\n            if self._ad != 2:\n                raise ValueError(\n                    f\"Cannot perform second derivative calculations when ad mode is {self._ad}.\",\n                )\n\n            rates = np.array([_[0].rate(**_[1]) for _ in self.instruments])\n            # solver is passed in order to extract curves as string\n            _ = np.array([gradient(rate, self.variables, order=2) for rate in rates])\n            self._J2 = np.transpose(_, (1, 2, 0))\n        return self._J2\n\n    @property\n    def grad_v_v_rT(self) -> NDArray[Nf64]:\n        \"\"\"\n        Alias of ``J2``.\n        \"\"\"\n        return self.J2  # pragma: no cover\n\n    @property\n    def grad_v_g(self) -> NDArray[Nf64]:\n        \"\"\"\n        1d array of objective function value with respect to curve variables,\n        of size (n,);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{v} g(\\\\mathbf{v}; \\\\mathbf{s}) = \\\\frac{\\\\partial g}{\\\\partial v_i}\n        \"\"\"  # noqa: E501\n        if self._grad_v_g is None:\n            self._grad_v_g = gradient(self.g, self.variables)\n        return self._grad_v_g\n\n    @property\n    def grad_s_vT(self) -> NDArray[Nf64]:\n        \"\"\"\n        2d Jacobian array of curve variables with respect to calibrating instruments,\n        of size (m, n);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{s}\\\\mathbf{v^T}]_{i,j} = \\\\frac{\\\\partial v_j}{\\\\partial s_i} = \\\\mathbf{J^+}\n        \"\"\"  # noqa: E501\n        if self._grad_s_vT is None:\n            self._grad_s_vT = getattr(self, self._grad_s_vT_method)()\n        return self._grad_s_vT\n\n    def _grad_s_vT_final_iteration_dual(self, algorithm: str | None = None) -> NDArray[Nf64]:\n        \"\"\"\n        This is not the ideal method since it requires reset_properties to reassess.\n        \"\"\"\n        algorithm = algorithm or self._grad_s_vT_final_iteration_algo\n        _s = self.s\n        self.s = np.array([Dual(v, [f\"s{i}\"], []) for i, v in enumerate(self.s)])\n        self._reset_properties_()\n        v_1 = self._update_step_(algorithm)\n        s_vars = [f\"s{i}\" for i in range(self.m)]\n        grad_s_vT = np.array([gradient(v, s_vars) for v in v_1]).T\n        self.s = _s\n        return grad_s_vT\n\n    def _grad_s_vT_final_iteration_analytical(self) -> NDArray[Nf64]:\n        \"\"\"Uses a pseudoinverse algorithm on floats\"\"\"\n        if self.n == 0:\n            # then there are no instruments: self is only a Solver container of `pre_solvers`\n            grad_s_vT: NDArray[Nf64] = np.array([[]], dtype=float)\n        else:\n            grad_s_vT = np.linalg.pinv(self.J)  # type: ignore[assignment]\n        return grad_s_vT\n\n    def _grad_s_vT_fixed_point_iteration(self) -> NDArray[Nf64]:\n        \"\"\"\n        This is not the ideal method because it requires second order and reset props.\n        \"\"\"\n        self._set_ad_order(2)\n        self._reset_properties_()\n        _s = self.s\n        self.s = np.array([Dual2(v, [f\"s{i}\"], [], []) for i, v in enumerate(self.s)])\n        s_vars = tuple(f\"s{i}\" for i in range(self.m))\n        grad2 = gradient(self.g, self.variables + s_vars, order=2)\n        grad_v_vT_f = grad2[: self.n, : self.n]\n        grad_s_vT_f = grad2[self.n :, : self.n]\n        grad_s_vT: NDArray[Nf64] = np.linalg.solve(grad_v_vT_f, -grad_s_vT_f.T).T  # type: ignore[assignment]\n\n        # The following are alternative representations. Actually faster to calculate and\n        # do not require sensitivity against S variables to be measured.\n        # See 'coding interest rates' equation 12.38\n        # _1 = np.einsum(\"iy, yz, jz\", self.J, self.W, self.J)\n        # _2 = np.einsum(\"z, zy, ijy\", self.x.astype(float), self.W, self.J2)\n        # _3 = 2* (_1 + _2)\n        # _11 = -2 * np.einsum(\"iz,zj->ji\", self.J, self.W)\n\n        self.s = _s\n        self._set_ad_order(1)\n        self._reset_properties_()\n        return grad_s_vT\n\n    @property\n    def grad_s_s_vT(self) -> NDArray[Nf64]:\n        \"\"\"\n        3d array of second derivatives of curve variables with respect to\n        calibrating instruments, of size (m, m, n);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{s} \\\\nabla_\\\\mathbf{s} \\\\mathbf{v^T}]_{i,j,k} = \\\\frac{\\\\partial^2 v_k}{\\\\partial s_i \\\\partial s_j}\n        \"\"\"  # noqa: E501\n        if self._grad_s_s_vT is None:\n            self._grad_s_s_vT = self._grad_s_s_vT_final_iteration_analytical()\n        return self._grad_s_s_vT\n\n    def _grad_s_s_vT_fwd_difference_method(self) -> NDArray[Nf64]:\n        \"\"\"Use a numerical method, iterating through changes in s to calculate.\"\"\"\n        ds = 10 ** (int(log(self.func_tol, 10) / 2))\n        grad_s_vT_0 = np.copy(self.grad_s_vT)\n        grad_s_s_vT = np.zeros(shape=(self.m, self.m, self.n))\n\n        for i in range(self.m):\n            self.s[i] += ds\n            self.iterate()\n            grad_s_s_vT[:, i, :] = (self.grad_s_vT - grad_s_vT_0) / ds\n            self.s[i] -= ds\n\n        # ensure exact symmetry (maybe redundant)\n        grad_s_s_vT = (grad_s_s_vT + np.swapaxes(grad_s_s_vT, 0, 1)) / 2\n        self.iterate()\n        return grad_s_s_vT\n\n    def _grad_s_s_vT_final_iteration_analytical(self, use_pre: bool = False) -> NDArray[Nf64]:\n        \"\"\"\n        Use an analytical formula and second order AD to calculate.\n\n        Not: must have 2nd order AD set to function, and valid properties set to\n        function\n        \"\"\"\n        if use_pre:\n            J2, grad_s_vT = self.J2_pre, self.grad_s_vT_pre\n        else:\n            J2, grad_s_vT = self.J2, self.grad_s_vT\n\n        # dv/dr_l * d2r_l / dvdv\n        _: NDArray[Nf64] = np.tensordot(J2, grad_s_vT, (2, 0))\n        # dv_z /ds * d2v / dv_zdv\n        _ = np.tensordot(grad_s_vT, _, (1, 0))\n        # dv_h /ds * d2v /dvdv_h\n        _ = -np.tensordot(grad_s_vT, _, (1, 1))\n        grad_s_s_vT = _\n        return grad_s_s_vT\n        # _ = np.matmul(grad_s_vT, np.matmul(J2, grad_s_vT))\n        # grad_s_s_vT = -np.tensordot(grad_s_vT, _, (1, 0))\n        # return grad_s_s_vT\n\n    # _pre versions incorporate all variables of solver and pre_solvers\n\n    def grad_f_rT_pre(self, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        2d Jacobian array of calibrating instrument rates with respect to FX rate\n        variables, of size (len(fx_vars), pre_m);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f}\\\\mathbf{r^T}]_{i,j} = \\\\frac{\\\\partial r_j}{\\\\partial f_i}\n\n        Parameters\n        ----------\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities.\n        \"\"\"\n        grad_f_rT = np.array([gradient(rate, fx_vars) for rate in self.r_pre]).T\n        return grad_f_rT\n\n    @property\n    def J2_pre(self) -> NDArray[Nf64]:\n        \"\"\"\n        3d array of second derivatives of calibrating instrument rates with\n        respect to curve variables for all ``Solvers`` including ``pre_solvers``,\n        of size (pre_n, pre_n, pre_m);\n\n        .. math::\n\n           [J2]_{i,j,k} = [\\\\nabla_\\\\mathbf{v} \\\\nabla_\\\\mathbf{v} \\\\mathbf{r^T}]_{i,j,k} = \\\\frac{\\\\partial^2 r_k}{\\\\partial v_i \\\\partial v_j}\n\n        Depends on ``self.r`` and ``pre_solvers.J2``.\n        \"\"\"  # noqa: E501\n        if len(self.pre_solvers) == 0:\n            return self.J2\n\n        if self._J2_pre is None:\n            if self._ad != 2:\n                raise ValueError(\n                    f\"Cannot perform second derivative calculations when ad mode is {self._ad}.\",\n                )\n\n            J2 = np.zeros(shape=(self.pre_n, self.pre_n, self.pre_m))\n            i, j = 0, 0\n            for pre_slvr in self.pre_solvers:\n                J2[\n                    i : i + pre_slvr.pre_n,\n                    i : i + pre_slvr.pre_n,\n                    j : j + pre_slvr.pre_m,\n                ] = pre_slvr.J2_pre\n                i, j = i + pre_slvr.pre_n, j + pre_slvr.pre_m\n\n            if self.m > 0:\n                # then self is not only a container for `pre_solvers`\n                rates = np.array([_[0].rate(**_[1]) for _ in self.instruments])\n                # solver is passed in order to extract curves as string\n                _ = np.array([gradient(r, self.pre_variables, order=2) for r in rates])\n                J2[:, :, -self.m :] = np.transpose(_, (1, 2, 0))\n            self._J2_pre = J2\n        return self._J2_pre\n\n    def grad_f_v_rT_pre(self, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        3d array of second derivatives of calibrating instrument rates with respect to\n        FX rates and curve variables, of size (len(fx_vars), pre_n, pre_m);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{v} \\\\mathbf{r^T}]_{i,j,k} = \\\\frac{\\\\partial^2 r_k}{\\\\partial f_i \\\\partial v_j}\n\n        Parameters\n        ----------\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities.\n        \"\"\"  # noqa: E501\n        # FX sensitivity requires reverting through all pre-solvers rates.\n        all_gradients = np.array(\n            [gradient(rate, self.pre_variables + tuple(fx_vars), order=2) for rate in self.r_pre],\n        ).swapaxes(0, 2)\n\n        grad_f_v_rT = all_gradients[self.pre_n :, : self.pre_n, :]\n        return grad_f_v_rT\n\n    def grad_f_f_rT_pre(self, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        3d array of second derivatives of calibrating instrument rates with respect to\n        FX rates, of size (len(fx_vars), len(fx_vars), pre_m);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{f} \\\\mathbf{r^T}]_{i,j,k} = \\\\frac{\\\\partial^2 r_k}{\\\\partial f_i \\\\partial f_j}\n\n        Parameters\n        ----------\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities.\n        \"\"\"  # noqa: E501\n        # FX sensitivity requires reverting through all pre-solvers rates.\n        grad_f_f_rT = np.array([gradient(rate, fx_vars, order=2) for rate in self.r_pre]).swapaxes(\n            0,\n            2,\n        )\n        return grad_f_f_rT\n\n    @property\n    def grad_s_s_vT_pre(self) -> NDArray[Nf64]:\n        \"\"\"\n        3d array of second derivatives of curve variables with respect to\n        calibrating instruments, of size (pre_m, pre_m, pre_n);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{s} \\\\nabla_\\\\mathbf{s} \\\\mathbf{v^T}]_{i,j,k} = \\\\frac{\\\\partial^2 v_k}{\\\\partial s_i \\\\partial s_j}\n        \"\"\"  # noqa: E501\n        if len(self.pre_solvers) == 0:\n            return self.grad_s_s_vT\n\n        if self._grad_s_s_vT_pre is None:\n            self._grad_s_s_vT_pre = self._grad_s_s_vT_final_iteration_analytical(use_pre=True)\n        return self._grad_s_s_vT_pre\n\n    @property\n    def grad_v_v_rT_pre(self) -> NDArray[Nf64]:\n        \"\"\"\n        Alias of ``J2_pre``.\n        \"\"\"\n        return self.J2_pre  # pragma: no cover\n\n    def grad_f_s_vT_pre(self, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        3d array of second derivatives of curve variables with respect to\n        FX rates and calibrating instrument rates, of size (len(fx_vars), pre_m, pre_n);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{s} \\\\mathbf{v^T}]_{i,j,k} = \\\\frac{\\\\partial^2 v_k}{\\\\partial f_i \\\\partial s_j}\n\n        Parameters\n        ----------\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities.\n        \"\"\"  # noqa: E501\n        # FX sensitivity requires reverting through all pre-solvers rates.\n        _ = -np.tensordot(self.grad_f_v_rT_pre(fx_vars), self.grad_s_vT_pre, (1, 1)).swapaxes(1, 2)\n        _ = np.tensordot(_, self.grad_s_vT_pre, (2, 0))\n        grad_f_s_vT: NDArray[Nf64] = _\n        return grad_f_s_vT\n\n    def grad_f_f_vT_pre(self, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        3d array of second derivatives of curve variables with respect to\n        FX rates, of size (len(fx_vars), len(fx_vars), pre_n);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{f} \\\\mathbf{v^T}]_{i,j,k} = \\\\frac{\\\\partial^2 v_k}{\\\\partial f_i \\\\partial f_j}\n\n        Parameters\n        ----------\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities.\n        \"\"\"  # noqa: E501\n        # FX sensitivity requires reverting through all pre-solvers rates.\n        _ = -np.tensordot(self.grad_f_f_rT_pre(fx_vars), self.grad_s_vT_pre, (2, 0))\n        _ -= np.tensordot(self.grad_f_rT_pre(fx_vars), self.grad_f_s_vT_pre(fx_vars), (1, 1))\n        grad_f_f_vT: NDArray[Nf64] = _\n        return grad_f_f_vT\n\n    def grad_f_vT_pre(self, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of the derivatives of curve variables with respect to FX rates, of\n        size (len(fx_vars), pre_n).\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f}\\\\mathbf{v^T}]_{i,j} = \\\\frac{\\\\partial v_j}{\\\\partial f_i} = -\\\\frac{\\\\partial r_z}{\\\\partial f_i} \\\\frac{\\\\partial v_j}{\\\\partial s_z}\n\n        Parameters\n        ----------\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities\n        \"\"\"  # noqa: E501\n        # FX sensitivity requires reverting through all pre-solvers rates.\n        grad_f_rT = np.array([gradient(rate, fx_vars) for rate in self.r_pre]).T\n        _: NDArray[Nf64] = -np.matmul(grad_f_rT, self.grad_s_vT_pre)\n        return _\n\n    def grad_f_f(self, f: Dual | Dual2 | Variable, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        1d array of total derivatives of FX conversion rate with respect to\n        FX rate variables, of size (len(fx_vars));\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f} f_{loc:bas}]_{i} = \\\\frac{d f}{d f_i}\n\n        Parameters\n        ----------\n        f : Dual or Dual2\n            The value of the local to base FX conversion rate.\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities\n        \"\"\"\n        grad_f_f = gradient(f, fx_vars)\n        grad_f_f += np.matmul(self.grad_f_vT_pre(fx_vars), gradient(f, self.pre_variables))\n        ret: NDArray[Nf64] = grad_f_f\n        return ret\n\n    @property\n    def grad_s_vT_pre(self) -> NDArray[Nf64]:\n        \"\"\"\n        2d Jacobian array of curve variables with respect to calibrating instruments\n        including all pre solvers attached to the Solver, of size (pre_m, pre_n).\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{s}\\\\mathbf{v^T}]_{i,j} = \\\\frac{\\\\partial v_j}{\\\\partial s_i} = \\\\mathbf{J^+}\n        \"\"\"  # noqa: E501\n        if len(self.pre_solvers) == 0:\n            return self.grad_s_vT\n\n        if self._grad_s_vT_pre is None:\n            grad_s_vT = np.zeros(shape=(self.pre_m, self.pre_n))\n\n            i, j = 0, 0\n            for pre_solver in self.pre_solvers:\n                # create the left side block matrix\n                m, n = pre_solver.pre_m, pre_solver.pre_n\n                grad_s_vT[i : i + m, j : j + n] = pre_solver.grad_s_vT_pre\n\n                # create the right column dependencies, only if self contains some instruments\n                # and variable of its own and is not only a container of `pre_solvers`\n                if self.n > 0:\n                    grad_v_r = np.array([gradient(r, pre_solver.pre_variables) for r in self.r]).T\n                    block = np.matmul(grad_v_r, self.grad_s_vT)\n                    block = -1 * np.matmul(pre_solver.grad_s_vT_pre, block)\n                    grad_s_vT[i : i + m, -self.n :] = block\n\n                i, j = i + m, j + n\n\n            if self.n > 0:\n                # create bottom right block, only if self contains some instruments\n                # and variables of its own and is not only a container of `pre_solvers`\n                grad_s_vT[-self.m :, -self.n :] = self.grad_s_vT\n\n            self._grad_s_vT_pre = grad_s_vT\n        return self._grad_s_vT_pre\n\n    def grad_s_f_pre(self, f: Dual | Dual2 | Variable) -> NDArray[Nf64]:\n        \"\"\"\n        1d array of FX conversion rate with respect to calibrating instruments,\n        of size (pre_m);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{s} f_{loc:bas}]_{i} = \\\\frac{\\\\partial f}{\\\\partial s_i}\n\n        Parameters\n        ----------\n        f : Dual or Dual2\n            The value of the local to base FX conversion rate.\n        \"\"\"\n        grad_s_f: NDArray[Nf64] = np.tensordot(\n            self.grad_s_vT_pre, gradient(f, self.pre_variables), (1, 0)\n        )\n        return grad_s_f\n\n    def grad_s_sT_f_pre(self, f: Dual | Dual2 | Variable) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of FX conversion rate with respect to\n        calibrating instruments, of size (pre_m, pre_m);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{s} \\\\nabla_\\\\mathbf{s}^\\\\mathbf{T} f_{loc:bas}]_{i,j} = \\\\frac{\\\\partial^2 f}{\\\\partial s_i \\\\partial s_j}\n\n        Parameters\n        ----------\n        f : Dual or Dual2\n            The value of the local to base FX conversion rate.\n        \"\"\"  # noqa: E501\n        grad_s_vT = self.grad_s_vT_pre\n        grad_v_vT_f = gradient(f, self.pre_variables, order=2)\n\n        _: NDArray[Nf64] = np.tensordot(grad_s_vT, grad_v_vT_f, (1, 0))\n        _ = np.tensordot(_, grad_s_vT, (1, 1))\n\n        grad_s_sT_f = _\n        return grad_s_sT_f\n\n    def grad_f_sT_f_pre(self, f: Dual | Dual2 | Variable, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of FX conversion rate with respect to\n        calibrating instruments, of size (pre_m, pre_m);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{s}^\\\\mathbf{T} f_{loc:bas}(\\\\mathbf{v(s, f), f)})]_{i,j} = \\\\frac{d^2 f}{d f_i \\\\partial s_j}\n\n        Parameters\n        ----------\n        f : Dual or Dual2\n            The value of the local to base FX conversion rate.\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities\n        \"\"\"  # noqa: E501\n        grad_s_vT = self.grad_s_vT_pre\n        grad_v_f = gradient(f, self.pre_variables)\n        grad_f_sT_v = self.grad_f_s_vT_pre(fx_vars)\n        _ = gradient(f, self.pre_variables + tuple(fx_vars), order=2)\n        grad_v_vT_f = _[: self.pre_n, : self.pre_n]\n        grad_f_vT_f = _[self.pre_n :, : self.pre_n]\n        # grad_f_fT_f = _[self.pre_n :, self.pre_n :]\n        grad_f_vT = self.grad_f_vT_pre(fx_vars)\n\n        _ = np.tensordot(grad_f_sT_v, grad_v_f, (2, 0))\n        _ += np.tensordot(grad_f_vT_f, grad_s_vT, (1, 1))\n\n        __ = np.tensordot(grad_f_vT, grad_v_vT_f, (1, 0))\n        __ = np.tensordot(__, grad_s_vT, (1, 1))\n\n        grad_f_sT_f: NDArray[Nf64] = _ + __\n        return grad_f_sT_f\n\n    def grad_f_fT_f_pre(self, f: Dual | Dual2 | Variable, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of FX conversion rate with respect to\n        calibrating instruments, of size (pre_m, pre_m);\n\n        .. math::\n\n           [\\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{f}^\\\\mathbf{T} f_{loc:bas}(\\\\mathbf{v(s, f), f)})]_{i,j} = \\\\frac{d^2 f}{d f_i d f_j}\n\n        Parameters\n        ----------\n        f : Dual or Dual2\n            The value of the local to base FX conversion rate.\n        fx_vars : list or tuple of str\n            The variable name tags for the FX rate sensitivities\n        \"\"\"  # noqa: E501\n        # grad_s_vT = self.grad_s_vT_pre\n        grad_v_f = gradient(f, self.pre_variables)\n        # grad_f_sT_v = self.grad_f_s_vT_pre(fx_vars)\n        _ = gradient(f, self.pre_variables + tuple(fx_vars), order=2)\n        grad_v_vT_f = _[: self.pre_n, : self.pre_n]\n        grad_f_vT_f = _[self.pre_n :, : self.pre_n]\n        grad_f_fT_f = _[self.pre_n :, self.pre_n :]\n        grad_f_vT = self.grad_f_vT_pre(fx_vars)\n        grad_f_fT_v = self.grad_f_f_vT_pre(fx_vars)\n\n        _ = grad_f_fT_f\n        _ += 2.0 * np.tensordot(grad_f_vT_f, grad_f_vT, (1, 1))\n        _ += np.tensordot(grad_f_fT_v, grad_v_f, (2, 0))\n\n        __ = np.tensordot(grad_f_vT, grad_v_vT_f, (1, 0))\n        __ = np.tensordot(__, grad_f_vT, (1, 1))\n\n        grad_f_fT_f = _ + __\n        return grad_f_fT_f\n\n    # grad_v_v_f: calculated within grad_s_vT_fixed_point_iteration\n\n    # delta and gamma calculations require all solver and pre_solver variables\n\n    def grad_s_Ploc(self, npv: Dual | Dual2 | Variable) -> NDArray[Nf64]:\n        \"\"\"\n        1d array of derivatives of local currency PV with respect to calibrating\n        instruments, of size (pre_m).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{s} P^{loc} = \\\\frac{\\\\partial P^{loc}}{\\\\partial s_i}\n\n        Parameters:\n            npv : Dual or Dual2\n                A local currency NPV of a period of a leg.\n        \"\"\"\n        grad_s_P: NDArray[Nf64] = np.matmul(self.grad_s_vT_pre, gradient(npv, self.pre_variables))\n        return grad_s_P\n\n    def grad_f_Ploc(self, npv: Dual | Dual2 | Variable, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        r\"\"\"\n        1d array of derivatives of local currency PV with respect to FX rate variable,\n        of size (len(fx_vars)).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{f} P^{loc}(\\\\mathbf{v(s, f), f}) = \\\\frac{\\\\partial P^{loc}}{\\\\partial f_i}+  \\\\frac{\\partial v_z}{\\\\partial f_i} \\\\frac{\\\\partial P^{loc}}{\\\\partial v_z}\n\n        Parameters:\n            npv : Dual or Dual2\n                A local currency NPV of a period of a leg.\n            fx_vars : list or tuple of str\n                The variable tags for automatic differentiation of FX rate sensitivity\n        \"\"\"  # noqa: E501\n        grad_f_P = gradient(npv, fx_vars)\n        grad_f_P += np.matmul(self.grad_f_vT_pre(fx_vars), gradient(npv, self.pre_variables))\n        ret: NDArray[Nf64] = grad_f_P\n        return ret\n\n    def grad_s_Pbase(\n        self, npv: Dual | Dual2 | Variable, grad_s_P: NDArray[Nf64], f: Dual | Dual2 | Variable\n    ) -> NDArray[Nf64]:\n        \"\"\"\n        1d array of derivatives of base currency PV with respect to calibrating\n        instruments, of size (pre_m).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{s} P^{bas}(\\\\mathbf{v(s, f)}) = \\\\nabla_\\\\mathbf{s} P^{loc}(\\\\mathbf{v(s, f)})  f_{loc:bas} + P^{loc} \\\\nabla_\\\\mathbf{s} f_{loc:bas}\n\n        Parameters:\n            npv : Dual or Dual2\n                A local currency NPV of a period of a leg.\n            grad_s_P : ndarray\n                The local currency delta risks w.r.t. calibrating instruments.\n            f : Dual or Dual2\n                The local:base FX rate.\n        \"\"\"  # noqa: E501\n        grad_s_Pbas: NDArray[Nf64] = _dual_float(npv) * np.matmul(\n            self.grad_s_vT_pre, gradient(f, self.pre_variables)\n        )\n        grad_s_Pbas += grad_s_P * _dual_float(f)  # <- use float to cast float array not Dual\n        return grad_s_Pbas\n\n    def grad_f_Pbase(\n        self,\n        npv: Dual | Dual2 | Variable,\n        grad_f_P: NDArray[Nf64],\n        f: Dual | Dual2 | Variable,\n        fx_vars: Sequence[str],\n    ) -> NDArray[Nf64]:\n        \"\"\"\n        1d array of derivatives of base currency PV with respect to FX rate variables,\n        of size (len(fx_vars)).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{s} P^{bas}(\\\\mathbf{v(s, f)}) = \\\\nabla_\\\\mathbf{s} P^{loc}(\\\\mathbf{v(s, f)})  f_{loc:bas} + P^{loc} \\\\nabla_\\\\mathbf{s} f_{loc:bas}\n\n        Parameters:\n            npv : Dual or Dual2\n                A local currency NPV of a period of a leg.\n            grad_f_P : ndarray\n                The local currency delta risks w.r.t. FX pair variables.\n            f : Dual or Dual2\n                The local:base FX rate.\n            fx_vars : list or tuple of str\n                The variable tags for automatic differentiation of FX rate sensitivity\n        \"\"\"  # noqa: E501\n        # use float here to cast float array not Dual\n        ret: NDArray[Nf64] = grad_f_P * _dual_float(f)\n        ret += _dual_float(npv) * self.grad_f_f(f, fx_vars)\n        return ret\n\n    def grad_s_sT_Ploc(self, npv: Dual2 | Variable) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of local currency PV with respect to calibrating\n        instruments, of size (pre_m, pre_m).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{s} \\\\nabla_\\\\mathbf{s}^\\\\mathbf{T} P^{loc}(\\\\mathbf{v, f}) = \\\\frac{ \\\\partial^2 P^{loc}(\\\\mathbf{v(s, f)}) }{\\\\partial s_i \\\\partial s_j}\n\n        Parameters:\n            npv : Dual2\n                A local currency NPV of a period of a leg.\n        \"\"\"  # noqa: E501\n        # instrument-instrument cross gamma:\n        _ = np.tensordot(gradient(npv, self.pre_variables, order=2), self.grad_s_vT_pre, (1, 1))\n        _ = np.tensordot(self.grad_s_vT_pre, _, (1, 0))\n\n        _ += np.tensordot(self.grad_s_s_vT_pre, gradient(npv, self.pre_variables), (2, 0))\n        grad_s_sT_P: NDArray[Nf64] = _\n        return grad_s_sT_P\n        # grad_s_sT_P = np.matmul(\n        #     self.grad_s_vT_pre,\n        #     np.matmul(\n        #         npv.gradient(self.pre_variables, order=2), self.grad_s_vT_pre.T\n        #     ),\n        # )\n        # grad_s_sT_P += np.matmul(\n        #     self.grad_s_s_vT_pre, npv.gradient(self.pre_variables)[:, None]\n        # )[:, :, 0]\n\n    def gradp_f_vT_Ploc(\n        self, npv: Dual | Dual2 | Variable, fx_vars: Sequence[str]\n    ) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of (partial) derivatives of local currency PV with respect to\n        FX rate variables and curve variables, of size (len(fx_vars), pre_n).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{v}^\\\\mathbf{T} P^{loc}(\\\\mathbf{v, f}) = \\\\frac{ \\\\partial ^2 P^{loc}(\\\\mathbf{v, f)}) }{\\\\partial f_i \\\\partial v_j}\n\n        Parameters:\n            npv : Dual2\n                A local currency NPV of a period of a leg.\n            fx_vars : list or tuple of str\n                The variable tags for automatic differentiation of FX rate sensitivity\n        \"\"\"  # noqa: E501\n        grad_x_xT_Ploc = gradient(npv, self.pre_variables + tuple(fx_vars), order=2)\n        grad_f_vT_Ploc = grad_x_xT_Ploc[self.pre_n :, : self.pre_n]\n        return grad_f_vT_Ploc\n\n    def grad_f_sT_Ploc(self, npv: Dual | Dual2 | Variable, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of local currency PV with respect to calibrating\n        instruments, of size (pre_m, pre_m).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{s}^\\\\mathbf{T} P^{loc}(\\\\mathbf{v(s, f), f}) = \\\\frac{ d^2 P^{loc}(\\\\mathbf{v(s, f), f)}) }{d f_i \\\\partial s_j}\n\n        Parameters:\n            npv : Dual2\n                A local currency NPV of a period of a leg.\n            fx_vars : list or tuple of str\n                The variable tags for automatic differentiation of FX rate sensitivity\n        \"\"\"  # noqa: E501\n        # fx_rate-instrument cross gamma:\n        _ = np.tensordot(\n            self.grad_f_vT_pre(fx_vars),\n            gradient(npv, self.pre_variables, order=2),\n            (1, 0),\n        )\n        _ += self.gradp_f_vT_Ploc(npv, fx_vars)\n        _ = np.tensordot(_, self.grad_s_vT_pre, (1, 1))\n        _ += np.tensordot(self.grad_f_s_vT_pre(fx_vars), gradient(npv, self.pre_variables), (2, 0))\n        grad_f_sT_Ploc: NDArray[Nf64] = _\n        return grad_f_sT_Ploc\n\n    def grad_f_fT_Ploc(self, npv: Dual | Dual2 | Variable, fx_vars: Sequence[str]) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of local currency PV with respect to FX rate variables,\n        of size (len(fx_vars), len(fx_vars)).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{s}^\\\\mathbf{T} P^{loc}(\\\\mathbf{v(s, f), f}) = \\\\frac{ d^2 P^{loc}(\\\\mathbf{v(s, f), f)}) }{d f_i d f_j}\n\n        Parameters:\n            npv : Dual2\n                A local currency NPV of a period of a leg.\n            fx_vars : list or tuple of str\n                The variable tags for automatic differentiation of FX rate sensitivity\n        \"\"\"  # noqa: E501\n        # fx_rate-instrument cross gamma:\n        gradp_f_vT_Ploc = self.gradp_f_vT_Ploc(npv, fx_vars)\n        grad_f_vT_pre = self.grad_f_vT_pre(fx_vars)\n        grad_v_Ploc = gradient(npv, self.pre_variables)\n        grad_v_vT_Ploc = gradient(npv, self.pre_variables, order=2)\n\n        _ = gradient(npv, fx_vars, order=2)\n        _ += np.tensordot(self.grad_f_f_vT_pre(fx_vars), grad_v_Ploc, (2, 0))\n        _ += np.tensordot(grad_f_vT_pre, gradp_f_vT_Ploc, (1, 1))\n        _ += np.tensordot(gradp_f_vT_Ploc, grad_f_vT_pre, (1, 1))\n\n        __ = np.tensordot(grad_f_vT_pre, grad_v_vT_Ploc, (1, 0))\n        __ = np.tensordot(__, grad_f_vT_pre, (1, 1))\n\n        grad_f_f_Ploc: NDArray[Nf64] = _ + __\n        return grad_f_f_Ploc\n\n    def grad_s_sT_Pbase(\n        self,\n        npv: Dual | Dual2 | Variable,\n        grad_s_sT_P: NDArray[Nf64],\n        f: Dual | Dual2 | Variable,\n    ) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of base currency PV with respect to calibrating\n        instrument rate variables, of size (pre_m, pre_m).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{s} \\\\nabla_\\\\mathbf{s}^\\\\mathbf{T} P^{bas}(\\\\mathbf{v(s, f), f})\n\n        Parameters:\n            npv : Dual or Dual2\n                A local currency NPV of a period of a leg.\n            grad_s_sT_P : ndarray\n                The local currency gamma risks w.r.t. calibrating instrument variables.\n            f : Dual or Dual2\n                The local:base FX rate.\n        \"\"\"\n        grad_s_f = self.grad_s_f_pre(f)\n        grad_s_sT_f = self.grad_s_sT_f_pre(f)\n        grad_s_P = self.grad_s_Ploc(npv)\n\n        _ = _dual_float(f) * grad_s_sT_P\n        _ += np.tensordot(grad_s_f[:, None], grad_s_P[None, :], (1, 0))\n        _ += np.tensordot(grad_s_P[:, None], grad_s_f[None, :], (1, 0))\n        _ += _dual_float(npv) * grad_s_sT_f  # <- use float to cast float array not Dual\n\n        grad_s_sT_Pbas: NDArray[Nf64] = _\n        return grad_s_sT_Pbas\n\n    def grad_f_sT_Pbase(\n        self,\n        npv: Dual | Dual2 | Variable,\n        grad_f_sT_P: NDArray[Nf64],\n        f: Dual | Dual2 | Variable,\n        fx_vars: Sequence[str],\n    ) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of base currency PV with respect to FX variables and\n        calibrating instrument rate variables, of size (len(fx_vars), pre_m).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{f} \\\\nabla_\\\\mathbf{s}^\\\\mathbf{T} P^{bas}(\\\\mathbf{v(s, f), f})\n\n        Parameters:\n            npv : Dual or Dual2\n                A local currency NPV of a period of a leg.\n            grad_f_sT_P : ndarray\n                The local currency gamma risks w.r.t. FX rate variables and\n                calibrating instrument variables.\n            f : Dual or Dual2\n                The local:base FX rate.\n            fx_vars : list or tuple of str\n                The variable tags for automatic differentiation of FX rate sensitivity\n        \"\"\"\n        grad_s_f = self.grad_s_f_pre(f)\n        grad_f_f = self.grad_f_f(f, fx_vars)\n        grad_s_P = self.grad_s_Ploc(npv)\n        grad_f_P = self.grad_f_Ploc(npv, fx_vars)\n        grad_f_sT_f = self.grad_f_sT_f_pre(f, fx_vars)\n\n        _ = _dual_float(f) * grad_f_sT_P\n        _ += np.tensordot(grad_f_f[:, None], grad_s_P[None, :], (1, 0))\n        _ += np.tensordot(grad_f_P[:, None], grad_s_f[None, :], (1, 0))\n        _ += _dual_float(npv) * grad_f_sT_f  # <- use float to cast float array not Dual\n\n        grad_s_sT_Pbas: NDArray[Nf64] = _\n        return grad_s_sT_Pbas\n\n    def grad_f_fT_Pbase(\n        self,\n        npv: Dual | Dual2 | Variable,\n        grad_f_fT_P: NDArray[Nf64],\n        f: Dual | Dual2 | Variable,\n        fx_vars: Sequence[str],\n    ) -> NDArray[Nf64]:\n        \"\"\"\n        2d array of derivatives of base currency PV with respect to calibrating\n        instrument rate variables, of size (pre_m, pre_m).\n\n        .. math::\n\n           \\\\nabla_\\\\mathbf{s} \\\\nabla_\\\\mathbf{s}^\\\\mathbf{T} P^{bas}(\\\\mathbf{v(s, f), f})\n\n        Parameters:\n            npv : Dual or Dual2\n                A local currency NPV of a period of a leg.\n            grad_f_fT_P : ndarray\n                The local currency gamma risks w.r.t. FX rate variables.\n            f : Dual or Dual2\n                The local:base FX rate.\n            fx_vars : list or tuple of str\n                The variable tags for automatic differentiation of FX rate sensitivity\n        \"\"\"\n        # grad_s_f = self.grad_s_f_pre(f)\n        grad_f_f = self.grad_f_f(f, fx_vars)\n        # grad_s_P = self.grad_s_Ploc(npv)\n        grad_f_P = self.grad_f_Ploc(npv, fx_vars)\n        grad_f_fT_f = self.grad_f_fT_f_pre(f, fx_vars)\n\n        _ = _dual_float(f) * grad_f_fT_P\n        _ += np.tensordot(grad_f_f[:, None], grad_f_P[None, :], (1, 0))\n        _ += np.tensordot(grad_f_P[:, None], grad_f_f[None, :], (1, 0))\n        _ += _dual_float(npv) * grad_f_fT_f  # <- use float to cast float array not Dual\n\n        grad_s_sT_Pbas: NDArray[Nf64] = _\n        return grad_s_sT_Pbas\n\n\nNO_PARAMETER_CURVES = [\n    ProxyCurve,\n    CompositeCurve,\n    MultiCsaCurve,\n    RolledCurve,\n    ShiftedCurve,\n    TranslatedCurve,\n]\n\n\nclass Solver(Gradients, _WithState):\n    r\"\"\"\n    A numerical solver to determine parameter values on multiple pricing objects simultaneously.\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import Solver, Curve, IRS, dt\n\n    Parameters\n    ----------\n    curves : sequence\n        Sequence of :class:`Curve` or :class:`Smile` objects where each one\n        has been individually configured for its node dates and interpolation structures,\n        and has a unique ``id``. Each object will be dynamically updated/mutated by the Solver.\n    surfaces : sequence\n        Sequence of :class:`Surface` or :class:`Cube` objects where each has been configured\n        with a unique ``id``. Each object will be dynamically updated/mutated.\n        Internally, ``surfaces`` and ``curves`` are joined and provide nothing more than\n        organisational distinction.\n    instruments : sequence\n        Sequence of calibrating instrument specifications that will be used by\n        the solver to determine the solved curves. See notes.\n    s : sequence\n        Sequence of objective rates that each solved calibrating instrument will solve\n        to. Must have the same length and order as ``instruments``.\n    weights : sequence, optional\n        The weights that should be used within the objective function when determining\n        the loss function associated with each calibrating instrument. Should be of\n        same length as ``instruments``. If not given defaults to all ones.\n    algorithm : str in {\"levenberg_marquardt\", \"gauss_newton\", \"gradient_descent\"}\n        The optimisation algorithm to use when solving curves via :meth:`iterate`.\n    fx : FXForwards, FXRates, optional\n        The fx object used in FX rate calculations for ``instruments`` rates or sensitivities.\n    instrument_labels : list of str, optional\n        The names of the calibrating instruments which will be used in delta risk\n        outputs.\n    id : str, optional\n        The identifier used to denote the instance and attribute risk factors.\n    pre_solvers : list,\n        A collection of ``Solver`` s that have already determined curves to which this\n        instance has a dependency. Used for aggregation of risk sensitivities.\n    max_iter : int\n        The maximum number of iterations to perform.\n    func_tol : float\n        The tolerance to determine convergence if the objective function is lower\n        than a specific value. Defaults to 1e-11.\n    conv_tol : float\n        The tolerance to determine convergence if successive objective function\n        values are similar. Defaults to 1e-14.\n    step_tol : float\n        The tolerance for the norm of the difference between successive parameter iterates.\n        Defaults to 1e-14.\n    grad_tol : float\n        The tolerance for the norm of the objective function gradient at an iterate. Defaults\n        to 1e-11.\n    ini_lambda : 3-tuple of float, optional\n        Parameters to control the Levenberg-Marquardt algorithm, defined as the\n        initial lambda value, the scaling factor for a successful iteration and the\n        scaling factor for an unsuccessful iteration. Defaults to (1000, 0.25, 2).\n    callback : callable, optional\n        Is called after each iteration. Used for debugging or optimization.\n\n    Notes\n    -------\n\n    **Purpose**\n\n    Once initialized, the *Solver* will numerically determine and set, via mutation, all the\n    relevant node values on each *Curve*, *Smile*, *Surface* or *Cube* simultaneously by\n    calling :meth:`iterate`. This mutation of those pricing objects will override any local AD\n    variables pre-configured by a user and use the *Solver's* own variable tags, for proper\n    *delta* and *gamma* management. The objective function of the *Solver* which it seeks to\n    minimize over all parameters, :math:`\\mathbf{v}`, is:\n\n    .. math::\n\n       g(\\mathbf{v}; \\mathbf{s}) = \\mathbf{(r(v) - s)^{T} W (r(v) - s)}\n\n    **Instrument Specification**\n\n    Thus, the *Solver* naturally attempts to match the corresponding value in ``s`` with the\n    result of the :meth:`~rateslib.instruments.protocols._WithRate.rate` method called on each\n    of the successive ``instruments``.\n\n    Each *Instrument* provided may set its pricing objects (i.e. ``curves``\n    and ``vol``) and ``metric`` preset at its initialization, so that the\n    :meth:`~rateslib.instruments.Metrics.rate` method for each *Instrument* in scope is\n    well defined. Best practice refers to these with string mappings that the *Solver*\n    records. As an example,\n\n    .. code-block:: python\n\n       instruments=[\n           ...\n           FXCall([args], curves=[\"eur\", \"usd\"], vol=\"eurusd_smile\", metric=\"vol\"),\n           ...\n       ]\n\n    The ``fx`` argument used in the :meth:`~rateslib.instruments.protocols._WithRate.rate` call will\n    be passed directly to each *Instrument* from the *Solver's* ``fx`` argument, being\n    representative of a consistent *FXForwards* object for all *Instruments*.\n\n    If the pricing objects and/or *metric* are not preset then the *Solver* ``instruments`` can be\n    given as a tuple where the second item is a dict representing keyword arguments passed\n    directly to the :meth:`~rateslib.instruments.protocols._WithRate.rate`\n    method. An example is:\n\n    .. code-block:: python\n\n       instruments=[\n           ...\n           (FixedRateBond([args]), {\"curves\": bond_curve, \"metric\": \"ytm\"}),\n           ...\n       ]\n\n    **Stopping Criteria**\n\n    - ``func_tol``: :math:`g(\\mathbf{v}_{i+1}; \\mathbf{s}) < \\epsilon_{func}`. This criteria is\n      only useful for the cases when the number of parameters and number of instruments are\n      sufficiently chosen that an objective function value close to zero is obtainable.\n    - ``conv_tol``: :math:`|g(\\mathbf{v}_{i+1}) - g(\\mathbf{v}_{i})| < \\epsilon_{conv}` and the\n      iterate is an improvement.\n    - ``grad_tol``: :math:`|| \\nabla_{\\mathbf{v}} g || < \\epsilon_{grad}`. This is often the\n      most robust indicator of having reached a stationary point in the optimisation and is a\n      necessary condition of optimality.\n    - ``step_tol``: :math:`|| \\mathbf{v}_{i+1} - \\mathbf{v}_{i} || < \\epsilon_{step}`. This\n      criteria is used mostly to detect 'stalled' or 'stuck' solutions. Even though the *Solver*\n      reports a success stopping under these conditions may be sub-optimal.\n\n    **Analysing**\n\n    The ``callback`` argument can be used to display results or perform tasks during iterations.\n    The signature of such a method is `callback(solver, i, v_i)` giving access to the *Solver*\n    object itself, the iteration number and the current parameter vector.\n\n    .. ipython:: python\n\n       curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0})\n       solver = Solver(\n           curves=[curve],\n           instruments=[IRS(dt(2022, 1, 1), \"6m\", spec=\"usd_irs\", curves=curve)],\n           s=[3.0],\n           callback=lambda solver, i, v_i: print(f\"iteration {i}: {v_i}\"),\n       )\n\n    Examples\n    --------\n\n    See the documentation user guide :ref:`here <c-solver-doc>`.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        curves: Sequence[Any] = (),\n        surfaces: Sequence[Any] = (),\n        instruments: Sequence[SupportsRate] = (),\n        s: Sequence[DualTypes] = (),\n        weights: Sequence[float] | NoInput = NoInput(0),\n        algorithm: str_ = NoInput(0),\n        fx: FXForwards_ = NoInput(0),\n        instrument_labels: Sequence[str] | NoInput = NoInput(0),\n        id: str_ = NoInput(0),  # noqa: A002\n        pre_solvers: Sequence[Solver] = (),\n        max_iter: int = 100,\n        func_tol: float = 1e-11,\n        conv_tol: float = 1e-14,\n        step_tol: float = 1e-14,\n        grad_tol: float = 1e-11,\n        ini_lambda: tuple[float, float, float] | NoInput = NoInput(0),\n        callback: Callable[[Solver, int, NDArray[Nobject]], None] | NoInput = NoInput(0),\n    ) -> None:\n        self._do_not_validate_ = False\n        self.callback = callback\n        self.algorithm = _drb(defaults.algorithm, algorithm).lower()\n        self.ini_lambda = _drb(defaults.ini_lambda, ini_lambda)\n        self.id: str = _drb(uuid4().hex[:5] + \"_\", id)  # 1 in a million clash\n        self.m = len(instruments)\n        self.func_tol, self.conv_tol, self.max_iter = func_tol, conv_tol, max_iter\n        self.step_tol, self.grad_tol = step_tol, grad_tol\n        self.pre_solvers = tuple(pre_solvers)\n\n        # validate `id`s so that DataFrame indexing does not share duplicated keys.\n        if len(set([self.id] + [p.id for p in self.pre_solvers])) < 1 + len(self.pre_solvers):\n            raise ValueError(\n                \"Solver `id`s must be unique when supplying `pre_solvers`, \"\n                f\"got ids: {[self.id] + [p.id for p in self.pre_solvers]}\",\n            )\n\n        # validate `s` and `instruments` with a naive length comparison\n        if len(s) != len(instruments):\n            raise ValueError(\n                f\"`s: {len(s)}` (rates)  must be same length as `instruments: {len(instruments)}`.\"\n            )\n        self.s = np.asarray(s)\n\n        # validate `instrument_labels` if given is same length as `m`\n        if not isinstance(instrument_labels, NoInput):\n            if self.m != len(instrument_labels):\n                raise ValueError(\n                    f\"`instrument_labels: {len(instrument_labels)}` must be same length as \"\n                    f\"`instruments: {len(instruments)}`.\"\n                )\n            else:\n                self.instrument_labels = tuple(instrument_labels)\n        else:\n            self.instrument_labels = tuple(f\"{self.id}{i}\" for i in range(self.m))\n\n        if isinstance(weights, NoInput):\n            self.weights: NDArray[Nf64] = np.ones(len(instruments), dtype=np.float64)\n        else:\n            if len(weights) != self.m:\n                raise ValueError(\n                    f\"`weights: {len(weights)}` must be same length as \"\n                    f\"`instruments: {len(instruments)}`.\"\n                )\n            self.weights = np.asarray(weights)\n        self.W = np.diag(self.weights)\n\n        # `surfaces` are treated identically to `curves`. Introduced in PR\n        self.curves: dict[str, SupportsSolverMutability] = {\n            curve.id: curve\n            for curve in list(curves) + list(surfaces)\n            if type(curve) not in NO_PARAMETER_CURVES\n        }\n        self.variables = ()\n        for curve in self.curves.values():\n            curve._set_ad_order(1)  # solver uses gradients in optimisation\n            self.variables += curve._get_node_vars()\n        self.n = len(self.variables)\n\n        # aggregate and organise variables and labels including pre_solvers\n        self.pre_curves: dict[str, Any] = {}\n        self.pre_variables: tuple[str, ...] = ()\n        self.pre_instrument_labels: tuple[tuple[str, str], ...] = ()\n        self.pre_instruments: tuple[tuple[SupportsRate, dict[str, Any]], ...] = ()\n        self.pre_rate_scalars = []\n        self.pre_m, self.pre_n = self.m, self.n\n        curve_collection: list[Any] = []\n        for pre_solver in self.pre_solvers:\n            self.pre_variables += pre_solver.pre_variables\n            self.pre_instrument_labels += pre_solver.pre_instrument_labels\n            self.pre_instruments += pre_solver.pre_instruments\n            self.pre_rate_scalars.extend(pre_solver.pre_rate_scalars)\n            self.pre_m += pre_solver.pre_m\n            self.pre_n += pre_solver.pre_n\n            self.pre_curves.update(pre_solver.pre_curves)\n            curve_collection.extend(pre_solver.pre_curves.values())\n        self.pre_curves.update(self.curves)\n        self.pre_curves.update(\n            {\n                curve.id: curve\n                for curve in curves\n                if type(curve) in NO_PARAMETER_CURVES\n                # no parameter curves added to the collection without variables\n            },\n        )\n        curve_collection.extend(curves)\n        for curve1, curve2 in combinations(curve_collection, 2):\n            if curve1.id == curve2.id:\n                raise ValueError(\n                    \"`curves` must each have their own unique `id`. If using \"\n                    \"pre-solvers as part of a dependency chain a curve can only be \"\n                    \"specified as a variable in one solver.\",\n                )\n        self.pre_variables += self.variables\n        self.pre_instrument_labels += tuple((self.id, lbl) for lbl in self.instrument_labels)\n\n        # Final elements\n        self._ad = 1\n        self.fx: FXForwards_ = fx\n        if isinstance(self.fx, FXRates | FXForwards):\n            self.fx._set_ad_order(1)\n        elif not isinstance(self.fx, NoInput):\n            raise ValueError(\"`fx` argument to Solver must be either FXForwards or NoInput(0).\")\n        self.instruments: tuple[tuple[SupportsRate, dict[str, Any]], ...] = tuple(\n            self._parse_instrument(inst) for inst in instruments\n        )\n        self.pre_instruments += self.instruments\n        self.rate_scalars = tuple(inst[0].rate_scalar for inst in self.instruments)\n        self.pre_rate_scalars += self.rate_scalars\n\n        # TODO need to check curves associated with fx object and set order.\n        # self._reset_properties_()  performed in iterate\n        self._result = {\n            \"status\": \"INITIALISED\",\n            \"state\": 0,\n            \"g\": None,\n            \"iterations\": 0,\n            \"time\": None,\n        }\n        self.iterate()\n\n    def __repr__(self) -> str:\n        return f\"<rl.Solver:{self.id} at {hex(id(self))}>\"\n\n    # State management and mutation\n\n    def _set_new_state(self) -> None:\n        self._states = self._associated_states()\n        self._state = hash(sum(v for v in self._states.values()))\n\n    @property\n    def _do_not_validate(self) -> bool:\n        return self._do_not_validate_\n\n    @_do_not_validate.setter\n    def _do_not_validate(self, value: bool) -> None:\n        self._do_not_validate_ = value\n        for solver in self.pre_solvers:\n            solver._do_not_validate = value\n\n    def _validate_state(self) -> None:\n        if self._do_not_validate:\n            return None  # do not perform state validation during iterations\n        if self._state != self._get_composited_state():\n            # then something has been mutated\n            states_ = self._associated_states()\n            fx_state_ = states_.pop(\"fx\")\n\n            for k, v in states_.items():\n                if self._states[k] != v:\n                    raise ValueError(\n                        \"The `curves` associated with `solver` have been updated without the \"\n                        \"`solver` performing additional iterations.\\n\"\n                        f\"In particular the object with id: '{k}' contained in solver with id: \"\n                        f\"'{self.id}' is detected to have been mutated.\\n\"\n                        \"Calculations are prevented in this \"\n                        \"state because they will likely be erroneous or a consequence of a bad \"\n                        \"design pattern.\"\n                    )\n\n            if not isinstance(self.fx, NoInput) and fx_state_ != self._states[\"fx\"]:\n                warnings.warn(\n                    f\"The `fx` object associated with `solver` having id '{self.id}' \"\n                    \"has been updated without \"\n                    \"the `solver` performing additional iterations.\\nCalculations can still be \"\n                    \"performed but, dependent upon those updates, errors may be negligible \"\n                    \"or significant.\",\n                    UserWarning,\n                )\n\n    @staticmethod\n    def _validate_and_get_state(obj: Any) -> int:\n        obj._validate_state()\n        return obj._state  # type: ignore[no-any-return]\n\n    def _associated_states(self) -> dict[str, int]:\n        states_: dict[str, int] = {\n            k: self._validate_and_get_state(v) for k, v in self.pre_curves.items()\n        }\n        if not isinstance(self.fx, NoInput):\n            states_[\"fx\"] = self._validate_and_get_state(self.fx)\n        else:\n            states_[\"fx\"] = 0\n        return states_\n\n    def _get_composited_state(self) -> int:\n        _: int = hash(sum(v for v in self._associated_states().values()))\n        return _\n\n    def _parse_instrument(\n        self, value: SupportsRate | tuple[SupportsRate, dict[str, Any]]\n    ) -> tuple[SupportsRate, dict[str, Any]]:\n        \"\"\"\n        Parses different input formats for an instrument given to the ``Solver``.\n\n        Parameters\n        ----------\n        value : Instrument or 3-tuple.\n            If a 3-tuple then it must have the following items:\n\n            - The ``Instrument``.\n            - Positional args supplied to the ``rate`` method as a tuple, or None.\n            - Keyword args supplied to the ``rate`` method as a dict, or None.\n\n        Returns\n        -------\n        tuple :\n            A 3-tuple attaching the self solver and self fx object as pricing params.\n\n        Examples\n        --------\n        ``value=Instrument()``\n\n        ``value=(Instrument(), (curve, None, fx), {\"other_arg\": 10.0})``\n\n        ``value=(Instrument(), None, {\"other_arg\": 10.0})``\n\n        ``value=(Instrument(), (curve, None, fx), None)``\n\n        ``value=(Instrument(), (curve,), {})``\n        \"\"\"\n        if not isinstance(value, tuple):\n            # is a direct Instrument so convert to tuple with pricing params\n            _: tuple[SupportsRate, dict[str, Any]] = (\n                value,\n                {\"solver\": self, \"fx\": self.fx},\n            )\n            return _\n        else:\n            # object is tuple\n            if len(value) != 2:\n                raise ValueError(\n                    \"`Instrument` supplied to `Solver` as tuple must be a 2-tuple of \"\n                    \"signature: (Instrument, keyword args[dict]).\",\n                )\n            ret0 = value[0]\n            ret1: dict[str, Any] = {\"solver\": self, \"fx\": self.fx}\n            if not (value[1] is None or value[1] == {}):\n                ret1 = {**ret1, **value[1]}\n            return ret0, ret1\n\n    def _reset_properties_(self, dual2_only: bool = False) -> None:\n        \"\"\"\n        Set all calculated attributes to `None` requiring re-evaluation.\n\n        Parameters\n        ----------\n        dual2_only : bool\n            Choose whether to reset properties only for the calculation of the\n            properties whose derivation **requires** Dual2 datatypes. Since the\n            ``Solver`` iterates ``Curve`` s by default it necessarily uses Dual\n            datatypes and first order derivatives. For the calculation of:\n\n              - ``J2`` and ``J2_pre``:\n                :math:`\\frac{\\\\partial^2 r_i}{\\\\partial v_j \\\\partial v_k}`\n              - ``grad_s_s_vT`` and ``grad_s_s_vT_pre``:\n                :math:`\\frac{\\\\partial^2 v_i}{\\\\partial s_j \\\\partial s_k}`\n\n        Returns\n        -------\n        None\n        \"\"\"\n        if not dual2_only:\n            self._v: NDArray[Nobject] | None = None  # depends on self.curves\n            self._r: NDArray[Nobject] | None = (\n                None  # depends on self.pre_curves and self.instruments\n            )\n            self._r_pre: NDArray[Nobject] | None = None  # depends on pre_solvers and self.r\n            self._x: NDArray[Nobject] | None = None  # depends on self.r, self.s\n            self._g: Dual | Dual2 | None = None  # depends on self.x, self.weights\n            self._grad_v_g: NDArray[Nf64] | None = None  # depends on self.g,\n            self._J: NDArray[Nf64] | None = None  # depends on self.r\n            self._grad_s_vT: NDArray[Nf64] | None = (\n                None  # final_iter_dual: depends on self.s and iteration\n            )\n            # fixed_point_iter: depends on self.f\n            # final_iter_anal: depends on self.J\n            self._grad_s_vT_pre: NDArray[Nf64] | None = (\n                None  # depends on self.grad_s_vT and pre_solvers.\n            )\n\n        self._J2 = None  # defines its own self.r under dual2\n        self._J2_pre = None  # depends on self.r and pre_solvers\n        self._grad_s_s_vT = None  # final_iter: depends on self.J2 and self.grad_s_vT\n        # finite_diff: TODO update comment\n        self._grad_s_s_vT_pre = None  # final_iter: depends on pre versions of above\n        # finite_diff: TODO update comment\n\n        # self._grad_v_v_f = None\n        # self._Jkm = None  # keep manifold originally used for exploring J2 calc method\n\n    # Pricing object ID mapping\n\n    @_validate_states\n    def _get_pre_curve(self, obj: str) -> Curve:\n        ret: Curve | FXVols | IRVols = self.pre_curves[obj]\n        if isinstance(ret, _BaseCurve):\n            return ret\n        else:\n            raise ValueError(\n                f\"A _BaseCurve object was sought with id:'{obj}' from Solver but another \"\n                f\"type object was returned:'{type(ret)}'.\"\n            )\n\n    @_validate_states\n    def _get_pre_fxvol(self, obj: str) -> FXVols:\n        _: Curve | FXVols | IRVols = self.pre_curves[obj]\n        if isinstance(_, FXVols):\n            return _\n        else:\n            raise ValueError(\n                f\"A type of `FXVol` object was sought with id:'{obj}' from Solver but another \"\n                f\"type object was returned:'{type(_)}'.\"\n            )\n\n    @_validate_states\n    def _get_pre_irvol(self, obj: str) -> _BaseIRSmile | _BaseIRCube[Any]:\n        _: Curve | FXVols | _BaseIRSmile | _BaseIRCube[Any] = self.pre_curves[obj]\n        if isinstance(_, _BaseIRSmile | _BaseIRCube):\n            return _\n        else:\n            raise ValueError(\n                f\"A type of `IRVol` object was sought with id:'{obj}' from Solver but another \"\n                f\"type object was returned:'{type(_)}'.\"\n            )\n\n    @_validate_states\n    def _get_fx(self) -> FXForwards_:\n        return self.fx\n\n    # Attributes\n\n    @property\n    def result(self) -> dict[str, Any]:\n        \"\"\"\n        Show statistics relevant to the last *Solver* iteration.\n\n        Valid *Solver* states are:\n\n        - 1: Success within tolerance of objective function close to zero.\n        - 2: Success within tolerance of successive iteration function values.\n        - 4: Success within tolerance of norm of difference of successive iteration parameter values.\n        - 5: Success within tolerance of function gradient norm close to zero.\n        - -1: Failed to satisfy tolerance after maximal allowed iteration.\n        \"\"\"  # noqa: E501\n        return self._result\n\n    @property\n    def v(self) -> NDArray[Nobject]:\n        \"\"\"\n        1d array of curve node variables for each ordered curve, size (n,).\n\n        Depends on ``self.curves``.\n        \"\"\"\n        if self._v is None:\n            self._v = np.block([_._get_node_vector() for _ in self.curves.values()])\n        return self._v\n\n    @property\n    def r(self) -> NDArray[Nobject]:  # type: ignore[override]\n        \"\"\"\n        1d array of mid-market rates of each calibrating instrument with given curves,\n        size (m,).\n\n        Depends on ``self.pre_curves`` and ``self.instruments``.\n        \"\"\"\n        if self._r is None:\n            self._r = np.array([_[0].rate(**_[1]) for _ in self.instruments])\n            # solver and fx are passed by default via parse_args to get string curves\n        return self._r\n\n    @property\n    def r_pre(self) -> NDArray[Nobject]:  # type: ignore[override]\n        if len(self.pre_solvers) == 0:\n            return self.r\n\n        if self._r_pre is None:\n            r_pre = np.empty(self.pre_m, dtype=\"object\")\n\n            i = 0\n            for pre_solver in self.pre_solvers:\n                m = pre_solver.pre_m\n                r_pre[i : i + m] = pre_solver.r_pre\n                i = i + m\n\n            if self.m > 0:\n                # create bottom right block if solver contains its own instruments and self\n                # is not just a container of `pre_solvers`\n                r_pre[-self.m :] = self.r\n\n            self._r_pre = r_pre\n        return self._r_pre\n\n    @property\n    def x(self) -> NDArray[Nobject]:\n        \"\"\"\n        1d array of error in each calibrating instrument rate, of size (m,).\n\n        .. math::\n\n           \\\\mathbf{x} = \\\\mathbf{r-S}\n\n        Depends on ``self.r`` and ``self.s``.\n        \"\"\"\n        if self._x is None:\n            self._x = self.r - self.s\n        return self._x\n\n    @property\n    def error(self) -> Series[float]:\n        \"\"\"\n        Return the error in calibrating instruments, including ``pre_solvers``, scaled\n        to the risk representation factor.\n\n        Returns\n        -------\n        Series\n        \"\"\"\n        pre_s: Series[float] = Series()\n        for pre_solver in self.pre_solvers:\n            if not pre_s.empty:\n                pre_s = concat([ser for ser in [pre_solver.error, pre_s] if not ser.empty])\n            else:\n                pre_s = pre_solver.error\n\n        if self.m > 0:\n            _: Series[float] = Series(\n                self.x.astype(float) * 100 / self.rate_scalars,\n                index=MultiIndex.from_tuples([(self.id, inst) for inst in self.instrument_labels]),\n            )\n            if not pre_s.empty:\n                s: Series[float] = concat([pre_s, _])\n            else:\n                s = _\n        else:\n            s = pre_s\n\n        return s\n\n    @property\n    def g(self) -> Dual | Dual2:  # type: ignore[override]\n        \"\"\"\n        Objective function scalar value of the solver;\n\n        .. math::\n\n           g = \\\\mathbf{(r-S)^{T}W(r-S)}\n\n        Depends on ``self.x`` and ``self.weights``.\n        \"\"\"\n        if self._g is None:\n            self._g = np.dot(self.x, self.weights * self.x)\n        return self._g\n\n    # def Jkm(self, extra_vars=[]):\n    #     \"\"\"\n    #     2d Jacobian array of rates with respect to discount factors, of size (n, m); :math:`[J]_{i,j} = \\\\frac{\\\\partial r_j}{\\\\partial v_i}`.  # noqa: E501\n    #     \"\"\"\n    #     _Jkm = np.array([rate.gradient(self.variables + extra_vars, keep_manifold=True) for rate in self.r]).T  # noqa: E501\n    #     return _Jkm\n\n    def _update_step_(self, algorithm: str) -> NDArray[Nobject]:\n        if algorithm == \"gradient_descent\":\n            y = np.matmul(self.J.transpose(), self.grad_v_g[:, np.newaxis])[:, 0]\n            alpha = np.dot(y, self.weights * self.x) / np.dot(y, self.weights * y)\n            v_1: NDArray[Nobject] = self.v - self.grad_v_g * alpha.real\n        elif algorithm == \"gauss_newton\":\n            if self.J.shape[0] == self.J.shape[1]:  # square system\n                A = self.J.transpose()\n                b = -np.array([x.real for x in self.x])[:, np.newaxis]\n            else:\n                A = np.matmul(self.J, np.matmul(self.W, self.J.transpose()))\n                b = -0.5 * self.grad_v_g[:, np.newaxis]\n            delta: NDArray[Nobject] = np.linalg.solve(A, b)[:, 0]\n            v_1 = self.v + delta\n        elif algorithm == \"levenberg_marquardt\":\n            if self.g_list[-2] < self.g.real:\n                # reject previous iteration and rescale lambda:\n                self.lambd *= self.ini_lambda[2]\n                # self._update_curves_with_parameters(self.v_prev)\n            else:\n                self.lambd *= self.ini_lambda[1]\n            # self.lambd *= self.ini_lambda[2] if self.g_prev < self.g.real else self.ini_lambda[1]\n            A = np.matmul(self.J, np.matmul(self.W, self.J.transpose()))\n            A += self.lambd * np.eye(self.n)\n            b = -0.5 * self.grad_v_g[:, np.newaxis]\n            delta = np.linalg.solve(A, b)[:, 0]\n            v_1 = self.v + delta\n        # elif algorithm == \"gradient_descent_final\":\n        #     _ = np.matmul(self.Jkm, np.matmul(self.W, self.x[:, np.newaxis]))\n        #     y = 2 * np.matmul(self.Jkm.transpose(), _)[:, 0]\n        #     alpha = np.dot(y, self.weights * self.x) / np.dot(y, self.weights * y)\n        #     v_1 = self.v - 2 * alpha * _[:, 0]\n        elif algorithm == \"gauss_newton_final\":\n            if self.J.shape[0] == self.J.shape[1]:  # square system\n                A = self.J.transpose()\n                b = -self.x[:, np.newaxis]\n            else:\n                A = np.matmul(self.J, np.matmul(self.W, self.J.transpose()))\n                b = -np.matmul(np.matmul(self.J, self.W), self.x[:, np.newaxis])\n\n            delta = dual_solve(A, b)[:, 0]  # type: ignore[assignment]\n            v_1 = self.v + delta\n        else:\n            raise NotImplementedError(f\"`algorithm`: {algorithm} (spelled correctly?)\")\n        return v_1\n\n    @_new_state_post\n    def _update_fx(self) -> None:\n        if not isinstance(self.fx, NoInput):\n            self.fx.update()  # note: with no variables this only updates states\n        for solver in self.pre_solvers:\n            solver._update_fx()\n\n    @_no_interior_validation\n    def iterate(self) -> None:\n        r\"\"\"\n        Solve the DF node values and update all the ``curves``.\n\n        This method uses a gradient based optimisation routine, to solve for all\n        the curve variables, :math:`\\mathbf{v}`, as follows,\n\n        .. math::\n\n           \\mathbf{v} = \\underset{\\mathbf{v}}{\\mathrm{argmin}} \\;\\; f(\\mathbf{v}) = \\underset{\\mathbf{v}}{\\mathrm{argmin}} \\;\\; (\\mathbf{r(v)} - \\mathbf{S})\\mathbf{W}(\\mathbf{r(v)} - \\mathbf{S})^\\mathbf{T}\n\n        where :math:`\\mathbf{r}` are the mid-market rates of the calibrating\n        instruments, :math:`\\mathbf{S}` are the observed and target rates, and\n        :math:`\\mathbf{W}` is the diagonal array of weights.\n\n        Returns\n        -------\n        None\n        \"\"\"  # noqa: E501\n\n        # Initialise data and clear and caches\n        self.g_list: list[float] = [1e10]\n        self.lambd: float = self.ini_lambda[0]\n        self._reset_properties_()\n        # self._update_fx()\n        t0 = time()\n\n        # Begin iteration\n        for i in range(self.max_iter):\n            self.g_list.append(self.g.real)\n            if self.g.real < self.g_list[i] and (self.g_list[i] - self.g.real) < self.conv_tol:\n                # Converge tolerance: |g(x_i+1) - g(x_i)| < conv_tol AND a better iterate.\n                # Condition enforces a better iterate to avoid the case where a null update\n                # results in the same solution and this is erroneously stopped due to this criteria.\n                return self._solver_result(1, i, time() - t0)\n            elif self.g.real < self.func_tol:\n                # Function tolerance: 0 <= g(x_i+1) < func_tol.\n                return self._solver_result(2, i, time() - t0)\n            elif np.sqrt(np.dot(self.grad_v_g, self.grad_v_g)) < self.grad_tol:\n                # Gradient tolerance: |d_v_g(x_i+1)| < grad_tol.\n                return self._solver_result(5, i, time() - t0)\n\n            if i != 0:\n                eps = v_1.astype(float, copy=True) - v_0  # type: ignore[has-type]  # noqa: F821\n                if np.sqrt(np.dot(eps, eps)) < self.step_tol:\n                    # Step tolerance: |x_i+1 - x_i| < step_tol.\n                    return self._solver_result(4, i, time() - t0)\n\n            v_0 = self.v.astype(float, copy=True)  # noqa: F841\n            v_1 = self._update_step_(self.algorithm)\n            # self.v_prev = v_0\n            self._update_curves_with_parameters(v_1)\n\n            if not isinstance(self.callback, NoInput):\n                self.callback(self, i, v_1)\n\n        return self._solver_result(-1, self.max_iter, time() - t0)\n\n    def _solver_result(self, state: int, i: int, time: float) -> None:\n        self._result = _solver_result(state, i, self.g.real, time, True, self.algorithm)\n        self._set_new_state()\n\n    @_new_state_post\n    def _update_curves_with_parameters(self, v_new: NDArray[Nobject]) -> None:\n        \"\"\"Populate the variable curves with the new values\"\"\"\n        var_counter = 0\n        for curve in self.curves.values():\n            # this was amended in PR126 as performance improvement to keep consistent `vars`\n            # and was restructured in PR## to decouple methods to accomodate vol surfaces\n            n_vars = curve._n - curve._ini_solve\n            curve._set_node_vector(v_new[var_counter : var_counter + n_vars], self._ad)\n            var_counter += n_vars\n\n        self._update_fx()\n        self._reset_properties_()\n\n    def _set_ad_order(self, order: int) -> None:\n        \"\"\"Defines the node DF in terms of float, Dual or Dual2 for AD order calcs.\"\"\"\n        for pre_solver in self.pre_solvers:\n            pre_solver._set_ad_order(order=order)\n        self._ad = order\n        for _, curve in self.pre_curves.items():\n            curve._set_ad_order(order)\n        if not isinstance(self.fx, NoInput):\n            self.fx._set_ad_order(order)\n        self._reset_properties_()\n\n    @_validate_states\n    @_no_interior_validation\n    def delta(\n        self, npv: dict[str, Dual], base: str_ = NoInput(0), fx: FX_ = NoInput(0)\n    ) -> DataFrame:\n        \"\"\"\n        Calculate the delta risk sensitivity of an instrument's NPV to the\n        calibrating instruments of the :class:`~rateslib.solver.Solver`, and to\n        FX rates.\n\n        Parameters\n        ----------\n        npv : dict,\n            The NPV (Dual) of the instrument or portfolio of instruments to risk.\n            Must be indexed by 3-digit currency\n            to discriminate between values expressed in different currencies.\n        base : str, optional\n            The currency (3-digit code) to report risk metrics in. If not given will\n            default to the local currency of the cashflows.\n        fx : FXRates, FXForwards, optional\n            The FX object to use to convert risk metrics. If needed but not given\n            will default to the ``fx`` object associated with the\n            :class:`~rateslib.solver.Solver`. It is not recommended to use this\n            argument with multi-currency instruments, see notes.\n\n        Returns\n        -------\n        DataFrame\n\n        Notes\n        -----\n\n        **Output Structure**\n\n        .. note::\n\n           *Instrument* values are scaled to 1bp (1/10000th of a unit) when they are\n           rate based. *FX* values are scaled to pips (1/10000th of an FX rate unit).\n\n        The output ``DataFrame`` has the following structure:\n\n        - A 3-level index by *'type'*, *'solver'*, and *'label'*;\n\n          - **type** is either *'instruments'* or *'fx'*, and fx exposures are only\n            calculated and displayed in some cases where genuine FX exposure arises.\n          - **solver** lists the different solver ``id`` s to identify between\n            different instruments in dependency chains from ``pre_solvers``.\n          - **label** lists the given instrument names in each solver using the\n            ``instrument_labels``.\n\n        - A 2-level column header index by *'local_ccy'* and *'display_ccy'*;\n\n          - **local_ccy** displays the currency for which cashflows are payable, and\n            therefore the local currency risk sensitivity amount.\n          - **display_ccy** displays the currency which the local currency risk\n            sensitivity has been converted to via an FX transformation.\n\n        Converting a delta from a local currency to another ``base`` currency also\n        introduces FX risk to the NPV of the instrument, which is included in the\n        output.\n\n        **Best Practice**\n\n        The ``fx`` option is provided to allow tactical and fast conversion of\n        delta risks to ``Instruments``. When constructing and pricing multi-currency\n        instruments it is likely that the :class:`~rateslib.solver.Solver` used is\n        associated with an :class:`~rateslib.fx.FXForwards` object to consistently\n        produce FX forward rates within an aribitrage free framework. In that case\n        it is more consistent to re-use those FX associations. If such an\n        association exists and a direct ``fx`` object is supplied a warning may be\n        emitted if they are not the same object.\n        \"\"\"\n        # self._do_not_validate = True  # state is validated prior to the call\n        base, fx = self._get_base_and_fx(base, fx)\n        if isinstance(fx, FXRates | FXForwards):\n            fx_vars: tuple[str, ...] = fx.variables\n        else:\n            fx_vars = tuple()\n\n        inst_scalar = np.array(self.pre_rate_scalars) / 100  # instruments scalar\n        fx_scalar = 0.0001\n        container = {}\n        for ccy in npv:\n            container[(\"instruments\", ccy, ccy)] = self.grad_s_Ploc(npv[ccy]) * inst_scalar\n            container[(\"fx\", ccy, ccy)] = self.grad_f_Ploc(npv[ccy], fx_vars) * fx_scalar\n\n            if not isinstance(base, NoInput) and base != ccy:\n                # is validated by  `_get_base_and _fx`\n                assert isinstance(fx, FXForwards | FXRates)  # noqa: S101\n                # extend the derivatives\n                f: Dual | Dual2 = fx.rate(f\"{ccy}{base}\")  # type: ignore[assignment]\n                container[(\"instruments\", ccy, base)] = (\n                    self.grad_s_Pbase(\n                        npv[ccy],\n                        container[(\"instruments\", ccy, ccy)] / inst_scalar,\n                        f,\n                    )\n                    * inst_scalar\n                )\n                container[(\"fx\", ccy, base)] = (\n                    self.grad_f_Pbase(npv[ccy], container[(\"fx\", ccy, ccy)] / fx_scalar, f, fx_vars)\n                    * fx_scalar\n                )\n\n        # construct the DataFrame from container with hierarchical indexes\n        inst_idx = MultiIndex.from_tuples(\n            [(\"instruments\",) + label for label in self.pre_instrument_labels],\n            names=[\"type\", \"solver\", \"label\"],\n        )\n        fx_idx = MultiIndex.from_tuples(\n            [(\"fx\", \"fx\", f[3:]) for f in fx_vars],\n            names=[\"type\", \"solver\", \"label\"],\n        )\n        indexes = {\"instruments\": inst_idx, \"fx\": fx_idx}\n        r_idx = inst_idx.append(fx_idx)\n        c_idx = MultiIndex.from_tuples([], names=[\"local_ccy\", \"display_ccy\"])\n        df = DataFrame(None, index=r_idx, columns=c_idx)\n        for key, array in container.items():\n            df.loc[indexes[key[0]], (key[1], key[2])] = array\n\n        if not isinstance(base, NoInput):\n            df.loc[r_idx, (\"all\", base)] = df.loc[r_idx, (slice(None), base)].sum(axis=1)\n\n        sorted_cols = df.columns.sort_values()\n        ret: DataFrame = df.loc[:, sorted_cols].astype(\"float64\")\n        # self._do_not_validate = False\n        return ret\n\n    def _get_base_and_fx(self, base: str_, fx: FX_) -> tuple[str_, FX_]:\n        # method is used by delta, gamma, and exo_delta. prohibit fx as scalar because it cannot\n        # convert from arbitrary currencies.\n        if not isinstance(fx, NoInput | FXRates | FXForwards):\n            raise ValueError(\n                \"`fx` used in sensitivity calculations cannot be a scalar. An FXRates or \"\n                \"FXForwards object is required, or the input left as NoInput(0), in which case \"\n                \"the `fx` object associated with a Solver is used in place.\"\n            )\n\n        if not isinstance(base, NoInput):\n            base = base.lower()\n            # then a valid fx object that can convert is required.\n            if not isinstance(fx, FXRates | FXForwards) and isinstance(self.fx, NoInput):\n                raise ValueError(\n                    f\"`base` is given as '{base}', but `fx` is not available.\\n\"\n                    \"Either provide an FXForwards object directly as `fx` or ensure that Solver.fx \"\n                    \"is a valid object.\\n\"\n                    \"Alternatively, omit the `base` argument altogether and get results displayed \"\n                    \"in local currency without base currency conversion.\"\n                )\n\n        if isinstance(fx, NoInput):\n            fx = self.fx\n        elif not isinstance(self.fx, NoInput) and id(fx) != id(self.fx):\n            warnings.warn(\n                \"Solver contains an `fx` object but an `fx` argument has been \"\n                \"supplied as object which is not the same. This can lead to risk sensitivity \"\n                \"inconsistencies, mathematically.\",\n                UserWarning,\n            )\n\n        return base, fx\n\n    @_validate_states\n    @_no_interior_validation\n    def gamma(\n        self, npv: dict[str, Dual2], base: str_ = NoInput(0), fx: FX_ = NoInput(0)\n    ) -> DataFrame:\n        \"\"\"\n        Calculate the cross-gamma risk sensitivity of an instrument's NPV to the\n        calibrating instruments of the :class:`~rateslib.solver.Solver`.\n\n        Parameters\n        ----------\n        npv : Dual2,\n            The NPV of the instrument or composition of instruments to risk.\n        base : str, optional\n            The currency (3-digit code) to report risk metrics in. If not given will\n            default to the local currency of the cashflows.\n        fx : FXRates, FXForwards, optional\n            The FX object to use to convert risk metrics. If needed but not given\n            will default to the ``fx`` object associated with the\n            :class:`~rateslib.solver.Solver`. It is not recommended to use this\n            argument with multi-currency instruments, see\n            :meth:`Solver.delta <rateslib.solver.Solver.delta>`.\n\n        Returns\n        -------\n        DataFrame\n\n        Notes\n        -----\n        .. note::\n\n           *Instrument* values are scaled to 1bp (1/10000th of a unit) when they are\n           rate based.\n\n           *FX* values are scaled to pips (1/10000th of an FX unit).\n\n        The output ``DataFrame`` has the following structure:\n\n        - A 5-level index by *'local_ccy'*, *'display_ccy'*, *'type'*, *'solver'*,\n          and *'label'*;\n\n          - **local_ccy** displays the currency for which cashflows are payable, and\n            therefore the local currency risk sensitivity amount.\n          - **display_ccy** displays the currency which the local currency risk\n            sensitivity has been converted to via an FX transformation.\n          - **type** is either *'instruments'* or *'fx'*, and fx exposures are only\n            calculated and displayed in some cases where genuine FX exposure arises.\n          - **solver** lists the different solver ``id`` s to identify between\n            different instruments in dependency chains from ``pre_solvers``.\n          - **label** lists the given instrument names in each solver using the\n            ``instrument_labels``.\n\n        - A 3-level column header index using the last three levels of the above;\n\n        Converting a gamma/delta from a local currency to another ``base`` currency also\n        introduces FX risk to the NPV of the instrument, which is included in the\n        output.\n\n        Examples\n        --------\n        This example replicates the analytical calculations demonstrated in\n        *Pricing and Trading Interest Rate Derivatives (2022)*, derived from\n        first principles.\n        The results are stated in the cross-gamma grid in figure 22.1.\n\n        .. ipython:: python\n           :suppress:\n\n           from rateslib import Solver, Curve, SBS, IRS, dt\n\n        .. ipython:: python\n\n           curve_r = Curve(\n               nodes={\n                   dt(2022, 1, 1): 1.0,\n                   dt(2023, 1, 1): 0.99,\n                   dt(2024, 1, 1): 0.98,\n                   dt(2025, 1, 1): 0.97,\n                   dt(2026, 1, 1): 0.96,\n                   dt(2027, 1, 1): 0.95,\n               },\n               id=\"r\"\n           )\n           curve_z = Curve(\n               nodes={\n                   dt(2022, 1, 1): 1.0,\n                   dt(2023, 1, 1): 0.99,\n                   dt(2024, 1, 1): 0.98,\n                   dt(2025, 1, 1): 0.97,\n                   dt(2026, 1, 1): 0.96,\n                   dt(2027, 1, 1): 0.95,\n               },\n               id=\"z\"\n           )\n           curve_s = Curve(\n               nodes={\n                   dt(2022, 1, 1): 1.0,\n                   dt(2023, 1, 1): 0.99,\n                   dt(2024, 1, 1): 0.98,\n                   dt(2025, 1, 1): 0.97,\n                   dt(2026, 1, 1): 0.96,\n                   dt(2027, 1, 1): 0.95,\n               },\n               id=\"s\"\n           )\n           args = dict(termination=\"1Y\", frequency=\"A\", fixing_method=\"ibor(0)\", leg2_fixing_method=\"ibor(0)\")\n           instruments = [\n               SBS(dt(2022, 1, 1), curves=[\"r\", \"s\", \"s\", \"s\"], **args),\n               SBS(dt(2023, 1, 1), curves=[\"r\", \"s\", \"s\", \"s\"], **args),\n               SBS(dt(2024, 1, 1), curves=[\"r\", \"s\", \"s\", \"s\"], **args),\n               SBS(dt(2025, 1, 1), curves=[\"r\", \"s\", \"s\", \"s\"], **args),\n               SBS(dt(2026, 1, 1), curves=[\"r\", \"s\", \"s\", \"s\"], **args),\n               SBS(dt(2022, 1, 1), curves=[\"r\", \"s\", \"z\", \"s\"], **args),\n               SBS(dt(2023, 1, 1), curves=[\"r\", \"s\", \"z\", \"s\"], **args),\n               SBS(dt(2024, 1, 1), curves=[\"r\", \"s\", \"z\", \"s\"], **args),\n               SBS(dt(2025, 1, 1), curves=[\"r\", \"s\", \"z\", \"s\"], **args),\n               SBS(dt(2026, 1, 1), curves=[\"r\", \"s\", \"z\", \"s\"], **args),\n               IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=[\"r\", \"s\"], leg2_fixing_method=\"ibor(0)\"),\n               IRS(dt(2023, 1, 1), \"1Y\", \"A\", curves=[\"r\", \"s\"], leg2_fixing_method=\"ibor(0)\"),\n               IRS(dt(2024, 1, 1), \"1Y\", \"A\", curves=[\"r\", \"s\"], leg2_fixing_method=\"ibor(0)\"),\n               IRS(dt(2025, 1, 1), \"1Y\", \"A\", curves=[\"r\", \"s\"], leg2_fixing_method=\"ibor(0)\"),\n               IRS(dt(2026, 1, 1), \"1Y\", \"A\", curves=[\"r\", \"s\"], leg2_fixing_method=\"ibor(0)\"),\n           ]\n           solver = Solver(\n               curves=[curve_r, curve_s, curve_z],\n               instruments=instruments,\n               s=[0.]*5 + [0.]*5 + [1.5]*5,\n               id=\"sonia\",\n               instrument_labels=[\n                   \"s1\", \"s2\", \"s3\", \"s4\", \"s5\",\n                   \"z1\", \"z2\", \"z3\", \"z4\", \"z5\",\n                   \"r1\", \"r2\", \"r3\", \"r4\", \"r5\",\n               ],\n           )\n           irs = IRS(dt(2022, 1, 1), \"5Y\", \"A\", notional=-8.3e8, curves=[\"z\", \"s\"], leg2_fixing_method=\"ibor(0)\", fixed_rate=25.0)\n           irs.delta(solver=solver)\n           irs.gamma(solver=solver)\n        \"\"\"  # noqa: E501\n        if self._ad != 2:\n            raise ValueError(\"`Solver` must be in ad order 2 to use `gamma` method.\")\n\n        # new\n        base, fx = self._get_base_and_fx(base, fx)\n        if isinstance(fx, FXRates | FXForwards):\n            fx_vars: tuple[str, ...] = fx.variables\n        else:\n            fx_vars = tuple()\n\n        inst_scalar = np.array(self.pre_rate_scalars) / 100  # instruments scalar\n        fx_scalar = np.ones(len(fx_vars)) * 0.0001\n        container: dict[tuple[str, str], dict[tuple[str, ...], Any]] = {}\n        for ccy in npv:\n            container[(ccy, ccy)] = {}\n            container[(ccy, ccy)][\"instruments\", \"instruments\"] = self.grad_s_sT_Ploc(\n                npv[ccy],\n            ) * np.matmul(inst_scalar[:, None], inst_scalar[None, :])\n            container[(ccy, ccy)][\"fx\", \"instruments\"] = self.grad_f_sT_Ploc(\n                npv[ccy],\n                fx_vars,\n            ) * np.matmul(fx_scalar[:, None], inst_scalar[None, :])\n            container[(ccy, ccy)][\"instruments\", \"fx\"] = container[(ccy, ccy)][\n                (\"fx\", \"instruments\")\n            ].T\n            container[(ccy, ccy)][\"fx\", \"fx\"] = self.grad_f_fT_Ploc(npv[ccy], fx_vars) * np.matmul(\n                fx_scalar[:, None],\n                fx_scalar[None, :],\n            )\n\n            if not isinstance(base, NoInput) and base != ccy:\n                # validated by `_get_base_and_fx`\n                assert isinstance(fx, FXRates | FXForwards)  # noqa: S101\n                # extend the derivatives\n                f: Dual | Dual2 = fx.rate(f\"{ccy}{base}\")  # type: ignore[assignment]\n                container[(ccy, base)] = {}\n                container[(ccy, base)][\"instruments\", \"instruments\"] = self.grad_s_sT_Pbase(\n                    npv[ccy],\n                    container[(ccy, ccy)][\"instruments\", \"instruments\"]\n                    / np.matmul(inst_scalar[:, None], inst_scalar[None, :]),\n                    f,\n                ) * np.matmul(inst_scalar[:, None], inst_scalar[None, :])\n                container[(ccy, base)][\"fx\", \"instruments\"] = self.grad_f_sT_Pbase(\n                    npv[ccy],\n                    container[(ccy, ccy)][\"fx\", \"instruments\"]\n                    / np.matmul(fx_scalar[:, None], inst_scalar[None, :]),\n                    f,\n                    fx_vars,\n                ) * np.matmul(fx_scalar[:, None], inst_scalar[None, :])\n                container[(ccy, base)][\"instruments\", \"fx\"] = container[(ccy, base)][\n                    (\"fx\", \"instruments\")\n                ].T\n                container[(ccy, base)][\"fx\", \"fx\"] = self.grad_f_fT_Pbase(\n                    npv[ccy],\n                    container[(ccy, ccy)][\"fx\", \"fx\"]\n                    / np.matmul(fx_scalar[:, None], fx_scalar[None, :]),\n                    f,\n                    fx_vars,\n                ) * np.matmul(fx_scalar[:, None], fx_scalar[None, :])\n\n        # construct the DataFrame from container with hierarchical indexes\n        currencies = list(npv.keys())\n        local_keys = [(ccy, ccy) for ccy in currencies]\n        base_keys = [] if base is NoInput.blank else [(ccy, base) for ccy in currencies]\n        all_keys = sorted(set(local_keys + base_keys))\n        inst_keys = [(\"instruments\",) + label for label in self.pre_instrument_labels]\n        fx_keys = [(\"fx\", \"fx\", f[3:]) for f in fx_vars]\n        idx_tuples = [c + _ for c in all_keys for _ in inst_keys + fx_keys]\n        ridx = MultiIndex.from_tuples(\n            list(idx_tuples),\n            names=[\"local_ccy\", \"display_ccy\", \"type\", \"solver\", \"label\"],\n        )\n        if base is not NoInput.blank:\n            ridx = ridx.append(\n                MultiIndex.from_tuples(\n                    [(\"all\", base) + _ for _ in inst_keys + fx_keys],\n                    names=[\"local_ccy\", \"display_ccy\", \"type\", \"solver\", \"label\"],\n                ),\n            )\n        cidx = MultiIndex.from_tuples(list(inst_keys + fx_keys), names=[\"type\", \"solver\", \"label\"])\n        df = DataFrame(None, index=ridx, columns=cidx)\n        for key, d in container.items():\n            array = np.block(\n                [\n                    [d[(\"instruments\", \"instruments\")], d[(\"instruments\", \"fx\")]],\n                    [d[(\"fx\", \"instruments\")], d[(\"fx\", \"fx\")]],\n                ],\n            )\n            locator = key + (slice(None), slice(None), slice(None))\n\n            with warnings.catch_warnings():\n                # TODO: pandas 3.0.0 can optionally turn off these PerformanceWarnings\n                warnings.simplefilter(action=\"ignore\", category=PerformanceWarning)\n                df.loc[locator, :] = array\n\n        if not isinstance(base, NoInput):\n            # sum over all the base rows to aggregate\n            gdf = (\n                df.loc[(currencies, base, slice(None), slice(None), slice(None)), :]\n                .groupby(level=[2, 3, 4])\n                .sum()\n            )\n            gdf.index = MultiIndex.from_tuples([(\"all\", base) + _ for _ in gdf.index])\n            df.loc[(\"all\", base, slice(None), slice(None), slice(None))] = gdf\n\n        return df.astype(\"float64\")\n\n    def _pnl_explain(\n        self,\n        npv: Dual | Dual2,\n        ds: Sequence[float],\n        dfx: Sequence[float] | None = None,\n        base: str_ = NoInput(0),\n        fx: FX_ = NoInput(0),\n        order: int = 1,\n    ) -> DataFrame:\n        \"\"\"\n        Calculate PnL from market movements over delta and, optionally, gamma.\n\n        Parameters\n        ----------\n        npv : Dual or Dual2,\n            The initial NPV of the instrument or composition of instruments to value.\n        ds : sequence of float\n            The projected market movements of calibrating instruments of the solver,\n            scaled to the appropriate value amount matching the delta representation.\n        dfx : sequence of float\n            The projected market movements of FX rates,\n            scaled to the appropriate value amount matching the delta representation.\n        base : str, optional\n            The currency (3-digit code) to report risk metrics in. If not given will\n            default to the local currency of the cashflows.\n        fx : FXRates, FXForwards, optional\n            The FX object to use to convert risk metrics. If needed but not given\n            will default to the ``fx`` object associated with the\n            :class:`~rateslib.solver.Solver`.\n        order : int in {1, 2}\n            Whether to return a first order delta PnL explain or a second order one\n            including gamma contribution.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        raise NotImplementedError()\n\n    @_validate_states\n    @_no_interior_validation\n    def market_movements(self, solver: Solver) -> DataFrame:\n        \"\"\"\n        Determine market movements between the *Solver's* instrument rates and those rates priced\n        from a second *Solver*.\n\n        Parameters\n        ----------\n        solver: Solver\n            The other *Solver* whose *Curves* are to be used for measuring the final instrument\n            rates of the existing *Solver's* instruments.\n\n        Returns\n        -------\n        DataFrame\n\n        Notes\n        -----\n        .. warning::\n           Market movement calculations are only possible between *Solvers* whose ``instruments``\n           are associated with *Curves* with string ID mappings (which is best practice and\n           demonstrated in :ref:`Mechanisms <mechanisms-curves-doc>`). This allows two different\n           *Solvers* to contain their own *Curves* (which may or may not be equivalent models),\n           and for the instrument rates of one *Solver* to be evaluated by the *Curves* present\n           in another *Solver*.\n        \"\"\"\n        r_0 = self.r_pre\n        r_1 = np.array(\n            [\n                _[0].rate(**{**_[1], \"solver\": solver, \"fx\": solver.fx})\n                for _ in self.pre_instruments\n            ],\n        )\n        return DataFrame(\n            (r_1 - r_0) * 100 / np.array(self.pre_rate_scalars),\n            index=self.pre_instrument_labels,\n        )\n\n    @_validate_states\n    @_no_interior_validation\n    def jacobian(self, solver: Solver) -> DataFrame:\n        \"\"\"\n        Calculate the Jacobian with respect to another *Solver's* instruments.\n\n        Parameters\n        ----------\n        solver : Solver\n            The other ``Solver`` for which the Jacobian is to be determined.\n\n        Returns\n        -------\n        DataFrame\n\n        Notes\n        -----\n        This Jacobian converts risk sensitivities expressed in the underlying *Solver's*\n        instruments to the instruments in the other ``solver``.\n\n        .. warning::\n           A Jacobian transformation is only possible between *Solvers* whose ``instruments``\n           are associated with *Curves* with string ID mappings (which is best practice and\n           demonstrated in :ref:`Mechanisms <mechanisms-curves-doc>`). This allows two different\n           *Solvers* to contain their own *Curves* (which may or may not be equivalent models),\n           and for the instrument rates of one *Solver* to be evaluated by the *Curves* present\n           in another *Solver*\n\n        Examples\n        --------\n        This example creates a Jacobian transformation between par tenor IRS and forward tenor\n        IRS. These models are completely consistent and lossless.\n\n        .. ipython:: python\n\n           par_curve = Curve(\n               nodes={\n                   dt(2022, 1, 1): 1.0,\n                   dt(2023, 1, 1): 1.0,\n                   dt(2024, 1, 1): 1.0,\n                   dt(2025, 1, 1): 1.0,\n               },\n               id=\"curve\",\n           )\n           par_instruments = [\n               IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n               IRS(dt(2022, 1, 1), \"2Y\", \"A\", curves=\"curve\"),\n               IRS(dt(2022, 1, 1), \"3Y\", \"A\", curves=\"curve\"),\n           ]\n           par_solver = Solver(\n               curves=[par_curve],\n               instruments=par_instruments,\n               s=[1.21, 1.635, 1.99],\n               id=\"par_solver\",\n               instrument_labels=[\"1Y\", \"2Y\", \"3Y\"],\n           )\n\n           fwd_curve = Curve(\n               nodes={\n                   dt(2022, 1, 1): 1.0,\n                   dt(2023, 1, 1): 1.0,\n                   dt(2024, 1, 1): 1.0,\n                   dt(2025, 1, 1): 1.0,\n               },\n               id=\"curve\"\n           )\n           fwd_instruments = [\n               IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n               IRS(dt(2023, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n               IRS(dt(2024, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n           ]\n           s_fwd = [float(_.rate(solver=par_solver)) for _ in fwd_instruments]\n           fwd_solver = Solver(\n               curves=[fwd_curve],\n               instruments=fwd_instruments,\n               s=s_fwd,\n               id=\"fwd_solver\",\n               instrument_labels=[\"1Y\", \"1Y1Y\", \"2Y1Y\"],\n           )\n\n           par_solver.jacobian(fwd_solver)\n\n        \"\"\"\n        # Get the instrument rates for self solver evaluated using the curves and links of other\n        r = np.array(\n            [\n                _[0].rate(**{**_[1], \"solver\": solver, \"fx\": solver.fx})\n                for _ in self.pre_instruments\n            ],\n        )\n        # Get the gradient of these rates with respect to the variable in other\n        grad_v_rT = np.array([gradient(_, solver.pre_variables) for _ in r]).T\n        return DataFrame(\n            np.matmul(solver.grad_s_vT_pre, grad_v_rT),\n            columns=self.pre_instrument_labels,\n            index=solver.pre_instrument_labels,\n        )\n\n    @_validate_states\n    @_no_interior_validation\n    def exo_delta(\n        self,\n        npv: dict[str, Dual | Dual2],\n        vars: Sequence[str],  # noqa: A002\n        vars_scalar: Sequence[float] | NoInput = NoInput(0),\n        vars_labels: Sequence[str] | NoInput = NoInput(0),\n        base: str_ = NoInput(0),\n        fx: FX_ = NoInput(0),\n    ) -> DataFrame:\n        \"\"\"\n        Calculate risk sensitivity to user defined, exogenous variables in the\n        *Solver Instruments* and the ``npv``.\n\n        See :ref:`What are exogenous variables? <cook-exogenous-doc>` in the cookbook.\n\n        Parameters\n        -----------\n        npv : dict,\n            The NPV (Dual) of the instrument or portfolio of instruments to risk.\n            Must be indexed by 3-digit currency\n            to discriminate between values expressed in different currencies.\n        vars : list[str]\n            The variable tags which to determine sensitivities for.\n        vars_scalar : list[float], optional\n            Scaling factors for each variable, for example converting rates to basis point etc.\n            Defaults to ones.\n        vars_labels : list[str], optional\n            Alternative names to relabel variables in DataFrames.\n        base : str, optional\n            The currency (3-digit code) to report risk metrics in. If not given will\n            default to the local currency of the cashflows.\n        fx : FXRates, FXForwards, optional\n            The FX object to use to convert risk metrics. If needed but not given\n            will default to the ``fx`` object associated with the\n            :class:`~rateslib.solver.Solver`. It is not recommended to use this\n            argument with multi-currency instruments, see notes.\n\n        Returns\n        -------\n        DataFrame\n        \"\"\"\n        base, fx = self._get_base_and_fx(base, fx)\n\n        if isinstance(vars_scalar, NoInput):\n            vars_scalar = [1.0] * len(vars)\n        if isinstance(vars_labels, NoInput):\n            vars_labels = vars\n\n        container = {}\n        for ccy in npv:\n            container[(\"exogenous\", ccy, ccy)] = self.grad_f_Ploc(npv[ccy], vars) * vars_scalar\n\n            if not isinstance(base, NoInput) and base != ccy:\n                assert isinstance(fx, FXRates | FXForwards)  # noqa S101\n                # extend the derivatives\n                f: Dual | Dual2 = fx.rate(f\"{ccy}{base}\")  # type: ignore[assignment]\n                container[(\"exogenous\", ccy, base)] = (\n                    self.grad_f_Pbase(\n                        npv[ccy],\n                        container[(\"exogenous\", ccy, ccy)] / vars_scalar,\n                        f,\n                        vars,\n                    )\n                    * vars_scalar\n                )\n\n        # construct the DataFrame from container with hierarchical indexes\n        exo_idx = MultiIndex.from_tuples(\n            [(\"exogenous\",) + (self.id, label) for label in vars_labels],\n            names=[\"type\", \"solver\", \"label\"],\n        )\n\n        indexes = {\"exogenous\": exo_idx}\n        r_idx = exo_idx\n        c_idx = MultiIndex.from_tuples([], names=[\"local_ccy\", \"display_ccy\"])\n        df = DataFrame(None, index=r_idx, columns=c_idx)\n        for key, array in container.items():\n            df.loc[indexes[key[0]], (key[1], key[2])] = array\n\n        if not isinstance(base, NoInput):\n            df.loc[r_idx, (\"all\", base)] = df.loc[r_idx, (slice(None), base)].sum(axis=1)\n\n        sorted_cols = df.columns.sort_values()\n        _: DataFrame = df.loc[:, sorted_cols].astype(\"float64\")\n        return _\n\n    @classmethod\n    def from_other(\n        cls,\n        *,\n        pricing_solver: Solver,\n        instruments: Sequence[SupportsRate],\n        curves: Sequence[Any] = (),\n        surfaces: Sequence[Any] = (),\n        pre_solvers: Sequence[Solver] = (),\n        fx: FXForwards_ = NoInput(0),\n        instrument_labels: Sequence[str] | NoInput = NoInput(0),\n        id: str_ = NoInput(0),  # noqa: A002\n        **kwargs: Any,\n    ) -> Solver:\n        \"\"\"\n        Create a :class:`~rateslib.solver.Solver` whose rates, ``s``, are automatically\n        generated from a ``pricing_solver``.\n\n        Parameters\n        ----------\n        pricing_solver: Solver\n            The :class:`~rateslib.solver.Solver` containing pricing object mappings and an ``fx``\n            object that can determine all of the instruments rates, ``s``, for the provided\n            ``instruments``.\n        **kwargs: Any\n            All other arguments expected by a :class:`~rateslib.solver.Solver`, except for ``s``,\n            which are generated from ``pricing_solver``.\n\n        Notes\n        -----\n        This method is designed for ease of implementation of a *'Pricing-Model-Risk-Model'*\n        framework.\n\n        Generating a :class:`~rateslib.solver.Solver` from another **only works** automatically\n        when ``instruments`` pricing objects have been mapped with the same string ids. For\n        example, suppose we desire to build a *'pricing curve'* with market instruments and\n        then, afterward, build a *'risk curve'* with different instruments.\n\n        First build the *'pricing curve'*:\n\n        .. ipython:: python\n\n           pricing_curve = Curve(\n               nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0, dt(2002, 1, 10): 1.0},\n               interpolation=\"spline\",\n               id=\"sofr\",\n           )\n           pricing_solver = Solver(\n               curves=[pricing_curve],\n               instruments=[\n                   IRS(dt(2000, 1, 1), \"1y\", spec=\"usd_irs\", curves=[\"sofr\"]),\n                   IRS(dt(2000, 1, 1), \"2y\", spec=\"usd_irs\", curves=[\"sofr\"]),\n               ],\n               s=[4.10, 4.25],\n               instrument_labels=[\"1y\", \"2y\"],\n               id=\"price_sv\",\n           )\n\n        Now we build the *'risk curve'* with instruments whose prices are automatically generated\n        from the *'pricing curve'*. This provides a more granular, localised set of risks.\n        Note that the ``id`` of both the *'pricing curve'* and the *'risk curve'* are the **same**\n        so that these can be dynamically mapped to the same instruments by different *Solvers*.\n\n        .. ipython:: python\n\n           risk_curve = Curve(\n               nodes={\n                   dt(2000, 1, 1): 1.0,\n                   dt(2000, 4, 1): 1.0,\n                   dt(2000, 7, 1): 1.0,\n                   dt(2000, 10, 1): 1.0,\n                   dt(2001, 1, 1): 1.0,\n                   dt(2001, 4, 1): 1.0,\n                   dt(2001, 7, 1): 1.0,\n                   dt(2001, 10, 1): 1.0,\n                   dt(2002, 1, 10): 1.0,\n               },\n               interpolation=\"log_linear\",\n               id=\"sofr\",\n           )\n           risk_solver = Solver.from_other(\n               pricing_solver=pricing_solver,\n               curves=[risk_curve],\n               instruments=[\n                   IRS(dt(2000, 1, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n                   IRS(dt(2000, 4, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n                   IRS(dt(2000, 7, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n                   IRS(dt(2000, 10, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n                   IRS(dt(2001, 1, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n                   IRS(dt(2001, 4, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n                   IRS(dt(2001, 7, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n                   IRS(dt(2001, 10, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n               ],\n               instrument_labels=[\"0m3m\", \"3m3m\", \"6m3m\", \"9m3m\", \"1y3m\", \"15m3m\", \"18m3m\", \"21m3m\"],\n               id=\"risk_sv\",\n           )\n\n        We can then extract delta or cross-gamma risks in different representations using either\n        of our *Solver* objects.\n\n        .. ipython:: python\n\n           irs = IRS(dt(2000, 3, 24), \"14m\", fixed_rate=3.95, spec=\"usd_irs\", curves=[\"sofr\"])\n           irs.delta(solver=pricing_solver)\n\n        .. ipython:: python\n\n           irs.delta(solver=risk_solver)\n\n        \"\"\"  # noqa: E501\n        return Solver(\n            pre_solvers=pre_solvers,\n            curves=curves,\n            surfaces=surfaces,\n            instruments=instruments,\n            s=[_dual_float(_.rate(solver=pricing_solver)) for _ in instruments],\n            fx=fx,\n            instrument_labels=instrument_labels,\n            id=id,\n            **kwargs,\n        )\n\n\n__all__ = [\"Gradients\", \"Solver\"]\n"
  },
  {
    "path": "python/rateslib/splines/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.rs import PPSplineDual, PPSplineDual2, PPSplineF64, bspldnev_single, bsplev_single\nfrom rateslib.splines.evaluate import evaluate\n\nPPSplineF64.__doc__ = \"\"\"\nPiecewise polynomial spline composed of float-64 values on the x-axis and\nfloat-64 values on the y-axis.\n\nParameters\n----------\nk: int\n    The order of the spline.\nt: sequence of float\n    The knot sequence of the spline.\nc: sequence of float, optional\n    The coefficients of the spline.\n\nSee Also\n--------\n\n.. seealso::\n   :class:`~rateslib.splines.PPSplineDual`: Spline where the y-axis contains :class:`~rateslib.dual.Dual`  data types.\n\n   :class:`~rateslib.splines.PPSplineDual2`: Spline where the y-axis contains :class:`~rateslib.dual.Dual2` data types.\n\"\"\"  # noqa: E501\n\nPPSplineDual.__doc__ = \"\"\"\nPiecewise polynomial spline composed of float-64 values on the x-axis and\n:class:`~rateslib.dual.Dual` values on the y-axis.\n\nParameters\n----------\nk: int\n    The order of the spline.\nt: sequence of float\n    The knot sequence of the spline.\nc: sequence of Dual, optional\n    The coefficients of the spline.\n\nSee Also\n--------\n\n.. seealso::\n   :class:`~rateslib.splines.PPSplineF64`: Spline where the y-axis contains float-64 data types.\n\n   :class:`~rateslib.splines.PPSplineDual2`: Spline where the y-axis contains :class:`~rateslib.dual.Dual2` data types.\n\"\"\"  # noqa: E501\n\nPPSplineDual2.__doc__ = \"\"\"\nPiecewise polynomial spline composed of float-64 values on the x-axis and\n:class:`~rateslib.dual.Dual2` values on the y-axis.\n\nParameters\n----------\nk: int\n    The order of the spline.\nt: sequence of float\n    The knot sequence of the spline.\nc: sequence of Dual2, optional\n    The coefficients of the spline.\n\n.. seealso::\n   :class:`~rateslib.splines.PPSplineF64`: Spline where the y-axis contains float-64 data types.\n\n   :class:`~rateslib.splines.PPSplineDual`: Spline where the y-axis contains :class:`~rateslib.dual.Dual` data types.\n\"\"\"  # noqa: E501\n\n__all__ = (\n    \"PPSplineDual\",\n    \"PPSplineDual2\",\n    \"PPSplineF64\",\n    \"bspldnev_single\",\n    \"bsplev_single\",\n    \"evaluate\",\n)\n"
  },
  {
    "path": "python/rateslib/splines/evaluate.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom rateslib import defaults\nfrom rateslib.dual import Dual, Dual2, Variable\nfrom rateslib.rs import PPSplineDual, PPSplineDual2, PPSplineF64\n\nif TYPE_CHECKING:\n    from rateslib.local_types import DualTypes, Number\n\n\ndef evaluate(\n    spline: PPSplineF64 | PPSplineDual | PPSplineDual2,\n    x: DualTypes,\n    m: int = 0,\n) -> Number:\n    \"\"\"\n    Evaluate a single x-axis data point, or a derivative value, on a *Spline*.\n\n    This method automatically calls :meth:`~rateslib.splines.PPSplineF64.ppdnev_single`,\n    :meth:`~rateslib.splines.PPSplineF64.ppdnev_single_dual` or\n    :meth:`~rateslib.splines.PPSplineF64.ppdnev_single_dual2`  based on the input form of ``x``.\n\n    This method is AD safe.\n\n    Parameters\n    ----------\n    spline: PPSplineF64, PPSplineDual, PPSplineDual2\n        The *Spline* on which to evaluate the data point.\n    x: float, Dual, Dual2\n        The x-axis data point to evaluate.\n    m: int, optional\n        The order of derivative to evaluate. If seeking value only use *m=0*.\n\n    Returns\n    -------\n    float, Dual, Dual2\n    \"\"\"\n    if isinstance(x, Variable):\n        if isinstance(spline, PPSplineDual):\n            x_: float | Dual | Dual2 = x._to_dual_type(order=1)\n        elif isinstance(spline, PPSplineDual2):\n            x_ = x._to_dual_type(order=2)\n        else:\n            x_ = x._to_dual_type(order=defaults._global_ad_order)\n    else:\n        x_ = x\n\n    if isinstance(x_, Dual):\n        return spline.ppdnev_single_dual(x_, m)\n    elif isinstance(x_, Dual2):\n        return spline.ppdnev_single_dual2(x_, m)\n    else:\n        return spline.ppdnev_single(x_, m)\n"
  },
  {
    "path": "python/rateslib/utils/calendars.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalTypes,\n        datetime,\n    )\n\n\ndef _get_first_bus_day(dates: list[datetime], calendar: CalTypes) -> datetime:\n    if len(dates) == 0:\n        raise ValueError(\"The list of `dates` from which to select a business day is empty.\")\n    for date in dates:\n        if calendar.is_bus_day(date):\n            return date\n    raise ValueError(\"No valid business days were found in `dates`.\")\n"
  },
  {
    "path": "python/rateslib/verify.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom __future__ import annotations\n\nimport hashlib\nimport json\nimport logging\nimport os\nimport sys\nimport warnings\nfrom datetime import datetime, timedelta\nfrom enum import Enum\nfrom json import JSONDecodeError\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n    )\n\nVERSION = \"2.7.1\"\n\n\nclass LicenceNotice(UserWarning):\n    _no_licence_warning = (\n        \"\\nRateslib is source-available (not open-source) software distributed under a \"\n        \"dual-licence model.\"\n        \"\\nNo commercial licence is registered for this installation. Use is therefore permitted \"\n        \"for non-commercial purposes only (at-home or university based academic use).\"\n        \"\\nAny use in commercial, professional, or for-profit environments, including evaluation \"\n        \"or trial use, requires a valid commercial licence or an approved evaluation licence.\"\n        \"\\nCertain features may require a registered commercial or evaluation licence in current \"\n        \"or future versions.\"\n        \"\\nFor licensing information or to register a licence, please visit: \"\n        \"https://rateslib.com/licence\"\n    )\n\n    _invalid_warning = (\n        \"\\nRateslib is source-available (not open-source) software distributed under a \"\n        \"dual-licence model.\"\n        \"\\nAn invalid licence file is detected for this installation. Use is therefore permitted \"\n        \"for non-commercial purposes only (at-home or university based academic use).\"\n        \"\\nAny use in commercial, professional, or for-profit environments, including evaluation \"\n        \"or trial use, requires a valid commercial licence or an approved evaluation licence.\"\n        \"\\nCertain features may require a registered commercial or evaluation licence in current \"\n        \"or future versions.\"\n        \"\\nFor licensing information or to register a licence, please visit: \"\n        \"https://rateslib.com/licence\"\n    )\n\n    _expired_warning = (\n        \"\\nYour existing licence for rateslib {0} expired on {1}.\\n\"\n        \"If you wish to extend your licence, please visit https://rateslib.com/licence for further \"\n        \"details.\\n\"\n        \"Otherwise, please uninstall rateslib.\\n\"\n        \"Expired licence details:\\n{2}\\n\"\n    )\n\n\nclass _LicenceStatus(Enum):\n    VALID = 0\n    EXPIRED_GRACE = 1\n    EXPIRED = 2\n    INVALID = 3\n    NO_LICENCE = 4\n\n\nclass Licence:\n    \"\"\"\n    A licence coordinator to control warnings and functionality.\n    \"\"\"\n\n    def __init__(self) -> None:\n        # search for licences in relevant paths\n        value = os.getenv(\"RATESLIB_LICENCE\") or _get_licence()\n\n        if value is None:\n            # then no licence data was found either in environment vars or in the standard path.\n            self._status = _LicenceStatus.NO_LICENCE\n        else:\n            verified_expiry = _verify_licence(value)\n\n            if verified_expiry is None:  # i.e. invalid signature key\n                self._status = _LicenceStatus.INVALID\n            else:\n                # measure the expiry relative to today\n                self._expiry = datetime.strptime(verified_expiry, \"%Y-%m-%d\")\n                if self.expiry > datetime.now():\n                    self._status = _LicenceStatus.VALID\n                elif self.expiry > datetime.now() - timedelta(days=14):\n                    self._status = _LicenceStatus.EXPIRED_GRACE\n                else:\n                    self._status = _LicenceStatus.EXPIRED\n\n        if self.status == _LicenceStatus.NO_LICENCE:\n            self._output(LicenceNotice._no_licence_warning, VERSION)\n        elif self.status == _LicenceStatus.INVALID:\n            self._output(LicenceNotice._invalid_warning, VERSION)\n        elif self.status == _LicenceStatus.EXPIRED:\n            self._output(\n                LicenceNotice._expired_warning,\n                VERSION,\n                self.expiry.strftime(\"%Y-%m-%d\"),\n                self.print_licence(),\n            )\n\n    def _output(self, text: str, *args: Any) -> None:\n        warnings.warn(message=text.format(*args), category=LicenceNotice, stacklevel=4)\n        logger = logging.getLogger(__name__)\n        logger.info(text.format(*args))\n\n    @property\n    def status(self) -> _LicenceStatus:\n        return self._status\n\n    @property\n    def expiry(self) -> datetime:\n        return self._expiry\n\n    @classmethod\n    def add_licence(cls, licence_text: str) -> None:\n        \"\"\"\n        Store the provided licence as a file on the local disk.\n\n        Will create or overwrite any existing licence file as necessary. Will raise\n        PermissionError if writing to disk fails due to restrictions.\n\n        Parameters\n        ----------\n        licence_text: str\n            The full JSON format str of the provided licence.\n\n        Returns\n        -------\n        None\n        \"\"\"\n        licence_file = _get_licence_path()\n        try:\n            if licence_file.exists():\n                current = licence_file.read_text()\n                if current != licence_text:\n                    print(f\"Warning: Existing licence differs. Overwriting {licence_file}\")\n\n            # only add if a valid licence string:\n            try:\n                valid = _verify_licence(licence_text)\n            except JSONDecodeError:\n                raise ValueError(\n                    \"The provided licence text does not appear to be valid JSON format or cannot \"\n                    f\"be decoded as such.\\n{licence_text}\"\n                )\n\n            if not valid:\n                raise ValueError(\n                    f\"The licence key is invalid and has not been saved to disk.\\n{licence_text}\"\n                )\n\n            licence_file.write_text(licence_text)\n            print(f\"License saved at {licence_file}\")\n        except PermissionError:\n            raise PermissionError(\n                f\"Cannot save licence file to {licence_file}.\\n \"\n                f\"Check your admin or corporate file permissions.\"\n            )\n\n    @classmethod\n    def remove_licence(cls) -> bool:\n        \"\"\"\n        Remove the stored licence file.\n\n        Raises PermissionError if the file cannot be deleted from disk due to restrictions.\n\n        Returns\n        -------\n        bool\n            *True* on successful removal and *False* if no licence file exists.\n        \"\"\"\n        licence_file = _get_licence_path()\n        try:\n            if licence_file.exists():\n                licence_file.unlink()\n                print(f\"License removed from {licence_file}\")\n                return True\n            else:\n                print(\"No licence file found to remove.\")\n                return False\n        except PermissionError:\n            raise PermissionError(\n                f\"Cannot remove licence file at {licence_file}. Check your permissions.\"\n            )\n\n    @classmethod\n    def print_licence(cls) -> str:\n        \"\"\"\n        Output the licence data to string.\n\n        Returns\n        -------\n        str\n            The JSON format of the licence.\n        \"\"\"\n        value = os.getenv(\"RATESLIB_LICENCE\") or _get_licence()\n        if value is None:\n            raise ValueError(\"No rateslib licence data was found on this machine\")\n        else:\n            return value\n\n\nAPP_NAME = \"rateslib\"\nLICENSE_FILENAME = \"rateslib_licence.txt\"\n\n\nPUBLIC_KEY: tuple[int, int] = (\n    65537,\n    86222696103896966718103037502072442336246185093318724988310224539490986842962518392592510336894335238460512594559929385462044884137775548353223089347652775415882082908041940084967476300969806363550378972881577687292674787317782507726743027399965228306794174501671206473081788525064813988527838836758351217651,\n)\n\n\ndef _rsa_encrypt(message_int: int, public_key: tuple[int, int]) -> int:\n    e, n = public_key\n    if not 0 <= message_int < n:\n        raise ValueError(\"Message too large for key\")\n    return pow(message_int, e, n)\n\n\ndef _get_licence_path() -> Path:\n    \"\"\"\n    Returns the path where the licence file should be stored.\n    Cross-platform user-specific location.\n    \"\"\"\n    if os.name == \"nt\":  # Windows\n        base = Path(os.getenv(\"APPDATA\", Path.home() / \"AppData\" / \"Roaming\"))\n    elif sys.platform == \"darwin\":  # macOS\n        base = Path.home() / \"Library\" / \"Application Support\"\n    else:  # Linux / Unix\n        base = Path.home() / \".local\" / \"share\"\n\n    data_dir = base / APP_NAME\n    data_dir.mkdir(parents=True, exist_ok=True)  # Ensure directory exists\n    return data_dir / LICENSE_FILENAME\n\n\ndef _get_licence() -> str | None:\n    \"\"\"\n    Retrieve the stored licence text, or None if not found.\n    \"\"\"\n    licence_file = _get_licence_path()\n    if licence_file.exists():\n        return licence_file.read_text()\n    return None\n\n\ndef _verify_licence(licence_plaintext: str) -> str | None:\n    loaded_dict = json.loads(licence_plaintext)\n    licence_dict = dict(sorted(loaded_dict.items()))\n\n    hex_s = licence_dict.pop(\"xkey\", None)\n    if hex_s is None:\n        return None\n\n    s = int(hex_s, 16)\n\n    m = json.dumps(licence_dict, sort_keys=True)\n    hex_h = hashlib.sha256(m.encode()).hexdigest()\n    h = int(hex_h, 16)  # h = int.from_bytes(hashlib.sha256(m.encode()).digest())\n\n    h_ = _rsa_encrypt(s, PUBLIC_KEY)\n\n    if h != h_:\n        return None\n    else:\n        try:\n            return loaded_dict[\"expiry\"]  # type: ignore[no-any-return]\n        except KeyError:\n            return None\n\n\n__all__ = [\"LicenceNotice\", \"Licence\"]\n"
  },
  {
    "path": "python/rateslib/volatility/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.volatility.fx import (\n    FXDeltaVolSmile,\n    FXDeltaVolSurface,\n    FXSabrSmile,\n    FXSabrSurface,\n    _BaseFXSmile,\n    _FXDeltaVolSmileNodes,\n    _FXDeltaVolSpline,\n    _FXDeltaVolSurfaceMeta,\n    _FXSabrSurfaceMeta,\n    _FXSmileMeta,\n    _SabrSmileNodes,\n)\nfrom rateslib.volatility.ir import (\n    IRSabrCube,\n    IRSabrSmile,\n    IRSplineCube,\n    IRSplineSmile,\n    _BaseIRCube,\n    _BaseIRSmile,\n    _IRCubeMeta,\n    _IRSmileMeta,\n    _IRSplineSmileNodes,\n    _IRVolPricingParams,\n    _IRVolSpline,\n)\n\n__all__ = [\n    \"FXSabrSmile\",\n    \"FXSabrSurface\",\n    \"FXDeltaVolSurface\",\n    \"FXDeltaVolSmile\",\n    \"IRSabrSmile\",\n    \"IRSabrCube\",\n    \"IRSplineSmile\",\n    \"IRSplineCube\",\n    \"_BaseFXSmile\",\n    \"_BaseIRSmile\",\n    \"_BaseIRCube\",\n    \"_FXDeltaVolSurfaceMeta\",\n    \"_FXSmileMeta\",\n    \"_FXDeltaVolSpline\",\n    \"_FXDeltaVolSmileNodes\",\n    \"_FXSabrSurfaceMeta\",\n    \"_SabrSmileNodes\",\n    \"_IRSplineSmileNodes\",\n    \"_IRCubeMeta\",\n    \"_IRSmileMeta\",\n    \"_IRVolPricingParams\",\n    \"_IRVolSpline\",\n]\n"
  },
  {
    "path": "python/rateslib/volatility/fx/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom rateslib.volatility.fx.base import _BaseFXSmile\nfrom rateslib.volatility.fx.delta_vol import FXDeltaVolSmile, FXDeltaVolSurface\nfrom rateslib.volatility.fx.sabr import FXSabrSmile, FXSabrSurface\nfrom rateslib.volatility.fx.utils import (\n    _FXDeltaVolSmileNodes,\n    _FXDeltaVolSpline,\n    _FXDeltaVolSurfaceMeta,\n    _FXSabrSurfaceMeta,\n    _FXSmileMeta,\n)\nfrom rateslib.volatility.utils import (\n    _SabrSmileNodes,\n)\n\n__all__ = [\n    \"FXSabrSmile\",\n    \"FXSabrSurface\",\n    \"FXDeltaVolSurface\",\n    \"FXDeltaVolSmile\",\n    \"_BaseFXSmile\",\n    \"_FXDeltaVolSurfaceMeta\",\n    \"_FXSmileMeta\",\n    \"_FXDeltaVolSpline\",\n    \"_FXDeltaVolSmileNodes\",\n    \"_FXSabrSurfaceMeta\",\n    \"_SabrSmileNodes\",\n]\n\nFXVols = FXDeltaVolSmile | FXDeltaVolSurface | FXSabrSmile | FXSabrSurface\nFXVolObj = (FXDeltaVolSmile, FXDeltaVolSurface, FXSabrSmile, FXSabrSurface)\n"
  },
  {
    "path": "python/rateslib/volatility/fx/base.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nfrom typing import TYPE_CHECKING, NoReturn, TypeAlias\n\nfrom rateslib.default import PlotOutput, plot\nfrom rateslib.dual import Dual, Dual2, Variable\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.mutability import _WithCache, _WithState\nfrom rateslib.volatility.fx.utils import _FXSmileMeta\n\nif TYPE_CHECKING:\n    from rateslib.local_types import FXForwards  # pragma: no cover\n\nDualTypes: TypeAlias = \"float | Dual | Dual2 | Variable\"  # if not defined causes _WithCache failure\n\n\nclass _BaseFXSmile(_WithState, _WithCache[float, DualTypes]):\n    \"\"\"Abstract base class for implementing *FX Smiles*.\"\"\"\n\n    _ad: int\n    _default_plot_x_axis: str\n    meta: _FXSmileMeta\n\n    @property\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the *Smile*.\"\"\"\n        return self._ad\n\n    def __iter__(self) -> NoReturn:\n        raise TypeError(\"`Smile` types are not iterable.\")\n\n    def plot(\n        self,\n        comparators: list[_BaseFXSmile] | NoInput = NoInput(0),\n        labels: list[str] | NoInput = NoInput(0),\n        x_axis: str | NoInput = NoInput(0),\n        f: DualTypes | FXForwards | NoInput = NoInput(0),\n    ) -> PlotOutput:\n        \"\"\"\n        Plot volatilities associated with the *Smile*.\n\n        .. warning::\n\n           The *'delta'* ``x_axis`` type for a *SabrSmile* is calculated based on a\n           **forward, unadjusted** delta and is expressed as a negated put option delta\n           consistent with the definition for a :class:`~rateslib.volatility.FXDeltaVolSmile`.\n\n        Parameters\n        ----------\n        comparators: list[Smile]\n            A list of Smiles which to include on the same plot as comparators.\n            Note the comments on\n            :meth:`FXDeltaVolSmile.plot <rateslib.volatility.FXDeltaVolSmile.plot>`.\n        labels : list[str]\n            A list of strings associated with the plot and comparators. Must be same\n            length as number of plots.\n        x_axis : str in {\"strike\", \"moneyness\", \"delta\"}\n            *'strike'* is the natural option for this *SabrSmile* types while *'delta'* is the\n            natural choice for *DeltaVolSmile* types.\n            If *'delta'* see the warning. If *'moneyness'* the strikes are converted using ``f``.\n        f: DualTypes\n            The FX forward rate at delivery.\n\n        Returns\n        -------\n        (fig, ax, line) : Matplotlib.Figure, Matplotplib.Axes, Matplotlib.Lines2D\n        \"\"\"\n        # reversed for intuitive strike direction\n        comparators = _drb([], comparators)\n        labels = _drb([], labels)\n\n        x_axis_: str = _drb(self.meta.plot_x_axis, x_axis)\n\n        x_, y_ = self._plot(x_axis_, f)  # type: ignore[attr-defined]\n\n        x = [x_]\n        y = [y_]\n        if not isinstance(comparators, NoInput):\n            for smile in comparators:\n                if not isinstance(smile, _BaseFXSmile):\n                    raise ValueError(\"A `comparator` must be a valid FX Smile type.\")\n                x_, y_ = smile._plot(x_axis_, f)  # type: ignore[attr-defined]\n                x.append(x_)\n                y.append(y_)\n\n        return plot(x, y, labels)\n"
  },
  {
    "path": "python/rateslib/volatility/fx/delta_vol.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nimport warnings\nfrom datetime import datetime, timezone\nfrom typing import TYPE_CHECKING\nfrom uuid import uuid4\n\nimport numpy as np\nfrom pandas import Series\n\nfrom rateslib import defaults\nfrom rateslib.default import (\n    PlotOutput,\n    plot3d,\n)\nfrom rateslib.dual import (\n    Dual,\n    Dual2,\n    Variable,\n    dual_exp,\n    dual_inv_norm_cdf,\n    dual_log,\n    dual_norm_cdf,\n    dual_norm_pdf,\n    newton_1dim,\n    newton_ndim,\n    set_order_convert,\n)\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import FXDeltaMethod, _get_fx_delta_type\nfrom rateslib.mutability import (\n    _clear_cache_post,\n    _new_state_post,\n    _validate_states,\n    _WithCache,\n    _WithState,\n)\nfrom rateslib.scheduling import get_calendar\nfrom rateslib.splines.evaluate import evaluate\nfrom rateslib.volatility.fx.base import _BaseFXSmile\nfrom rateslib.volatility.fx.utils import (\n    _delta_type_constants,\n    _FXDeltaVolSmileNodes,\n    _FXDeltaVolSurfaceMeta,\n    _FXSmileMeta,\n    _moneyness_from_delta_closed_form,\n)\nfrom rateslib.volatility.utils import (\n    _OptionModelBlack76,\n    _surface_index_left,\n    _t_var_interp,\n    _validate_weights,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import DualTypes, DualTypes_, Sequence  # pragma: no cover\n\n\nUTC = timezone.utc\n\n\nclass FXDeltaVolSmile(_BaseFXSmile):\n    r\"\"\"\n    Create an *FX Volatility Smile* at a given expiry indexed by delta percent.\n\n    See also the :ref:`FX Vol Surfaces section in the user guide <c-fx-smile-doc>`.\n\n    Parameters\n    ----------\n    nodes: dict[float, DualTypes]\n        Key-value pairs for a delta index amount and associated volatility. See examples.\n    eval_date: datetime\n        Acts as the initial node of a *Curve*. Should be assigned today's immediate date.\n    expiry: datetime\n        The expiry date of the options associated with this *Smile*\n    delta_type: FXDeltaMethod or str\n        The type of delta calculation that is used on the options to attain a delta which\n        is referenced by the node keys.\n    id: str, optional\n        The unique identifier to distinguish between *Smiles* in a multicurrency framework\n        and/or *Surface*.\n    ad: int, optional\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    The *delta* axis of this *Smile* is a **negated put delta**, i.e. 0.25 corresponds to a put\n    delta of -0.25. This permits increasing strike for increasing delta index.\n    For a 'forward' delta type 0.25 corresponds to a call delta of 0.75 via\n    put-call delta parity. For a 'spot' delta type it would not because under a 'spot' delta\n    type put-call delta parity is not 1.0, but related to the spot versus forward interest rates.\n\n    The **interpolation function** between nodes is a **cubic spline**.\n\n    - For an *unadjusted* ``delta_type`` the range of the delta index is set to [0,1], and the\n      cubic spline is **natural** with second order derivatives set to zero at the endpoints.\n\n    - For *premium adjusted* ``delta_types`` the range of the delta index is in [0, *d*] where *d*\n      is set large enough to encompass 99.99% of all possible values. The right endpoint is clamped\n      with a first derivative of zero to avoid uncontrolled behaviour. The value of *d* is derived\n      using :math:`d = e^{\\sigma \\sqrt{t} (3.75 + \\frac{1}{2} \\sigma \\sqrt{t})}`\n\n    \"\"\"\n\n    _ini_solve = 0  # All node values are solvable\n    _default_plot_x_axis = \"delta\"\n    _nodes: _FXDeltaVolSmileNodes\n    _id: str\n    _ad: int\n\n    @_new_state_post\n    def __init__(\n        self,\n        nodes: dict[float, DualTypes],\n        eval_date: datetime,\n        expiry: datetime,\n        delta_type: FXDeltaMethod | str,\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        ad: int = 0,\n    ):\n        self._id: str = (\n            uuid4().hex[:5] + \"_\" if isinstance(id, NoInput) else id\n        )  # 1 in a million clash\n\n        self._nodes = _FXDeltaVolSmileNodes(\n            meta=_FXSmileMeta(\n                _expiry=expiry,\n                _eval_date=eval_date,\n                _delta_type=_get_fx_delta_type(delta_type),\n                _plot_x_axis=\"delta\",\n                _pair=None,\n                _delivery=get_calendar(NoInput(0)).lag_bus_days(\n                    expiry, defaults.fx_delivery_lag, True\n                ),\n                _delivery_lag=defaults.fx_delivery_lag,\n                _calendar=get_calendar(NoInput(0)),\n            ),\n            nodes=nodes,\n        )\n\n        self._set_ad_order(ad)  # includes _csolve()\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Smile* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def meta(self) -> _FXSmileMeta:  # type: ignore[override]\n        \"\"\"An instance of :class:`~rateslib.volatility.fx._FXSmileMeta`.\"\"\"\n        return self.nodes.meta\n\n    @property\n    def nodes(self) -> _FXDeltaVolSmileNodes:\n        \"\"\"An instance of :class:`~rateslib.volatility.fx._FXDeltaVolSmileNodes`.\"\"\"\n        return self._nodes\n\n    @property\n    def _n(self) -> int:\n        \"\"\"The number of parameters of the *Surface*.\"\"\"\n        return self.nodes.n\n\n    def __getitem__(self, item: DualTypes) -> DualTypes:\n        \"\"\"\n        Get a value from the DeltaVolSmile given an item which is a delta_index.\n        \"\"\"\n        if item > self.nodes.spline.t[-1]:\n            # raise ValueError(\n            #     \"Cannot index the FXDeltaVolSmile for a delta index out of bounds.\\n\"\n            #     f\"Got: {item}, valid range: [{self.t[0]}, {self.t[-1]}]\"\n            # )\n            return self.nodes.spline.spline.ppev_single(self.nodes.spline.t[-1])\n        elif item < self.nodes.spline.t[0]:\n            # raise ValueError(\n            #     \"Cannot index the FXDeltaVolSmile for a delta index out of bounds.\\n\"\n            #     f\"Got: {item}, valid range: [{self.t[0]}, {self.t[-1]}]\"\n            # )\n            return self.nodes.spline.spline.ppev_single(self.nodes.spline.t[0])\n        else:\n            return evaluate(self.nodes.spline.spline, item, 0)\n\n    def _get_index(\n        self, delta_index: DualTypes, expiry: datetime | NoInput = NoInput(0)\n    ) -> DualTypes:\n        \"\"\"\n        Return a volatility from a given delta index\n        Used internally alongside Surface, where a surface also requires an expiry.\n        \"\"\"\n        return self[delta_index]\n\n    def get(\n        self,\n        delta: DualTypes,\n        delta_type: FXDeltaMethod | str,\n        phi: float,\n        z_w: DualTypes,\n    ) -> DualTypes:\n        \"\"\"\n        Return a volatility for a provided real option delta.\n\n        This function is more explicit than the `__getitem__` method of the *Smile* because it\n        permits forward/spot, adjusted/unadjusted and put/call option delta conversions,\n        by deriving an appropriate delta index relevant to that of the *Smile* ``delta_type``.\n\n        Parameters\n        ----------\n        delta: float\n            The delta to obtain a volatility for.\n        delta_type: FXDeltaMethod or str\n            The delta type the given delta is expressed in.\n        phi: float\n            Whether the given delta is assigned to a put or call option.\n        z_w: DualTypes\n            Required only for spot delta types. This is a scaling factor between spot and\n            forward rate, equal to :math:`w_(m_{delivery})/w_(m_{spot})`, where *w* is curve\n            for the domestic currency collateralised in the foreign currency. If not required\n            enter 1.0.\n\n        Returns\n        -------\n        DualTypes\n        \"\"\"\n        delta_type_ = _get_fx_delta_type(delta_type)\n        eta_0, z_w_0, _ = _delta_type_constants(delta_type_, z_w, 0.0)  # u: unused\n        eta_1, z_w_1, _ = _delta_type_constants(self.meta.delta_type, z_w, 0.0)  # u: unused\n        # then delta types are both unadjusted, used closed form.\n        if eta_0 == eta_1 and eta_0 == 0.5:\n            d_i: DualTypes = (-z_w_1 / z_w_0) * (delta - 0.5 * z_w_0 * (phi + 1.0))\n            return self[d_i]\n        # then delta types are both adjusted, use 1-d solver.\n        elif eta_0 == eta_1 and eta_0 == -0.5:\n            u = _moneyness_from_delta_one_dimensional(\n                delta,\n                delta_type_,\n                self.meta.delta_type,\n                self,\n                self.meta.t_expiry,\n                z_w,\n                phi,\n            )\n            delta_idx = (-z_w_1 / z_w_0) * (delta - z_w_0 * u * (phi + 1.0) * 0.5)\n            return self[delta_idx]\n        else:  # delta adjustment types are different, use 2-d solver.\n            u, delta_idx = _moneyness_from_delta_two_dimensional(\n                delta, delta_type_, self, self.meta.t_expiry, z_w, phi\n            )\n            return self[delta_idx]\n\n    def get_from_strike(\n        self,\n        k: DualTypes,\n        f: DualTypes,\n        expiry: datetime | NoInput = NoInput(0),\n        z_w: DualTypes | NoInput = NoInput(0),\n    ) -> tuple[DualTypes, DualTypes, DualTypes]:\n        \"\"\"\n        Given an option strike return associated delta and vol values.\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2\n            The strike of the option.\n        f: float, Dual, Dual2\n            The forward rate at delivery of the option.\n        expiry: datetime, optional\n            Typically used with *Surfaces*.\n            If given, performs a check to ensure consistency of valuations. Raises if expiry\n            requested and expiry of the *Smile* do not match. Used internally.\n        z_w: float, Dual, Dual2, Variable, optional\n            :math:`z_w` is the factor used to convert between spot and forward type delta values.\n            It is calculated for a specific option from the *Curve* for discounting cashflows in the\n            domestic (i.e. LHS side or notional) currency using the appropriate collateral rate for\n            the option, taking the DF at delivery divided by the DF at spot. If spot type delta\n            is not used this value is not required.\n\n\n        Returns\n        -------\n        delta_index: float, Dual, Dual2, Variable\n            The delta index that can be used as lookup value on the *Smile*\n        vol: float, Dual, Dual2, Variable\n            The volatility value attained from lookup of the index on the *Smile*.\n        k: float, Dual, Dual2, Variable\n            The strike value associated with the option of the delta index.\n\n        Notes\n        -----\n        This function will return a delta index associated with the *FXDeltaVolSmile* and the\n        volatility attributed to the delta at that point. Recall that the delta index is the\n        negated put option delta for the given strike ``k``.\n        \"\"\"\n        expiry = _drb(self.meta.expiry, expiry)\n        if self.meta.expiry != expiry:\n            raise ValueError(\n                \"`expiry` of VolSmile and OptionPeriod do not match: calculation aborted \"\n                \"due to potential pricing errors.\",\n            )\n\n        u: DualTypes = k / f  # moneyness\n        eta, z_w, z_u = _delta_type_constants(self.meta.delta_type, z_w, u)\n\n        # Variables are passed to these functions so that iteration can take place using float\n        # which is faster and then a final iteration at the fixed point can be included with Dual\n        # variables to capture fixed point sensitivity.\n        def root(\n            delta: DualTypes,\n            u: DualTypes,\n            sqrt_t: DualTypes,\n            z_u: DualTypes,\n            z_w: DualTypes,\n            ad: int,\n        ) -> tuple[DualTypes, DualTypes]:\n            # Function value\n            delta_index = -delta\n            vol_ = self[delta_index] / 100.0\n            vol_ = _dual_float(vol_) if ad == 0 else vol_\n            vol_sqrt_t = sqrt_t * vol_\n            d_plus_min = -dual_log(u) / vol_sqrt_t + eta * vol_sqrt_t\n            f0 = delta + z_w * z_u * dual_norm_cdf(-d_plus_min)\n            # Derivative\n            dvol_ddelta = -1.0 * evaluate(self.nodes.spline.spline, delta_index, 1) / 100.0\n            dvol_ddelta = _dual_float(dvol_ddelta) if ad == 0 else dvol_ddelta\n            dd_ddelta = dvol_ddelta * (dual_log(u) * sqrt_t / vol_sqrt_t**2 + eta * sqrt_t)\n            f1 = 1 - z_w * z_u * dual_norm_pdf(-d_plus_min) * dd_ddelta\n            return f0, f1\n\n        # Initial approximation is obtained through the closed form solution of the delta given\n        # an approximated delta at close to the base of the smile.\n        avg_vol = _dual_float(self.nodes.values[int(self.nodes.n / 2)]) / 100.0\n        d_plus_min = -dual_log(_dual_float(u)) / (\n            avg_vol * _dual_float(self.meta.t_expiry_sqrt)\n        ) + eta * avg_vol * _dual_float(self.meta.t_expiry_sqrt)\n        delta_0 = -_dual_float(z_u) * _dual_float(z_w) * dual_norm_cdf(-d_plus_min)\n\n        solver_result = newton_1dim(\n            root,\n            delta_0,\n            args=(u, self.meta.t_expiry_sqrt, z_u, z_w),\n            pre_args=(0,),\n            final_args=(1,),\n            conv_tol=1e-13,\n        )\n        delta = solver_result[\"g\"]\n        delta_index = -delta\n        return delta_index, self[delta_index], k\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[np.object_]]:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return np.array(self.nodes.values)\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        return tuple(f\"{self.id}{i}\" for i in range(self.nodes.n))\n\n    # Plotting\n\n    def _plot(\n        self,\n        x_axis: str,\n        f: DualTypes | NoInput,\n    ) -> tuple[list[float], list[DualTypes]]:\n        x: list[float] = list(\n            np.linspace(_dual_float(self.nodes.plot_upper_bound), self.nodes.spline.t[0], 301)\n        )\n        vols: list[float] | list[Dual] | list[Dual2] = self.nodes.spline.spline.ppev(x)\n        if x_axis in [\"moneyness\", \"strike\"]:\n            if self.meta.delta_type != FXDeltaMethod.Forward:\n                warnings.warn(\n                    \"FXDeltaVolSmile.plot() approximates 'moneyness' and 'strike' using the \"\n                    \"convention that the Smile has a `delta_type` of 'forward'.\\nThe Smile \"\n                    f\"has type: '{self.meta.delta_type}' so this is likely to lead to inexact \"\n                    f\"plots.\",\n                    UserWarning,\n                )\n\n            x = x[40:-40]\n            vols = vols[40:-40]\n            sq_t = self.meta.t_expiry_sqrt\n            x_as_u: list[DualTypes] = [\n                dual_exp(_s / 100.0 * sq_t * (dual_inv_norm_cdf(_D) + 0.5 * _s / 100.0 * sq_t))  # type: ignore[operator]\n                for (_D, _s) in zip(x, vols, strict=True)\n            ]\n            if x_axis == \"strike\":\n                if isinstance(f, NoInput):\n                    raise ValueError(\n                        \"`f` (ATM-forward FX rate) is required by `FXDeltaVolSmile.plot` \"\n                        \"to convert 'moneyness' to 'strike'.\"\n                    )\n                return ([_ * _dual_float(f) for _ in x_as_u], vols)  # type: ignore[misc, return-value]\n            return (x_as_u, vols)  # type: ignore[return-value]\n        return (x, vols)  # type: ignore[return-value]\n\n    # Mutation\n\n    @_new_state_post\n    @_clear_cache_post\n    def _set_node_vector(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        \"\"\"\n        Update the node values in a Solver. ``ad`` in {1, 2}.\n        Only the real values in vector are used, dual components are dropped and restructured.\n        \"\"\"\n        DualType: type[Dual] | type[Dual2] = Dual if ad == 1 else Dual2\n        DualArgs: tuple[list[float]] | tuple[list[float], list[float]] = (\n            ([],) if ad == 1 else ([], [])\n        )\n        base_obj = DualType(0.0, [f\"{self.id}{i}\" for i in range(self.nodes.n)], *DualArgs)\n        ident = np.eye(self.nodes.n)\n\n        nodes_: dict[float, DualTypes] = {}\n        for i, k in enumerate(self.nodes.keys):\n            nodes_[k] = DualType.vars_from(\n                base_obj,  # type: ignore[arg-type]\n                vector[i].real,\n                base_obj.vars,\n                ident[i, :].tolist(),\n                *DualArgs[1:],\n            )\n        self._nodes = _FXDeltaVolSmileNodes(nodes=nodes_, meta=self.meta)\n        self.nodes.spline.csolve(self.nodes, self.ad)\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        if order == getattr(self, \"ad\", None):\n            return None\n        elif order not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n        else:\n            self._ad = order\n            nodes: dict[float, DualTypes] = {\n                k: set_order_convert(v, order, [f\"{self.id}{i}\"])\n                for i, (k, v) in enumerate(self.nodes.nodes.items())\n            }\n            self._update_nodes_and_csolve(nodes)\n\n    # the caller must handle cache and state\n    def _update_nodes_and_csolve(self, nodes: dict[float, DualTypes]) -> None:\n        self._nodes = _FXDeltaVolSmileNodes(nodes=nodes, meta=self.meta)\n        self.nodes.spline.csolve(self.nodes, self.ad)\n\n    @_new_state_post\n    @_clear_cache_post\n    def update(\n        self,\n        nodes: dict[float, DualTypes],\n    ) -> None:\n        \"\"\"\n        Update a *Smile* with new, manually passed nodes.\n\n        For arguments see :class:`~rateslib.volatility.FXDeltaVolSmile`\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Smile* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Smile* instance.\n           This class is labelled as a **mutable on update** object.\n\n        \"\"\"\n        if any(isinstance(_, Dual2) for _ in nodes.values()):\n            ad_: int = 2\n        elif any(isinstance(_, Dual) for _ in nodes.values()):\n            ad_ = 1\n        elif any(isinstance(_, Variable) for _ in nodes.values()):\n            ad_ = defaults._global_ad_order\n        else:\n            ad_ = 0\n\n        nodes = {\n            k: set_order_convert(v, ad_, [f\"{self.id}{i}\"])\n            for i, (k, v) in enumerate(nodes.items())\n        }\n        self._ad = ad_\n        self._update_nodes_and_csolve(nodes)\n\n    @_new_state_post\n    @_clear_cache_post\n    def update_node(self, key: float, value: DualTypes) -> None:\n        \"\"\"\n        Update a single node value on the *Smile*.\n\n        Parameters\n        ----------\n        key: float\n            The node date to update. Must exist in ``nodes``.\n        value: float, Dual, Dual2, Variable\n            Value to update on the *Curve*.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Curve* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Curve* instance.\n           This class is labelled as a **mutable on update** object.\n\n        .. warning::\n\n           This method does not validate the AD order of the input value. Ensure that any\n           supplied values are consistent with the AD order of the object.\n        \"\"\"\n        nodes: dict[float, DualTypes] = self.nodes.nodes.copy()\n        if key not in nodes:\n            raise KeyError(f\"`key`: '{key}' is not in Curve ``nodes``.\")\n        nodes[key] = value\n        self._update_nodes_and_csolve(nodes)\n\n    # Serialization\n\n\nclass FXDeltaVolSurface(_WithState, _WithCache[datetime, FXDeltaVolSmile]):\n    r\"\"\"\n    Create an *FX Volatility Surface* parametrised by cross-sectional *Smiles* at different\n    expiries.\n\n    See also the :ref:`FX Vol Surfaces section in the user guide <c-fx-smile-doc>`.\n\n    Parameters\n    ----------\n    delta_indexes: list[float]\n        Axis values representing the delta indexes on each cross-sectional *Smile*.\n    expiries: list[datetime]\n        Datetimes representing the expiries of each cross-sectional *Smile*, in ascending order.\n    node_values: 2d-shape of float, Dual, Dual2\n        An array of values representing each node value on each cross-sectional *Smile*. Should be\n        an array of size: (length of ``expiries``, length of ``delta_indexes``).\n    eval_date: datetime\n        Acts as the initial node of a *Curve*. Should be assigned today's immediate date.\n    delta_type: FXDeltaMethod or str\n        The type of delta calculation that is used as the *Smiles* definition to obtain a delta\n        index which is referenced by the node keys.\n    weights: Series, optional\n        Weights used for temporal volatility interpolation. See notes.\n    id: str, optional\n        The unique identifier to label the *Surface* and its variables.\n    ad: int, optional\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    See :class:`~rateslib.volatility.FXDeltaVolSmile` for a description of delta indexes and\n    *Smile* construction.\n\n    **Temporal Interpolation**\n\n    Interpolation along the expiry axis occurs by performing total linear variance interpolation\n    for each *delta index* and then dynamically constructing a *Smile* with the usual cubic\n    interpolation.\n\n    If ``weights`` are given this uses the scaling approach of forward volatility (as demonstrated\n    in Clark's *FX Option Pricing*) for calendar days (different options 'cuts' and timezone are\n    not implemented). A datetime indexed `Series` must be provided, where any calendar date that\n    is not included will be assigned the default weight of 1.0.\n\n    See :ref:`constructing FX volatility surfaces <c-fx-smile-doc>` for more details.\n\n    **Calibration**\n\n    *Instruments* that do not match the ``delta_type`` of this *Surface* can still be used within\n    a :class:`~rateslib.solver.Solver` to calibrate the surface. This is quite common, when\n    *Options* less than or equal to one year expiry might use a *'spot'* delta type whilst longer\n    expiries use *'forward'* delta type.\n\n    Internally this is all handled appropriately with necessary conversions, but it is the users\n    responsibility to label the *Surface* and *Instrument* with the correct types. Failing to\n    take correct delta types into account often introduces a mismatch -\n    large enough to be relevant for calibration and pricing, but small enough that it may not be\n    noticed at first. Parametrising the *Surface* with a *'forward'* delta type is the\n    **recommended**\n    choice because it is more standardised and the configuration of which *delta types* to use for\n    the *Instruments* can be a separate consideration.\n\n    For performance reasons it is recommended to match unadjusted delta type *Surfaces* with\n    calibrating *Instruments* that also have unadjusted delta types. And vice versa with\n    premium adjusted\n    delta types. However, *rateslib* has internal root solvers which can handle these\n    cross-delta type\n    specifications, although it degrades the performance of the *Solver* because the calculations\n    are made more difficult. Mixing 'spot' and 'forward' is not a difficult distinction to\n    refactor and that does not cause performance degradation.\n\n    \"\"\"\n\n    _ini_solve = 0\n    _mutable_by_association = True\n    _id: str\n    _meta: _FXDeltaVolSurfaceMeta\n    _smiles: list[FXDeltaVolSmile]\n\n    def __init__(\n        self,\n        delta_indexes: list[float],\n        expiries: list[datetime],\n        node_values: list[DualTypes],\n        eval_date: datetime,\n        delta_type: FXDeltaMethod | str,\n        weights: Series[float] | NoInput = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        ad: int = 0,\n    ):\n        self._id: str = (\n            uuid4().hex[:5] + \"_\" if isinstance(id, NoInput) else id\n        )  # 1 in a million clash\n\n        self._meta = _FXDeltaVolSurfaceMeta(\n            _eval_date=eval_date,\n            _delta_type=_get_fx_delta_type(delta_type),\n            _plot_x_axis=\"delta\",\n            _weights=_validate_weights(weights, eval_date, expiries),\n            _delta_indexes=delta_indexes,\n            _expiries=expiries,\n        )\n\n        node_values_: np.ndarray[tuple[int, ...], np.dtype[np.object_]] = np.asarray(node_values)\n        self._smiles = [\n            FXDeltaVolSmile(\n                nodes=dict(zip(self.meta.delta_indexes, node_values_[i, :], strict=False)),\n                expiry=expiry,\n                eval_date=self.meta.eval_date,\n                delta_type=self.meta.delta_type,\n                id=f\"{self.id}_{i}_\",\n            )\n            for i, expiry in enumerate(self.meta.expiries)\n        ]\n\n        self._set_ad_order(ad)  # includes csolve on each smile\n        self._set_new_state()\n\n    @property\n    def _n(self) -> int:\n        \"\"\"The number of pricing parameters of the *Surface*.\"\"\"\n        return len(self.meta.expiries) * len(self.meta.delta_indexes)\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Surface* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def meta(self) -> _FXDeltaVolSurfaceMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility.fx._FXDeltaVolSurfaceMeta`.\"\"\"\n        return self._meta\n\n    @property\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the *Surface*.\"\"\"\n        return self._ad\n\n    @property\n    def smiles(self) -> list[FXDeltaVolSmile]:\n        \"\"\"A list of cross-sectional :class:`FXDeltaVolSmile` instances.\"\"\"\n        return self._smiles\n\n    def _get_composited_state(self) -> int:\n        return hash(sum(smile._state for smile in self.smiles))\n\n    def _validate_state(self) -> None:\n        if self._state != self._get_composited_state():\n            # If any of the associated curves have been mutated then the cache is invalidated\n            self._clear_cache()\n            self._set_new_state()\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        self._ad = order\n        for smile in self.smiles:\n            smile._set_ad_order(order)\n\n    @_new_state_post\n    @_clear_cache_post\n    def _set_node_vector(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        m = len(self.meta.delta_indexes)\n        for i in range(int(len(vector) / m)):\n            # smiles are indexed by expiry, shortest first\n            self.smiles[i]._set_node_vector(vector[i * m : i * m + m], ad)\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[np.object_]]:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return np.array([_.nodes.values for _ in self.smiles]).ravel()\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        vars_: tuple[str, ...] = ()\n        for smile in self.smiles:\n            vars_ += tuple(f\"{smile.id}{i}\" for i in range(smile._n))\n        return vars_\n\n    @_validate_states\n    def get_smile(self, expiry: datetime) -> FXDeltaVolSmile:\n        \"\"\"\n        Construct a *DeltaVolSmile* with linear total variance interpolation over delta indexes.\n\n        Parameters\n        ----------\n        expiry: datetime\n            The expiry for the *Smile* as cross-section of *Surface*.\n\n        Returns\n        -------\n        FXDeltaVolSmile\n        \"\"\"\n        if defaults.curve_caching and expiry in self._cache:\n            return self._cache[expiry]\n\n        expiry_posix = expiry.replace(tzinfo=UTC).timestamp()\n        e_idx, e_next_idx = _surface_index_left(self.meta.expiries_posix, expiry_posix)\n\n        if expiry == self.meta.expiries[0]:\n            smile = self.smiles[0]\n        elif abs(expiry_posix - self.meta.expiries_posix[e_next_idx]) < 1e-10:\n            # expiry aligns with a known smile\n            smile = self.smiles[e_idx + 1]\n        elif expiry_posix > self.meta.expiries_posix[-1]:\n            # use the data from the last smile\n            smile = FXDeltaVolSmile(\n                nodes={\n                    k: _t_var_interp(\n                        expiries=self.meta.expiries,\n                        expiries_posix=self.meta.expiries_posix,\n                        expiry=expiry,\n                        expiry_posix=expiry_posix,\n                        expiry_index=e_idx,\n                        expiry_next_index=e_next_idx,\n                        eval_posix=self.meta.eval_posix,\n                        weights_cum=self.meta.weights_cum,\n                        vol1=vol1,\n                        vol2=vol1,\n                        bounds_flag=1,\n                    )\n                    for k, vol1 in zip(\n                        self.meta.delta_indexes, self.smiles[e_next_idx].nodes.values, strict=False\n                    )\n                },\n                eval_date=self.meta.eval_date,\n                expiry=expiry,\n                ad=self.ad,\n                delta_type=self.meta.delta_type,\n                id=self.smiles[e_next_idx].id + \"_ext\",\n            )\n        elif expiry <= self.meta.eval_date:\n            raise ValueError(\"`expiry` before the `eval_date` of the Surface is invalid.\")\n        elif expiry_posix < self.meta.expiries_posix[0]:\n            # use the data from the first smile\n            smile = FXDeltaVolSmile(\n                nodes={\n                    k: _t_var_interp(\n                        expiries=self.meta.expiries,\n                        expiries_posix=self.meta.expiries_posix,\n                        expiry=expiry,\n                        expiry_posix=expiry_posix,\n                        expiry_index=e_idx,\n                        expiry_next_index=e_next_idx,\n                        eval_posix=self.meta.eval_posix,\n                        weights_cum=self.meta.weights_cum,\n                        vol1=vol1,\n                        vol2=vol1,\n                        bounds_flag=-1,\n                    )\n                    for k, vol1 in zip(\n                        self.meta.delta_indexes, self.smiles[0].nodes.values, strict=False\n                    )\n                },\n                eval_date=self.meta.eval_date,\n                expiry=expiry,\n                ad=self.ad,\n                delta_type=self.meta.delta_type,\n                id=self.smiles[0].id + \"_ext\",\n            )\n        else:\n            ls, rs = self.smiles[e_idx], self.smiles[e_next_idx]  # left_smile, right_smile\n            smile = FXDeltaVolSmile(\n                nodes={\n                    k: _t_var_interp(\n                        expiries=self.meta.expiries,\n                        expiries_posix=self.meta.expiries_posix,\n                        expiry=expiry,\n                        expiry_posix=expiry_posix,\n                        expiry_index=e_idx,\n                        expiry_next_index=e_next_idx,\n                        eval_posix=self.meta.eval_posix,\n                        weights_cum=self.meta.weights_cum,\n                        vol1=vol1,\n                        vol2=vol2,\n                        bounds_flag=0,\n                    )\n                    for k, vol1, vol2 in zip(\n                        self.meta.delta_indexes,\n                        ls.nodes.values,\n                        rs.nodes.values,\n                        strict=False,\n                    )\n                },\n                eval_date=self.meta.eval_date,\n                expiry=expiry,\n                ad=self.ad,\n                delta_type=self.meta.delta_type,\n                id=ls.id + \"_\" + rs.id + \"_intp\",\n            )\n\n        return self._cached_value(expiry, smile)\n\n    # _validate_states not required since called by `get_smile` internally\n    def get_from_strike(\n        self,\n        k: DualTypes,\n        f: DualTypes,\n        expiry: datetime | NoInput = NoInput(0),\n        z_w: DualTypes_ = NoInput(0),\n    ) -> tuple[DualTypes, DualTypes, DualTypes]:\n        \"\"\"\n        Given an option strike and expiry return associated delta and vol values.\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2\n            The strike of the option.\n        f: float, Dual, Dual2\n            The forward rate at delivery of the option.\n        expiry: datetime\n            Required to produce the cross-sectional *Smile* on the *Surface*.\n        z_w: float, Dual, Dual2, Variable, optional\n            :math:`z_w` is the factor used to convert between spot and forward type delta values.\n            It is calculated for a specific option from the *Curve* for discounting cashflows in the\n            domestic (i.e. LHS side or notional) currency using the appropriate collateral rate for\n            the option, taking the DF at delivery divided by the DF at spot. If spot type delta\n            is not used this value is not required.\n\n        Returns\n        -------\n        delta_index: float, Dual, Dual2, Variable\n            The delta index that can be used as lookup value on the *Smile*\n        vol: float, Dual, Dual2, Variable\n            The volatility value attained from lookup of the index on the *Smile*.\n        k: float, Dual, Dual2, Variable\n            The strike value associated with the option of the delta index.\n\n        Notes\n        -----\n        This function will return a delta index associated with the *FXDeltaVolSmile* and the\n        volatility attributed to the delta at that point. Recall that the delta index is the\n        negated put option delta for the given strike ``k``.\n        \"\"\"\n        if isinstance(expiry, NoInput):\n            raise ValueError(\"`expiry` required to get cross-section of FXDeltaVolSurface.\")\n        smile = self.get_smile(expiry)\n        return smile.get_from_strike(k, f, expiry, z_w)\n\n    # _validate_states not required since called by `get_smile` internally\n    def _get_index(self, delta_index: DualTypes, expiry: datetime) -> DualTypes:\n        \"\"\"\n        Return a volatility from a given delta index.\n        Used internally alongside Surface, where a surface also requires an expiry.\n        \"\"\"\n        return self.get_smile(expiry)[delta_index]\n\n    def plot(self) -> PlotOutput:\n        plot_upper_bound = max([_.nodes.plot_upper_bound for _ in self.smiles])\n        deltas = np.linspace(0.0, plot_upper_bound, 20)\n        vols = np.array([[_._get_index(d, NoInput(0)) for d in deltas] for _ in self.smiles])\n        expiries = [\n            (_ - self.meta.eval_posix) / (365 * 24 * 60 * 60.0) for _ in self.meta.expiries_posix\n        ]\n        return plot3d(deltas, expiries, vols)  # type: ignore[arg-type, return-value]\n\n\ndef _moneyness_from_atm_delta_one_dimensional(\n    delta_type: FXDeltaMethod,\n    vol_delta_type: FXDeltaMethod,\n    vol: DualTypes | FXDeltaVolSmile,\n    t_e: DualTypes,\n    z_w: DualTypes,\n    phi: float,\n) -> DualTypes:\n    def root1d(\n        g: DualTypes,\n        delta_type: FXDeltaMethod,\n        vol_delta_type: FXDeltaMethod,\n        phi: float,\n        sqrt_t_e: float,\n        z_w: DualTypes,\n        ad: int,\n    ) -> tuple[DualTypes, DualTypes]:\n        u = g\n\n        eta_0, z_w_0, z_u_0 = _delta_type_constants(delta_type, z_w, u)\n        eta_1, z_w_1, z_u_1 = _delta_type_constants(vol_delta_type, z_w, u)\n        dz_u_0_du = 0.5 - eta_0\n\n        delta_idx = z_w_1 * z_u_0 / 2.0\n        if isinstance(vol, FXDeltaVolSmile):\n            vol_: DualTypes = vol[delta_idx] / 100.0\n            dvol_ddeltaidx = evaluate(vol.nodes.spline.spline, delta_idx, 1) / 100.0\n        else:\n            vol_ = vol / 100.0\n            dvol_ddeltaidx = 0.0\n        vol_ = _dual_float(vol_) if ad == 0 else vol_\n        dvol_ddeltaidx = _dual_float(dvol_ddeltaidx) if ad == 0 else dvol_ddeltaidx\n        vol_sqrt_t = vol_ * sqrt_t_e\n\n        # Calculate function values\n        d0 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_0)\n        _phi0 = dual_norm_cdf(phi * d0)\n        f0 = phi * z_w_0 * z_u_0 * (0.5 - _phi0)\n\n        # Calculate derivative values\n        ddelta_idx_du = dz_u_0_du * z_w_1 * 0.5\n\n        lnu = dual_log(u) / (vol_**2 * sqrt_t_e)\n        dd_du = -1 / (u * vol_sqrt_t) + dvol_ddeltaidx * (lnu + eta_0 * sqrt_t_e) * ddelta_idx_du\n\n        nd0 = dual_norm_pdf(phi * d0)\n        f1 = -dz_u_0_du * z_w_0 * phi * _phi0 - z_u_0 * z_w_0 * nd0 * dd_du\n\n        return f0, f1\n\n    if isinstance(vol, FXDeltaVolSmile):\n        avg_vol: DualTypes = _dual_float(vol.nodes.values[int(vol.nodes.n / 2)])\n    else:\n        avg_vol = vol\n    g01 = (\n        phi\n        * 0.5\n        * (z_w if delta_type in [FXDeltaMethod.Spot, FXDeltaMethod.SpotPremiumAdjusted] else 1.0)\n    )\n    g00 = _moneyness_from_delta_closed_form(g01, avg_vol, t_e, 1.0, phi)\n\n    root_solver = newton_1dim(\n        root1d,\n        g00,\n        args=(delta_type, vol_delta_type, phi, t_e**0.5, z_w),\n        pre_args=(0,),\n        final_args=(1,),\n        raise_on_fail=True,\n    )\n\n    u: DualTypes = root_solver[\"g\"]\n    return u\n\n\ndef _moneyness_from_delta_one_dimensional(\n    delta: DualTypes,\n    delta_type: FXDeltaMethod,\n    vol_delta_type: FXDeltaMethod,\n    vol: FXDeltaVolSmile | DualTypes,\n    t_e: DualTypes,\n    z_w: DualTypes,\n    phi: float,\n) -> DualTypes:\n    def root1d(\n        g: DualTypes,\n        delta: DualTypes,\n        delta_type: FXDeltaMethod,\n        vol_delta_type: FXDeltaMethod,\n        phi: float,\n        sqrt_t_e: DualTypes,\n        z_w: DualTypes,\n        ad: int,\n    ) -> tuple[DualTypes, DualTypes]:\n        u = g\n\n        eta_0, z_w_0, z_u_0 = _delta_type_constants(delta_type, z_w, u)\n        eta_1, z_w_1, z_u_1 = _delta_type_constants(vol_delta_type, z_w, u)\n        dz_u_0_du = 0.5 - eta_0\n\n        delta_idx = (-z_w_1 / z_w_0) * (delta - z_w_0 * z_u_0 * (phi + 1.0) * 0.5)\n        if isinstance(vol, FXDeltaVolSmile):\n            vol_: DualTypes = vol[delta_idx] / 100.0\n            dvol_ddeltaidx = evaluate(vol.nodes.spline.spline, delta_idx, 1) / 100.0\n        else:\n            vol_ = vol / 100.0\n            dvol_ddeltaidx = 0.0\n        vol_ = _dual_float(vol_) if ad == 0 else vol_\n        dvol_ddeltaidx = _dual_float(dvol_ddeltaidx) if ad == 0 else dvol_ddeltaidx\n        vol_sqrt_t = vol_ * sqrt_t_e\n\n        # Calculate function values\n        d0 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_0)\n        _phi0 = dual_norm_cdf(phi * d0)\n        f0 = delta - z_w_0 * z_u_0 * phi * _phi0\n\n        # Calculate derivative values\n        ddelta_idx_du = dz_u_0_du * z_w_1 * (phi + 1.0) * 0.5\n\n        lnu = dual_log(u) / (vol_**2 * sqrt_t_e)\n        dd_du = -1 / (u * vol_sqrt_t) + dvol_ddeltaidx * (lnu + eta_0 * sqrt_t_e) * ddelta_idx_du\n\n        nd0 = dual_norm_pdf(phi * d0)\n        f1 = -dz_u_0_du * z_w_0 * phi * _phi0 - z_u_0 * z_w_0 * nd0 * dd_du\n\n        return f0, f1\n\n    if isinstance(vol, FXDeltaVolSmile):\n        avg_vol: DualTypes = _dual_float(vol.nodes.values[int(vol.nodes.n / 2)])\n    else:\n        avg_vol = vol\n    g01 = delta if phi > 0 else max(delta, -0.75)\n    g00 = _moneyness_from_delta_closed_form(g01, avg_vol, t_e, 1.0, phi)\n\n    msg = (\n        f\"If the delta, {delta:.1f}, is premium adjusted for a call option is it infeasible?\"\n        if phi > 0\n        else \"\"\n    )\n    try:\n        root_solver = newton_1dim(\n            root1d,\n            g00,\n            args=(delta, delta_type, vol_delta_type, phi, t_e**0.5, z_w),\n            pre_args=(0,),\n            final_args=(1,),\n        )\n    except ValueError as e:\n        raise ValueError(f\"Newton root solver failed, with error: {e.__str__()}.\\n{msg}\")\n\n    if root_solver[\"state\"] == -1:\n        raise ValueError(\n            f\"Newton root solver failed, after {root_solver['iterations']} iterations.\\n{msg}\",\n        )\n\n    u: DualTypes = root_solver[\"g\"]\n    return u\n\n\ndef _moneyness_from_atm_delta_two_dimensional(\n    delta_type: FXDeltaMethod,\n    vol: FXDeltaVolSmile,\n    t_e: DualTypes,\n    z_w: DualTypes,\n    phi: float,\n) -> tuple[DualTypes, DualTypes]:\n    def root2d(\n        g: list[DualTypes],\n        delta_type: FXDeltaMethod,\n        vol_delta_type: FXDeltaMethod,\n        phi: float,\n        sqrt_t_e: DualTypes,\n        z_w: DualTypes,\n        ad: int,\n    ) -> tuple[list[DualTypes], list[list[DualTypes]]]:\n        u, delta_idx = g[0], g[1]\n\n        eta_0, z_w_0, z_u_0 = _delta_type_constants(delta_type, z_w, u)\n        eta_1, z_w_1, z_u_1 = _delta_type_constants(vol_delta_type, z_w, u)\n        dz_u_0_du = 0.5 - eta_0\n        dz_u_1_du = 0.5 - eta_1\n\n        vol_ = vol[delta_idx] / 100.0\n        vol_ = _dual_float(vol_) if ad == 0 else vol_\n        vol_sqrt_t = vol_ * sqrt_t_e\n\n        # Calculate function values\n        d0 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_0)\n        _phi0 = dual_norm_cdf(phi * d0)\n        f0_0 = phi * z_w_0 * z_u_0 * (0.5 - _phi0)\n\n        d1 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_1)\n        _phi1 = dual_norm_cdf(-d1)\n        f0_1 = delta_idx - z_w_1 * z_u_1 * _phi1\n\n        # Calculate Jacobian values\n        dvol_ddeltaidx = evaluate(vol.nodes.spline.spline, delta_idx, 1) / 100.0\n        dvol_ddeltaidx = _dual_float(dvol_ddeltaidx) if ad == 0 else dvol_ddeltaidx\n\n        dd_du = -1 / (u * vol_sqrt_t)  # this is the same for 0 or 1 variety\n        nd0 = dual_norm_pdf(phi * d0)\n        nd1 = dual_norm_pdf(-d1)\n        lnu = dual_log(u) / (vol_**2 * sqrt_t_e)\n        dd0_ddeltaidx = (lnu + eta_0 * sqrt_t_e) * dvol_ddeltaidx\n        dd1_ddeltaidx = (lnu + eta_1 * sqrt_t_e) * dvol_ddeltaidx\n\n        f1_00 = phi * z_w_0 * dz_u_0_du * (0.5 - _phi0) - z_w_0 * z_u_0 * nd0 * dd_du\n        f1_10 = -z_w_1 * dz_u_1_du * _phi1 + z_w_1 * z_u_1 * nd1 * dd_du\n        f1_01 = -z_w_0 * z_u_0 * nd0 * dd0_ddeltaidx\n        f1_11 = 1.0 + z_w_1 * z_u_1 * nd1 * dd1_ddeltaidx\n\n        return [f0_0, f0_1], [[f1_00, f1_01], [f1_10, f1_11]]\n\n    avg_vol = _dual_float(vol.nodes.values[int(vol.nodes.n / 2)])\n    g01 = (\n        phi\n        * 0.5\n        * (z_w if delta_type in [FXDeltaMethod.Spot, FXDeltaMethod.SpotPremiumAdjusted] else 1.0)\n    )\n    g00 = _moneyness_from_delta_closed_form(g01, avg_vol, t_e, 1.0, phi)\n\n    root_solver = newton_ndim(\n        root2d,\n        [g00, abs(g01)],\n        args=(delta_type, vol.meta.delta_type, phi, t_e**0.5, z_w),\n        pre_args=(0,),\n        final_args=(1,),\n        raise_on_fail=True,\n    )\n\n    u, delta_idx = root_solver[\"g\"][0], root_solver[\"g\"][1]\n    return u, delta_idx\n\n\ndef _moneyness_from_delta_two_dimensional(\n    delta: DualTypes,\n    delta_type: FXDeltaMethod,\n    vol: FXDeltaVolSmile,\n    t_e: DualTypes,\n    z_w: DualTypes,\n    phi: float,\n) -> tuple[DualTypes, DualTypes]:\n    def root2d(\n        g: Sequence[DualTypes],\n        delta: DualTypes,\n        delta_type: FXDeltaMethod,\n        vol_delta_type: FXDeltaMethod,\n        phi: float,\n        sqrt_t_e: float,\n        z_w: DualTypes,\n        ad: int,\n    ) -> tuple[list[DualTypes], list[list[DualTypes]]]:\n        u, delta_idx = g[0], g[1]\n\n        eta_0, z_w_0, z_u_0 = _delta_type_constants(delta_type, z_w, u)\n        eta_1, z_w_1, z_u_1 = _delta_type_constants(vol_delta_type, z_w, u)\n        dz_u_0_du = 0.5 - eta_0\n        dz_u_1_du = 0.5 - eta_1\n\n        vol_ = vol[delta_idx] / 100.0\n        vol_ = _dual_float(vol_) if ad == 0 else vol_\n        vol_sqrt_t = vol_ * sqrt_t_e\n\n        # Calculate function values\n        d0 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_0)\n        _phi0 = dual_norm_cdf(phi * d0)\n        f0_0: DualTypes = delta - z_w_0 * z_u_0 * phi * _phi0\n\n        d1 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_1)\n        _phi1 = dual_norm_cdf(-d1)\n        f0_1: DualTypes = delta_idx - z_w_1 * z_u_1 * _phi1\n\n        # Calculate Jacobian values\n        dvol_ddeltaidx = evaluate(vol.nodes.spline.spline, delta_idx, 1) / 100.0\n        dvol_ddeltaidx = _dual_float(dvol_ddeltaidx) if ad == 0 else dvol_ddeltaidx\n\n        dd_du = -1 / (u * vol_sqrt_t)\n        nd0 = dual_norm_pdf(phi * d0)\n        nd1 = dual_norm_pdf(-d1)\n        lnu = dual_log(u) / (vol_**2 * sqrt_t_e)\n        dd0_ddeltaidx = (lnu + eta_0 * sqrt_t_e) * dvol_ddeltaidx\n        dd1_ddeltaidx = (lnu + eta_1 * sqrt_t_e) * dvol_ddeltaidx\n\n        f1_00: DualTypes = -z_w_0 * dz_u_0_du * phi * _phi0 - z_w_0 * z_u_0 * nd0 * dd_du\n        f1_10: DualTypes = -z_w_1 * dz_u_1_du * _phi1 + z_w_1 * z_u_1 * nd1 * dd_du\n        f1_01: DualTypes = -z_w_0 * z_u_0 * nd0 * dd0_ddeltaidx\n        f1_11: DualTypes = 1.0 + z_w_1 * z_u_1 * nd1 * dd1_ddeltaidx\n\n        return [f0_0, f0_1], [[f1_00, f1_01], [f1_10, f1_11]]\n\n    avg_vol = _dual_float(vol.nodes.values[int(vol.nodes.n / 2)])\n    g01 = delta if phi > 0 else max(delta, -0.75)\n    g00 = _moneyness_from_delta_closed_form(g01, avg_vol, t_e, 1.0, phi)\n\n    msg = (\n        f\"If the delta, {_dual_float(delta):.1f}, is premium adjusted for a \"\n        \"call option is it infeasible?\"\n        if phi > 0\n        else \"\"\n    )\n    try:\n        root_solver = newton_ndim(\n            root2d,\n            [g00, abs(g01)],\n            args=(delta, delta_type, vol.meta.delta_type, phi, t_e**0.5, z_w),\n            pre_args=(0,),\n            final_args=(1,),\n            raise_on_fail=False,\n        )\n    except ValueError as e:\n        raise ValueError(f\"Newton root solver failed, with error: {e.__str__()}.\\n{msg}\")\n\n    if root_solver[\"state\"] == -1:\n        raise ValueError(\n            f\"Newton root solver failed, after {root_solver['iterations']} iterations.\\n{msg}\",\n        )\n    u, delta_idx = root_solver[\"g\"][0], root_solver[\"g\"][1]\n    return u, delta_idx\n\n\ndef _moneyness_from_delta_three_dimensional(\n    delta_type: FXDeltaMethod,\n    vol: DualTypes | FXDeltaVolSmile,\n    t_e: DualTypes,\n    z_w: DualTypes,\n    phi: float,\n) -> tuple[DualTypes, DualTypes, DualTypes]:\n    \"\"\"\n    Solve the ATM delta problem where delta is not explicit.\n\n    Book2: section \"Strike and Volatility implied from ATM delta\" (FXDeltaVolSMile)\n    \"\"\"\n\n    def root3d(\n        g: list[DualTypes],\n        delta_type: FXDeltaMethod,\n        vol_delta_type: FXDeltaMethod,\n        phi: float,\n        sqrt_t_e: DualTypes,\n        z_w: DualTypes,\n        ad: int,\n    ) -> tuple[list[DualTypes], list[list[DualTypes]]]:\n        u, delta_idx, delta = g[0], g[1], g[2]\n\n        eta_0, z_w_0, z_u_0 = _delta_type_constants(delta_type, z_w, u)\n        eta_1, z_w_1, z_u_1 = _delta_type_constants(vol_delta_type, z_w, u)\n        dz_u_0_du = 0.5 - eta_0\n        dz_u_1_du = 0.5 - eta_1\n\n        if isinstance(vol, FXDeltaVolSmile):\n            vol_: DualTypes = vol[delta_idx] / 100.0\n            dvol_ddeltaidx = evaluate(vol.nodes.spline.spline, delta_idx, 1) / 100.0\n        else:\n            vol_ = vol / 100.0\n            dvol_ddeltaidx = 0.0\n        vol_ = _dual_float(vol_) if ad == 0 else vol_\n        vol_sqrt_t = vol_ * sqrt_t_e\n\n        # Calculate function values\n        d0 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_0)\n        _phi0 = dual_norm_cdf(phi * d0)\n        f0_0 = delta - z_w_0 * z_u_0 * phi * _phi0\n\n        d1 = _OptionModelBlack76._d_plus_min_u(u, vol_sqrt_t, eta_1)\n        _phi1 = dual_norm_cdf(-d1)\n        f0_1 = delta_idx - z_w_1 * z_u_1 * _phi1\n\n        f0_2 = delta - phi * z_u_0 * z_w_0 / 2.0\n\n        # Calculate Jacobian values\n        dvol_ddeltaidx = _dual_float(dvol_ddeltaidx) if ad == 0 else dvol_ddeltaidx\n\n        dd_du = -1 / (u * vol_sqrt_t)\n        nd0 = dual_norm_pdf(phi * d0)\n        nd1 = dual_norm_pdf(-d1)\n        lnu = dual_log(u) / (vol_**2 * sqrt_t_e)\n        dd0_ddeltaidx = (lnu + eta_0 * sqrt_t_e) * dvol_ddeltaidx\n        dd1_ddeltaidx = (lnu + eta_1 * sqrt_t_e) * dvol_ddeltaidx\n\n        f1_00 = -z_w_0 * dz_u_0_du * phi * _phi0 - z_w_0 * z_u_0 * nd0 * dd_du  # dh0/du\n        f1_10 = -z_w_1 * dz_u_1_du * _phi1 + z_w_1 * z_u_1 * nd1 * dd_du  # dh1/du\n        f1_20 = -phi * z_w_0 * dz_u_0_du / 2.0  # dh2/du\n        f1_01 = -z_w_0 * z_u_0 * nd0 * dd0_ddeltaidx  # dh0/ddidx\n        f1_11 = 1.0 + z_w_1 * z_u_1 * nd1 * dd1_ddeltaidx  # dh1/ddidx\n        f1_21 = 0.0  # dh2/ddidx\n        f1_02 = 1.0  # dh0/ddelta\n        f1_12 = 0.0  # dh1/ddelta\n        f1_22 = 1.0  # dh2/ddelta\n\n        return [f0_0, f0_1, f0_2], [\n            [f1_00, f1_01, f1_02],\n            [f1_10, f1_11, f1_12],\n            [f1_20, f1_21, f1_22],\n        ]\n\n    if isinstance(vol, FXDeltaVolSmile):\n        avg_vol: DualTypes = _dual_float(vol.nodes.values[int(vol.nodes.n / 2)])\n        vol_delta_type = vol.meta.delta_type\n    else:\n        avg_vol = vol\n        vol_delta_type = delta_type\n    g02 = (\n        0.5\n        * phi\n        * (z_w if delta_type in [FXDeltaMethod.Spot, FXDeltaMethod.SpotPremiumAdjusted] else 1.0)\n    )\n    g01 = g02 if phi > 0 else max(g02, -0.75)\n    g00 = _moneyness_from_delta_closed_form(g01, avg_vol, t_e, 1.0, phi)\n\n    root_solver = newton_ndim(\n        root3d,\n        [g00, abs(g01), g02],\n        args=(delta_type, vol_delta_type, phi, t_e**0.5, z_w),\n        pre_args=(0,),\n        final_args=(1,),\n        raise_on_fail=True,\n    )\n\n    u, delta_idx, delta = root_solver[\"g\"][0], root_solver[\"g\"][1], root_solver[\"g\"][1]\n    return u, delta_idx, delta\n"
  },
  {
    "path": "python/rateslib/volatility/fx/sabr.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nfrom datetime import datetime, timezone\nfrom typing import TYPE_CHECKING\nfrom uuid import uuid4\n\nimport numpy as np\nfrom pandas import Series\n\nfrom rateslib import defaults\nfrom rateslib.dual import (\n    Dual,\n    Dual2,\n    Variable,\n    dual_exp,\n    dual_inv_norm_cdf,\n    dual_log,\n    dual_norm_cdf,\n    set_order_convert,\n)\nfrom rateslib.dual.utils import _dual_float, _to_number\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import FXDeltaMethod\nfrom rateslib.fx import FXForwards\nfrom rateslib.mutability import (\n    _clear_cache_post,\n    _new_state_post,\n    _validate_states,\n    _WithCache,\n    _WithState,\n)\nfrom rateslib.scheduling import get_calendar\nfrom rateslib.volatility.fx.base import _BaseFXSmile\nfrom rateslib.volatility.fx.utils import (\n    _FXSabrSurfaceMeta,\n    _FXSmileMeta,\n)\nfrom rateslib.volatility.utils import (\n    _SabrModel,\n    _SabrSmileNodes,\n    _surface_index_left,\n    _t_var_interp_d_sabr_d_k_or_f,\n    _validate_weights,\n)\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        CalInput,\n        DualTypes,\n        DualTypes_,\n        Number,\n        Sequence,\n        datetime_,\n        int_,\n        str_,\n    )\n\n\nUTC = timezone.utc\n\n\nclass FXSabrSmile(_BaseFXSmile):\n    r\"\"\"\n    Create an *FX Volatility Smile* at a given expiry indexed by strike using SABR parameters.\n\n    Parameters\n    ----------\n    nodes: dict[str, float]\n        The parameters for the SABR model. Keys must be *'alpha', 'beta', 'rho', 'nu'*. See below.\n    eval_date: datetime\n        Acts as the initial node of a *Curve*. Should be assigned today's immediate date.\n    expiry: datetime\n        The expiry date of the options associated with this *Smile*\n    id: str, optional\n        The unique identifier to distinguish between *Smiles* in a multicurrency framework\n        and/or *Surface*.\n    delivery_lag: int, optional\n        The number of business days after expiry that the physical settlement of the FX\n        exchange occurs. Uses ``defaults.fx_delivery_lag``. Used in determination of ATM forward\n        rates.\n    calendar : Cal, UnionCal, NamedCal, str, optional\n        The holiday calendar object to use for FX delivery day determination. If str, looks up\n        named calendar from static data.\n    pair : str, optional\n        The FX currency pair used to determine ATM forward rates.\n    ad: int, optional\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    The keys for ``nodes`` are described as the following:\n\n    - ``alpha``: The initial volatility parameter (e.g. 0.10 for 10%) of the SABR model,\n      in (0, inf).\n    - ``beta``: The scaling parameter between normal (0) and lognormal (1)\n      of the SABR model in [0, 1].\n    - ``rho``: The correlation between spot and volatility of the SABR model,\n      e.g. -0.10, in [-1.0, 1.0)\n    - ``nu``: The volatility of volatility parameter of the SABR model, e.g. 0.80.\n\n    The parameters :math:`\\alpha, \\rho, \\nu` will be calibrated/mutated by\n    a :class:`~rateslib.solver.Solver` object. These should be entered as *float* and the argument\n    ``ad`` can be used to automatically tag these as variables.\n\n    The parameter :math:`\\beta` will **not** be calibrated/mutated by a\n    :class:`~rateslib.solver.Solver`. This value can be entered either as a *float*, or a\n    :class:`~rateslib.dual.Variable` to capture exogenous sensivities.\n\n    The arguments ``delivery_lag``, ``calendar`` and ``pair`` are only required if using an\n    :class:`~rateslib.fx.FXForwards` object to forecast ATM-forward FX rates for pricing. If\n    the forward rates are supplied directly as numeric values these arguments are not required.\n\n    Examples\n    --------\n    See :ref:`Constructing a Smile <c-fx-smile-constructing-doc>`.\n\n    \"\"\"\n\n    _ini_solve = 1\n    _meta: _FXSmileMeta\n    _id: str\n    _nodes: _SabrSmileNodes\n\n    @_new_state_post\n    def __init__(\n        self,\n        nodes: dict[str, DualTypes],\n        eval_date: datetime,\n        expiry: datetime,\n        delivery_lag: int_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        pair: str_ = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        ad: int = 0,\n    ):\n        self._id: str = (\n            uuid4().hex[:5] + \"_\" if isinstance(id, NoInput) else id\n        )  # 1 in a million clash\n\n        delivery_lag_ = _drb(defaults.fx_delivery_lag, delivery_lag)\n        cal_ = get_calendar(calendar)\n        self._meta = _FXSmileMeta(\n            _eval_date=eval_date,\n            _expiry=expiry,\n            _plot_x_axis=\"strike\",\n            _calendar=cal_,\n            _delivery_lag=delivery_lag_,\n            _delivery=cal_.lag_bus_days(expiry, delivery_lag_, True),\n            _pair=_drb(None, pair),\n            _delta_type=FXDeltaMethod.Forward,  # unused for SABR Model\n        )\n\n        for _ in [\"alpha\", \"beta\", \"rho\", \"nu\"]:\n            if _ not in nodes:\n                raise ValueError(\n                    f\"'{_}' is a required SABR parameter that must be included in ``nodes``\"\n                )\n        self._nodes: _SabrSmileNodes = _SabrSmileNodes(\n            _alpha=_to_number(nodes[\"alpha\"]),\n            _beta=nodes[\"beta\"],  # type: ignore[arg-type]\n            _rho=_to_number(nodes[\"rho\"]),\n            _nu=_to_number(nodes[\"nu\"]),\n        )\n\n        self._set_ad_order(ad)\n\n    @property\n    def _n(self) -> int:\n        \"\"\"The number of pricing parameters in ``nodes``.\"\"\"\n        return self.nodes.n\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Smile* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def meta(self) -> _FXSmileMeta:  # type: ignore[override]\n        \"\"\"An instance of :class:`~rateslib.volatility.fx._FXSmileMeta`.\"\"\"\n        return self._meta\n\n    @property\n    def nodes(self) -> _SabrSmileNodes:\n        \"\"\"An instance of :class:`~rateslib.volatility.fx._FXSabrSmileNodes`.\"\"\"\n        return self._nodes\n\n    def get_from_strike(\n        self,\n        k: DualTypes,\n        f: DualTypes | FXForwards,\n        expiry: datetime_ = NoInput(0),\n        z_w: DualTypes_ = NoInput(0),\n    ) -> tuple[DualTypes, DualTypes, DualTypes]:\n        \"\"\"\n        Given an option strike return the volatility.\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2\n            The strike of the option.\n        f: float, Dual, Dual2\n            The forward rate at delivery of the option.\n        expiry: datetime, optional\n            Typically uses with *Surfaces*.\n            If given, performs a check to ensure consistency of valuations. Raises if expiry\n            requested and expiry of the *Smile* do not match. Used internally.\n        z_w: DualTypes, optional\n            Not used by *SabrSmile*\n\n        Returns\n        -------\n        null: float, Dual, Dual2, Variable\n            A *SabrSmile* has no requirement for a delta index.\n        vol: float, Dual, Dual2, Variable\n            The volatility value attained from lookup of the index on the *Smile*.\n        k: float, Dual, Dual2, Variable\n            The strike value associated with the option of the delta index.\n\n        Notes\n        -----\n        This function returns a tuple consistent with an\n        :class:`~rateslib.volatility.FXDeltaVolSmile`, however since the *FXSabrSmile* has no\n        concept of a `delta index` the first element returned is always zero and can be\n        effectively ignored.\n        \"\"\"\n        expiry = _drb(self._meta.expiry, expiry)\n        if self._meta.expiry != expiry:\n            raise ValueError(\n                \"`expiry` of VolSmile and OptionPeriod do not match: calculation aborted \"\n                \"due to potential pricing errors.\",\n            )\n\n        if isinstance(f, FXForwards):\n            if self._meta.pair is None:\n                raise ValueError(\n                    \"`FXSabrSmile` must be specified with a `pair` argument to use \"\n                    \"`FXForwards` objects for forecasting ATM-forward FX rates.\"\n                )\n            f_: DualTypes = f.rate(self._meta.pair, self._meta.delivery)\n        elif isinstance(f, float | Dual | Dual2 | Variable):\n            f_ = f\n        else:\n            raise ValueError(\"`f` (ATM-forward FX rate) must be a value or FXForwards object.\")\n\n        vol_ = _SabrModel._d_sabr_d_k_or_f(\n            _to_number(k),\n            _to_number(f_),\n            self._meta.t_expiry,\n            self.nodes.alpha,\n            self.nodes.beta,\n            self.nodes.rho,\n            self.nodes.nu,\n            derivative=0,\n        )[0]\n        return 0.0, vol_ * 100.0, k\n\n    def _d_sabr_d_k_or_f(\n        self,\n        k: DualTypes,\n        f: DualTypes | FXForwards,\n        expiry: datetime,\n        as_float: bool,\n        derivative: int,\n    ) -> tuple[DualTypes, DualTypes | None]:\n        \"\"\"Get the derivative of sabr vol with respect to strike\n\n        as_float: bool\n            Allow expedited calculation by avoiding dual numbers. Useful during the root solving\n            phase of Newton iterations.\n        derivative: int\n            For with respect to `k` use 1, or `f` use 2.\n        \"\"\"\n        t_e = (expiry - self._meta.eval_date).days / 365.0\n        if isinstance(f, FXForwards):\n            f__: DualTypes = f.rate(self._meta.pair, self._meta.delivery)\n        else:\n            f__ = f  # type: ignore[assignment]\n\n        if as_float:\n            k_: Number = _dual_float(k)\n            f_: Number = _dual_float(f__)\n            a_: Number = _dual_float(self.nodes.alpha)\n            b_: float | Variable = _dual_float(self.nodes.beta)\n            p_: Number = _dual_float(self.nodes.rho)\n            v_: Number = _dual_float(self.nodes.nu)\n        else:\n            k_ = _to_number(k)\n            f_ = _to_number(f__)\n            a_ = self.nodes.alpha  #\n            b_ = self.nodes.beta\n            p_ = self.nodes.rho\n            v_ = self.nodes.nu\n\n        return _SabrModel._d_sabr_d_k_or_f(k_, f_, t_e, a_, b_, p_, v_, derivative)\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[np.object_]]:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return np.array([self.nodes.alpha, self.nodes.rho, self.nodes.nu])\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        return tuple(f\"{self.id}{i}\" for i in range(3))\n\n    @_new_state_post\n    @_clear_cache_post\n    def _set_node_vector(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        \"\"\"\n        Update the node values in a Solver. ``ad`` in {1, 2}.\n        Only the real values in vector are used, dual components are dropped and restructured.\n        \"\"\"\n        DualType: type[Dual] | type[Dual2] = Dual if ad == 1 else Dual2\n        DualArgs: tuple[list[float]] | tuple[list[float], list[float]] = (\n            ([],) if ad == 1 else ([], [])\n        )\n        base_obj = DualType(0.0, [f\"{self.id}{i}\" for i in range(3)], *DualArgs)\n        ident = np.eye(3)\n\n        self._nodes = _SabrSmileNodes(\n            _beta=self.nodes.beta,\n            _alpha=DualType.vars_from(\n                base_obj,  # type: ignore[arg-type]\n                vector[0].real,\n                base_obj.vars,\n                ident[0, :].tolist(),\n                *DualArgs[1:],\n            ),\n            _rho=DualType.vars_from(\n                base_obj,  # type: ignore[arg-type]\n                vector[1].real,\n                base_obj.vars,\n                ident[1, :].tolist(),\n                *DualArgs[1:],\n            ),\n            _nu=DualType.vars_from(\n                base_obj,  # type: ignore[arg-type]\n                vector[2].real,\n                base_obj.vars,\n                ident[2, :].tolist(),\n                *DualArgs[1:],\n            ),\n        )\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        \"\"\"This does not alter the beta node, since that is not varied by a Solver.\n        beta values that are AD sensitive should be given as a Variable and not Dual/Dual2.\n        \"\"\"\n        if order == getattr(self, \"_ad\", None):\n            return None\n        elif order not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = order\n\n        self._nodes = _SabrSmileNodes(\n            _beta=self.nodes.beta,\n            _alpha=set_order_convert(self.nodes.alpha, order, [f\"{self.id}0\"]),\n            _rho=set_order_convert(self.nodes.rho, order, [f\"{self.id}1\"]),\n            _nu=set_order_convert(self.nodes.nu, order, [f\"{self.id}2\"]),\n        )\n\n    @_new_state_post\n    @_clear_cache_post\n    def update_node(self, key: str, value: DualTypes) -> None:\n        \"\"\"\n        Update a single node value on the *SABRSmile*.\n\n        Parameters\n        ----------\n        key: str in {\"alpha\", \"beta\", \"rho\", \"nu\"}\n            The node value to update.\n        value: float, Dual, Dual2, Variable\n            Value to update on the *Smile*.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Curve* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Curve* instance.\n           This class is labelled as a **mutable on update** object.\n\n        \"\"\"\n        params = [\"alpha\", \"beta\", \"rho\", \"nu\"]\n        if key not in params:\n            raise KeyError(\"`key` is not in ``nodes``.\")\n        kwargs = {f\"_{_}\": getattr(self.nodes, _) for _ in params if _ != key}\n        kwargs.update({f\"_{key}\": value})\n        self._nodes = _SabrSmileNodes(**kwargs)\n        self._set_ad_order(self.ad)\n\n    # Plotting\n\n    def _plot(\n        self,\n        x_axis: str,\n        f: DualTypes | FXForwards | NoInput,\n    ) -> tuple[list[float], list[DualTypes]]:\n        if isinstance(f, NoInput):\n            raise ValueError(\"`f` (ATM-forward FX rate) is required by `FXSabrSmile.plot`.\")\n        elif isinstance(f, FXForwards):\n            if self._meta.pair is None:\n                raise ValueError(\n                    \"`FXSabrSmile` must be specified with a `pair` argument to use \"\n                    \"`FXForwards` objects for forecasting ATM-forward FX rates.\"\n                )\n            f_: float = _dual_float(f.rate(self._meta.pair, self._meta.delivery))\n        elif isinstance(f, float | Dual | Dual2 | Variable):\n            f_ = _dual_float(f)\n        else:\n            raise ValueError(\"`f` (ATM-forward FX rate) must be a value or FXForwards object.\")\n\n        v_ = _dual_float(self.get_from_strike(f_, f_)[1]) / 100.0\n        sq_t = self._meta.t_expiry_sqrt\n        x_low = _dual_float(\n            dual_exp(0.5 * v_**2 * sq_t**2 - dual_inv_norm_cdf(0.95) * v_ * sq_t) * f_\n        )\n        x_top = _dual_float(\n            dual_exp(0.5 * v_**2 * sq_t**2 - dual_inv_norm_cdf(0.05) * v_ * sq_t) * f_\n        )\n\n        x = np.linspace(x_low, x_top, 301, dtype=np.float64)\n        u: Sequence[float] = x / f_  # type: ignore[assignment]\n        y: list[DualTypes] = [self.get_from_strike(_, f_)[1] for _ in x]\n        if x_axis == \"moneyness\":\n            return list(u), y\n        elif x_axis == \"delta\":\n            # z_w = 1.0  # delta type is assumed to be 'forward' for SabrSmile\n            # z_u = 1.0  #  delta type is assumed to be 'unadjusted' for SabrSmile\n            eta_1 = 0.5  # for same reason\n\n            sq_t = self._meta.t_expiry_sqrt\n            dn = [\n                -dual_log(u_) * 100.0 / (s_ * sq_t) + eta_1 * s_ * sq_t / 100.0\n                for u_, s_ in zip(u, y, strict=True)\n            ]\n            delta_index = [dual_norm_cdf(-d_) for d_ in dn]\n            return delta_index, y  # type: ignore[return-value]\n        else:  # x_axis = \"strike\"\n            return list(x), y\n\n\nclass FXSabrSurface(_WithState, _WithCache[datetime, FXSabrSmile]):\n    r\"\"\"\n    Create an *FX Volatility Surface* parametrised by cross-sectional *Smiles* at different\n    expiries.\n\n    See also the :ref:`FX Vol Surfaces section in the user guide <c-fx-smile-doc>`.\n\n    Parameters\n    ----------\n    expiries: list[datetime]\n       Datetimes representing the expiries of each cross-sectional *Smile*, in ascending order.\n    node_values: 2d-shape of float, Dual, Dual2\n       An array of values representing each *alpha, beta, rho, nu* node value on each\n       cross-sectional *Smile*. Should be an array of size: (length of ``expiries``, 4).\n    eval_date: datetime\n       Acts as the initial node of a *Curve*. Should be assigned today's immediate date.\n    weights: Series, optional\n       Weights used for temporal volatility interpolation. See notes.\n    delivery_lag: int, optional\n        The number of business days after expiry that the physical settlement of the FX\n        exchange occurs. Uses ``defaults.fx_delivery_lag``. Used in determination of ATM forward\n        rates for different expiries.\n    calendar : Cal, UnionCal, NamedCal, str, optional\n        The holiday calendar object to use for FX delivery day determination. If str, looks up\n        named calendar from static data.\n    pair : str, optional\n        The FX currency pair used to determine ATM forward rates.\n    id: str, optional\n       The unique identifier to label the *Surface* and its variables.\n    ad: int, optional\n       Sets the automatic differentiation order. Defines whether to convert node\n       values to float, :class:`~rateslib.dual.Dual` or\n       :class:`~rateslib.dual.Dual2`. It is advised against\n       using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    See :class:`~rateslib.volatility.FXSabrSmile` for a description of SABR parameters for\n    *Smile* construction.\n\n    **Temporal Interpolation**\n\n    Interpolation along the expiry axis occurs by performing total linear variance interpolation\n    for a given *strike* measured on neighboring *Smiles*.\n\n    If ``weights`` are given this uses the scaling approach of forward volatility (as demonstrated\n    in Clark's *FX Option Pricing*) for calendar days (different options 'cuts' and timezone are\n    not implemented). A datetime indexed `Series` must be provided, where any calendar date that\n    is not included will be assigned the default weight of 1.0.\n\n    See :ref:`constructing FX volatility surfaces <c-fx-smile-doc>` for more details.\n\n    **Extrapolation**\n\n    When an ``expiry`` is sought that is prior to the first parametrised *Smile expiry* or after the\n    final parametrised *Smile expiry* extrapolation is required. This is not recommended,\n    however. It would be wiser to create parameterised *Smiles* at *expiries* which suit those\n    one wishes to obtian values for.\n\n    When seeking an ``expiry`` beyond the final expiry, a new\n    :class:`~rateslib.volatility.SabrSmile` is created at that specific *expiry* using the\n    same SABR parameters as matching the final parametrised *Smile*. This will capture the\n    evolution of ATM-forward rates through time.\n\n    When seeking an ``expiry`` prior to the first expiry, the volatility found on the first *Smile*\n    will be used an interpolated, using total linear variance accooridng to the given ``weights``.\n    If ``weights`` are not used then this will return the same value as obtained from that\n    first parametrised *Smile*. This does not account any evolution of ATM-forward rates.\n\n    \"\"\"\n\n    _ini_solve = 0\n    _mutable_by_association = True\n    _meta: _FXSabrSurfaceMeta\n    _id: str\n    _smiles: list[FXSabrSmile]\n\n    def __init__(\n        self,\n        expiries: list[datetime],\n        node_values: list[DualTypes],\n        eval_date: datetime,\n        weights: Series[float] | NoInput = NoInput(0),\n        delivery_lag: int_ = NoInput(0),\n        calendar: CalInput = NoInput(0),\n        pair: str_ = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        ad: int = 0,\n    ):\n        self._id: str = (\n            uuid4().hex[:5] + \"_\" if isinstance(id, NoInput) else id\n        )  # 1 in a million clash\n\n        self._meta = _FXSabrSurfaceMeta(\n            _eval_date=eval_date,\n            _pair=_drb(None, pair),\n            _calendar=get_calendar(calendar),\n            _delivery_lag=_drb(defaults.fx_delivery_lag, delivery_lag),\n            _weights=_validate_weights(weights, eval_date, expiries),\n            _expiries=expiries,\n        )\n\n        node_values_: np.ndarray[tuple[int, ...], np.dtype[np.object_]] = np.asarray(node_values)\n        self._smiles = [\n            FXSabrSmile(\n                nodes=dict(zip([\"alpha\", \"beta\", \"rho\", \"nu\"], node_values_[i, :], strict=True)),\n                expiry=expiry,\n                eval_date=self._meta.eval_date,\n                delivery_lag=delivery_lag,\n                calendar=calendar,\n                pair=pair,\n                id=f\"{self.id}_{i}_\",\n            )\n            for i, expiry in enumerate(self.meta.expiries)\n        ]\n\n        self._set_ad_order(ad)  # includes csolve on each smile\n        self._set_new_state()\n\n    @property\n    def _n(self) -> int:\n        \"\"\"Number of pricing parameters of the *Surface*.\"\"\"\n        return len(self.meta.expiries) * 3  # alpha, beta, rho\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Surface* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def meta(self) -> _FXSabrSurfaceMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility.fx._FXSabrSurfaceMeta`.\"\"\"\n        return self._meta\n\n    @property\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the *Surface*.\"\"\"\n        return self._ad\n\n    @property\n    def smiles(self) -> list[FXSabrSmile]:\n        \"\"\"A list of cross-sectional :class:`FXSabrSmile` instances.\"\"\"\n        return self._smiles\n\n    def _get_composited_state(self) -> int:\n        return hash(sum(smile._state for smile in self.smiles))\n\n    def _validate_state(self) -> None:\n        if self._state != self._get_composited_state():\n            # If any of the associated curves have been mutated then the cache is invalidated\n            self._clear_cache()\n            self._set_new_state()\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int) -> None:\n        self._ad = order\n        for smile in self.smiles:\n            smile._set_ad_order(order)\n\n    @_new_state_post\n    @_clear_cache_post\n    def _set_node_vector(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        m = 3\n        for i in range(int(len(vector) / m)):\n            # smiles are indexed by expiry, shortest first\n            self.smiles[i]._set_node_vector(vector[i * m : i * m + m], ad)\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[np.object_]]:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return np.array([list(_._get_node_vector()) for _ in self.smiles]).ravel()\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        vars_: tuple[str, ...] = ()\n        for smile in self.smiles:\n            vars_ += tuple(f\"{smile.id}{i}\" for i in range(3))\n        return vars_\n\n    # @_validate_states: not required because state is validated by interior function\n    def get_from_strike(\n        self,\n        k: DualTypes,\n        f: DualTypes | FXForwards,\n        expiry: datetime,\n        z_w: DualTypes | NoInput = NoInput(0),\n    ) -> tuple[DualTypes, DualTypes, DualTypes]:\n        \"\"\"\n        Given an option strike return the volatility.\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2\n            The strike of the option.\n        f: float, Dual, Dual2\n            The forward rate at delivery of the option.\n        expiry: datetime, optional\n            The expiry of the option. Required for temporal interpolation between\n            cross-sectional *Smiles*.\n        z_w: DualTypes, optional\n            Not used by *SabrSurface*\n\n        Returns\n        -------\n        null: float, Dual, Dual2, Variable\n            A *SabrSurface* has no requirement for a delta index.\n        vol: float, Dual, Dual2, Variable\n            The volatility value attained from lookup of the index on the *Smile*.\n        k: float, Dual, Dual2, Variable\n            The strike value associated with the option of the delta index.\n\n        Notes\n        -----\n        This function returns a tuple consistent with an\n        :class:`~rateslib.volatility.FXDeltaVolSmile`, however since the *FXSabrSmile* has no\n        concept of a `delta index` the first element returned is always zero and can be\n        effectively ignored.\n        \"\"\"\n        vol_ = self._d_sabr_d_k_or_f(k, f, expiry, as_float=False, derivative=0)[0]\n        return 0.0, vol_ * 100.0, k\n\n    @_validate_states\n    def _d_sabr_d_k_or_f(\n        self,\n        k: DualTypes,\n        f: DualTypes | FXForwards,\n        expiry: datetime,\n        as_float: bool,\n        derivative: int,\n    ) -> tuple[DualTypes, DualTypes | None]:\n        expiry_posix = expiry.replace(tzinfo=UTC).timestamp()\n        e_idx, e_next_idx = _surface_index_left(self.meta.expiries_posix, expiry_posix)\n\n        if expiry == self.meta.expiries[0]:\n            # expiry matches the expiry on the first Smile, call that method directly.\n            return self.smiles[0]._d_sabr_d_k_or_f(k, f, expiry, as_float, derivative)\n        elif abs(expiry_posix - self.meta.expiries_posix[e_next_idx]) < 1e-10:\n            # expiry matches an expiry of a known Smile (not the first), call method directly.\n            return self.smiles[e_next_idx]._d_sabr_d_k_or_f(k, f, expiry, as_float, derivative)\n        elif expiry_posix > self.meta.expiries_posix[-1]:\n            # expiry is beyond that of the last known Smile. Construct a new Smile at the expiry\n            # by using the SABR parameters of the final Smile. (allows for ATM-forward calculation)\n            smile = FXSabrSmile(\n                nodes={\n                    \"alpha\": self.smiles[e_next_idx].nodes.alpha,\n                    \"beta\": self.smiles[e_next_idx].nodes.beta,\n                    \"rho\": self.smiles[e_next_idx].nodes.rho,\n                    \"nu\": self.smiles[e_next_idx].nodes.nu,\n                },\n                eval_date=self._meta.eval_date,\n                expiry=expiry,\n                ad=self.ad,\n                pair=NoInput(0) if self._meta.pair is None else self._meta.pair,\n                delivery_lag=self._meta.delivery_lag,\n                calendar=self._meta.calendar,\n                id=self.smiles[e_next_idx].id + \"_ext\",\n            )\n            return smile._d_sabr_d_k_or_f(k, f, expiry, as_float, derivative)\n        elif expiry <= self._meta.eval_date:\n            raise ValueError(\"`expiry` before the `eval_date` of the Surface is invalid.\")\n        elif expiry_posix < self.meta.expiries_posix[0]:\n            # expiry is before the expiry of the first known Smile.\n            # calculate the vol as if it were for expiry on the first Smile and then use\n            # temporal interpolation (including weights) to obtain an adjusted volatility.\n            vol_, dvol_k_or_f = self.smiles[0]._d_sabr_d_k_or_f(\n                k=k,\n                f=f,\n                expiry=self.smiles[0]._meta.expiry,\n                as_float=as_float,\n                derivative=derivative,\n            )\n            return _t_var_interp_d_sabr_d_k_or_f(\n                expiries=self.meta.expiries,\n                expiries_posix=self.meta.expiries_posix,\n                expiry=expiry,\n                expiry_posix=expiry_posix,\n                expiry_index=e_idx,\n                expiry_next_index=e_next_idx,\n                eval_posix=self._meta.eval_posix,\n                weights_cum=self.meta.weights_cum,\n                vol1=vol_,\n                dvol1_dk=dvol_k_or_f,  # type: ignore[arg-type]\n                vol2=vol_,\n                dvol2_dk=dvol_k_or_f,  # type: ignore[arg-type]\n                bounds_flag=-1,\n                derivative=derivative > 0,\n            )\n        else:\n            # expiry is sandwiched between two known Smile expiries.\n            # Calculate the vol for strike on either of these Smiles and then interpolate\n            # for the correct expiry, including weights.\n            ls, rs = self.smiles[e_idx], self.smiles[e_next_idx]  # left_smile, right_smile\n            if not isinstance(f, FXForwards):\n                raise ValueError(\n                    \"`f` must be supplied as `FXForwards` in order to calculate\"\n                    \"dynamic ATM-forward rates for temporally-interpolated SABR volatility.\"\n                )\n            lvol, d_lvol_dk_or_f = ls._d_sabr_d_k_or_f(\n                k=k, f=f, expiry=ls._meta.expiry, as_float=as_float, derivative=derivative\n            )\n            rvol, d_rvol_dk_or_f = rs._d_sabr_d_k_or_f(\n                k=k, f=f, expiry=rs._meta.expiry, as_float=as_float, derivative=derivative\n            )\n            return _t_var_interp_d_sabr_d_k_or_f(\n                expiries=self.meta.expiries,\n                expiries_posix=self.meta.expiries_posix,\n                expiry=expiry,\n                expiry_posix=expiry_posix,\n                expiry_index=e_idx,\n                expiry_next_index=e_next_idx,\n                eval_posix=self._meta.eval_posix,\n                weights_cum=self.meta.weights_cum,\n                vol1=lvol,\n                dvol1_dk=d_lvol_dk_or_f,  # type: ignore[arg-type]\n                vol2=rvol,\n                dvol2_dk=d_rvol_dk_or_f,  # type: ignore[arg-type]\n                bounds_flag=0,\n                derivative=derivative > 0,\n            )\n"
  },
  {
    "path": "python/rateslib/volatility/fx/utils.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nimport json\nfrom dataclasses import dataclass\nfrom datetime import datetime, timezone\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, TypeAlias\n\nfrom pandas import Series\n\nfrom rateslib.dual import (\n    Dual,\n    Dual2,\n    Variable,\n    dual_exp,\n    dual_inv_norm_cdf,\n    set_order_convert,\n)\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import (\n    NoInput,\n)\nfrom rateslib.enums.parameters import FXDeltaMethod\nfrom rateslib.splines import PPSplineDual, PPSplineDual2, PPSplineF64\n\nif TYPE_CHECKING:\n    from rateslib.local_types import Any, CalTypes\n\nDualTypes: TypeAlias = \"float | Dual | Dual2 | Variable\"  # if not defined causes _WithCache failure\n\nUTC = timezone.utc\n\nTERMINAL_DATE = datetime(2100, 1, 1)\n\n\n@dataclass\nclass _FXSmileMeta:\n    \"\"\"A container of meta data associated with a :class:`~rateslib.volatility._BaseFXSmile`\n    used to make calculations.\"\"\"\n\n    _eval_date: datetime\n    _expiry: datetime\n    _plot_x_axis: str\n    _delta_type: FXDeltaMethod\n    _pair: str | None\n    _calendar: CalTypes\n    _delivery: datetime\n    _delivery_lag: int\n\n    @property\n    def eval_date(self) -> datetime:\n        \"\"\"Evaluation date of the *Smile*.\"\"\"\n        return self._eval_date\n\n    @property\n    def expiry(self) -> datetime:\n        \"\"\"Expiry date of the options priced by this *Smile*\"\"\"\n        return self._expiry\n\n    @property\n    def plot_x_axis(self) -> str:\n        \"\"\"The default ``x_axis`` parameter passed to\n        :meth:`~rateslib.volatility._BaseSmile.plot`\"\"\"\n        return self._plot_x_axis\n\n    @property\n    def delta_type(self) -> FXDeltaMethod:\n        \"\"\"The delta type of the delta indexes associated with the ``nodes`` of the *Smile*.\"\"\"\n        return self._delta_type\n\n    @property\n    def calendar(self) -> CalTypes:\n        \"\"\"Settlement calendar used to determine ``delivery`` from ``expiry``.\"\"\"\n        return self._calendar\n\n    @property\n    def pair(self) -> str | None:\n        \"\"\"FX pair against which options priced by this *Smile* settle against.\"\"\"\n        return self._pair\n\n    @cached_property\n    def t_expiry(self) -> float:\n        \"\"\"Calendar days from eval to expiry divided by 365.\"\"\"\n        return (self._expiry - self._eval_date).days / 365.0\n\n    @cached_property\n    def t_expiry_sqrt(self) -> float:\n        \"\"\"Square root of ``t_expiry``.\"\"\"\n        ret: float = self.t_expiry**0.5\n        return ret\n\n    @property\n    def delivery(self) -> datetime:\n        \"\"\"Delivery date of the forward FX rate applicable to options priced by this *Smile*\"\"\"\n        return self._delivery\n\n    @property\n    def delivery_lag(self) -> int:\n        \"\"\"Business day settlement lag between ``expiry`` and ``delivery``.\"\"\"\n        return self._delivery_lag\n\n\nclass _FXDeltaVolSmileNodes:\n    \"\"\"\n    A container for data relating to interpolating the `nodes` of a\n    :class:`~rateslib.volatility.FXDeltaVolSmile`.\n    \"\"\"\n\n    _nodes: dict[float, DualTypes]\n    _meta: _FXSmileMeta\n    _spline: _FXDeltaVolSpline\n\n    def __init__(self, nodes: dict[float, DualTypes], meta: _FXSmileMeta) -> None:\n        self._nodes = nodes\n        self._meta = meta\n\n        if self.meta.delta_type in [\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ]:\n            vol: DualTypes = self.values[-1] / 100.0\n            upper_bound: float = _dual_float(\n                dual_exp(\n                    vol * self.meta.t_expiry_sqrt * (3.75 - 0.5 * vol * self.meta.t_expiry_sqrt),\n                )\n            )\n        else:\n            upper_bound = 1.0\n\n        if self.n in [1, 2]:\n            t = [0.0] * 4 + [upper_bound] * 4\n        else:\n            t = [0.0] * 4 + self.keys[1:-1] + [upper_bound] * 4\n        self._spline = _FXDeltaVolSpline(t=t)\n\n    def __eq__(self, other: Any) -> bool:\n        if not isinstance(other, _FXDeltaVolSmileNodes):\n            return False\n        return self._nodes == other._nodes and self._meta == other._meta\n\n    @property\n    def plot_upper_bound(self) -> float:\n        \"\"\"The right side delta index bound used in a *'delta' x-axis* plot.\"\"\"\n        if self.meta.delta_type in [\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ]:\n            # upper_bound      = exp(vol * t_expiry_sqrt * (3.75 - 0.5 * vol * t_expiry_sqrt)\n            # plot_upper_bound = exp(vol * t_expiry_sqrt * (3.25 - 0.5 * vol * t_expiry_sqrt)\n            return (\n                self.spline.t[-1] - _dual_float(self.values[-1]) * self.meta.t_expiry_sqrt / 200.0\n            )\n        else:\n            return 1.0\n\n    @property\n    def meta(self) -> _FXSmileMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility.fx._FXSmileMeta`.\"\"\"\n        return self._meta\n\n    @property\n    def nodes(self) -> dict[float, DualTypes]:\n        \"\"\"The initial nodes dict passed for construction of this class.\"\"\"\n        return self._nodes\n\n    @cached_property\n    def keys(self) -> list[float]:\n        \"\"\"A list of the delta index keys in ``nodes``.\"\"\"\n        return list(self.nodes.keys())\n\n    @cached_property\n    def values(self) -> list[DualTypes]:\n        \"\"\"A list of the delta index values in ``nodes``.\"\"\"\n        return list(self.nodes.values())\n\n    @property\n    def n(self) -> int:\n        \"\"\"The number of pricing parameters in ``nodes``.\"\"\"\n        return len(self.keys)\n\n    @property\n    def spline(self) -> _FXDeltaVolSpline:\n        \"\"\"An instance of :class:`~rateslib.volatility.fx._FXDeltaVolSpline`.\"\"\"\n        return self._spline\n\n\nclass _FXDeltaVolSpline:\n    \"\"\"\n    A container for data relating to interpolating the `nodes` of\n    a :class:`~rateslib.volatility.FXDeltaVolSmile` using a cubic PPSpline.\n    \"\"\"\n\n    _t: list[float]\n    _spline: PPSplineF64 | PPSplineDual | PPSplineDual2\n\n    def __init__(self, t: list[float]) -> None:\n        self._t = t\n        self._spline = PPSplineF64(4, [0.0] * 5, None)  # placeholder: csolve will reengineer\n\n    @property\n    def t(self) -> list[float]:\n        \"\"\"The knot sequence of the PPSpline.\"\"\"\n        return self._t\n\n    @property\n    def spline(self) -> PPSplineF64 | PPSplineDual | PPSplineDual2:\n        \"\"\"An instance of :class:`~rateslib.splines.PPSplineF64`,\n        :class:`~rateslib.splines.PPSplineDual` or :class:`~rateslib.splines.PPSplineDual2`\"\"\"\n        return self._spline\n\n    def _csolve_n_other(\n        self, nodes: _FXDeltaVolSmileNodes, ad: int\n    ) -> tuple[list[float], list[DualTypes], int, int]:\n        \"\"\"\n        Solve a spline with more than one node value.\n        Premium adjusted delta types have an unbounded right side delta index so a derivative of\n        0 is applied to the spline as a boundary condition.\n        Premium unadjusted delta types have a right side delta index approximately equal to 1.0.\n        Use a natural spline boundary condition here.\n        \"\"\"\n        tau = nodes.keys.copy()\n        y = nodes.values.copy()\n\n        # left side constraint\n        tau.insert(0, self.t[0])\n        y.insert(0, set_order_convert(0.0, ad, None))\n        left_n = 2  # natural spline\n\n        # right side constraint\n        tau.append(self.t[-1])\n        y.append(set_order_convert(0.0, ad, None))\n        if nodes.meta.delta_type in [\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ]:\n            right_n = 1  # 1st derivative at zero\n        else:\n            right_n = 2  # natural spline\n        return tau, y, left_n, right_n\n\n    def csolve(self, nodes: _FXDeltaVolSmileNodes, ad: int) -> None:\n        \"\"\"\n        Construct a spline of appropriate AD order and solve the spline coefficients for the\n        given ``nodes``.\n\n        Parameters\n        ----------\n        nodes: _FXDeltaVolSmileNodes\n            Required information for constructing a PPSpline.\n        ad: int\n            The AD order of the constructed PPSPline.\n\n        Returns\n        -------\n        None\n        \"\"\"\n        if ad == 0:\n            Spline: type[PPSplineF64] | type[PPSplineDual] | type[PPSplineDual2] = PPSplineF64\n        elif ad == 1:\n            Spline = PPSplineDual\n        else:\n            Spline = PPSplineDual2\n\n        if nodes.n == 1:\n            # one node defines a flat line, all spline coefficients are the equivalent value.\n            self._spline = Spline(4, self.t, nodes.values * 4)  # type: ignore[arg-type]\n        else:\n            tau, y, left_n, right_n = self._csolve_n_other(nodes, ad)\n            self._spline = Spline(4, self.t, None)\n            self._spline.csolve(tau, y, left_n, right_n, False)  # type: ignore[arg-type]\n\n    def to_json(self) -> str:\n        \"\"\"\n        Serialize this object to JSON format.\n\n        The object can be deserialized using the :meth:`~rateslib.serialization.from_json` method.\n\n        Returns\n        -------\n        str\n        \"\"\"\n        obj = dict(\n            PyNative=dict(\n                _FXDeltaVolSpline=dict(\n                    t=self.t,\n                )\n            )\n        )\n        return json.dumps(obj)\n\n    @classmethod\n    def _from_json(cls, loaded_json: dict[str, Any]) -> _FXDeltaVolSpline:\n        return _FXDeltaVolSpline(\n            t=loaded_json[\"t\"],\n        )\n\n    def __eq__(self, other: Any) -> bool:\n        \"\"\"CurveSplines are considered equal if their knot sequence and endpoints are equivalent.\n        For the same nodes this will resolve to give the same spline coefficients.\n        \"\"\"\n        if not isinstance(other, _FXDeltaVolSpline):\n            return False\n        else:\n            return self.t == other.t\n\n\n@dataclass(frozen=True)\nclass _FXDeltaVolSurfaceMeta:\n    \"\"\"\n    An immutable container of meta data associated with a\n    :class:`~rateslib.volatility.FXDeltaVolSurface` used to make calculations.\n    \"\"\"\n\n    _eval_date: datetime\n    _delta_type: FXDeltaMethod\n    _plot_x_axis: str\n    _weights: Series[float] | None\n    _delta_indexes: list[float]\n    _expiries: list[datetime]\n\n    def __post_init__(self) -> None:\n        for idx in range(1, len(self.expiries)):\n            if self.expiries[idx - 1] >= self.expiries[idx]:\n                raise ValueError(\"Surface `expiries` are not sorted or contain duplicates.\\n\")\n\n    @property\n    def delta_indexes(self) -> list[float]:\n        \"\"\"A list of delta indexes associated with each cross-sectional\n        :class:`~rateslib.volatility.FXDeltaVolSmile`.\"\"\"\n        return self._delta_indexes\n\n    @property\n    def expiries(self) -> list[datetime]:\n        \"\"\"A list of the expiries of each cross-sectional\n        :class:`~rateslib.volatility.FXDeltaVolSmile`.\"\"\"\n        return self._expiries\n\n    @cached_property\n    def expiries_posix(self) -> list[float]:\n        \"\"\"A list of the unix timestamps of each date in ``expiries``.\"\"\"\n        return [_.replace(tzinfo=UTC).timestamp() for _ in self.expiries]\n\n    @property\n    def weights(self) -> Series[float] | None:\n        \"\"\"Weights used for temporal volatility interpolation.\"\"\"\n        return self._weights\n\n    @cached_property\n    def weights_cum(self) -> Series[float] | None:\n        \"\"\"Weight adjusted time to expiry (in calendar days) per date for temporal volatility\n        interpolation.\"\"\"\n        if self.weights is None:\n            return None\n        else:\n            return self.weights.cumsum()\n\n    @property\n    def eval_date(self) -> datetime:\n        \"\"\"Evaluation date of the *Surface*.\"\"\"\n        return self._eval_date\n\n    @property\n    def eval_posix(self) -> float:\n        \"\"\"The unix timestamp of the ``eval_date``.\"\"\"\n        return self.eval_date.replace(tzinfo=UTC).timestamp()\n\n    @property\n    def delta_type(self) -> FXDeltaMethod:\n        \"\"\"The delta type of the delta indexes associated with the ``nodes`` of each\n        cross-sectional *Smile*.\"\"\"\n        return self._delta_type\n\n    @property\n    def plot_x_axis(self) -> str:\n        \"\"\"The default ``x_axis`` parameter passed to\n        :meth:`~rateslib.volatility._BaseSmile.plot`\"\"\"\n        return self._plot_x_axis\n\n\n@dataclass(frozen=True)\nclass _FXSabrSurfaceMeta:\n    \"\"\"\n    An immutable container of meta data associated with a\n    :class:`~rateslib.volatility.FXSabrSurface` used to make calculations.\n    \"\"\"\n\n    _eval_date: datetime\n    _pair: str | None\n    _calendar: CalTypes\n    _delivery_lag: int\n    _weights: Series[float] | None\n    _expiries: list[datetime]\n\n    def __post_init__(self) -> None:\n        for idx in range(1, len(self.expiries)):\n            if self.expiries[idx - 1] >= self.expiries[idx]:\n                raise ValueError(\"Surface `expiries` are not sorted or contain duplicates.\\n\")\n\n    @property\n    def weights(self) -> Series[float] | None:\n        \"\"\"Weights used for temporal volatility interpolation.\"\"\"\n        return self._weights\n\n    @cached_property\n    def weights_cum(self) -> Series[float] | None:\n        \"\"\"Weight adjusted time to expiry (in calendar days) per date for temporal volatility\n        interpolation.\"\"\"\n        if self.weights is None:\n            return None\n        else:\n            return self.weights.cumsum()\n\n    @property\n    def expiries(self) -> list[datetime]:\n        \"\"\"A list of the expiries of each cross-sectional\n        :class:`~rateslib.volatility.FXSabrSmile`.\"\"\"\n        return self._expiries\n\n    @cached_property\n    def expiries_posix(self) -> list[float]:\n        \"\"\"A list of the unix timestamps of each date in ``expiries``.\"\"\"\n        return [_.replace(tzinfo=UTC).timestamp() for _ in self.expiries]\n\n    @cached_property\n    def eval_posix(self) -> float:\n        \"\"\"The unix timestamp of the ``eval_date``.\"\"\"\n        return self.eval_date.replace(tzinfo=UTC).timestamp()\n\n    @property\n    def delivery_lag(self) -> int:\n        \"\"\"Business day settlement lag between ``expiry`` and ``delivery``.\"\"\"\n        return self._delivery_lag\n\n    @property\n    def eval_date(self) -> datetime:\n        \"\"\"Evaluation date of the *Surface*.\"\"\"\n        return self._eval_date\n\n    @property\n    def pair(self) -> str | None:\n        \"\"\"FX pair against which options priced by this *Surface* settle against.\"\"\"\n        return self._pair\n\n    @property\n    def calendar(self) -> CalTypes:\n        \"\"\"Settlement calendar used to determine ``delivery`` from ``expiry``.\"\"\"\n        return self._calendar\n\n\ndef _delta_type_constants(\n    delta_type: FXDeltaMethod, w: DualTypes | NoInput, u: DualTypes | NoInput\n) -> tuple[float, DualTypes, DualTypes]:\n    \"\"\"\n    Get the values: (eta, z_w, z_u) for the type of expressed delta\n\n    w: should be input as w_deli / w_spot\n    u: should be input as K / f_d\n    \"\"\"\n    if delta_type == FXDeltaMethod.Forward:\n        return 0.5, 1.0, 1.0\n    elif delta_type == FXDeltaMethod.Spot:\n        return 0.5, w, 1.0  # type: ignore[return-value]\n    elif delta_type == FXDeltaMethod.ForwardPremiumAdjusted:\n        return -0.5, 1.0, u  # type: ignore[return-value]\n    else:  # \"spot_pa\"\n        return -0.5, w, u  # type: ignore[return-value]\n\n\ndef _moneyness_from_atm_delta_closed_form(vol: DualTypes, t_e: DualTypes) -> DualTypes:\n    \"\"\"\n    Return `u` given premium unadjusted `delta`, of either 'spot' or 'forward' type.\n\n    This function preserves AD.\n\n    Book2: section \"Strike and Volatility implied from ATM delta\" (FXDeltaVolSMile)\n\n    Parameters\n    -----------\n    vol: float, Dual, Dual2\n        The volatility (in %, e.g. 10.0) to use in calculations.\n    t_e: float,\n        The time to expiry.\n\n    Returns\n    -------\n    float, Dual or Dual2\n    \"\"\"\n    return dual_exp((vol / 100.0) ** 2 * t_e / 2.0)\n\n\ndef _moneyness_from_delta_closed_form(\n    delta: DualTypes,\n    vol: DualTypes,\n    t_e: DualTypes,\n    z_w_0: DualTypes,\n    phi: float,\n) -> DualTypes:\n    \"\"\"\n    Return `u` given premium unadjusted `delta`, of either 'spot' or 'forward' type.\n\n    This function preserves AD.\n\n    Book2: section \"Strike and Volatility implied from a given option's delta\" (FXDeltaVolSmile)\n\n    Parameters\n    -----------\n    delta: float\n        The input unadjusted delta for which to determine the moneyness for.\n    vol: float, Dual, Dual2\n        The volatility (in %, e.g. 10.0) to use in calculations.\n    t_e: float, Dual, Dual2\n        The time to expiry.\n    z_w_0: float, Dual, Dual2\n        The scalar for 'spot' or 'forward' delta types.\n        If 'forward', this should equal 1.0.\n        If 'spot', this should be :math:`w_deli / w_spot`.\n    phi: float\n        1.0 if is call, -1.0 if is put.\n\n    Returns\n    -------\n    float, Dual or Dual2\n    \"\"\"\n    vol_sqrt_t = vol * t_e**0.5 / 100.0\n    _: DualTypes = dual_inv_norm_cdf(phi * delta / z_w_0)\n    _ = dual_exp(vol_sqrt_t * (0.5 * vol_sqrt_t - phi * _))\n    return _\n"
  },
  {
    "path": "python/rateslib/volatility/ir/__init__.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom rateslib.volatility.ir.base import _BaseIRCube, _BaseIRSmile\nfrom rateslib.volatility.ir.sabr import IRSabrCube, IRSabrSmile\nfrom rateslib.volatility.ir.spline import (\n    IRSplineCube,\n    IRSplineSmile,\n    _IRSplineSmileNodes,\n    _IRVolSpline,\n)\nfrom rateslib.volatility.ir.utils import _IRCubeMeta, _IRSmileMeta, _IRVolPricingParams\n\n__all__ = [\n    \"IRSabrSmile\",\n    \"IRSplineSmile\",\n    \"IRSabrCube\",\n    \"IRSplineCube\",\n    \"_BaseIRSmile\",\n    \"_BaseIRCube\",\n    \"_IRSmileMeta\",\n    \"_IRCubeMeta\",\n    \"_IRVolPricingParams\",\n    \"_IRSplineSmileNodes\",\n    \"_IRVolSpline\",\n]\n\nIRVols = IRSabrSmile | IRSabrCube | IRSplineSmile | IRSplineCube\nIRVolObj = (IRSabrSmile, IRSabrCube, IRSplineSmile, IRSplineCube)\n"
  },
  {
    "path": "python/rateslib/volatility/ir/base.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nfrom abc import ABC, abstractmethod\nfrom datetime import datetime, timezone\nfrom typing import TYPE_CHECKING, Generic, NoReturn, TypeAlias, TypeVar\n\nimport numpy as np\n\nfrom rateslib.curves.interpolation import index_left\nfrom rateslib.default import PlotOutput, plot\nfrom rateslib.dual import Dual, Dual2, Variable\nfrom rateslib.dual.utils import _dual_float\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import OptionPricingModel\nfrom rateslib.mutability import _clear_cache_post, _new_state_post, _WithCache, _WithState\nfrom rateslib.volatility.ir.utils import (\n    _bilinear_interp,\n    _get_ir_expiry,\n    _get_ir_tenor,\n    _IRCubeMeta,\n    _IRSmileMeta,\n)\n\nUTC = timezone.utc\nT = TypeVar(\"T\")\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        Arr1dObj,\n        Arr3dObj,\n        CurvesT_,\n        DualTypes_,\n        Iterable,\n        Sequence,\n        _IRVolPricingParams,\n        datetime_,\n        float_,\n    )\n\nDualTypes: TypeAlias = \"float | Dual | Dual2 | Variable\"  # if not defined causes _WithCache failure\n\n\nclass _WithMutability(ABC):\n    \"\"\"Abstract base class containing the necessary methods to interoperate with a\n    :class:`~rateslib.solver.Solver`.\"\"\"\n\n    # Get methods allow the Solver to extract and order the parameters of the pricing object.\n\n    @property\n    @abstractmethod\n    def _n(self) -> int:\n        \"\"\"The number of parameters associated with the pricing object.\"\"\"\n        pass\n\n    @property\n    @abstractmethod\n    def _ini_solve(self) -> int:\n        \"\"\"The number of parameters that are initially ignored by\n        :class:`~rateslib.solver.Solver` and not mutated during iterations.\"\"\"\n        pass\n\n    @abstractmethod\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[np.object_]]:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        pass\n\n    @abstractmethod\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        pass\n\n    # Set methods allow the Solver to make mutable updates to the pricing object\n    # Direct methods implement the underlying operations, wrapped methods (which are\n    # automatically provided) control additionals such as cache clearing and state management.\n\n    @abstractmethod\n    def _set_node_vector_direct(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        \"\"\"\n        Allow Solver to update parameter values of the pricing object.\n        ``ad`` in {1, 2}.\n        Only the real values in vector are used, dual components are dropped and restructured.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def _set_ad_order_direct(self, order: int | None) -> None:\n        \"\"\"\n        Update the parameter values of the pricing object.\n\n        None: Do nothing regardless of the AD order of the parameters as stated.\n        0: Convert all values to float.\n        1: Convert to Dual with vars ordered by `_get_node_vars`\n        2: Convert to Dual2 with vars ordered by `_get_node_vars`\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def _set_single_node(self, key: Any, value: DualTypes) -> None:\n        \"\"\"\n        Update a single named node on the pricing object.\n        \"\"\"\n        pass\n\n    @_new_state_post\n    @_clear_cache_post\n    def _set_node_vector(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        \"\"\"\n        Update the node values in a Solver. ``ad`` in {1, 2}.\n        Only the real values in vector are used, dual components are dropped and restructured.\n        \"\"\"\n        return self._set_node_vector_direct(vector, ad)\n\n    @_clear_cache_post\n    def _set_ad_order(self, order: int | None) -> None:\n        \"\"\"\n        When pricing objects are mutated by a Solver this method should convert pricing\n        parameters to DualTypes with `vars` as defined by the solver, i.e. overwriting\n        any user specific DualTypes.\n\n        If `order` is *None*, this method will do nothing.\n\n        If `order` is in [0, 1, 2] and that matches the existing AD order of the object then\n        nothing is also done.\n\n        If `order` is in [0, 1, 2] and that represents a new AD order then values are converted\n        using `vars` configured and expected by a Solver.\n\n        If `order` is in [-1, -2] this forces a conversion to the appropriate order, even if the\n        object matches the requested AD order. I.e. user variables will be overridden regardless.\n        \"\"\"\n        return self._set_ad_order_direct(order)\n\n    @_new_state_post\n    @_clear_cache_post\n    def update_node(self, key: str, value: DualTypes) -> None:\n        \"\"\"\n        Update a single node value on the *Smile*.\n\n        Parameters\n        ----------\n        key: str in {\"alpha\", \"beta\", \"rho\", \"nu\"}\n            The node value to update.\n        value: float, Dual, Dual2, Variable\n            Value to update on the *Smile*.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Curve* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Curve* instance.\n           This class is labelled as a **mutable on update** object.\n\n        \"\"\"\n        return self._set_single_node(key, value)\n\n\nclass _BaseIRSmile(_WithState, _WithCache[float, DualTypes], ABC):\n    \"\"\"\n    Abstract base class for implementing *IR Volatility Smiles*.\n\n    Any :class:`~rateslib.volatility._BaseIRSmile` is required to implement the following\n    **properties**:\n\n    - **id** (str)\n    - **ad** (int)\n    - **meta** (:class:`~rateslib.volatility._IRSmileMeta`)\n    - **pricing_params** (Iterable[float | Dual | Dual2 | Variable])\n\n    Any :class:`~rateslib.volatility._BaseIRSmile` is required to implement the following\n    **methods**:\n\n    - **_plot(x_axis, f, y_axis, curves)**\n    - **_get_from_strike(k, f, curves)**\n    - **_d_sigma_d_f(k, f)**\n\n    The directly provided methods with these implementations are:\n\n    - :meth:`~rateslib.volatility._BaseIRSmile.plot`.\n    - :meth:`~rateslib.volatility._BaseIRSmile.get_from_strike`.\n\n    \"\"\"\n\n    _default_plot_x_axis: str\n\n    @property\n    @abstractmethod\n    def id(self) -> str:\n        \"\"\"\n        A str identifier to name the *Smile* used in :class:`~rateslib.solver.Solver` mappings.\n        \"\"\"\n        pass\n\n    @property\n    @abstractmethod\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the\n        :class:`~rateslib.volatility._BaseIRSmile`.\"\"\"\n        pass\n\n    @property\n    @abstractmethod\n    def meta(self) -> _IRSmileMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility.ir.utils._IRSmileMeta`.\"\"\"\n        pass\n\n    @property\n    @abstractmethod\n    def pricing_params(self) -> Iterable[float | Dual | Dual2 | Variable]:\n        \"\"\"An ordered set of pricing parameters associated with the\n        :class:`~rateslib.volatility._BaseIRSmile`.\"\"\"\n        pass\n\n    @abstractmethod\n    def _get_from_strike(\n        self,\n        k: DualTypes,\n        f: DualTypes,\n    ) -> _IRVolPricingParams:\n        \"\"\"\n        Given an option strike and forward rate return the volatility.\n\n        Note this function does not validate the expiry and tenor of the intended option.\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2\n            The strike of the option.\n        f: float, Dual, Dual2\n            The forward rate at delivery of the option.\n\n        Returns\n        -------\n        _IRVolPricingParams\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def _plot(\n        self,\n        x_axis: str,\n        f: float,\n        y_axis: str,\n        tgt_shift: float_,\n    ) -> tuple[Iterable[float], Iterable[float]]:\n        \"\"\"Perform the necessary calculation to derive (x,y) coordinates for a chart.\"\"\"\n        pass\n\n    @abstractmethod\n    def _d_sigma_d_f(\n        self,\n        k: DualTypes,\n        f: DualTypes,\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the derivative :math:`\\frac{d \\\\sigma}{d f}` for a generic spline model.\n        \"\"\"\n        pass\n\n    def _plot_conversion(\n        self,\n        y_axis: str,\n        x_axis: str,\n        f: float,\n        shift: float,\n        tgt_shift: float,\n        x: Iterable[float],\n        y: Iterable[float],\n    ) -> tuple[Iterable[float], Iterable[float]]:\n        # def _hagan_convert(k: DualTypes, sigma_b: DualTypes) -> DualTypes:\n        #     if abs(f - k) < 1e-13:\n        #         center = f + shf\n        #     else:\n        #         center = (f - k) / dual_log((f + shf) / (k + shf))\n        #     return sigma_b * center * (1 - sigma_b ** 2 * sq_t / 24)\n\n        match (self.meta.pricing_model, y_axis.lower()):\n            case (OptionPricingModel.Black76, \"black_vol\"):\n                if shift == tgt_shift:\n                    y_ = y\n                else:\n                    y_ = [\n                        _\n                        * (((f + shift) * (k + shift)) / ((f + tgt_shift) * (k + tgt_shift))) ** 0.5\n                        for _, k in zip(y, x, strict=True)\n                    ]\n            case (OptionPricingModel.Bachelier, \"normal_vol\"):\n                y_ = y\n            case (OptionPricingModel.Black76, \"normal_vol\"):\n                y_ = [\n                    sigma_b * ((f + shift) * (k + shift)) ** 0.5\n                    for (k, sigma_b) in zip(x, y, strict=True)\n                ]\n            case (OptionPricingModel.Bachelier, \"black_vol\"):\n                y_ = [\n                    sigma_n * ((f + tgt_shift) * (k + tgt_shift)) ** -0.5\n                    for (k, sigma_n) in zip(x, y, strict=True)\n                ]\n            case _:\n                raise ValueError(\"`y_axis` must be in {'normal_vol', 'black_vol'}.\")\n\n        if x_axis == \"moneyness\":\n            u: Iterable[float] = x / f  # type: ignore[operator, assignment]\n            return u, y_\n        else:  # x_axis = \"strike\"\n            return x, y_\n\n    def plot(\n        self,\n        comparators: list[_BaseIRSmile] | NoInput = NoInput(0),\n        labels: list[str] | NoInput = NoInput(0),\n        x_axis: str | NoInput = NoInput(0),\n        y_axis: str | NoInput = NoInput(0),\n        f: DualTypes | NoInput = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n        shift: float_ = NoInput(0),\n    ) -> PlotOutput:\n        r\"\"\"\n        Plot volatilities associated with the *Smile*.\n\n        .. role:: green\n\n        .. role:: red\n\n        Parameters\n        ----------\n        comparators: list[Smile], :green:`optional`\n            A list of Smiles which to include on the same plot as comparators.\n        labels : list[str], :green:`optional`\n            A list of strings associated with the plot and comparators. Must be same\n            length as number of plots.\n        x_axis : str in {\"strike\", \"moneyness\"}, :green:`optional (set by object)`\n            *'strike'* is the natural option for this *SabrSmile*.\n            If *'moneyness'* the strikes are converted using ``f``.\n        y_axis : str in {\"black_vol\", \"normal_vol\"}, :green:`optional (set by object)`\n            Convert the y-axis to a different representation using an approximation.\n        f: DualTypes, :green:`optional`\n            The mid-market IRS rate. If ``curves`` are not given then ``f`` is required.\n        curves: Curves, :green:`optional`\n            The *Curves* in the required form for an :class:`~rateslib.instruments.IRS`. If ``f``\n            is not given then ``curves`` are required.\n        shift: float, :green:`optional`\n            If plotting a *'black_vol'* this will use an approximation to convert any native\n            shift into another that is specified here. If not given uses the native shift meta\n            attribute of the *Smile*.\n\n        Returns\n        -------\n        (fig, ax, line) : Matplotlib.Figure, Matplotplib.Axes, Matplotlib.Lines2D\n\n        Notes\n        -----\n        Any approximations converting between *normal* and *black* vol are done so with the\n        first order approximation generally attributable to Fei Zhou. These approximations are only\n        used for charting. Actual instrument pricing metrics are determined more accurately\n        with root solvers.\n\n        .. math::\n\n           \\sigma_{LN+h} \\approx \\frac{\\sigma_{N}}{\\sqrt{(F+h)(K+h)}}\n\n        and,\n\n        .. math::\n\n           \\sigma_{LN+h} \\approx \\sigma_{LN+h2} \\sqrt{ \\frac{(F+h_2)(K+h_2)}{(F+h)(K+h)}}\n\n        for *h* and :math:`h_2` potentially different shifts.\n\n        \"\"\"  # noqa: E501\n        if isinstance(f, NoInput) and isinstance(curves, NoInput):\n            raise ValueError(\"`f` (ATM-forward interest rate) is required by `_BaseIRSmile.plot`.\")\n        elif isinstance(f, float | Dual | Dual2 | Variable):\n            f_: float = _dual_float(f)\n        elif not isinstance(curves, NoInput):\n            f_ = _dual_float(self.meta.irs_fixing.irs.rate(curves=curves))\n        del f\n\n        # reversed for intuitive strike direction\n        comparators = _drb([], comparators)\n        labels = _drb([], labels)\n\n        x_axis_: str = _drb(self.meta.plot_x_axis, x_axis)\n        y_axis_: str = _drb(self.meta.plot_y_axis, y_axis)\n        del x_axis, y_axis\n\n        x_, y_ = self._plot(x_axis_, f_, y_axis_, shift)\n\n        x: list[list[float]] = [list(x_)]\n        y: list[list[float]] = [list(y_)]\n        if not isinstance(comparators, NoInput):\n            for smile in comparators:\n                if not isinstance(smile, _BaseIRSmile):\n                    raise ValueError(\"A `comparator` must be a valid IR Smile type.\")\n                x_, y_ = smile._plot(x_axis_, f_, y_axis_, shift)\n                x.append(list(x_))\n                y.append(list(y_))\n\n        return plot(x, y, labels)\n\n    def get_from_strike(\n        self,\n        k: DualTypes,\n        expiry: datetime_ = NoInput(0),\n        tenor: datetime_ = NoInput(0),\n        f: DualTypes_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n    ) -> _IRVolPricingParams:\n        \"\"\"\n        Given an option strike return the volatility.\n\n        Note if the ``expiry`` and ``tenor`` are given these will be validated against the\n        *_BaseIRSmile* *meta* parameters.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2, Variable, :red:`required`\n            The strike of the option.\n        expiry: datetime, :green:`optional`\n            The expiry of the option. Required for temporal interpolation.\n        tenor: datetime, :green:`optional`\n            The termination date of the underlying *IRS*, required for parameter interpolation.\n        f: float, Dual, Dual2, Variable, :green:`optional`\n            The forward rate at delivery of the option.\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** on :class:`~rateslib.instruments.IRSCall`\n            for details of allowed inputs. Required if ``f`` is not given.\n\n        Returns\n        -------\n        _IRVolPricingParams\n        \"\"\"\n        if not isinstance(expiry, NoInput) and self.meta.expiry != expiry:\n            raise ValueError(\n                f\"`expiry` of _BaseIRSmile and intended price do not match. Got: {expiry} \"\n                f\"and {self.meta.expiry}.\\nCalculation aborted due to potential pricing errors.\",\n            )\n        if not isinstance(tenor, NoInput) and self.meta.irs_fixing.termination != tenor:\n            raise ValueError(\n                f\"`tenor` of _BaseIRSmile and intended price do not match. Got: {tenor} \"\n                f\"and {self.meta.irs_fixing.termination}.\\nCalculation aborted due to potential \"\n                f\"pricing errors.\",\n            )\n\n        if isinstance(f, NoInput):\n            f_: DualTypes = self.meta.irs_fixing.irs.rate(curves=curves)\n        else:\n            f_ = f\n        del f\n\n        return self._get_from_strike(f=f_, k=k)\n\n    def __iter__(self) -> NoReturn:\n        raise TypeError(\"`_BaseIRSmile` types are not iterable.\")\n\n\nclass _BaseIRCube(Generic[T], _WithState, _WithCache[tuple[datetime, datetime], _BaseIRSmile], ABC):\n    \"\"\"\n    Abstract base class for implementing *IR Volatility Cubes*.\n\n    Any :class:`~rateslib.volatility._BaseIRCube` is required to implement the following\n    **properties**:\n\n    - **id** (str)\n    - **ad** (int)\n    - **meta** (:class:`~rateslib.volatility._IRCubeMeta`)\n    - **pricing_params** (3D ndarray)\n\n    Any :class:`~rateslib.volatility._BaseIRCube` is required to implement the following\n    **methods**:\n\n    - **_construct_smile(expiry, tenor, params)**\n    - **_get_from_strike(k, f, curves)**\n\n    The directly provided methods with these implementations are:\n\n    - :meth:`~rateslib.volatility._BaseIRCube.plot`.\n    - :meth:`~rateslib.volatility._BaseIRCube.get_from_strike`.\n\n    \"\"\"\n\n    _SmileType: type[_BaseIRSmile]\n    _node_values_: Arr3dObj\n\n    @property\n    @abstractmethod\n    def id(self) -> str:\n        \"\"\"\n        A str identifier to name the *Cube* used in :class:`~rateslib.solver.Solver` mappings.\n        \"\"\"\n        pass\n\n    @property\n    @abstractmethod\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the\n        :class:`~rateslib.volatility._BaseIRCube`.\"\"\"\n        pass\n\n    @property\n    @abstractmethod\n    def meta(self) -> _IRCubeMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility.ir.utils._IRCubeMeta`.\"\"\"\n        pass\n\n    @property\n    @abstractmethod\n    def pricing_params(self) -> Arr3dObj:\n        \"\"\"A 3-d array of pricing parameters with axes (expiry, tenor, strike).\"\"\"\n        pass\n\n    def _bilinear_interpolation(\n        self,\n        expiry: datetime,\n        tenor: datetime,\n    ) -> Arr1dObj:\n        \"\"\"\n        Linearly interpolate the expiries / tenors array and return interpolated values\n        for the alpha, rho and nu parameters.\n\n        Returns\n        -------\n        (alpha, rho, nu)\n        \"\"\"\n        # For out of bounds expiry values convert to boundary expiries with tenor time adjustment\n        if expiry < self.meta.expiry_dates[0]:\n            return self._bilinear_interpolation(\n                expiry=self.meta.expiry_dates[0],\n                tenor=tenor + (self.meta.expiry_dates[0] - expiry),\n            )\n        elif expiry > self.meta.expiry_dates[-1]:\n            return self._bilinear_interpolation(\n                expiry=self.meta.expiry_dates[-1],\n                tenor=tenor - (expiry - self.meta.expiry_dates[-1]),\n            )\n\n        e_posix = expiry.replace(tzinfo=UTC).timestamp()\n        t_posix = tenor.replace(tzinfo=UTC).timestamp()\n\n        match (self.meta._n_expiries, self.meta._n_tenors):\n            case (1, 1):\n                # nothing to interpolate: return the only parameters of the surface\n                return self.pricing_params[0, 0, :]\n\n            case (1, _):\n                # interpolate only over tenor\n                e_l = 0\n                e_l_p = 0\n                t_posix_1 = t_posix - (e_posix - self.meta.expiries_posix[0])\n                t_l_1 = index_left(\n                    list_input=self.meta.tenor_dates_posix[0, :],  # type: ignore[arg-type]\n                    list_length=self.meta._n_tenors,\n                    value=t_posix_1,\n                )\n                t_l_1_p = t_l_1 + 1\n                v_ = (0.0, 0.0)  # only one expiry so no interpolation over that dimension\n                t_l_2, t_l_2_p = t_l_1, t_l_1_p\n                h_: tuple[float, float] = (\n                    (t_posix_1 - self.meta.tenor_dates_posix[e_l, t_l_1])\n                    / (\n                        self.meta.tenor_dates_posix[e_l, t_l_1_p]\n                        - self.meta.tenor_dates_posix[e_l, t_l_1]\n                    ),\n                ) * 2\n\n            case (_, 1):\n                # interpolate only over expiry\n                e_l = index_left(\n                    list_input=self.meta.expiries_posix,\n                    list_length=self.meta._n_expiries,\n                    value=e_posix,\n                )\n                e_l_p = e_l + 1\n                t_l_1, t_l_2 = 0, 0\n                t_l_1_p, t_l_2_p = 0, 0\n                h_ = (0, 0)\n                v_ = (\n                    (e_posix - self.meta.expiries_posix[e_l])\n                    / (self.meta.expiries_posix[e_l_p] - self.meta.expiries_posix[e_l]),\n                ) * 2\n\n            case _:\n                # perform true bilinear interpolation\n                e_l = index_left(\n                    list_input=self.meta.expiries_posix,\n                    list_length=self.meta._n_expiries,\n                    value=e_posix,\n                )\n                e_l_p = e_l + 1\n                v_ = (\n                    (e_posix - self.meta.expiries_posix[e_l])\n                    / (self.meta.expiries_posix[e_l_p] - self.meta.expiries_posix[e_l]),\n                ) * 2\n\n                # these are the relative tenors as measured per each benchmark expiry\n                t_posix_1 = t_posix - (e_posix - self.meta.expiries_posix[e_l])\n                t_posix_2 = t_posix - (e_posix - self.meta.expiries_posix[e_l_p])\n\n                t_l_1 = index_left(\n                    list_input=self.meta.tenor_dates_posix[e_l, :],  # type: ignore[arg-type]\n                    list_length=self.meta._n_tenors,\n                    value=t_posix_1,\n                )\n                t_l_1_p = t_l_1 + 1\n                t_l_2 = index_left(\n                    list_input=self.meta.tenor_dates_posix[e_l_p, :],  # type: ignore[arg-type]\n                    list_length=self.meta._n_tenors,\n                    value=t_posix_2,\n                )\n                t_l_2_p = t_l_2 + 1\n\n                h_ = (\n                    (t_posix_1 - self.meta.tenor_dates_posix[e_l, t_l_1])\n                    / (\n                        self.meta.tenor_dates_posix[e_l, t_l_1 + 1]\n                        - self.meta.tenor_dates_posix[e_l, t_l_1]\n                    ),\n                    (t_posix_2 - self.meta.tenor_dates_posix[e_l_p, t_l_2])\n                    / (\n                        self.meta.tenor_dates_posix[e_l_p, t_l_2 + 1]\n                        - self.meta.tenor_dates_posix[e_l_p, t_l_2]\n                    ),\n                )\n\n        h_ = (min(max(h_[0], 0), 1), min(max(h_[1], 0), 1))\n\n        return np.array(\n            [\n                _bilinear_interp(\n                    tl=param[e_l, t_l_1],\n                    tr=param[e_l, t_l_1_p],\n                    bl=param[e_l_p, t_l_2],\n                    br=param[e_l_p, t_l_2_p],\n                    h=h_,\n                    v=v_,\n                )\n                for param in [\n                    self.pricing_params[:, :, i] for i in range(self.pricing_params.shape[2])\n                ]\n            ]\n        )\n\n    def _construct_smile(\n        self,\n        expiry: datetime,\n        tenor: datetime,\n        params: Sequence[DualTypes] | Arr1dObj,\n    ) -> _BaseIRSmile:\n        if isinstance(self.meta.time_scalars, NoInput):\n            ts: DualTypes_ = NoInput(0)\n        else:\n            if expiry > self.meta.time_scalars.index[-1]:\n                ts = NoInput(0)\n            else:\n                ts = self.meta.time_scalars[expiry]\n\n        return self._SmileType(  # type: ignore[call-arg]\n            nodes=dict(zip(self.meta.indexes, params, strict=True)),\n            eval_date=self.meta.eval_date,\n            expiry=expiry,\n            irs_series=self.meta.irs_series,\n            tenor=tenor,\n            shift=self.meta.shift,\n            ad=None,  # inherit the AD variables from the params\n            time_scalar=ts,\n            **self.meta.smile_params,\n        )\n\n    def get_from_strike(\n        self,\n        k: DualTypes,\n        expiry: datetime,\n        tenor: datetime,\n        f: DualTypes_ = NoInput(0),\n        curves: CurvesT_ = NoInput(0),\n    ) -> _IRVolPricingParams:\n        \"\"\"\n        Given an option strike, expiry and tenor, return the volatility.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2, Variable, :red:`required`\n            The strike of the option.\n        expiry: datetime, :red:`required`\n            The expiry of the option. Required for temporal interpolation.\n        tenor: datetime, :red:`required`\n            The termination date of the underlying *IRS*, required for parameter interpolation.\n        f: float, Dual, Dual2, :green:`optional`\n            The forward rate at delivery of the option.\n        curves: _Curves, :green:`optional`\n            Pricing objects. See **Pricing** notes of an :class:`~rateslib.instruments.IRSCall`\n            for details of allowed inputs.\n\n        Returns\n        -------\n        _IRVolPricingParams\n        \"\"\"\n        smile = self.get_smile(expiry, tenor)\n        return smile.get_from_strike(k=k, f=f, curves=curves)\n\n    def get_smile(self, expiry: datetime | str, tenor: datetime | str) -> _BaseIRSmile:\n        \"\"\"\n        Return a constructed :class:`~rateslib.volatility._BaseIRSmile` for a given\n        expiry and tenor.\n\n        .. role:: red\n\n        .. role:: green\n\n        Parameters\n        -----------\n        expiry: datetime, str, :red:`required`\n            The expiry of the option. Required for temporal interpolation.\n        tenor: datetime, str, :red:`required`\n            The termination date of the underlying *IRS*, required for parameter interpolation.\n\n        Returns\n        -------\n        _BaseIRSmile\n        \"\"\"\n        expiry_ = _get_ir_expiry(\n            eval_date=self.meta.eval_date, irs_series=self.meta.irs_series, expiry=expiry\n        )\n        tenor_ = _get_ir_tenor(expiry=expiry_, irs_series=self.meta.irs_series, tenor=tenor)\n        del expiry, tenor\n\n        if (expiry_, tenor_) in self._cache:\n            smile = self._cache[expiry_, tenor_]\n        else:\n            params = self._bilinear_interpolation(expiry=expiry_, tenor=tenor_)\n            smile = self._cached_value(\n                key=(expiry_, tenor_), val=self._construct_smile(expiry_, tenor_, params)\n            )\n        return smile\n\n    def _get_node_vector(self) -> Arr1dObj:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return self.pricing_params.ravel()\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        vars_: tuple[str, ...] = tuple(\n            f\"{self.id}{i}\"\n            for i in range(self.meta._n_expiries * self.meta._n_tenors * len(self.meta.indexes))\n        )\n        return vars_\n\n    def _set_single_node(\n        self, key: tuple[datetime | str, datetime | str, T], value: DualTypes\n    ) -> None:\n        \"\"\"\n        Update some generic parameters on the *SabrCube*.\n\n        Parameters\n        ----------\n        key: tuple of (datetime, datetime, str in {\"alpha\", \"rho\", \"nu\"})\n            The node value to update, indexed by (expiry, tenor, SABR param).\n        value: Array, float, Dual, Dual2, Variable\n            Value to update on the *Cube*.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n        This function may update all of the AD variable names to be a consistent pricing object\n        familiar to a :class:`~rateslib.solver.Solver`.\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Curve* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Curve* instance.\n           This class is labelled as a **mutable on update** object.\n\n        \"\"\"\n        expiry_ = _get_ir_expiry(\n            eval_date=self.meta.eval_date, irs_series=self.meta.irs_series, expiry=key[0]\n        )\n        tenor_ = _get_ir_tenor(expiry=expiry_, irs_series=self.meta.irs_series, tenor=key[1])\n\n        if expiry_ not in self.meta.expiry_dates:\n            raise KeyError(f\"'{expiry_}' is not in `meta.expiry_dates`.\")\n\n        tenor_row = self.meta.expiry_dates.index(expiry_)\n        if tenor_ not in self.meta.tenor_dates[tenor_row]:\n            raise KeyError(f\"'{tenor_}' is not in `meta.tenor_dates`.\")\n\n        return self._set_single_node_direct((expiry_, tenor_, key[2]), value)\n\n    @abstractmethod\n    def _set_single_node_direct(self, key: tuple[datetime, datetime, T], value: DualTypes) -> None:\n        pass\n"
  },
  {
    "path": "python/rateslib/volatility/ir/sabr.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nfrom datetime import datetime, timezone\nfrom typing import TYPE_CHECKING\nfrom uuid import uuid4\n\nimport numpy as np\nfrom pandas import DataFrame, Index\n\nfrom rateslib.data.fixings import IRSSeries, _get_irs_series\nfrom rateslib.dual import Dual, Dual2, Variable, set_order_convert\nfrom rateslib.dual.utils import _dual_float, _to_number, dual_exp, dual_inv_norm_cdf\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import OptionPricingModel\nfrom rateslib.mutability import (\n    _new_state_post,\n)\nfrom rateslib.volatility.ir.base import _BaseIRCube, _BaseIRSmile, _WithMutability\nfrom rateslib.volatility.ir.utils import (\n    _IRCubeMeta,\n    _IRSmileMeta,\n    _IRVolPricingParams,\n)\nfrom rateslib.volatility.utils import _SabrModel, _SabrSmileNodes\n\nUTC = timezone.utc\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Arr1dObj,\n        Arr2dObj,\n        Arr3dObj,\n        DualTypes,\n        DualTypes_,\n        Iterable,\n        Number,\n        Series,\n        float_,\n    )\n\n\nclass IRSabrSmile(_BaseIRSmile, _WithMutability):\n    r\"\"\"\n    Create an *IR Volatility Smile* at a given expiry indexed for a specific IRS tenor\n    using SABR parameters.\n\n    An *IRSabrSmile* is intended as a grid point element of the more general\n    :class:`~rateslib.volatility.IRSabrCube`, which users are recommended to use instead.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    .. role:: green\n\n    .. role:: red\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import IRSabrSmile, dt\n\n    .. ipython:: python\n\n       irss = IRSabrSmile(\n           eval_date=dt(2000, 1, 1),\n           expiry=dt(2000, 7, 1),\n           tenor=\"1y\",\n           irs_series=\"usd_irs\",\n           beta=0.5,\n           nodes=dict(alpha=0.2, rho=-0.05, nu=0.65),\n           shift=0.0,\n       )\n       irss.plot(f=2.5513, x_axis=\"strike\", y_axis=\"normal_vol\")\n\n    .. plot::\n\n       from rateslib import IRSabrSmile, dt\n       irss = IRSabrSmile(\n           eval_date=dt(2000, 1, 1),\n           expiry=dt(2000, 7, 1),\n           tenor=\"1y\",\n           irs_series=\"usd_irs\",\n           beta=0.5,\n           nodes=dict(alpha=0.2, rho=-0.05, nu=0.65),\n           shift=0.0,\n       )\n       fig, ax, lines = irss.plot(f=2.5513, x_axis=\"strike\", y_axis=\"normal_vol\")\n       plt.show()\n       plt.close()\n\n    For further examples see :ref:`Constructing a Smile <c-ir-smile-constructing-doc>`.\n\n    Parameters\n    ----------\n    nodes: dict[str, float], :red:`required`\n        The parameters for the SABR model. Keys must be *'alpha', 'rho', 'nu'*. See below.\n    beta: float, Variable, :red:`required`\n        The SABR beta parameter assumed by this *Smile*.\n    eval_date: datetime, :red:`required`\n        Acts like the initial node of a *Curve*. Should be assigned today's immediate date.\n    expiry: datetime, :red:`required`\n        The expiry date of the options associated with this *Smile*.\n    irs_series: IRSSeries, :red:`required`\n        The :class:`~rateslib.data.fixings.IRSSeries` that contains the parameters for the\n        underlying :class:`~rateslib.instruments.IRS` that the swaptions are settled against.\n    tenor: datetime, str, :red:`required`\n        The tenor parameter for the underlying :class:`~rateslib.instruments.IRS` that the\n        swaptions are settled against.\n    shift: float, Variable, :green:`optional (set as zero)`\n        The number of basis points to apply to the strike and forward under a 'Black Shifted\n        Volatility' model.\n    time_scalar: float, Dual, Dual2, Variable, :green:`optional (set as one)`\n        A quantity to remap calendar day time to expiry from ``eval_date`` to another measure\n        of time.\n    id: str, optional, :green:`optional (set as random)`\n        The unique identifier to distinguish between *Smiles* in a multicurrency framework\n        and/or *Surface*.\n    ad: int, :green:`optional (set by default)`\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    A SABR model uses a (shifted) Black (log-normal) volatility with a Black-76 option pricing\n    formula.\n\n    The keys for ``nodes`` are described as the following:\n\n    - ``alpha``: The initial volatility parameter (e.g. 0.10 for 10%) of the SABR model,\n      in (0, inf).\n    - ``rho``: The correlation between spot and volatility of the SABR model,\n      e.g. -0.10, in [-1.0, 1.0)\n    - ``nu``: The volatility of volatility parameter of the SABR model, e.g. 0.80.\n\n    The parameters :math:`\\alpha, \\rho, \\nu` will be calibrated/mutated by\n    a :class:`~rateslib.solver.Solver` object. These should be entered as *float* and the argument\n    ``ad`` can be used to automatically tag these as variables.\n\n    The parameter :math:`\\beta` will **not** be calibrated/mutated by a\n    :class:`~rateslib.solver.Solver`. This value can be entered either as a *float*, or a\n    :class:`~rateslib.dual.Variable` to capture exogenous sensitivities.\n\n    \"\"\"\n\n    @_new_state_post\n    def __init__(\n        self,\n        nodes: dict[str, DualTypes],\n        beta: float | Variable,\n        eval_date: datetime,\n        expiry: datetime | str,\n        irs_series: IRSSeries | str,\n        tenor: datetime | str,\n        *,\n        shift: DualTypes_ = NoInput(0),\n        time_scalar: DualTypes_ = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        ad: int | None = 0,\n    ):\n        self._id: str = (\n            uuid4().hex[:5] + \"_\" if isinstance(id, NoInput) else id\n        )  # 1 in a million clash\n        self._meta: _IRSmileMeta = _IRSmileMeta(\n            _tenor_input=tenor,\n            _irs_series=_get_irs_series(irs_series),\n            _eval_date=eval_date,\n            _expiry_input=expiry,\n            _plot_x_axis=\"strike\",\n            _plot_y_axis=\"black_vol\",\n            _shift=_drb(0.0, shift),\n            _pricing_model=OptionPricingModel.Black76,\n            _time_scalar=_drb(1.0, time_scalar),\n        )\n\n        try:\n            self._nodes: _SabrSmileNodes = _SabrSmileNodes(\n                _alpha=_to_number(nodes[\"alpha\"]),\n                _beta=beta,\n                _rho=_to_number(nodes[\"rho\"]),\n                _nu=_to_number(nodes[\"nu\"]),\n            )\n        except KeyError as e:\n            for _ in [\"alpha\", \"rho\", \"nu\"]:\n                if _ not in nodes:\n                    raise ValueError(\n                        f\"'{_}' is a required SABR parameter that must be included in ``nodes``\"\n                    )\n            raise e  # pragma: no cover\n\n        self._set_ad_order(ad)\n\n    ### Object unique elements\n\n    @property\n    def _n(self) -> int:\n        return self.nodes.n\n\n    @property\n    def _ini_solve(self) -> int:\n        return 1\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Smile* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def nodes(self) -> _SabrSmileNodes:\n        \"\"\"An instance of :class:`~rateslib.volatility.utils._SabrSmileNodes`.\"\"\"\n        return self._nodes\n\n    def _d_sabr_d_k_or_f(\n        self,\n        k: DualTypes,\n        f: DualTypes,\n        expiry: datetime,\n        as_float: bool,\n        derivative: int,\n    ) -> tuple[DualTypes, DualTypes | None]:\n        \"\"\"Get the derivative of sabr vol with respect to strike\n\n        as_float: bool\n            Allow expedited calculation by avoiding dual numbers. Useful during the root solving\n            phase of Newton iterations.\n        derivative: int\n            For with respect to `k` use 1, or `f` use 2.\n        \"\"\"\n        t_e = _to_number(self.meta.t_expiry)\n        K = k + self.meta.rate_shift\n        F = f + self.meta.rate_shift\n        del k, f\n\n        if as_float:\n            k_: Number = _dual_float(K)\n            f_: Number = _dual_float(F)\n            a_: Number = _dual_float(self.nodes.alpha)\n            b_: float | Variable = _dual_float(self.nodes.beta)\n            p_: Number = _dual_float(self.nodes.rho)\n            v_: Number = _dual_float(self.nodes.nu)\n        else:\n            k_ = _to_number(K)\n            f_ = _to_number(F)\n            a_ = self.nodes.alpha  #\n            b_ = self.nodes.beta\n            p_ = self.nodes.rho\n            v_ = self.nodes.nu\n\n        return _SabrModel._d_sabr_d_k_or_f(k_, f_, t_e, a_, b_, p_, v_, derivative)\n\n    ### _WithMutability ABCs:\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[np.object_]]:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return np.array([self.nodes.alpha, self.nodes.rho, self.nodes.nu])\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        return tuple(f\"{self.id}{i}\" for i in range(3))\n\n    def _set_node_vector_direct(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        \"\"\"\n        Update the node values in a Solver. ``ad`` in {1, 2}.\n        Only the real values in vector are used, dual components are dropped and restructured.\n        \"\"\"\n        DualType: type[Dual] | type[Dual2] = Dual if ad == 1 else Dual2\n        DualArgs: tuple[list[float]] | tuple[list[float], list[float]] = (\n            ([],) if ad == 1 else ([], [])\n        )\n        base_obj = DualType(0.0, [f\"{self.id}{i}\" for i in range(3)], *DualArgs)\n        ident = np.eye(3)\n\n        self._nodes = _SabrSmileNodes(\n            _beta=self.nodes.beta,\n            _alpha=DualType.vars_from(\n                base_obj,  # type: ignore[arg-type]\n                vector[0].real,\n                base_obj.vars,\n                ident[0, :].tolist(),\n                *DualArgs[1:],\n            ),\n            _rho=DualType.vars_from(\n                base_obj,  # type: ignore[arg-type]\n                vector[1].real,\n                base_obj.vars,\n                ident[1, :].tolist(),\n                *DualArgs[1:],\n            ),\n            _nu=DualType.vars_from(\n                base_obj,  # type: ignore[arg-type]\n                vector[2].real,\n                base_obj.vars,\n                ident[2, :].tolist(),\n                *DualArgs[1:],\n            ),\n        )\n\n    def _set_ad_order_direct(self, order: int | None) -> None:\n        \"\"\"This does not alter the beta node, since that is not varied by a Solver.\n        beta values that are AD sensitive should be given as a Variable and not Dual/Dual2.\n\n        Using `None` allows this Smile to be constructed without overwriting any variable names.\n        \"\"\"\n        # -1, -2 force updates to new variables\n        if order is None or order == getattr(self, \"ad\", None):\n            return None\n        elif abs(order) not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = abs(order)\n        self._nodes = _SabrSmileNodes(\n            _beta=self.nodes.beta,\n            _alpha=set_order_convert(self.nodes.alpha, order, [f\"{self.id}0\"]),\n            _rho=set_order_convert(self.nodes.rho, order, [f\"{self.id}1\"]),\n            _nu=set_order_convert(self.nodes.nu, order, [f\"{self.id}2\"]),\n        )\n\n    def _set_single_node(self, key: str, value: DualTypes) -> None:\n        params = [\"alpha\", \"rho\", \"nu\", \"beta\"]\n        if key not in params:\n            raise KeyError(f\"'{key}' is not in `nodes`.\")\n        kwargs = {f\"_{_}\": getattr(self.nodes, _) for _ in params if _ != key}\n        kwargs.update({f\"_{key}\": value})\n        self._nodes = _SabrSmileNodes(**kwargs)\n        self._set_ad_order(self.ad)\n\n    # _BaseIRSmile ABCS:\n\n    def _plot(\n        self,\n        x_axis: str,\n        f: float,\n        y_axis: str,\n        tgt_shift: float_,\n    ) -> tuple[Iterable[float], Iterable[float]]:\n        shf = _dual_float(self.meta.shift) / 100.0\n        v_ = _dual_float(self.get_from_strike(k=f, f=f).vol) / 100.0\n        sq_t = self._meta.t_expiry_sqrt\n        x_low = _dual_float(\n            dual_exp(0.5 * v_**2 * sq_t**2 - dual_inv_norm_cdf(0.95) * v_ * sq_t) * (f + shf) - shf\n        )\n        x_top = _dual_float(\n            dual_exp(0.5 * v_**2 * sq_t**2 - dual_inv_norm_cdf(0.05) * v_ * sq_t) * (f + shf) - shf\n        )\n\n        x = np.linspace(x_low, x_top, 301, dtype=np.float64)\n        y: Iterable[float] = [_dual_float(self.get_from_strike(k=_, f=f).vol) for _ in x]\n\n        return self._plot_conversion(\n            y_axis=y_axis, x_axis=x_axis, f=f, shift=shf, tgt_shift=_drb(shf, tgt_shift), x=x, y=y\n        )\n\n    @property\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the\n        :class:`~rateslib.volatility._BaseIRSmile`.\"\"\"\n        return self._ad\n\n    @property\n    def pricing_params(self) -> tuple[float | Dual | Dual2 | Variable, ...]:\n        \"\"\"An ordered set of pricing parameters associated with the\n        :class:`~rateslib.volatility._BaseIRSmile`.\"\"\"\n        return self.nodes.alpha, self.nodes.rho, self.nodes.nu\n\n    @property\n    def meta(self) -> _IRSmileMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility.ir.utils._IRSmileMeta`.\"\"\"\n        return self._meta\n\n    def _get_from_strike(self, k: DualTypes, f: DualTypes) -> _IRVolPricingParams:\n        \"\"\"\n        Given an option strike return the volatility.\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2\n            The strike of the option.\n        f: float, Dual, Dual2\n            The forward rate at delivery of the option.\n        expiry: datetime, optional\n            The expiry of the option. Required for temporal interpolation.\n        tenor: datetime, optional\n            The termination date of the underlying *IRS*, required for parameter interpolation.\n        curves: _Curves,\n            Pricing objects. See **Pricing** on :class:`~rateslib.instruments.IRSCall`\n            for details of allowed inputs.\n\n        Returns\n        -------\n        _IRVolPricingParams\n        \"\"\"\n        vol_ = _SabrModel._d_sabr_d_k_or_f(\n            _to_number(k + self.meta.rate_shift),\n            _to_number(f + self.meta.rate_shift),\n            _to_number(self._meta.t_expiry),\n            self.nodes.alpha,\n            self.nodes.beta,\n            self.nodes.rho,\n            self.nodes.nu,\n            derivative=0,\n        )[0]\n        return _IRVolPricingParams(\n            vol=vol_ * 100.0,\n            k=k,\n            f=f,\n            shift=self.meta.shift,\n            pricing_model=OptionPricingModel.Black76,\n            t_e=self._meta.t_expiry,\n        )\n\n    def _d_sigma_d_f(\n        self,\n        k: DualTypes,\n        f: DualTypes,\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the derivative :math:`\\frac{d \\\\sigma}{d f}` for a generic spline model.\n        \"\"\"\n        return _SabrModel._d_sabr_d_k_or_f(  # type: ignore[return-value]\n            _to_number(k + self.meta.rate_shift),\n            _to_number(f + self.meta.rate_shift),\n            _to_number(self._meta.t_expiry),\n            self.nodes.alpha,\n            self.nodes.beta,\n            self.nodes.rho,\n            self.nodes.nu,\n            derivative=2,\n        )[1]\n\n\nclass IRSabrCube(_BaseIRCube[str], _WithMutability):\n    r\"\"\"\n    Create an *IR Volatility Cube* parametrized by :class:`~rateslib.volatility.IRSabrSmile` at\n    different *expiries* and *IRS* *tenors*.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import IRSabrCube, dt\n\n    .. ipython:: python\n\n       irsc = IRSabrCube(\n           eval_date=dt(2000, 1, 1),\n           expiries=[\"3m\", \"1y\"],\n           tenors=[\"1y\", \"2y\"],\n           irs_series=\"usd_irs\",\n           beta=0.5,\n           alpha=[[0.21, 0.22], [0.20, 0.20]],\n           rho=-0.05,  # <-- applied to all values in the array\n           nu=[[0.5, 0.55], [0.65, 0.65]],\n       )\n       irss = irsc.get_smile(\"6m\", \"1y\")\n       irss.plot(f=2.5513, x_axis=\"strike\", y_axis=\"normal_vol\")\n\n    .. plot::\n\n       from rateslib import IRSabrCube, dt\n       irsc = IRSabrCube(\n           eval_date=dt(2000, 1, 1),\n           expiries=[\"3m\", \"1y\"],\n           tenors=[\"1y\", \"2y\"],\n           irs_series=\"usd_irs\",\n           beta=0.5,\n           alpha=[[0.21, 0.22], [0.20, 0.20]],\n           rho=-0.05,  # <-- applied to all values in the array\n           nu=[[0.5, 0.55], [0.65, 0.65]],\n       )\n       irss = irsc.get_smile(\"6m\", \"1y\")\n       fig, ax, lines = irss.plot(f=2.5513, x_axis=\"strike\", y_axis=\"normal_vol\")\n       plt.show()\n       plt.close()\n\n\n    For further information see also the\n    :ref:`IR Vol Smiles & Cubes <c-ir-smile-doc>` section in the user guide.\n\n    .. role:: green\n\n    .. role:: red\n\n    Parameters\n    ----------\n    eval_date: datetime, :red:`required`\n        Acts as the initial node of a *Curve*. Should be assigned today's immediate date.\n        If expiry is given as string used to derive the specific date.\n    expiries: list[datetime | str], :red:`required`\n        Datetimes representing the expiries of each parametrized *Smile*, in ascending order.\n    tenors: list[str], :red:`required`\n        The tenors of each underlying *IRS* from each expiry for the parameterised *Smiles*.\n    alpha: float, Variable, or 2D-ndarray of such, :red:`required`\n        The alpha, :math:`\\alpha_{expiry, tenor}`, parameters of each (expiry, tenor) node.\n    rho: float, Variable, or 2D-ndarray of such, :red:`required`\n        The rho, :math:`\\rho_{expiry, tenor}`, parameters of each (expiry, tenor) node.\n    nu: float, Variable, or 2D-ndarray of such, :red:`required`\n        The nu, :math:`\\nu_{expiry, tenor}`, parameters of each (expiry, tenor) node.\n    irs_series: str, IRSSeries, :red:`required`\n        The :class:`~rateslib.data.fixings.IRSSeries` that contains the parameters for the\n        underlying :class:`~rateslib.instruments.IRS` that the swaptions are settled against.\n    beta: float, Variable, :red:`required`\n        The beta, :math:`\\beta`, parameter of the SABR model.\n    weights: Series, :green:`optional`\n       Weights used for temporal volatility interpolation. Please see\n       :ref:`IR vol time remapping <cook-ir-vol-time-doc>` before using this argument.\n    id: str, :green:`optional`\n       The unique identifier to label the *Surface* and its variables.\n    ad: int, :green:`optional`\n       Sets the automatic differentiation order. Defines whether to convert node\n       values to float, :class:`~rateslib.dual.Dual` or\n       :class:`~rateslib.dual.Dual2`. It is advised against\n       using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    SABR parameters for any **(expiry, tenor)** pair are bilinearly interpolated from\n    immediately neighbouring grid points. Grid points outside of the domain of the given\n    ``expiries`` and ``tenors`` assume values from the singular nearest grid point.\n    \"\"\"\n\n    _ini_solve = 0\n    _SmileType = IRSabrSmile\n    _meta: _IRCubeMeta\n    _id: str\n\n    def __init__(\n        self,\n        eval_date: datetime,\n        expiries: list[datetime | str],\n        tenors: list[str],\n        alpha: DualTypes | Arr2dObj,\n        rho: DualTypes | Arr2dObj,\n        nu: DualTypes | Arr2dObj,\n        irs_series: str | IRSSeries,\n        beta: DualTypes,\n        shift: DualTypes_ = NoInput(0),\n        weights: Series[float] | NoInput = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        ad: int = 0,\n    ):\n        self._id: str = (\n            uuid4().hex[:5] + \"_\" if isinstance(id, NoInput) else id\n        )  # 1 in a million clash\n\n        self._meta = _IRCubeMeta(\n            _eval_date=eval_date,\n            _tenors=tenors,\n            _weights=weights,\n            _expiries=expiries,\n            _irs_series=_get_irs_series(irs_series),\n            _shift=_drb(0.0, shift),\n            _indexes=[\"alpha\", \"rho\", \"nu\"],\n            _smile_params=dict(beta=beta),\n            _pricing_model=OptionPricingModel.Black76,\n        )\n\n        _shape = (self.meta._n_expiries, self.meta._n_tenors)\n        self._node_values_: Arr3dObj = np.empty(shape=_shape + (3,), dtype=object)\n        for i, kw in enumerate([alpha, rho, nu]):\n            if isinstance(kw, float | Dual | Dual2 | Variable):\n                self._node_values_[:, :, i] = np.full(fill_value=kw, shape=_shape)\n            else:\n                self._node_values_[:, :, i] = np.asarray(kw)\n\n        self._set_ad_order(ad)  # includes csolve on each smile\n        self._set_new_state()\n\n    @property\n    def beta(self) -> DualTypes:\n        \"\"\"The *beta*  value of each :class:`~rateslib.volatility.IRSabrSmile` associated with\n        this *Cube*.\"\"\"\n        return self.meta.smile_params[\"beta\"]  # type: ignore[no-any-return]\n\n    @property\n    def alpha(self) -> DataFrame:\n        \"\"\"The *alpha*  value of each :class:`~rateslib.volatility.IRSabrSmile` associated with\n        this *Cube*.\"\"\"\n        return DataFrame(\n            index=Index(data=self.meta.expiries, name=\"expiry\"),\n            columns=Index(data=self.meta.tenors, name=\"tenor\"),\n            data=self._node_values_[:, :, 0],\n        )\n\n    @property\n    def alpha_float(self) -> DataFrame:\n        \"\"\"The *alpha*  value of each :class:`~rateslib.volatility.IRSabrSmile` associated with\n        this *Cube* in float format.\"\"\"\n        return self.alpha.map(lambda x: _dual_float(x))\n\n    @property\n    def rho(self) -> DataFrame:\n        \"\"\"The *rho*  value of each :class:`~rateslib.volatility.IRSabrSmile` associated with\n        this *Cube*.\"\"\"\n        return DataFrame(\n            index=Index(data=self.meta.expiries, name=\"expiry\"),\n            columns=Index(data=self.meta.tenors, name=\"tenor\"),\n            data=self._node_values_[:, :, 1],\n        )\n\n    @property\n    def rho_float(self) -> DataFrame:\n        \"\"\"The *rho*  value of each :class:`~rateslib.volatility.IRSabrSmile` associated with\n        this *Cube* in float format.\"\"\"\n        return self.rho.map(lambda x: _dual_float(x))\n\n    @property\n    def nu(self) -> DataFrame:\n        \"\"\"The *nu*  value of each :class:`~rateslib.volatility.IRSabrSmile` associated with\n        this *Cube*.\"\"\"\n        return DataFrame(\n            index=Index(data=self.meta.expiries, name=\"expiry\"),\n            columns=Index(data=self.meta.tenors, name=\"tenor\"),\n            data=self._node_values_[:, :, 2],\n        )\n\n    @property\n    def nu_float(self) -> DataFrame:\n        \"\"\"The *nu*  value of each :class:`~rateslib.volatility.IRSabrSmile` associated with\n        this *Cube* in float format.\"\"\"\n        return self.nu.map(lambda x: _dual_float(x))\n\n    @property\n    def _n(self) -> int:\n        \"\"\"Number of pricing parameters of the *Cube*.\"\"\"\n        en = self._node_values_.shape[0]\n        tn = self._node_values_.shape[1]\n        return en * tn * 3  # alpha, beta, rho\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Surface* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def meta(self) -> _IRCubeMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility._IRCubeMeta`.\"\"\"\n        return self._meta\n\n    @property\n    def pricing_params(self) -> Arr3dObj:\n        \"\"\"The pricing parameters of the *Cube* as 3-d array by (expiry, tenor, strike).\"\"\"\n        return self._node_values_\n\n    @property\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the *Surface*.\"\"\"\n        return self._ad\n\n    def _set_ad_order_direct(self, order: int | None) -> None:\n        if order == getattr(self, \"ad\", None):\n            return None\n        elif order not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = order\n        vec = self._get_node_vector()\n        vars_ = self._get_node_vars()\n        new_vec = [set_order_convert(v, order, [t]) for v, t in zip(vec, vars_, strict=False)]\n        en = self._node_values_.shape[0]\n        tn = self._node_values_.shape[1]\n        n = en * tn\n        self._node_values_[:, :, 0] = np.reshape(list(new_vec[:n]), (en, tn))\n        self._node_values_[:, :, 1] = np.reshape(list(new_vec[n : 2 * n]), (en, tn))\n        self._node_values_[:, :, 2] = np.reshape(list(new_vec[2 * n :]), (en, tn))\n        return None\n\n    def _set_node_vector_direct(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        en = self._node_values_.shape[0]\n        tn = self._node_values_.shape[1]\n        n = en * tn\n        if ad == 0:\n            self._node_values_[:, :, 0] = np.reshape([_dual_float(_) for _ in vector[:n]], (en, tn))\n            self._node_values_[:, :, 1] = np.reshape(\n                [_dual_float(_) for _ in vector[n : 2 * n]], (en, tn)\n            )\n            self._node_values_[:, :, 2] = np.reshape(\n                [_dual_float(_) for _ in vector[2 * n :]], (en, tn)\n            )\n        else:\n            DualType: type[Dual] | type[Dual2] = Dual if ad == 1 else Dual2\n            DualArgs: tuple[list[float]] | tuple[list[float], list[float]] = (\n                ([],) if ad == 1 else ([], [])\n            )\n            vars_ = self._get_node_vars()\n            base_obj = DualType(0.0, vars_, *DualArgs)\n            ident = np.eye(len(vars_))\n            for i in range(3):\n                self._node_values_[:, :, i] = np.reshape(\n                    [\n                        DualType.vars_from(\n                            base_obj,  #  type: ignore[arg-type]\n                            _dual_float(vector[n * i + j]),\n                            base_obj.vars,\n                            ident[n * i + j, :].tolist(),\n                            *DualArgs[1:],\n                        )\n                        for j in range(n)\n                    ],\n                    (en, tn),\n                )\n\n    def _get_node_vector(self) -> Arr1dObj:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return np.block(\n            [\n                self._node_values_[:, :, 0].ravel(),  # alphas\n                self._node_values_[:, :, 1].ravel(),  # rhos\n                self._node_values_[:, :, 2].ravel(),  # nus\n            ]\n        )\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        vars_: tuple[str, ...] = ()\n        for tag in [\"_a_\", \"_p_\", \"_v_\"]:\n            vars_ += tuple(\n                f\"{self.id}{tag}{i}_{j}\"\n                for i in range(self._node_values_.shape[0])\n                for j in range(self._node_values_.shape[1])\n            )\n        return vars_\n\n    def _set_single_node_direct(\n        self, key: tuple[datetime, datetime, str], value: DualTypes\n    ) -> None:\n        \"\"\"\n        Update some generic parameters on the *SabrCube*.\n\n        Parameters\n        ----------\n        key: tuple of (datetime, datetime, str in {\"alpha\", \"rho\", \"nu\"})\n            The node value to update, indexed by (expiry, tenor, SABR param).\n        value: Array, float, Dual, Dual2, Variable\n            Value to update on the *Cube*.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n        This function may update all of the AD variable names to be a consistent pricing object\n        familiar to a :class:`~rateslib.solver.Solver`.\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Curve* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Curve* instance.\n           This class is labelled as a **mutable on update** object.\n\n        \"\"\"\n        params = [\"alpha\", \"rho\", \"nu\"]\n        if key[2] not in params:\n            raise KeyError(f\"'{key[2]}' is not in `nodes`.\")\n\n        tenor_row = self.meta.expiry_dates.index(key[0])\n        self._node_values_[\n            self.meta.expiry_dates.index(key[0]),\n            self.meta.tenor_dates[tenor_row].tolist().index(key[1]),\n            self.meta.indexes.index(key[2]),\n        ] = value\n\n        self._set_ad_order(self.ad)\n        return None\n"
  },
  {
    "path": "python/rateslib/volatility/ir/spline.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nfrom datetime import datetime, timezone\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\nfrom uuid import uuid4\n\nimport numpy as np\n\nfrom rateslib.data.fixings import IRSSeries, _get_irs_series\nfrom rateslib.dual import Dual, Dual2, Variable, set_order_convert\nfrom rateslib.dual.utils import _dual_float, _get_order_of, dual_exp, dual_inv_norm_cdf\nfrom rateslib.enums.generics import NoInput, _drb\nfrom rateslib.enums.parameters import OptionPricingModel, _get_option_pricing_model\nfrom rateslib.mutability import (\n    _new_state_post,\n)\nfrom rateslib.splines import PPSplineDual, PPSplineDual2, PPSplineF64\nfrom rateslib.splines.evaluate import evaluate\nfrom rateslib.volatility.ir.base import _BaseIRCube, _BaseIRSmile, _WithMutability\nfrom rateslib.volatility.ir.utils import (\n    _IRCubeMeta,\n    _IRSmileMeta,\n    _IRVolPricingParams,\n)\n\nUTC = timezone.utc\n\nSPLINE_LOWER = -5000.0\nSPLINE_UPPER = 10000.0\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        Arr3dObj,\n        DualTypes,\n        DualTypes_,\n        Iterable,\n        Number,\n        Sequence,\n        Series,\n        float_,\n        int_,\n    )\n\n\nclass _IRSplineSmileNodes:\n    \"\"\"\n    A container for data relating to interpolating the `nodes` of a\n    :class:`~rateslib.volatility.IRSplineSmile`.\n    \"\"\"\n\n    _nodes: dict[float, DualTypes]\n    _spline: _IRVolSpline\n\n    def __init__(self, nodes: dict[float, DualTypes], k: int) -> None:\n        self._nodes = dict(sorted(nodes.items()))\n\n        match (self.n, k):\n            case (1, _) | (2, _):\n                # 1 DoF yields a flat smile, but treat it as a line of zero gradient\n                # 2 DoF yields a straight line, usually with some non-zero gradient\n                k = 2\n                t = [SPLINE_LOWER, SPLINE_LOWER, SPLINE_UPPER, SPLINE_UPPER]\n            case (_, 2):\n                # 3 or more DoF but piecewise linear endpoints have 2 knots\n                t = [SPLINE_LOWER, SPLINE_LOWER] + self.keys[1:-1] + [SPLINE_UPPER, SPLINE_UPPER]\n            case (_, 4):\n                # 3 or more DoF but piecewise cubic ensure endpoints have 4 knots.\n                t = [SPLINE_LOWER] * 4 + self.keys[1:-1] + [SPLINE_UPPER] * 4\n\n        self._spline = _IRVolSpline(t=t, k=k)\n\n    def __eq__(self, other: Any) -> bool:\n        if not isinstance(other, _IRSplineSmileNodes):\n            return False\n        return self._nodes == other._nodes and self.k == other.k\n\n    @property\n    def nodes(self) -> dict[float, DualTypes]:\n        \"\"\"The initial nodes dict passed for construction of this class.\"\"\"\n        return self._nodes\n\n    @cached_property\n    def keys(self) -> list[float]:\n        \"\"\"A list of the relative strike keys in ``nodes``.\"\"\"\n        return list(self.nodes.keys())\n\n    @cached_property\n    def values(self) -> list[DualTypes]:\n        \"\"\"A list of the delta index values in ``nodes``.\"\"\"\n        return list(self.nodes.values())\n\n    @property\n    def n(self) -> int:\n        \"\"\"The number of pricing parameters in ``nodes``.\"\"\"\n        return len(self.keys)\n\n    @property\n    def k(self) -> int:\n        \"\"\"The order of the interpolating polynomial spline.\"\"\"\n        return self.spline.k\n\n    @property\n    def spline(self) -> _IRVolSpline:\n        \"\"\"An instance of :class:`~rateslib.volatility.ir._IRVolSpline`.\"\"\"\n        return self._spline\n\n\nclass _IRVolSpline:\n    \"\"\"\n    A container for data relating to interpolating the `nodes` of\n    a :class:`~rateslib.volatility.IRSplineSmile` using a PPSpline.\n    \"\"\"\n\n    _k: int\n    _t: list[float]\n    _spline: PPSplineF64 | PPSplineDual | PPSplineDual2\n\n    def __init__(self, t: list[float], k: int) -> None:\n        self._t = t\n        self._k = k\n        self._spline = PPSplineF64(k, [0.0] * 5, None)  # placeholder: csolve will reengineer\n\n    @property\n    def t(self) -> list[float]:\n        \"\"\"The knot sequence of the PPSpline.\"\"\"\n        return self._t\n\n    @property\n    def k(self) -> int:\n        \"\"\"The order of the spline.\"\"\"\n        return self._k\n\n    @property\n    def spline(self) -> PPSplineF64 | PPSplineDual | PPSplineDual2:\n        \"\"\"An instance of :class:`~rateslib.splines.PPSplineF64`,\n        :class:`~rateslib.splines.PPSplineDual` or :class:`~rateslib.splines.PPSplineDual2`\"\"\"\n        return self._spline\n\n    def evaluate(self, x: DualTypes, m: int = 0) -> Number:\n        \"\"\"Perform the :meth:`~rateslib.splines.evaluate` method on the object's ``spline``.\"\"\"\n        return evaluate(spline=self.spline, x=x, m=m)\n\n    def _csolve_n_other(\n        self, nodes: _IRSplineSmileNodes, ad: int\n    ) -> tuple[list[float], list[DualTypes], int, int]:\n        \"\"\"\n        Solve a spline with more than one node value.\n        Premium adjusted delta types have an unbounded right side delta index so a derivative of\n        0 is applied to the spline as a boundary condition.\n        Premium unadjusted delta types have a right side delta index approximately equal to 1.0.\n        Use a natural spline boundary condition here.\n        \"\"\"\n        tau = nodes.keys.copy()\n        y = nodes.values.copy()\n\n        if self.k == 4:\n            # now insert the natural spline 2nd derivative constraint\n            y.insert(0, set_order_convert(0.0, ad, None))\n            tau.insert(0, SPLINE_LOWER)\n            left_n = 2  # natural spline\n        else:  # == 2\n            left_n = 0\n\n        if self.k == 4:\n            tau.append(self.t[-1])\n            y.append(set_order_convert(0.0, ad, None))\n            right_n = 2  # natural spline\n        else:  # == 2\n            right_n = 0\n\n        return tau, y, left_n, right_n\n\n    def csolve(self, nodes: _IRSplineSmileNodes, ad: int) -> None:\n        \"\"\"\n        Construct a spline of appropriate AD order and solve the spline coefficients for the\n        given ``nodes``.\n\n        Parameters\n        ----------\n        nodes: _IRSplineSmileNodes\n            Required information for constructing a PPSpline.\n        ad: int\n            The AD order of the constructed PPSPline.\n\n        Returns\n        -------\n        None\n        \"\"\"\n        if ad == 0:\n            Spline: type[PPSplineF64] | type[PPSplineDual] | type[PPSplineDual2] = PPSplineF64\n        elif ad == 1:\n            Spline = PPSplineDual\n        else:\n            Spline = PPSplineDual2\n\n        if nodes.n == 1:\n            # one node defines a flat line, all spline coefficients are the equivalent value.\n            # no need to solve, just craft the spline directly.\n            self._spline = Spline(self.k, self.t, nodes.values * self.k)  # type: ignore[arg-type]\n        else:\n            tau, y, left_n, right_n = self._csolve_n_other(nodes, ad)\n            self._spline = Spline(self.k, self.t, None)\n            self._spline.csolve(tau, y, left_n, right_n, False)  # type: ignore[arg-type]\n\n    # def to_json(self) -> str:\n    #     \"\"\"\n    #     Serialize this object to JSON format.\n    #\n    #     The object can be deserialized using the :meth:`~rateslib.serialization.from_json` method.\n    #\n    #     Returns\n    #     -------\n    #     str\n    #     \"\"\"\n    #     obj = dict(\n    #         PyNative=dict(\n    #             _FXDeltaVolSpline=dict(\n    #                 t=self.t,\n    #             )\n    #         )\n    #     )\n    #     return json.dumps(obj)\n    #\n    # @classmethod\n    # def _from_json(cls, loaded_json: dict[str, Any]) -> _FXDeltaVolSpline:\n    #     return _FXDeltaVolSpline(\n    #         t=loaded_json[\"t\"],\n    #     )\n\n    def __eq__(self, other: Any) -> bool:\n        \"\"\"CurveSplines are considered equal if their knot sequence and endpoints are equivalent.\n        For the same nodes this will resolve to give the same spline coefficients.\n        \"\"\"\n        if not isinstance(other, _IRVolSpline):\n            return False\n        else:\n            return self.t == other.t and self.k == other.k\n\n\nclass IRSplineSmile(_BaseIRSmile, _WithMutability):\n    r\"\"\"\n    Create an *IR Volatility Smile* at a given expiry indexed for a specific IRS tenor\n    with volatility values interpolated by a polynomial spline curve.\n\n    An *IRSplineSmile* is intended as a grid point element of the more general\n    :class:`~rateslib.volatility.IRSplineCube`, which users are recommended to use instead.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import IRSplineSmile, dt\n\n    .. ipython:: python\n\n       irss = IRSplineSmile(\n           eval_date=dt(2000, 1, 1),\n           expiry=dt(2000, 7, 1),\n           tenor=\"1y\",\n           irs_series=\"usd_irs\",\n           nodes={\n               -25.0: 33.375,\n               -10.0: 32.551,\n               0.0: 32.488,\n               10.0: 32.859,\n               25.0: 34.164\n           },\n           k=4,\n       )\n       irss.plot(f=2.5513, x_axis=\"strike\", y_axis=\"normal_vol\")\n\n    .. plot::\n\n       from rateslib import IRSplineSmile, dt\n       irss = IRSplineSmile(\n           eval_date=dt(2000, 1, 1),\n           expiry=dt(2000, 7, 1),\n           tenor=\"1y\",\n           irs_series=\"usd_irs\",\n           nodes={\n               -25.0: 33.375,\n               -10.0: 32.551,\n               0.0: 32.488,\n               10.0: 32.859,\n               25.0: 34.164\n           },\n           k=4,\n       )\n       fig, ax, lines = irss.plot(f=2.5513, x_axis=\"strike\", y_axis=\"normal_vol\")\n       plt.show()\n       plt.close()\n\n    For further examples see :ref:`Constructing a Smile <c-ir-smile-constructing-doc>`.\n\n    .. role:: green\n\n    .. role:: red\n\n    Parameters\n    ----------\n    nodes: dict[float, float], :red:`required`\n        The parameters for the spline. Keys must be basis points relative to the forward rate,\n        and values are normal volatility basis points.\n    eval_date: datetime, :red:`required`\n        Acts like the initial node of a *Curve*. Should be assigned today's immediate date.\n    expiry: datetime, :red:`required`\n        The expiry date of the options associated with this *Smile*.\n    irs_series: IRSSeries, :red:`required`\n        The :class:`~rateslib.data.fixings.IRSSeries` that contains the parameters for the\n        underlying :class:`~rateslib.instruments.IRS` that the swaptions are settled against.\n    tenor: datetime, str, :red:`required`\n        The tenor parameter for the underlying :class:`~rateslib.instruments.IRS` that the\n        swaptions are settled against.\n    k: int in {2, 4}, :green:`optional (set as 2)`\n        The order of the interpolating spline, with (2, 4) representing (linear, cubic)\n        interpolation respectively.\n    pricing_model: str, OptionPricingModel, :green:`optional (set as 'normal_vol')`\n        The option pricing model used by this object. Parameters must be represented\n        in the appropriate form for the model.\n    shift: float, :green:`optional (set as zero)`\n        The shift applied to the forward and strike in pricing formula or in plot conversions.\n    time_scalar: float, Dual, Dual2, Variable, :green:`optional (set as one)`\n        A quantity to remap calendar day time to expiry from ``eval_date`` to another measure\n        of time.\n    id: str, optional, :green:`optional (set as random)`\n        The unique identifier to distinguish between *Smiles* in a multicurrency framework\n        and/or *Surface*.\n    ad: int, :green:`optional (set by default)`\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    The keys for ``nodes`` must be basis points relative to the forward rate. For example\n\n    .. code-block:: python\n\n       nodes = {-200.: 50.0, -100.: 47.0, 0.: 46.0, 100.: 48, 200.: 52.0}\n\n    This means that the volatility model of this spline is naturally dependent on the forward\n    *IRS* rate, very similar to an :class:`~rateslib.volatility.FXDeltaVolSmile`, and any type\n    SABR type *Smile*.\n\n    The value of ``nodes`` are treated as the parameters that will be calibrated/mutated by\n    a :class:`~rateslib.solver.Solver` object. The order of the spline, ``k``, in {2, 4} is a\n    hyper-parameter of this model and will not be mutated.\n\n    The primary reason for the implementation of this *IRSplineSmile* is generally for expression\n    of risk to normal volatility values. In particular using ``k=2`` allows a risk representation\n    with localized strikes. For a more thorough demonstration of this see\n    :ref:`IR Vol Pricing and Risks <cook-ir-vol-risks-doc>`.\n\n    \"\"\"  # noqa: E501\n\n    @_new_state_post\n    def __init__(\n        self,\n        nodes: dict[float, DualTypes],\n        eval_date: datetime,\n        expiry: datetime | str,\n        irs_series: IRSSeries | str,\n        tenor: datetime | str,\n        *,\n        k: int_ = NoInput(0),\n        pricing_model: OptionPricingModel | str = \"normal_vol\",\n        shift: DualTypes_ = NoInput(0),\n        time_scalar: DualTypes_ = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        ad: int | None = 0,\n    ):\n        k_ = _drb(2, k)\n        del k\n        if k_ not in [2, 4]:\n            raise ValueError(\n                f\"`k` must imply linear(2) or cubic(4) spline interpolation. Got {k_}.\"\n            )\n        self._id: str = (\n            uuid4().hex[:5] + \"_\" if isinstance(id, NoInput) else id\n        )  # 1 in a million clash\n        self._meta: _IRSmileMeta = _IRSmileMeta(\n            _tenor_input=tenor,\n            _irs_series=_get_irs_series(irs_series),\n            _eval_date=eval_date,\n            _expiry_input=expiry,\n            _plot_x_axis=\"moneyness\",\n            _plot_y_axis=\"normal_vol\",\n            _shift=_drb(0.0, shift),\n            _pricing_model=_get_option_pricing_model(pricing_model),\n            _time_scalar=_drb(1.0, time_scalar),\n        )\n\n        self._nodes = _IRSplineSmileNodes(nodes=nodes, k=k_)\n\n        self._set_ad_order(ad)\n\n    ### Object unique elements\n\n    @property\n    def _n(self) -> int:\n        return self.nodes.n\n\n    @property\n    def _ini_solve(self) -> int:\n        return 0\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Smile* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def nodes(self) -> _IRSplineSmileNodes:\n        \"\"\"An instance of :class:`~rateslib.volatility._IRSplineSmileNodes`.\"\"\"\n        return self._nodes\n\n    ### _WithMutability ABCs:\n\n    def _get_node_vector(self) -> np.ndarray[tuple[int, ...], np.dtype[np.object_]]:\n        \"\"\"Get a 1d array of variables associated with nodes of this object updated by Solver\"\"\"\n        return np.array(self.nodes.values)\n\n    def _get_node_vars(self) -> tuple[str, ...]:\n        \"\"\"Get the variable names of elements updated by a Solver\"\"\"\n        return tuple(f\"{self.id}{i}\" for i in range(self._n))\n\n    def _set_node_vector_direct(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        \"\"\"\n        Update the node values in a Solver. ``ad`` in {1, 2}.\n        Only the real values in vector are used, dual components are dropped and restructured.\n        \"\"\"\n        DualType: type[Dual] | type[Dual2] = Dual if ad == 1 else Dual2\n        DualArgs: tuple[list[float]] | tuple[list[float], list[float]] = (\n            ([],) if ad == 1 else ([], [])\n        )\n        base_obj = DualType(0.0, [f\"{self.id}{i}\" for i in range(self.nodes.n)], *DualArgs)\n        ident = np.eye(self.nodes.n)\n\n        nodes_: dict[float, DualTypes] = {}\n        for i, k in enumerate(self.nodes.keys):\n            nodes_[k] = DualType.vars_from(\n                base_obj,  # type: ignore[arg-type]\n                vector[i].real,\n                base_obj.vars,\n                ident[i, :].tolist(),\n                *DualArgs[1:],\n            )\n        self._nodes = _IRSplineSmileNodes(nodes=nodes_, k=self.nodes.k)\n        self.nodes.spline.csolve(self.nodes, self.ad)\n\n    def _set_ad_order_direct(self, order: int | None) -> None:\n        # -1, -2 force updates to new variables\n        if order is None or order == getattr(self, \"ad\", None):\n            if self.nodes.spline.spline.c is None:\n                self.nodes.spline.csolve(self.nodes, _get_order_of(self.pricing_params[0]))\n            return None\n        elif abs(order) not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = abs(order)\n        nodes: dict[float, DualTypes] = {\n            k: set_order_convert(v, abs(order), [f\"{self.id}{i}\"])\n            for i, (k, v) in enumerate(self.nodes.nodes.items())\n        }\n        self._nodes = _IRSplineSmileNodes(nodes=nodes, k=self.nodes.spline.k)\n        self.nodes.spline.csolve(self.nodes, self.ad)\n\n    def _set_single_node(self, key: float, value: DualTypes) -> None:\n        if key not in self.nodes.keys:\n            raise KeyError(f\"'{key}' is not in `nodes`.\")\n        self.nodes._nodes[key] = value\n        self.nodes.spline.csolve(self.nodes, self.ad)\n\n    # _BaseIRSmile ABCS:\n\n    def _plot(\n        self,\n        x_axis: str,\n        f: float,\n        y_axis: str,\n        tgt_shift: float_,\n    ) -> tuple[Iterable[float], Iterable[float]]:\n\n        # approximate a range for the x-axis\n        shf = _dual_float(self.meta.shift) / 100.0\n        sq_t = self._meta.t_expiry_sqrt\n        v_ = _dual_float(self.get_from_strike(k=f, f=f).vol) / 100.0\n        if self.meta.pricing_model == OptionPricingModel.Black76:\n            v_ = v_\n        else:\n            v_ = v_ / (f + shf)\n\n        x_low = _dual_float(\n            dual_exp(0.5 * v_**2 * sq_t**2 - dual_inv_norm_cdf(0.95) * v_ * sq_t) * (f + shf) - shf\n        )\n        x_top = _dual_float(\n            dual_exp(0.5 * v_**2 * sq_t**2 - dual_inv_norm_cdf(0.05) * v_ * sq_t) * (f + shf) - shf\n        )\n\n        x = np.linspace(x_low, x_top, 301, dtype=np.float64)\n        y: Iterable[float] = [_dual_float(self.get_from_strike(k=_, f=f).vol) for _ in x]\n\n        return self._plot_conversion(\n            y_axis=y_axis, x_axis=x_axis, f=f, shift=shf, tgt_shift=_drb(shf, tgt_shift), x=x, y=y\n        )\n\n    @property\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the\n        :class:`~rateslib.volatility._BaseIRSmile`.\"\"\"\n        return self._ad\n\n    @property\n    def pricing_params(self) -> Sequence[float | Dual | Dual2 | Variable]:\n        \"\"\"An ordered set of pricing parameters associated with the\n        :class:`~rateslib.volatility._BaseIRSmile`.\"\"\"\n        return self.nodes.values\n\n    @property\n    def meta(self) -> _IRSmileMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility.ir.utils._IRSmileMeta`.\"\"\"\n        return self._meta\n\n    def _get_from_strike(self, k: DualTypes, f: DualTypes) -> _IRVolPricingParams:\n        \"\"\"\n        Given an option strike return the volatility.\n\n        Parameters\n        -----------\n        k: float, Dual, Dual2\n            The strike of the option.\n        f: float, Dual, Dual2\n            The forward rate at delivery of the option.\n        expiry: datetime, optional\n            The expiry of the option. Required for temporal interpolation.\n        tenor: datetime, optional\n            The termination date of the underlying *IRS*, required for parameter interpolation.\n        curves: _Curves,\n            Pricing objects. See **Pricing** on :class:`~rateslib.instruments.IRSCall`\n            for details of allowed inputs.\n\n        Returns\n        -------\n        _IRVolPricingParams\n        \"\"\"\n        vol_ = self.nodes.spline.evaluate(x=(k - f) * 100.0, m=0)\n        return _IRVolPricingParams(\n            vol=vol_,\n            k=k,\n            f=f,\n            shift=self.meta.shift,\n            pricing_model=self.meta.pricing_model,\n            t_e=self.meta.t_expiry,\n        )\n\n    def _d_sigma_d_f(\n        self,\n        k: DualTypes,\n        f: DualTypes,\n    ) -> DualTypes:\n        \"\"\"\n        Calculate the derivative :math:`\\frac{d \\\\sigma}{d f}` for a generic spline model.\n        \"\"\"\n        return self.nodes.spline.evaluate(x=(k - f) * 100.0, m=1) * -1.0\n\n\nclass IRSplineCube(_BaseIRCube[float | Variable], _WithMutability):\n    r\"\"\"\n    Create an *IR Volatility Cube* parametrized by :class:`~rateslib.volatility.IRSplineSmile` at\n    different *expiries* and *IRS* *tenors*.\n\n    .. warning::\n\n       *Swaptions* and *IR Volatility* are in Beta status introduced in v2.7.0\n\n    .. rubric:: Examples\n\n    .. ipython:: python\n       :suppress:\n\n       from rateslib import IRSplineCube, dt\n\n    .. ipython:: python\n\n       irsc = IRSplineCube(\n           eval_date=dt(2000, 1, 1),\n           expiries=[\"3m\", \"1y\"],\n           tenors=[\"1y\", \"2y\"],\n           strikes=[-25.0, 0.0, 25.0],\n           irs_series=\"usd_irs\",\n           parameters=[    # <- normal vol at each strike for each row expiry and column tenor\n               [[33.5, 32.5, 34.1], [33.7, 32.6, 34.6]],\n               [[33.4, 32.2, 33.9], [33.1, 32.1, 34.1]],\n           ],\n           k=4,\n       )\n       irss = irsc.get_smile(\"6m\", \"1y\")\n       irss.plot(f=2.5513, x_axis=\"strike\", y_axis=\"normal_vol\")\n\n    .. plot::\n\n       from rateslib import IRSplineCube, dt\n       irsc = IRSplineCube(\n           eval_date=dt(2000, 1, 1),\n           expiries=[\"3m\", \"1y\"],\n           tenors=[\"1y\", \"2y\"],\n           strikes=[-25.0, 0.0, 25.0],\n           irs_series=\"usd_irs\",\n           parameters=[    # <- normal vol at each strike for each row expiry and column tenor\n               [[33.5, 32.5, 34.1], [33.7, 32.6, 34.6]],\n               [[33.4, 32.2, 33.9], [33.1, 32.1, 34.1]],\n           ],\n           k=4,\n       )\n       irss = irsc.get_smile(\"6m\", \"1y\")\n       fig, ax, lines = irss.plot(f=2.5513, x_axis=\"strike\", y_axis=\"normal_vol\")\n       plt.show()\n       plt.close()\n\n\n\n    For further information see also the\n    :ref:`IR Vol Smiles & Cubes <c-ir-smile-doc>` section in the user guide.\n\n    .. role:: green\n\n    .. role:: red\n\n    Parameters\n    ----------\n    expiries: list[datetime | str], :red:`required`\n        Datetimes representing the expiries of each parametrised *Smile*, in ascending order.\n    tenors: list[str], :red:`required`\n        The tenors of each underlying *IRS* from each expiry for the parameterised *Smiles*.\n    strikes: list[float], :red:`required`\n        The indexes for the strike values on each *Smile*, expressed in basis points relative to the\n        ATM forward rate.\n    eval_date: datetime, :red:`required`\n        Acts as the initial node of a *Curve*. Should be assigned today's immediate date.\n        If expiry is given as string used to derive the specific date.\n    irs_series: str, IRSSeries, :red:`required`\n        The :class:`~rateslib.data.fixings.IRSSeries` that contains the parameters for the\n        underlying :class:`~rateslib.instruments.IRS` that the swaptions are settled against.\n    parameters: float, Dual, Dual2, Variable or 3d-ndarray of such\n        The parameters for each *Smile* either adopting a single universal value or as a 3D array\n        with axes (expiry, tenor, strike).\n    k: int in {2, 4}, :green:`optional (set as 2)`\n        The order of the interpolating spline, with (2, 4) representing (linear, cubic)\n        interpolation respectively.\n    pricing_model: str, OptionPricingModel, :green:`optional (set as 'normal_vol')`\n        The option pricing model used by this object. Parameters must be represented\n        in the appropriate form for the model.\n    shift: float, :green:`optional (set as zero)`\n        The shift applied to the forward and strike in pricing formula or in plot conversions.\n    weights: Series, :green:`optional`\n       Weights used for temporal volatility interpolation. Please see\n       :ref:`IR vol time remapping <cook-ir-vol-time-doc>` before using this argument.\n    id: str, :green:`optional`\n        The unique identifier to label the *Surface* and its variables.\n    ad: int, :green:`optional`\n        Sets the automatic differentiation order. Defines whether to convert node\n        values to float, :class:`~rateslib.dual.Dual` or\n        :class:`~rateslib.dual.Dual2`. It is advised against\n        using this setting directly. It is mainly used internally.\n\n    Notes\n    -----\n    Normal vol parameters for any **(expiry, tenor, strike)** triplet are bilinearly\n    interpolated from immediately neighbouring grid points. Grid points outside of the\n    domain of the given ``expiries`` and ``tenors`` assume values from the singular nearest\n    grid point.\n\n    \"\"\"\n\n    _ini_solve = 0\n    _SmileType = IRSplineSmile\n    _meta: _IRCubeMeta\n    _id: str\n\n    def __init__(\n        self,\n        expiries: list[datetime | str],\n        tenors: list[str],\n        strikes: list[float],\n        eval_date: datetime,\n        irs_series: str | IRSSeries,\n        parameters: DualTypes | Arr3dObj,\n        shift: DualTypes_ = NoInput(0),\n        pricing_model: OptionPricingModel | str = \"normal_vol\",\n        k: int_ = NoInput(0),\n        weights: Series[float] | NoInput = NoInput(0),\n        id: str | NoInput = NoInput(0),  # noqa: A002\n        ad: int = 0,\n    ):\n        self._id: str = (\n            uuid4().hex[:5] + \"_\" if isinstance(id, NoInput) else id\n        )  # 1 in a million clash\n\n        self._meta = _IRCubeMeta(\n            _eval_date=eval_date,\n            _tenors=tenors,\n            _weights=weights,\n            _indexes=strikes,\n            _expiries=expiries,\n            _irs_series=_get_irs_series(irs_series),\n            _shift=_drb(0.0, shift),\n            _smile_params=dict(\n                k=_drb(2, k),\n                pricing_model=_get_option_pricing_model(pricing_model),\n            ),\n            _pricing_model=_get_option_pricing_model(pricing_model),\n        )\n\n        _shape = (self.meta._n_expiries, self.meta._n_tenors, len(strikes))\n        self._node_values_: Arr3dObj = np.empty(shape=_shape, dtype=object)\n        if isinstance(parameters, float | Dual | Dual2 | Variable):\n            self._node_values_.fill(parameters)\n        else:\n            p = np.asarray(parameters)\n            if p.shape != _shape:\n                raise ValueError(\n                    \"If providing `parameters` must be a 3D array-like with shape \"\n                    \"(expiries, tenors, strikes).\"\n                )\n            self._node_values_ = p\n\n        self._set_ad_order(ad)  # includes csolve on each smile\n        self._set_new_state()\n\n    @property\n    def _n(self) -> int:\n        \"\"\"Number of pricing parameters of the *Cube*.\"\"\"\n        en = self._node_values_.shape[0]\n        tn = self._node_values_.shape[1]\n        sn = self._node_values_.shape[2]\n        return en * tn * sn\n\n    @property\n    def id(self) -> str:\n        \"\"\"A str identifier to name the *Surface* used in\n        :class:`~rateslib.solver.Solver` mappings.\"\"\"\n        return self._id\n\n    @property\n    def meta(self) -> _IRCubeMeta:\n        \"\"\"An instance of :class:`~rateslib.volatility._IRCubeMeta`.\"\"\"\n        return self._meta\n\n    @property\n    def pricing_params(self) -> Arr3dObj:\n        \"\"\"The pricing parameters of the *Cube* as 3-d array by (expiry, tenor, strike).\"\"\"\n        return self._node_values_\n\n    @property\n    def ad(self) -> int:\n        \"\"\"Int in {0,1,2} describing the AD order associated with the *Surface*.\"\"\"\n        return self._ad\n\n    def _set_ad_order_direct(self, order: int | None) -> None:\n        # -1, and -2 input will force direct vars settings.\n        if order is None or order == getattr(self, \"ad\", None):\n            return None\n        elif abs(order) not in [0, 1, 2]:\n            raise ValueError(\"`order` can only be in {0, 1, 2} for auto diff calcs.\")\n\n        self._ad = abs(order)\n        vec = self._get_node_vector()\n        vars_ = self._get_node_vars()\n        new_vec = [set_order_convert(v, abs(order), [t]) for v, t in zip(vec, vars_, strict=False)]\n        self._node_values_ = np.reshape(\n            np.array(new_vec), (self.meta._n_expiries, self.meta._n_tenors, len(self.meta.indexes))\n        )\n        return None\n\n    def _set_node_vector_direct(\n        self, vector: np.ndarray[tuple[int, ...], np.dtype[np.object_]], ad: int\n    ) -> None:\n        shape = self._node_values_.shape\n        if ad == 0:\n            self._node_values_ = np.reshape([_dual_float(_) for _ in vector], shape)\n        else:\n            DualType: type[Dual] | type[Dual2] = Dual if ad == 1 else Dual2\n            DualArgs: tuple[list[float]] | tuple[list[float], list[float]] = (\n                ([],) if ad == 1 else ([], [])\n            )\n            vars_ = self._get_node_vars()\n            base_obj = DualType(0.0, vars_, *DualArgs)\n            ident = np.eye(len(vars_))\n            self._node_values_ = np.reshape(\n                [\n                    DualType.vars_from(\n                        base_obj,  #  type: ignore[arg-type]\n                        _dual_float(v),\n                        base_obj.vars,\n                        ident[j, :].tolist(),\n                        *DualArgs[1:],\n                    )\n                    for j, v in enumerate(vector)\n                ],\n                shape,\n            )\n\n    def _set_single_node_direct(\n        self, key: tuple[datetime, datetime, float | Variable], value: DualTypes\n    ) -> None:\n        \"\"\"\n        Update some generic parameters on the *SplineCube*.\n\n        Parameters\n        ----------\n        key: tuple of (datetime, datetime, float)\n            The node value to update, indexed by (expiry, tenor, strike).\n        value: Array, float, Dual, Dual2, Variable\n            Value to update on the *Cube*.\n\n        Returns\n        -------\n        None\n\n        Notes\n        -----\n        This function may update all of the AD variable names to be a consistent pricing object\n        familiar to a :class:`~rateslib.solver.Solver`.\n\n        .. warning::\n\n           *Rateslib* is an object-oriented library that uses complex associations. Although\n           Python may not object to directly mutating attributes of a *Curve* instance, this\n           should be avoided in *rateslib*. Only use official ``update`` methods to mutate the\n           values of an existing *Curve* instance.\n           This class is labelled as a **mutable on update** object.\n\n        \"\"\"\n        if key[2] not in self.meta.indexes:\n            raise KeyError(f\"'{key[2]}' is not in `meta.indexes`.\")\n\n        tenor_row = self.meta.expiry_dates.index(key[0])\n        self._node_values_[\n            self.meta.expiry_dates.index(key[0]),\n            self.meta.tenor_dates[tenor_row].tolist().index(key[1]),\n            self.meta.indexes.index(key[2]),\n        ] = value\n\n        self._set_ad_order(self.ad)\n        return None\n"
  },
  {
    "path": "python/rateslib/volatility/ir/utils.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nfrom dataclasses import dataclass\nfrom datetime import datetime, timezone\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, NamedTuple\n\nimport numpy as np\nfrom pandas import Series\n\nfrom rateslib import calendars\nfrom rateslib.data.fixings import IRSFixing, _get_irs_series\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.scheduling import Adjuster, add_tenor\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Any,\n        Arr2dObj,\n        DualTypes,\n        IRSSeries,\n        OptionPricingModel,\n        datetime_,\n    )\n\nUTC = timezone.utc\n\n\nclass _IRVolPricingParams(NamedTuple):\n    \"\"\"Container for parameters for pricing IR options.\"\"\"\n\n    vol: DualTypes\n    \"\"\"The volatility parameter associated with the specified ``pricing_model``.\"\"\"\n\n    k: DualTypes\n    \"\"\"The strike price of the option.\"\"\"\n\n    f: DualTypes\n    \"\"\"The mid-market forward rate of underlying.\"\"\"\n\n    shift: DualTypes\n    \"\"\"The shift (basis points) applied to the strike and forward under the ``pricing_model``.\"\"\"\n\n    t_e: DualTypes\n    \"\"\"The time to expiry used in the pricing formula.\"\"\"\n\n    pricing_model: OptionPricingModel\n    \"\"\"The specific option pricing formula used for valuation.\"\"\"\n\n    @property\n    def rate_shift(self) -> DualTypes:\n        \"\"\"The shift (rate percentage terms) applied to the strike and forward under\n        the ``pricing_model``.\"\"\"\n        return self.shift / 100.0\n\n\nclass _IRSmileMeta:\n    \"\"\"\n    A container of meta data associated with a :class:`~rateslib.volatility._BaseIRSmile`\n    used to make calculations.\n    \"\"\"\n\n    def __init__(\n        self,\n        _eval_date: datetime,\n        _expiry_input: datetime | str,\n        _tenor_input: datetime | str,\n        _irs_series: IRSSeries,\n        _shift: DualTypes,\n        _plot_x_axis: str,\n        _plot_y_axis: str,\n        _pricing_model: OptionPricingModel,\n        _time_scalar: DualTypes,\n    ):\n        self._eval_date = _eval_date\n        self._expiry_input = _expiry_input\n        self._tenor_input = _tenor_input\n        self._irs_series = _irs_series\n        self._plot_x_axis = _plot_x_axis\n        self._plot_y_axis = _plot_y_axis\n        self._time_scalar = _time_scalar\n        self._irs_fixing = IRSFixing(\n            irs_series=self.irs_series,\n            publication=self.expiry,\n            tenor=self.tenor_input,\n            value=NoInput(0),\n            identifier=NoInput(0),\n        )\n        self._shift = _shift\n        self._pricing_model = _pricing_model\n\n    @property\n    def time_scalar(self) -> DualTypes:\n        \"\"\"A quantity to multiple calendar day time to expiry to remap time.\"\"\"\n        return self._time_scalar\n\n    @property\n    def pricing_model(self) -> OptionPricingModel:\n        \"\"\"The option pricing model associated with this *Smile* volatility output.\"\"\"\n        return self._pricing_model\n\n    @property\n    def eval_date(self) -> datetime:\n        \"\"\"Evaluation date of the *Smile*.\"\"\"\n        return self._eval_date\n\n    @property\n    def shift(self) -> DualTypes:\n        \"\"\"\n        The number of basis points used by this *Smile* when using 'Black Shifted Volatility'.\n        \"\"\"\n        return self._shift\n\n    @cached_property\n    def rate_shift(self) -> DualTypes:\n        \"\"\"\n        The ``shift`` amount expressed in rate percentage terms.\n        \"\"\"\n        return self.shift / 100.0\n\n    @property\n    def plot_x_axis(self) -> str:\n        \"\"\"The default ``x_axis`` parameter passed to\n        :meth:`~rateslib.volatility._BaseIRSmile.plot`\"\"\"\n        return self._plot_x_axis\n\n    @property\n    def plot_y_axis(self) -> str:\n        \"\"\"The default ``y_axis`` parameter passed to\n        :meth:`~rateslib.volatility._BaseIRSmile.plot`\"\"\"\n        return self._plot_y_axis\n\n    @property\n    def irs_series(self) -> IRSSeries:\n        \"\"\"The :class:`~rateslib.data.fixings.IRSSeries` of for the conventions of the *Smile*.\"\"\"\n        return self._irs_series\n\n    @property\n    def expiry_input(self) -> datetime | str:\n        \"\"\"Expiry input of the options priced by this *Smile*.\"\"\"\n        return self._expiry_input\n\n    @cached_property\n    def expiry(self) -> datetime:\n        \"\"\"Derived expiry date of the options priced by this *Smile*.\"\"\"\n        if isinstance(self.expiry_input, str):\n            return add_tenor(\n                start=self.eval_date,\n                tenor=self.expiry_input,\n                modifier=self.irs_series.modifier,\n                calendar=self.irs_series.calendar,\n            )\n        else:\n            return self.expiry_input\n\n    @property\n    def tenor_input(self) -> datetime | str:\n        \"\"\"Tenor input of the underlying IRS priced by this *Smile*.\"\"\"\n        return self._tenor_input\n\n    @property\n    def irs_fixing(self) -> IRSFixing:\n        \"\"\"The :class:`~rateslib.data.fixings.IRSFixing` underlying for the swaptions priced\n        by this *Smile*.\"\"\"\n        return self._irs_fixing\n\n    @cached_property\n    def t_expiry(self) -> DualTypes:\n        \"\"\"Calendar days from eval to expiry divided by 365 multiplied by remapping.\"\"\"\n        return (self.expiry - self.eval_date).days / 365.0 * self.time_scalar\n\n    def _t_expiry(self, expiry: datetime) -> DualTypes:\n        \"\"\"Calendar days from eval to specified expiry divided by 365 multiplied by remapping.\"\"\"\n        return (expiry - self.eval_date).days / 365.0 * self.time_scalar\n\n    @cached_property\n    def t_expiry_sqrt(self) -> DualTypes:\n        \"\"\"Square root of ``t_expiry``.\"\"\"\n        ret: DualTypes = self.t_expiry**0.5\n        return ret\n\n\n@dataclass(frozen=True)\nclass _IRCubeMeta:\n    \"\"\"\n    An immutable container of meta data associated with a\n    :class:`~rateslib.volatility._BaseIRCube` used to make calculations.\n    \"\"\"\n\n    _eval_date: datetime\n    _weights: Series[float] | NoInput\n    _expiries: list[str | datetime]\n    _tenors: list[str]\n    _irs_series: IRSSeries\n    _shift: DualTypes\n    _indexes: list[Any]\n    _smile_params: dict[str, Any]\n    _pricing_model: OptionPricingModel\n\n    def __post_init__(self) -> None:\n        for idx in range(1, len(self.expiries)):\n            if self.expiry_dates[idx - 1] >= self.expiry_dates[idx]:\n                raise ValueError(\"Cube `expiries` are not sorted or contain duplicates.\\n\")\n        if not isinstance(self._weights, NoInput):\n            object.__setattr__(\n                self,\n                \"_weights\",\n                _scale_weights(\n                    eval_date=self.eval_date,\n                    weights=self._weights,\n                    expiries=self.expiry_dates,\n                ),\n            )\n\n    @property\n    def shift(self) -> DualTypes:\n        \"\"\"\n        The number of basis points used by any *Smile* when using 'Black Shifted Volatility'.\n        \"\"\"\n        return self._shift\n\n    @property\n    def _n_expiries(self) -> int:\n        \"\"\"The number of expiries.\"\"\"\n        return len(self._expiries)\n\n    @property\n    def _n_tenors(self) -> int:\n        \"\"\"The number of tenors.\"\"\"\n        return len(self._tenors)\n\n    @property\n    def irs_series(self) -> IRSSeries:\n        \"\"\"\n        The :class:`~rateslib.data.fixings.IRSSeries` of the underlying\n        :class:`~rateslib.instruments.IRS`\n        \"\"\"\n        return self._irs_series\n\n    @property\n    def smile_params(self) -> dict[str, Any]:\n        \"\"\"\n        A list of additional parameters used only by the specific *Cube* in constructing its\n        individual *Smile* types.\n        \"\"\"\n        return self._smile_params\n\n    @property\n    def weights(self) -> Series[float] | NoInput:\n        \"\"\"Weights used for temporal volatility interpolation.\"\"\"\n        return self._weights\n\n    @cached_property\n    def time_scalars(self) -> Series[float] | NoInput:\n        \"\"\"Weight adjusted time to expiry (in calendar days) per date for temporal volatility\n        interpolation.\"\"\"\n        if isinstance(self.weights, NoInput):\n            return NoInput(0)\n        else:\n            c = Series(index=self.weights.index, data=1.0)\n            c.iloc[0] = 0.0\n            return self.weights.cumsum() / c.cumsum()\n\n    @property\n    def tenors(self) -> list[str]:\n        \"\"\"A list of the tenors as measured according the underlying from each expiry.\"\"\"\n        return self._tenors\n\n    @property\n    def indexes(self) -> list[Any]:\n        \"\"\"A list of the indexes used as strikes for the third dimension of the *Cube*.\"\"\"\n        return self._indexes\n\n    @cached_property\n    def tenor_dates(self) -> Arr2dObj:\n        \"\"\"An array of *IRS* termination dates measured from each expiry's effective date.\"\"\"\n        arr = np.empty(shape=(self._n_expiries, self._n_tenors), dtype=object)\n        for i, expiry in enumerate(self.expiry_dates):\n            effective = self.irs_series.calendar.adjust(expiry, self.irs_series.settle)\n            for j, tenor in enumerate(self.tenors):\n                arr[i, j] = add_tenor(\n                    start=effective,\n                    tenor=tenor,\n                    modifier=self.irs_series.modifier,\n                    calendar=self.irs_series.calendar,\n                )\n        return arr\n\n    @cached_property\n    def tenor_dates_posix(self) -> Arr2dObj:\n        \"\"\"An array of *IRS* termination dates as unix timestamp.\"\"\"\n        return np.reshape(\n            [_.replace(tzinfo=UTC).timestamp() for _ in self.tenor_dates.ravel()],\n            (self._n_expiries, self._n_tenors),\n        )\n\n    def _t_expiry(self, expiry: datetime) -> float:\n        \"\"\"Calendar days from eval to specified expiry divided by 365.\"\"\"\n        return (expiry - self.eval_date).days / 365.0\n\n    # @cached_property\n    # def tenor_posix(self) -> list[float]:\n    #     \"\"\"A list of the tenors as posix timestamp.\"\"\"\n    #     return [_.replace(tzinfo=UTC).timestamp() for _ in self.tenor_dates]\n\n    @property\n    def expiries(self) -> list[datetime | str]:\n        \"\"\"A list of the expiries.\"\"\"\n        return self._expiries\n\n    @cached_property\n    def expiry_dates(self) -> list[datetime]:\n        \"\"\"A list of the expiries as datetime.\"\"\"\n        _: list[datetime] = []\n        for date in self.expiries:\n            if isinstance(date, str):\n                _.append(\n                    add_tenor(\n                        start=self._eval_date,\n                        tenor=date,\n                        modifier=self.irs_series.modifier,\n                        calendar=self.irs_series.calendar,\n                    )\n                )\n            else:\n                _.append(date)\n        return _\n\n    @cached_property\n    def expiries_posix(self) -> list[float]:\n        \"\"\"A list of the unix timestamps of each date in ``expiries``.\"\"\"\n        return [_.replace(tzinfo=UTC).timestamp() for _ in self.expiry_dates]\n\n    @cached_property\n    def eval_posix(self) -> float:\n        \"\"\"The unix timestamp of the ``eval_date``.\"\"\"\n        return self.eval_date.replace(tzinfo=UTC).timestamp()\n\n    @property\n    def eval_date(self) -> datetime:\n        \"\"\"Evaluation date of the *Surface*.\"\"\"\n        return self._eval_date\n\n    @property\n    def pricing_model(self) -> OptionPricingModel:\n        \"\"\"The option pricing model associated with this *Cube's* volatility output.\"\"\"\n        return self._pricing_model\n\n\ndef _get_ir_expiry_and_payment(\n    eval_date: datetime_,\n    expiry: str | datetime,\n    irs_series: str | IRSSeries,\n    payment_lag: int | datetime_,\n) -> tuple[datetime, datetime]:\n    \"\"\"\n    Determines the expiry and payment date of an IR option using the following rules.\n\n    Parameters\n    ----------\n    eval_date: datetime\n        The evaluation date, which is today (if required)\n    expiry: str, datetime\n        The expiry date\n    irs_series: IRSSeries, str\n        The :class:`~rateslib.enums.parameters.IRSSeries` of the underlying IRS.\n    payment_lag: Adjuster, int, datetime\n        Number of business days to lag payment by after expiry.\n\n    Returns\n    -------\n    tuple of datetime\n    \"\"\"\n    irs_series_ = _get_irs_series(irs_series)\n    del irs_series\n\n    if isinstance(expiry, str):\n        # then use the objects to derive the expiry\n\n        if isinstance(eval_date, NoInput):\n            raise ValueError(\"`expiry` as string tenor requires `eval_date`.\")\n        # then the expiry will be implied\n        expiry_ = add_tenor(\n            start=eval_date,\n            tenor=expiry,\n            modifier=irs_series_.modifier,\n            calendar=irs_series_.calendar,\n            roll=eval_date.day,\n            settlement=False,\n            mod_days=False,\n        )\n    else:\n        expiry_ = expiry\n\n    if isinstance(payment_lag, int):\n        payment_lag_: datetime | Adjuster = Adjuster.BusDaysLagSettle(payment_lag)\n    elif isinstance(payment_lag, NoInput):\n        payment_lag_ = irs_series_.settle\n    else:\n        payment_lag_ = payment_lag\n    del payment_lag\n\n    if isinstance(payment_lag_, datetime):\n        payment_ = payment_lag_\n    else:\n        payment_ = payment_lag_.adjust(expiry_, irs_series_.calendar)\n\n    return expiry_, payment_\n\n\ndef _get_ir_expiry(\n    eval_date: datetime,\n    irs_series: str | IRSSeries,\n    expiry: datetime | str,\n) -> datetime:\n    \"\"\"\n    Determines the expiry of a Swaption possibly from string tenor.\n\n    Parameters\n    ----------\n    eval_date: datetime\n        The horizon or evaluation date, i.e. today.\n    irs_series: IRSSeries, str\n        The :class:`~rateslib.enums.parameters.IRSSeries` of the underlying IRS.\n    expiry: str, datetime\n        The expiry for the swaption.\n\n    Returns\n    -------\n    datetime\n    \"\"\"\n    if isinstance(expiry, datetime):\n        return expiry\n\n    irs_series_ = _get_irs_series(irs_series)\n    del irs_series\n\n    expiry_ = add_tenor(  # TODO: maybe adopt a Schedule here instead of add tenor\n        start=eval_date,\n        tenor=expiry,\n        modifier=irs_series_.modifier,\n        calendar=irs_series_.calendar,\n        roll=eval_date.day,\n        settlement=False,\n        mod_days=False,\n    )\n    return expiry_\n\n\ndef _get_ir_tenor(\n    expiry: datetime,\n    irs_series: str | IRSSeries,\n    tenor: str | datetime,\n) -> datetime:\n    \"\"\"\n    Determines the termination of an IRS associated with a Swaption expiry.\n\n    Parameters\n    ----------\n    expiry: datetime\n        The expiry date\n    irs_series: IRSSeries, str\n        The :class:`~rateslib.enums.parameters.IRSSeries` of the underlying IRS.\n    tenor: str, datetime\n        The tenor for the IRS\n\n    Returns\n    -------\n    tuple of datetime\n    \"\"\"\n    if isinstance(tenor, datetime):\n        return tenor\n\n    irs_series_ = _get_irs_series(irs_series)\n    del irs_series\n\n    effective = irs_series_.settle.adjust(expiry, irs_series_.calendar)\n    tenor_ = add_tenor(  # TODO: maybe adopt a Schedule here instead of add tenor\n        start=effective,\n        tenor=tenor,\n        modifier=irs_series_.modifier,\n        calendar=irs_series_.calendar,\n        roll=effective.day,\n        settlement=False,\n        mod_days=False,\n    )\n    return tenor_\n\n\ndef _bilinear_interp(\n    tl: DualTypes,\n    tr: DualTypes,\n    bl: DualTypes,\n    br: DualTypes,\n    h: tuple[float, float],\n    v: tuple[float, float],\n) -> DualTypes:\n    \"\"\"\n    tl, tr, bl, br: the values on the vertices of a unit square.\n    h: the progression along the horizontal top edge and the horizontal bottom edge in [0,1].\n    v: the progression along the vertical left edge and the vertical right edge in [0,1].\n    p: the interior point as the intersection when lines are drawn between the progression on edges.\n    \"\"\"\n    return (\n        tl * (1 - h[0]) * (1 - v[0])\n        + tr * (h[0]) * (1 - v[1])\n        + bl * (1 - h[1]) * v[0]\n        + br * h[1] * v[1]\n    )\n\n\ndef _scale_weights(\n    eval_date: datetime,\n    weights: Series[float],\n    expiries: list[datetime],\n) -> Series[float]:\n    # the last weight is considered the end point of interest\n    w = weights.sort_index(ascending=True)  # sorted input\n    del weights\n    d = calendars.get(\"all\").cal_date_range(eval_date, w.index[-1])\n    s = Series(data=1.0, index=d)\n    s.update(w)\n    s.update(Series(index=[eval_date], data=0.0))\n    c = s.cumsum()\n    adj_expiries = [eval_date] + expiries\n    for i, expiry in enumerate(adj_expiries):\n        if i == 0:\n            continue\n        if expiry < s.index[-1]:\n            # this expiry is within the middle of the weights series\n            left_index = (adj_expiries[i - 1] - eval_date).days\n            right_index = (expiry - adj_expiries[i - 1]).days + left_index\n            left_count = c[adj_expiries[i - 1]]\n            right_count = c[adj_expiries[i]]\n            s.iloc[left_index + 1 : right_index + 1] *= (right_index - left_index) / (\n                right_count - left_count\n            )\n        elif adj_expiries[i - 1] < s.index[-1]:\n            # the weights extend beyond the last expiry but to to the present expiry\n            left_index = (adj_expiries[i - 1] - eval_date).days\n            right_index = (s.index[-1] - adj_expiries[i - 1]).days + left_index\n            left_count = c[adj_expiries[i - 1]]\n            right_count = c[s.index[-1]]\n            s.iloc[left_index + 1 : right_index + 1] *= (right_index - left_index) / (\n                right_count - left_count\n            )\n        else:\n            # the weights have been exhausted\n            break\n\n    if s.index[-1] > expiries[-1]:\n        # scale the weights beyond last expiry\n        left_index = (adj_expiries[-1] - eval_date).days\n        right_index = (s.index[-1] - adj_expiries[-1]).days + left_index\n        left_count = c[adj_expiries[-1]]\n        right_count = c[s.index[-1]]\n        s.iloc[left_index + 1 : right_index + 1] *= (right_index - left_index) / (\n            right_count - left_count\n        )\n\n    return s\n"
  },
  {
    "path": "python/rateslib/volatility/utils.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\n\nfrom __future__ import annotations  # type hinting\n\nfrom dataclasses import dataclass\nfrom datetime import datetime, timedelta, timezone\nfrom typing import TYPE_CHECKING, TypeAlias\n\nfrom pandas import Series\n\nfrom rateslib.dual import (\n    Dual,\n    Dual2,\n    Variable,\n    dual_log,\n    dual_norm_cdf,\n    dual_norm_pdf,\n    ift_1dim,\n)\nfrom rateslib.dual.utils import _dual_float, _to_number\nfrom rateslib.enums.generics import (\n    NoInput,\n)\nfrom rateslib.rs import _sabr_x0 as _rs_sabr_x0\nfrom rateslib.rs import _sabr_x1 as _rs_sabr_x1\nfrom rateslib.rs import _sabr_x2 as _rs_sabr_x2\nfrom rateslib.rs import index_left_f64\nfrom rateslib.scheduling import get_calendar\n\nif TYPE_CHECKING:\n    from rateslib.local_types import (  # pragma: no cover\n        Number,\n    )\n\nDualTypes: TypeAlias = \"float | Dual | Dual2 | Variable\"  # if not defined causes _WithCache failure\n\nTERMINAL_DATE = datetime(2100, 1, 1)\nUTC = timezone.utc\n\n\n@dataclass(frozen=True)\nclass _SabrSmileNodes:\n    \"\"\"\n    A container for data relating to the SABR parameters of a\n    :class:`~rateslib.volatility.FXSabrSmile` and :class:`~rateslib.volatility.IRSabrSmile`.\n    \"\"\"\n\n    _alpha: Number\n    _beta: float | Variable\n    _rho: Number\n    _nu: Number\n\n    @property\n    def alpha(self) -> Number:\n        \"\"\"The :math:`\\\\alpha` parameter of the SABR function.\"\"\"\n        return self._alpha\n\n    @property\n    def beta(self) -> float | Variable:\n        \"\"\"The :math:`\\\\beta` parameter of the SABR function.\"\"\"\n        return self._beta\n\n    @property\n    def rho(self) -> Number:\n        \"\"\"The :math:`\\\\rho` parameter of the SABR function.\"\"\"\n        return self._rho\n\n    @property\n    def nu(self) -> Number:\n        \"\"\"The :math:`\\\\nu` parameter of the SABR function.\"\"\"\n        return self._nu\n\n    @property\n    def n(self) -> int:\n        \"\"\"The number of parameters.\"\"\"\n        return 4\n\n\ndef _validate_weights(\n    weights: Series[float] | NoInput,\n    eval_date: datetime,\n    expiries: list[datetime],\n) -> Series[float] | None:\n    if isinstance(weights, NoInput):\n        return None\n\n    w: Series[float] = Series(\n        1.0, index=get_calendar(\"all\").cal_date_range(eval_date, TERMINAL_DATE)\n    )\n    w.update(weights)\n    # restrict to sorted and filtered for outliers\n    w = w.sort_index()\n    w = w[eval_date:]  # type: ignore[misc]\n\n    node_points: list[datetime] = [eval_date] + expiries + [TERMINAL_DATE]\n    for i in range(len(expiries) + 1):\n        s, e = node_points[i] + timedelta(days=1), node_points[i + 1]\n        days = (e - s).days + 1\n        w[s:e] = (  # type: ignore[misc]\n            w[s:e] * days / w[s:e].sum()  # type: ignore[misc]\n        )  # scale the weights to allocate the correct time between nodes.\n    w[eval_date] = 0.0  # type: ignore[call-overload]\n    return w\n\n\ndef _t_var_interp(\n    expiries: list[datetime],\n    expiries_posix: list[float],\n    expiry: datetime,\n    expiry_posix: float,\n    expiry_index: int,\n    expiry_next_index: int,\n    eval_posix: float,\n    weights_cum: Series[float] | None,\n    vol1: DualTypes,\n    vol2: DualTypes,\n    bounds_flag: int,\n) -> DualTypes:\n    \"\"\"\n    Return the volatility of an intermediate timestamp via total linear variance interpolation.\n    Possibly scaled by time weights if weights is available.\n\n    Parameters\n    ----------\n    expiry_index: int\n        The index defining the interval within which expiry falls.\n    expiries_posix: list[datetime]\n        The list of datetimes associated with the expiries of the *Surface*.\n    expiries_posix: list[float]\n        The list of posix timestamps associated with the expiries of the *Surface*.\n    expiry: datetime\n        The target expiry to be interpolated.\n    expiry_posix: float\n        The pre-calculated posix timestamp for expiry.\n    expiry_index: int\n        The integer index of the expiries period in which the expiry falls.\n    expiry_next_index: int\n        Will be expiry_index + 1, unless the surface only has one expiry, in which case it will\n        equal the expiry_index.\n    eval_posix: float\n         The pre-calculated posix timestamp for eval date of the *Surface*\n    weights_cum: Series[float] or NoInput\n         The cumulative sum of the weights indexes by date\n    vol1: float, Dual, DUal2\n        The volatility of the left side\n    vol2: float, Dual, Dual2\n        The volatility on the right side\n    bounds_flag: int\n        -1: left side extrapolation, 0: normal interpolation, 1: right side extrapolation\n\n    Notes\n    -----\n    This function performs different interpolation if weights are given or not. ``bounds_flag``\n    is used to parse the inputs when *Smiles* to the left and/or right are not available.\n    \"\"\"\n    return _t_var_interp_d_sabr_d_k_or_f(\n        expiries,\n        expiries_posix,\n        expiry,\n        expiry_posix,\n        expiry_index,\n        expiry_next_index,\n        eval_posix,\n        weights_cum,\n        vol1,\n        dvol1_dk=0.0,\n        vol2=vol2,\n        dvol2_dk=0.0,\n        bounds_flag=bounds_flag,\n        derivative=False,\n    )[0]\n\n\ndef _t_var_interp_d_sabr_d_k_or_f(\n    expiries: list[datetime],\n    expiries_posix: list[float],\n    expiry: datetime,\n    expiry_posix: float,\n    expiry_index: int,\n    expiry_next_index: int,\n    eval_posix: float,\n    weights_cum: Series[float] | None,\n    vol1: DualTypes,\n    dvol1_dk: DualTypes,\n    vol2: DualTypes,\n    dvol2_dk: DualTypes,\n    bounds_flag: int,\n    derivative: bool,\n) -> tuple[DualTypes, DualTypes | None]:\n    if weights_cum is None:  # weights must also be NoInput\n        if bounds_flag == 0:\n            t1 = expiries_posix[expiry_index] - eval_posix\n            t2 = expiries_posix[expiry_next_index] - eval_posix\n        elif bounds_flag == -1:\n            # left side extrapolation\n            t1 = 0.0\n            t2 = expiries_posix[expiry_index] - eval_posix\n        else:  # bounds_flag == 1:\n            # right side extrapolation\n            t1 = expiries_posix[expiry_next_index] - eval_posix\n            t2 = TERMINAL_DATE.replace(tzinfo=UTC).timestamp() - eval_posix\n\n        t_hat = expiry_posix - eval_posix\n        t = expiry_posix - eval_posix\n    else:\n        if bounds_flag == 0:\n            t1 = weights_cum[expiries[expiry_index]]\n            t2 = weights_cum[expiries[expiry_next_index]]\n        elif bounds_flag == -1:\n            # left side extrapolation\n            t1 = 0.0\n            t2 = weights_cum[expiries[expiry_index]]\n        else:  # bounds_flag == 1:\n            # right side extrapolation\n            t1 = weights_cum[expiries[expiry_next_index]]\n            t2 = weights_cum[TERMINAL_DATE]\n\n        t_hat = weights_cum[expiry]  # number of vol weighted calendar days\n        t = (expiry_posix - eval_posix) / 86400.0  # number of calendar days\n\n    t_quotient = (t_hat - t1) / (t2 - t1)\n    vol = ((t1 * vol1**2 + t_quotient * (t2 * vol2**2 - t1 * vol1**2)) / t) ** 0.5\n    if derivative:\n        dvol_dk = (\n            (t2 / t) * t_quotient * vol2 * dvol2_dk + (t1 / t) * (1 - t_quotient) * vol1 * dvol1_dk\n        ) / vol\n    else:\n        dvol_dk = None\n    return vol, dvol_dk\n\n\nclass _OptionModelBlack76:\n    \"\"\"Container for option pricing formulae relating to the lognormal Black-76 model.\"\"\"\n\n    @staticmethod\n    def _d_plus_min(\n        K: DualTypes, f: DualTypes, rate_shift: DualTypes, vol_sqrt_t: DualTypes, eta: float\n    ) -> DualTypes:\n        # AD preserving calculation of d_plus in Black-76 formula  (eta should +/- 0.5)\n        return dual_log((f + rate_shift) / (K + rate_shift)) / vol_sqrt_t + eta * vol_sqrt_t\n\n    @staticmethod\n    def _d_plus_min_u(shifted_u: DualTypes, vol_sqrt_t: DualTypes, eta: float) -> DualTypes:\n        # AD preserving calculation of d_plus in Black-76 formula  (eta should +/- 0.5)\n        return -dual_log(shifted_u) / vol_sqrt_t + eta * vol_sqrt_t\n\n    @staticmethod\n    def _d_min(\n        K: DualTypes, f: DualTypes, rate_shift: DualTypes, vol_sqrt_t: DualTypes\n    ) -> DualTypes:\n        return _OptionModelBlack76._d_plus_min(K, f, rate_shift, vol_sqrt_t, -0.5)\n\n    @staticmethod\n    def _d_plus(\n        K: DualTypes, f: DualTypes, rate_shift: DualTypes, vol_sqrt_t: DualTypes\n    ) -> DualTypes:\n        return _OptionModelBlack76._d_plus_min(K, f, rate_shift, vol_sqrt_t, +0.5)\n\n    @staticmethod\n    def _value(\n        F: DualTypes,\n        K: DualTypes,\n        rate_shift: DualTypes,\n        t_e: DualTypes,\n        v2: DualTypes,\n        vol: DualTypes,\n        phi: float,\n    ) -> DualTypes:\n        \"\"\"\n        Option price in points terms for immediate premium settlement.\n\n        Parameters\n        -----------\n        F: float, Dual, Dual2\n            The forward price for settlement at the delivery date.\n        K: float, Dual, Dual2\n            The strike price of the option.\n        t_e: float, Dual, Dual2\n            The annualised time to expiry.\n        v2: float, Dual, Dual2\n            The discounting rate to delivery (ccy2 on FX options), at the appropriate collateral\n            rate.\n        vol: float, Dual, Dual2\n            The volatility measured over the period until expiry.\n        phi: float\n            Whether to calculate for call (1.0) or put (-1.0).\n\n        Returns\n        --------\n        float, Dual, Dual2\n        \"\"\"\n        vol_sqrt_t = vol * t_e**0.5\n        d1 = _OptionModelBlack76._d_plus(K, F, rate_shift, vol_sqrt_t)\n        d2 = d1 - vol_sqrt_t\n        Nd1, Nd2 = dual_norm_cdf(phi * d1), dual_norm_cdf(phi * d2)\n        _: DualTypes = phi * ((F + rate_shift) * Nd1 - (K + rate_shift) * Nd2)\n        # Spot formulation instead of F (Garman Kohlhagen formulation)\n        # https://quant.stackexchange.com/a/63661/29443\n        # r1, r2 = dual_log(df1) / -t, dual_log(df2) / -t\n        # S_imm = F * df2 / df1\n        # d1 = (dual_log(S_imm / K) + (r2 - r1 + 0.5 * vol ** 2) * t) / vs\n        # d2 = d1 - vs\n        # Nd1, Nd2 = dual_norm_cdf(d1), dual_norm_cdf(d2)\n        # _ = df1 * S_imm * Nd1 - K * df2 * Nd2\n        return _ * v2\n\n    @classmethod\n    def convert_to_bachelier(\n        cls,\n        f: DualTypes,\n        k: DualTypes,\n        shift: DualTypes,\n        vol: DualTypes,\n        t_e: DualTypes,\n    ) -> DualTypes:\n        phi = 1.0 if k > f else -1.0\n        s_tgt = cls._value(\n            F=f, K=k, rate_shift=shift / 100.0, t_e=t_e, v2=1.0, vol=vol / 100.0, phi=phi\n        )\n\n        if vol < 0.0:\n            raise RuntimeError(\n                \"`vol` cannot be negative.\\nIf this has occurred during a Solver calibration:\\n\"\n                \"- are your convergence tolerances wide enough?\\n\"\n                \"- are your initial parameters too far from target? (perhaps use gradient_descent \"\n                \"to find a better starting point)\\n\"\n                \"- have you tried slackening the `ini_lambda` to say (20000, 0.5, 4)?\"\n            )\n\n        def s(g: DualTypes) -> DualTypes:\n            \"\"\"s(g) is the price, s, of an option given a volatility, g,\"\"\"\n            return _OptionModelBachelier._value(\n                F=f,\n                K=k,\n                t_e=t_e,\n                v2=1.0,\n                vol=g,\n                phi=phi,\n            )\n\n        ini_guess = _dual_float(vol * (f + shift / 100.0)) / 100.0\n        result = ift_1dim(\n            s=s,\n            s_tgt=s_tgt,\n            h=\"modified_brent\",\n            ini_h_args=(0.01 * ini_guess, 10.0 * ini_guess),\n        )\n        g: DualTypes = result[\"g\"]\n        return g * 100.0\n\n    @classmethod\n    def convert_to_new_shift(\n        cls,\n        f: DualTypes,\n        k: DualTypes,\n        old_shift: DualTypes,\n        target_shift: DualTypes,\n        vol: DualTypes,\n        t_e: DualTypes,\n    ) -> DualTypes:\n        phi = -1.0 if k < f else 1.0\n\n        if old_shift == target_shift:\n            return vol\n\n        s_tgt = cls._value(\n            F=f,\n            K=k,\n            rate_shift=old_shift / 100.0,\n            t_e=t_e,\n            v2=1.0,\n            vol=vol / 100.0,\n            phi=phi,\n        )\n\n        def s(g: DualTypes) -> DualTypes:\n            \"\"\"s(g) is the price, s, of an option given a volatility, g,\"\"\"\n            return cls._value(\n                F=f,\n                K=k,\n                rate_shift=target_shift / 100.0,\n                t_e=t_e,\n                v2=1.0,\n                vol=g,\n                phi=phi,\n            )\n\n        ini_guess = (\n            _dual_float(\n                vol\n                * (\n                    ((f + old_shift / 100.0) * (k + old_shift / 100.0))\n                    / ((f + target_shift / 100.0) * (k + target_shift / 100.0))\n                )\n                ** 0.5\n            )\n            / 100.0\n        )\n        # result = ift_1dim(s=s, s_tgt=s_tgt, h=\"modified_brent\", ini_h_args=(0.0001, 10.0))\n        result = ift_1dim(\n            s=s,\n            s_tgt=s_tgt,\n            h=\"modified_brent\",\n            ini_h_args=(0.01 * ini_guess, 10.0 * ini_guess),\n        )\n        g: DualTypes = result[\"g\"]\n        return g * 100.0\n\n\nclass _OptionModelBachelier:\n    \"\"\"Container for option pricing formulae relating to the lognormal Black-76 model.\"\"\"\n\n    @staticmethod\n    def _value(\n        F: DualTypes,\n        K: DualTypes,\n        t_e: DualTypes,\n        v2: DualTypes,\n        vol: DualTypes,\n        phi: float,\n    ) -> DualTypes:\n        \"\"\"\n        Option price in points terms for immediate premium settlement.\n\n        Parameters\n        -----------\n        F: float, Dual, Dual2\n            The forward price for settlement at the delivery date.\n        K: float, Dual, Dual2\n            The strike price of the option.\n        t_e: float, Dual, Dual2\n            The annualised time to expiry.\n        v2: float, Dual, Dual2\n            The discounting rate to delivery (ccy2 on FX options), at the appropriate collateral\n            rate.\n        vol: float, Dual, Dual2\n            The volatility measured over the period until expiry.\n        phi: float\n            Whether to calculate for call (1.0) or put (-1.0).\n\n        Returns\n        --------\n        float, Dual, Dual2\n        \"\"\"\n        vs = vol * t_e**0.5\n        d = (F - K) / vs\n\n        P = dual_norm_cdf(phi * d)\n        p = dual_norm_pdf(d)\n\n        _: DualTypes = phi * (F - K) * P + vs * p\n        return _ * v2\n\n    @classmethod\n    def convert_to_black76(\n        cls,\n        f: DualTypes,\n        k: DualTypes,\n        shift: DualTypes,\n        vol: DualTypes,\n        t_e: DualTypes,\n    ) -> DualTypes:\n        phi = -1.0 if k < f else 1.0\n        s_tgt = cls._value(F=f, K=k, t_e=t_e, v2=1.0, vol=vol / 100.0, phi=phi)\n\n        def s(g: DualTypes) -> DualTypes:\n            \"\"\"s(g) is the price, s, of an option given a volatility, g,\"\"\"\n            return _OptionModelBlack76._value(\n                F=f,\n                K=k,\n                rate_shift=shift / 100.0,\n                t_e=t_e,\n                v2=1.0,\n                vol=g,\n                phi=phi,\n            )\n\n        ini_guess = vol / (100.0 * ((f + shift / 100.0) * (k + shift / 100.0)) ** 0.5)\n        # result = ift_1dim(s=s, s_tgt=s_tgt, h=\"modified_brent\", ini_h_args=(0.0001, 10.0))\n        result = ift_1dim(\n            s=s,\n            s_tgt=s_tgt,\n            h=\"modified_brent\",\n            ini_h_args=(0.01 * ini_guess, 10.0 * ini_guess),\n            func_tol=1e-11,\n        )\n        g: DualTypes = result[\"g\"]\n        return g * 100.0\n\n\nclass _SabrModel:\n    \"\"\"Container for formulae relating to the SABR volatility model.\"\"\"\n\n    @staticmethod\n    def _d_sabr_d_k_or_f(\n        k: Number,\n        f: Number,\n        t: Number,\n        a: Number,\n        b: float | Variable,\n        p: Number,\n        v: Number,\n        derivative: int,\n    ) -> tuple[Number, Number | None]:\n        \"\"\"\n        Calculate the SABR function and its derivative with respect to k or f.\n\n        For formula see for example I. Clark \"Foreign Exchange Option\n        Pricing\" section 3.10.\n\n        Rateslib uses the representation sigma(k) = X0 * X1 * X2, with these variables as defined in\n        \"Coding Interest Rates\" chapter 13 to handle AD using dual numbers effectively.\n\n        For no derivative and just the SABR function value use 0.\n        For derivatives with respect to `k` use 1.\n        For derivatives with respect to `f` use 2.\n\n        See \"Coding Interest Rates: FX Swaps and Bonds edition 2\"\n        \"\"\"\n        b_: Number = _to_number(b)\n        X0, dX0 = _SabrModel._sabr_X0(k, f, t, a, b_, p, v, derivative)\n        X1, dX1 = _SabrModel._sabr_X1(k, f, t, a, b_, p, v, derivative)\n        X2, dX2 = _SabrModel._sabr_X2(k, f, t, a, b_, p, v, derivative)\n\n        if derivative == 0:\n            return X0 * X1 * X2, None\n        else:\n            return X0 * X1 * X2, dX0 * X1 * X2 + X0 * dX1 * X2 + X0 * X1 * dX2  # type: ignore[operator]\n\n    @staticmethod\n    def _sabr_X0(\n        k: Number,\n        f: Number,\n        t: Number,\n        a: Number,\n        b: Number,\n        p: Number,\n        v: Number,\n        derivative: int = 0,\n    ) -> tuple[Number, Number | None]:\n        \"\"\"\n        X0 = a / ((fk)^((1-b)/2) * (1 + (1-b)^2/24 ln^2(f/k) + (1-b)^4/1920 ln^4(f/k) )\n\n        If ``derivative`` is 1 also returns dX0/dk, derived using sympy auto code generator.\n        If ``derivative`` is 2 also returns dX0/df, derived using sympy auto code generator.\n        \"\"\"\n        return _rs_sabr_x0(k, f, t, a, b, p, v, derivative)\n\n    @staticmethod\n    def _sabr_X1(\n        k: Number,\n        f: Number,\n        t: Number,\n        a: Number,\n        b: Number,\n        p: Number,\n        v: Number,\n        derivative: int = 0,\n    ) -> tuple[Number, Number | None]:\n        \"\"\"\n        X1 = 1 + t ( (1-b)^2 / 24 * a^2 / (fk)^(1-b) + 1/4 p b v a / (fk)^((1-b)/2) + (2-3p^2)/24 v^2 )\n\n        If ``derivative`` also returns dX0/dk, calculated using sympy.\n        \"\"\"  # noqa: E501\n        return _rs_sabr_x1(k, f, t, a, b, p, v, derivative)\n\n    @staticmethod\n    def _sabr_X2(\n        k: Number,\n        f: Number,\n        t: Number,\n        a: Number,\n        b: Number,\n        p: Number,\n        v: Number,\n        derivative: int = 0,\n    ) -> tuple[Number, Number | None]:\n        \"\"\"\n        X2 = z / chi(z)\n\n        z = v / a * (fk) ^((1-b)/2) * ln(f/k)\n        chi(z) = ln( (sqrt(1-2pz+z^2) + z -p) / (1-p) )\n\n        If ``derivative`` = 1 also returns dX2/dk, calculated using sympy.\n        If ``derivative`` = 2 also returns dX2/df, calculated using sympy.\n        \"\"\"\n        return _rs_sabr_x2(k, f, t, a, b, p, v, derivative)\n\n\ndef _surface_index_left(expiries_posix: list[float], expiry_posix: float) -> tuple[int, int]:\n    \"\"\"use `index_left_f64` to derive left and right index,\n    but exclude surfaces with only one expiry.\"\"\"\n    if len(expiries_posix) == 1:\n        return 0, 0\n    else:\n        e_idx = index_left_f64(expiries_posix, expiry_posix)\n        e_next_idx = e_idx + 1\n        return e_idx, e_next_idx\n"
  },
  {
    "path": "python/tests/curves/test_curves.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\nfrom math import exp, log\n\nimport numpy as np\nimport pytest\nfrom matplotlib import pyplot as plt\nfrom pandas import Series\nfrom rateslib import default_context, defaults, fixings\nfrom rateslib.curves import (\n    CompositeCurve,\n    Curve,\n    LineCurve,\n    MultiCsaCurve,\n    average_rate,\n    index_left,\n    index_value,\n)\nfrom rateslib.curves.curves import CreditImpliedCurve, _BaseCurve, _CurveMeta, _try_index_value\nfrom rateslib.curves.utils import _CurveNodes, _CurveSpline\nfrom rateslib.data.loader import FixingMissingDataError\nfrom rateslib.dual import Dual, Dual2, Variable, gradient\nfrom rateslib.dual.utils import _get_order_of\nfrom rateslib.enums.generics import Err, NoInput, Ok\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.instruments import IRS\nfrom rateslib.periods import FloatPeriod\nfrom rateslib.scheduling import Cal, dcf, get_calendar\nfrom rateslib.solver import Solver\n\n\n@pytest.fixture\ndef curve():\n    return Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolation=\"linear\",\n        id=\"v\",\n        convention=\"Act360\",\n        ad=1,\n    )\n\n\n@pytest.fixture\ndef line_curve():\n    return LineCurve(\n        nodes={\n            dt(2022, 3, 1): 2.00,\n            dt(2022, 3, 31): 2.01,\n        },\n        interpolation=\"linear\",\n        id=\"v\",\n        ad=1,\n    )\n\n\n@pytest.fixture\ndef index_curve():\n    return Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.999,\n        },\n        interpolation=\"linear_index\",\n        id=\"v\",\n        ad=1,\n        index_base=110.0,\n    )\n\n\ndef test_meta_attribute(curve, line_curve):\n    assert isinstance(curve._meta, _CurveMeta)\n    assert isinstance(line_curve._meta, _CurveMeta)\n\n\n@pytest.mark.parametrize(\"method\", [\"flat_forward\", \"flat_backward\"])\ndef test_flat_interp(method) -> None:\n    curve = Curve(\n        {dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.9, dt(2002, 1, 1): 0.8},\n        interpolation=method,\n    )\n    assert curve[dt(2000, 1, 1)] == 1.0\n    assert curve[dt(2001, 1, 1)] == 0.9\n    assert curve[dt(2002, 1, 1)] == 0.8\n\n    if method == \"flat_forward\":\n        assert curve[dt(2000, 7, 1)] == 1.0\n    else:\n        assert curve[dt(2000, 7, 1)] == 0.9\n\n\n@pytest.mark.parametrize((\"curve_style\", \"expected\"), [(\"df\", 0.995), (\"line\", 2.005)])\ndef test_linear_interp(curve_style, expected, curve, line_curve) -> None:\n    if curve_style == \"df\":\n        obj = curve\n    else:\n        obj = line_curve\n    result = obj[dt(2022, 3, 16)]\n    assert abs(result - Dual(expected, [\"v1\", \"v0\"], [0.5, 0.5])) < 1e-10\n    assert np.all(np.isclose(result.dual, np.array([0.5, 0.5])))\n\n\ndef test_log_linear_interp() -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolation=\"log_linear\",\n        id=\"v\",\n        convention=\"Act360\",\n        ad=1,\n    )\n    val = exp((log(1.00) + log(0.99)) / 2)\n    result = curve[dt(2022, 3, 16)]\n    expected = Dual(val, [\"v0\", \"v1\"], [0.49749372, 0.50251891])\n    assert abs(result - expected) < 1e-15\n    assert all(np.isclose(gradient(result, [\"v0\", \"v1\"]), expected.dual))\n\n\ndef test_linear_zero_rate_interp() -> None:\n    # not tested\n    pass\n\n\ndef test_line_curve_rate(line_curve) -> None:\n    expected = Dual(2.005, [\"v0\", \"v1\"], [0.5, 0.5])\n    result = line_curve.rate(effective=dt(2022, 3, 16))\n    assert abs(result - expected) < 1e-10\n    assert np.all(np.isclose(result.dual, np.array([0.5, 0.5])))\n\n\n@pytest.mark.parametrize(\n    (\"scm\", \"exp\"),\n    [\n        (\"none_simple\", 5.56617834937),\n        (\"isda_flat_compounding\", 5.57234801943),\n        (\"isda_compounding\", 5.58359355318),\n    ],\n)\ndef test_curve_rate_floating_spread(scm, exp) -> None:\n    curve = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.9985, dt(2022, 3, 1): 0.995})\n    result = curve.rate(dt(2022, 1, 1), dt(2022, 3, 1), None, 250, scm)\n    assert (result - exp) < 1e-8\n\n\ndef test_curve_rate_raises(curve) -> None:\n    with pytest.raises(ValueError, match=\"Must supply a valid `spread_compound\"):\n        curve.rate(dt(2022, 3, 3), \"7d\", float_spread=10.0, spread_compound_method=\"bad\")\n\n\n@pytest.mark.parametrize(\n    (\"li\", \"ll\", \"val\", \"expected\"),\n    [\n        ([0, 1, 2, 3, 4], 5, 0, 0),\n        ([0, 1, 2, 3, 4], 5, 0.5, 0),\n        ([0, 1, 2, 3, 4], 5, 1, 0),\n        ([0, 1, 2, 3, 4], 5, 1.5, 1),\n        ([0, 1, 2, 3, 4], 5, 2, 1),\n        ([0, 1, 2, 3, 4], 5, 2.5, 2),\n        ([0, 1, 2, 3, 4], 5, 3, 2),\n        ([0, 1, 2, 3, 4], 5, 3.5, 3),\n        ([0, 1, 2, 3, 4], 5, 4, 3),\n        ([0, 1, 2, 3, 4], 5, 4.5, 3),  # extrapolate\n        ([0, 1, 2, 3, 4], 5, -0.5, 0),  # extrapolate\n    ],\n)\ndef test_index_left(li, ll, val, expected) -> None:\n    result = index_left(li, ll, val)\n    assert result == expected\n\n\ndef test_zero_rate_plot() -> None:\n    # test calcs without raise\n    curve_zero = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.99,\n            dt(2024, 1, 1): 0.979,\n            dt(2025, 1, 1): 0.967,\n        },\n        interpolation=\"linear_zero_rate\",\n    )\n    curve_zero.plot(\"1d\")\n    plt.close(\"all\")\n\n\ndef test_curve_equality_type_differ(curve, line_curve) -> None:\n    assert curve != line_curve\n\n\ndef test_copy_curve(curve, line_curve) -> None:\n    copied = curve.copy()\n    assert copied == curve\n    assert id(copied) != id(curve)\n\n    copied = line_curve.copy()\n    assert copied == line_curve\n    assert id(copied) != id(line_curve)\n\n\n@pytest.mark.parametrize(\n    (\"attr\", \"val\"),\n    [\n        (\"_nodes\", _CurveNodes({dt(2000, 1, 1): 1.0})),\n        (\"_interpolator\", \"some_value\"),\n        (\"_id\", \"x\"),\n        (\"_ad\", 0),\n        (\"_meta\", \"some_value\"),\n    ],\n)\ndef test_curve_equality_checks(attr, val, curve) -> None:\n    copied_curve = curve.copy()\n    assert copied_curve == curve\n    setattr(copied_curve, attr, val)\n    assert copied_curve != curve\n\n\ndef test_curve_equality_spline_coeffs() -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n            dt(2022, 5, 1): 0.98,\n            dt(2022, 6, 4): 0.97,\n            dt(2022, 7, 4): 0.96,\n        },\n        interpolation=\"linear\",\n        id=\"v\",\n        convention=\"Act360\",\n        ad=0,\n        t=[\n            dt(2022, 5, 1),\n            dt(2022, 5, 1),\n            dt(2022, 5, 1),\n            dt(2022, 5, 1),\n            dt(2022, 6, 4),\n            dt(2022, 7, 4),\n            dt(2022, 7, 4),\n            dt(2022, 7, 4),\n            dt(2022, 7, 4),\n        ],\n    )\n    curve2 = Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n            dt(2022, 5, 1): 0.98,\n            dt(2022, 6, 4): 0.97,\n            dt(2022, 7, 4): 0.93,  # <- note generates different spline\n        },\n        interpolation=\"linear\",\n        id=\"v\",\n        convention=\"Act360\",\n        ad=0,\n        t=[\n            dt(2022, 5, 1),\n            dt(2022, 5, 1),\n            dt(2022, 5, 1),\n            dt(2022, 5, 1),\n            dt(2022, 6, 4),\n            dt(2022, 7, 4),\n            dt(2022, 7, 4),\n            dt(2022, 7, 4),\n            dt(2022, 7, 4),\n        ],\n    )\n    assert curve2 != curve  # should detect on curve2.spline.c\n    curve2.update_node(dt(2022, 7, 4), 0.96)\n    assert curve2 == curve  # spline.c will be resolved on calculation to the same values\n\n\ndef test_curve_interp_raises() -> None:\n    interp = \"BAD\"\n\n    err = \"Curve interpolation: 'bad' not ava\"\n    with pytest.raises(ValueError, match=err):\n        Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 2, 1): 0.9,\n            },\n            id=\"curve\",\n            interpolation=interp,\n        )\n\n\ndef test_curve_sorted_nodes_raises() -> None:\n    err = \"Curve node dates are not sorted or contain duplicates.\"\n    with pytest.raises(ValueError, match=err):\n        Curve(\n            nodes={\n                dt(2022, 2, 1): 0.9,\n                dt(2022, 1, 1): 1.0,\n            },\n            id=\"curve\",\n        )\n\n\ndef test_curve_interp_case() -> None:\n    curve_lower = Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolation=\"log_linear\",\n        id=\"id\",\n        convention=\"Act360\",\n        ad=1,\n    )\n    curve_upper = Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolation=\"LOG_LINEAR\",\n        id=\"id\",\n        convention=\"Act360\",\n        ad=1,\n    )\n    assert curve_lower[dt(2022, 3, 16)] == curve_upper[dt(2022, 3, 16)]\n\n\ndef test_custom_interpolator() -> None:\n    def interp(date, nodes):\n        return date\n\n    curve = Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolation=interp,\n        id=\"v\",\n        convention=\"Act360\",\n        ad=1,\n    )\n\n    assert curve[dt(2022, 3, 15)] == dt(2022, 3, 15)\n\n\ndef test_df_is_zero_in_past(curve) -> None:\n    assert curve[dt(1999, 1, 1)] == 0.0\n\n\ndef test_curve_none_return(curve) -> None:\n    result = curve.rate(dt(2022, 2, 1), dt(2022, 2, 2))\n    assert result is None\n\n\n@pytest.mark.parametrize(\n    (\"endpoints\", \"expected\"),\n    [\n        (\"natural\", [1.0, 0.995913396831872, 0.9480730429565414, 0.95]),\n        (\"not_a_knot\", [1.0, 0.9967668788593117, 0.9461282456344617, 0.95]),\n        ((\"not_a_knot\", \"natural\"), [1.0, 0.9965809643843604, 0.9480575781858877, 0.95]),\n        ((\"natural\", \"not_a_knot\"), [1.0, 0.9959615881004005, 0.9461971628597721, 0.95]),\n    ],\n)\ndef test_spline_endpoints(endpoints, expected) -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.99,\n            dt(2024, 1, 1): 0.97,\n            dt(2025, 1, 1): 0.95,\n            dt(2026, 1, 1): 0.95,\n        },\n        endpoints=endpoints,\n        t=[\n            dt(2022, 1, 1),\n            dt(2022, 1, 1),\n            dt(2022, 1, 1),\n            dt(2022, 1, 1),\n            dt(2023, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2026, 1, 1),\n            dt(2026, 1, 1),\n            dt(2026, 1, 1),\n        ],\n    )\n    for i, date in enumerate([dt(2022, 1, 1), dt(2022, 7, 1), dt(2025, 7, 1), dt(2026, 1, 1)]):\n        result = curve[date]\n        assert (result - expected[i]) < 1e-12\n\n\n@pytest.mark.parametrize(\"endpoints\", [(\"natural\", \"bad\"), (\"bad\", \"natural\")])\ndef test_spline_endpoints_raise(endpoints) -> None:\n    with pytest.raises(NotImplementedError, match=\"Endpoint method\"):\n        Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.99,\n                dt(2024, 1, 1): 0.97,\n                dt(2025, 1, 1): 0.95,\n                dt(2026, 1, 1): 0.95,\n            },\n            endpoints=endpoints,\n            t=[\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2023, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2026, 1, 1),\n                dt(2026, 1, 1),\n                dt(2026, 1, 1),\n                dt(2026, 1, 1),\n            ],\n        )\n\n\ndef test_not_a_knot_raises() -> None:\n    with pytest.raises(ValueError, match=\"`endpoints` cannot be 'not_a_knot'\"):\n        Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2024, 1, 1): 0.97,\n                dt(2026, 1, 1): 0.95,\n            },\n            endpoints=\"not_a_knot\",\n            t=[\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2024, 1, 1),\n                dt(2026, 1, 1),\n                dt(2026, 1, 1),\n                dt(2026, 1, 1),\n                dt(2026, 1, 1),\n            ],\n        )\n\n\ndef test_set_ad_order_no_spline() -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.99,\n        },\n        id=\"v\",\n    )\n    assert curve[dt(2022, 1, 1)] == 1.0\n    assert curve.ad == 0\n\n    curve._set_ad_order(1)\n    assert curve[dt(2022, 1, 1)] == Dual(1.0, [\"v0\"], [])\n    assert curve.ad == 1\n\n    old_id = id(curve.nodes)\n    curve._set_ad_order(2)\n    assert curve[dt(2022, 1, 1)] == Dual2(1.0, [\"v0\"], [], [])\n    assert curve.ad == 2\n    assert id(curve.nodes) != old_id  # new nodes object thus a new id\n\n    expected_id = id(curve.nodes)\n    curve._set_ad_order(2)\n    assert id(curve.nodes) == expected_id  # new objects not created when order unchged\n\n\ndef test_set_ad_order_raises(curve) -> None:\n    with pytest.raises(ValueError, match=\"`order` can only be in {0, 1, 2}\"):\n        curve._set_ad_order(100)\n\n\ndef test_index_left_raises() -> None:\n    with pytest.raises(ValueError, match=\"`index_left` designed for intervals.\"):\n        index_left([1], 1, 100)\n\n\n# def test_curve_shift():\n#     curve = Curve(\n#         nodes={\n#             dt(2022, 1, 1): 1.0,\n#             dt(2023, 1, 1): 0.988,\n#             dt(2024, 1, 1): 0.975,\n#             dt(2025, 1, 1): 0.965,\n#             dt(2026, 1, 1): 0.955,\n#             dt(2027, 1, 1): 0.9475\n#         },\n#         t=[\n#             dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1), dt(2024, 1, 1),\n#             dt(2025, 1, 1),\n#             dt(2026, 1, 1),\n#             dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1), dt(2027, 1, 1),\n#         ],\n#     )\n#     result_curve = curve.shift(25)\n#     diff = np.array([\n#         result_curve.rate(_, \"1D\") - curve.rate(_, \"1D\") - 0.25 for _ in [\n#             dt(2022, 1, 10), dt(2023, 3, 24), dt(2024, 11, 11), dt(2026, 4, 5)\n#         ]\n#     ])\n#     assert np.all(np.abs(diff) < 1e-7)\n\n\n@pytest.mark.parametrize(\"ad_order\", [0, 1, 2])\n# @pytest.mark.parametrize(\"composite\", [True, False])\ndef test_curve_shift_ad_order(ad_order) -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.988,\n            dt(2024, 1, 1): 0.975,\n            dt(2025, 1, 1): 0.965,\n            dt(2026, 1, 1): 0.955,\n            dt(2027, 1, 1): 0.9475,\n        },\n        t=[\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n        ],\n        ad=ad_order,\n    )\n    result_curve = curve.shift(25)\n    diff = np.array(\n        [\n            result_curve.rate(_, \"1D\") - curve.rate(_, \"1D\") - 0.25\n            for _ in [dt(2022, 1, 10), dt(2023, 3, 24), dt(2024, 11, 11), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(diff) < 1e-7)\n\n    result_curve._set_ad_order((ad_order + 1) % 3)\n    assert result_curve.ad == (ad_order + 1) % 3\n\n\n@pytest.mark.skip(reason=\"composite argument removed from shift method in v2.1\")\ndef test_curve_shift_association() -> None:\n    # test a dynamic shift association with curves, active after a Solver mutation\n    args = (dt(2022, 2, 1), \"1d\")\n    curve = Curve(\n        nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.988},\n    )\n    solver = Solver(\n        curves=[curve],\n        instruments=[IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=curve)],\n        s=[2.0],\n    )\n    base = curve.rate(*args)\n    ass_shifted_curve = curve.shift(100)\n    stat_shifted_curve = curve.shift(100, composite=False)\n    assert abs(base - ass_shifted_curve.rate(*args) + 1.00) < 1e-5\n    assert abs(base - stat_shifted_curve.rate(*args) + 1.00) < 1e-5\n\n    solver.s = [3.0]\n    solver.iterate()\n    base = curve.rate(*args)\n    assert abs(base - ass_shifted_curve.rate(*args) + 1.00) < 1e-5\n    assert abs(ass_shifted_curve.rate(*args) - stat_shifted_curve.rate(*args)) > 0.95\n\n\ndef test_curve_shift_dual_input() -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.988,\n            dt(2024, 1, 1): 0.975,\n            dt(2025, 1, 1): 0.965,\n            dt(2026, 1, 1): 0.955,\n            dt(2027, 1, 1): 0.9475,\n        },\n        t=[\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n        ],\n    )\n    result_curve = curve.shift(Dual(25, [\"z\"], []))\n    diff = np.array(\n        [\n            result_curve.rate(_, \"1D\") - curve.rate(_, \"1D\") - 0.25\n            for _ in [dt(2022, 1, 10), dt(2023, 3, 24), dt(2024, 11, 11), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(diff) < 1e-7)\n\n\ndef test_composite_curve_shift() -> None:\n    c1 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999})\n    c2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.998})\n    cc = CompositeCurve([c1, c2])\n    result = cc.shift(20).rate(dt(2022, 1, 1), \"1d\")\n    expected = c1.rate(dt(2022, 1, 1), \"1d\") + c2.rate(dt(2022, 1, 1), \"1d\") + 0.2\n    assert abs(result - expected) < 1e-3\n\n\n@pytest.mark.parametrize(\"ad_order\", [0, 1, 2])\n# @pytest.mark.parametrize(\"composite\", [True, False])\ndef test_linecurve_shift(ad_order) -> None:\n    curve = LineCurve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.988,\n            dt(2024, 1, 1): 0.975,\n            dt(2025, 1, 1): 0.965,\n            dt(2026, 1, 1): 0.955,\n            dt(2027, 1, 1): 0.9475,\n        },\n        t=[\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n        ],\n        ad=ad_order,\n    )\n    result_curve = curve.shift(25)\n    diff = np.array(\n        [\n            result_curve[_] - curve[_] - 0.25\n            for _ in [dt(2022, 1, 10), dt(2023, 3, 24), dt(2024, 11, 11), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(diff) < 1e-7)\n\n\ndef test_linecurve_shift_dual_input() -> None:\n    curve = LineCurve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.988,\n            dt(2024, 1, 1): 0.975,\n            dt(2025, 1, 1): 0.965,\n            dt(2026, 1, 1): 0.955,\n            dt(2027, 1, 1): 0.9475,\n        },\n        t=[\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n        ],\n    )\n    result_curve = curve.shift(Dual(25, [\"z\"], []))\n    diff = np.array(\n        [\n            result_curve[_] - curve[_] - 0.25\n            for _ in [dt(2022, 1, 10), dt(2023, 3, 24), dt(2024, 11, 11), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(diff) < 1e-7)\n\n\n@pytest.mark.parametrize(\"ad_order\", [0, 1, 2])\n# @pytest.mark.parametrize(\"composite\", [True, False])\ndef test_indexcurve_shift(ad_order) -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.988,\n            dt(2024, 1, 1): 0.975,\n            dt(2025, 1, 1): 0.965,\n            dt(2026, 1, 1): 0.955,\n            dt(2027, 1, 1): 0.9475,\n        },\n        t=[\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n        ],\n        ad=ad_order,\n        index_base=110.0,\n        interpolation=\"log_linear\",\n    )\n    result_curve = curve.shift(25)\n    diff = np.array(\n        [\n            result_curve.rate(_, \"1D\") - curve.rate(_, \"1D\") - 0.25\n            for _ in [dt(2022, 1, 10), dt(2023, 3, 24), dt(2024, 11, 11), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(diff) < 1e-7)\n    assert result_curve.meta.index_base == curve.meta.index_base\n\n\ndef test_indexcurve_shift_dual_input() -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.988,\n            dt(2024, 1, 1): 0.975,\n            dt(2025, 1, 1): 0.965,\n            dt(2026, 1, 1): 0.955,\n            dt(2027, 1, 1): 0.9475,\n        },\n        t=[\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n        ],\n        index_base=110.0,\n        interpolation=\"log_linear\",\n    )\n    result_curve = curve.shift(Dual(25, [\"z\"], []))\n    diff = np.array(\n        [\n            result_curve.rate(_, \"1D\") - curve.rate(_, \"1D\") - 0.25\n            for _ in [dt(2022, 1, 10), dt(2023, 3, 24), dt(2024, 11, 11), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(diff) < 1e-7)\n    assert result_curve.meta.index_base == curve.meta.index_base\n\n\n@pytest.mark.parametrize(\"c_obj\", [\"c\", \"l\", \"i\"])\n@pytest.mark.parametrize(\"ini_ad\", [0, 1, 2])\n@pytest.mark.parametrize(\n    \"spread\", [1.0, Dual(1.0, [\"z\"], []), Dual2(1.0, [\"z\"], [], []), Variable(1.0, [\"z\"])]\n)\n# @pytest.mark.parametrize(\"composite\", [False, True])\ndef test_curve_shift_ad_orders(curve, line_curve, index_curve, c_obj, ini_ad, spread):\n    if c_obj == \"c\":\n        c = curve\n    elif c_obj == \"l\":\n        c = line_curve\n    else:\n        c = index_curve\n    c._set_ad_order(ini_ad)\n\n    if ini_ad + _get_order_of(spread) == 3:\n        with pytest.raises(TypeError, match=\"Cannot create a ShiftedCurve with mixed AD orders\"):\n            c.shift(spread)\n        return None\n\n    result = c.shift(spread)\n    expected = max(_get_order_of(spread), ini_ad)\n    assert result._ad == expected\n\n\n@pytest.mark.parametrize(\n    (\"crv\", \"tol\"),\n    [\n        (\n            Curve(\n                nodes={\n                    dt(2022, 1, 1): 1.0,\n                    dt(2023, 1, 1): 0.988,\n                    dt(2024, 1, 1): 0.975,\n                    dt(2025, 1, 1): 0.965,\n                    dt(2026, 1, 1): 0.955,\n                    dt(2027, 1, 1): 0.9475,\n                },\n                t=[\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2025, 1, 1),\n                    dt(2026, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                ],\n            ),\n            1e-8,\n        ),\n        (\n            Curve(\n                nodes={\n                    dt(2022, 1, 1): 1.0,\n                    dt(2023, 1, 1): 0.988,\n                    dt(2024, 1, 1): 0.975,\n                    dt(2025, 1, 1): 0.965,\n                    dt(2026, 1, 1): 0.955,\n                    dt(2027, 1, 1): 0.9475,\n                },\n                t=[\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2025, 1, 1),\n                    dt(2026, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                ],\n                index_base=110.0,\n            ),\n            1e-8,\n        ),\n        (\n            Curve(\n                nodes={\n                    dt(2022, 1, 1): 1.0,\n                    dt(2023, 1, 1): 0.988,\n                    dt(2024, 1, 1): 0.975,\n                    dt(2025, 1, 1): 0.965,\n                    dt(2026, 1, 1): 0.955,\n                    dt(2027, 1, 1): 0.9475,\n                },\n                t=[\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2025, 1, 1),\n                    dt(2026, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                ],\n                index_base=110.0,\n                interpolation=\"linear_index\",\n            ),\n            1e-8,\n        ),\n        (\n            LineCurve(\n                nodes={\n                    dt(2022, 1, 1): 1.7,\n                    dt(2023, 1, 1): 1.65,\n                    dt(2024, 1, 1): 1.4,\n                    dt(2025, 1, 1): 1.3,\n                    dt(2026, 1, 1): 1.25,\n                    dt(2027, 1, 1): 1.35,\n                },\n                t=[\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2024, 1, 1),\n                    dt(2025, 1, 1),\n                    dt(2026, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                ],\n            ),\n            1e-8,\n        ),\n        (\n            Curve(\n                nodes={\n                    dt(2022, 1, 1): 1.0,\n                    dt(2023, 1, 2): 0.988,\n                    dt(2024, 1, 1): 0.975,\n                    dt(2025, 1, 1): 0.965,\n                    dt(2026, 1, 1): 0.955,\n                    dt(2027, 1, 1): 0.9475,\n                },\n                t=[\n                    dt(2022, 1, 1),\n                    dt(2022, 1, 1),\n                    dt(2022, 1, 1),\n                    dt(2022, 1, 1),\n                    dt(2023, 1, 2),\n                    dt(2024, 1, 1),\n                    dt(2025, 1, 1),\n                    dt(2026, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2027, 1, 1),\n                ],\n            ),\n            1e-3,\n        ),\n    ],\n)\ndef test_curve_translate(crv, tol) -> None:\n    result_curve = crv.translate(dt(2023, 1, 1))\n    diff = np.array(\n        [\n            result_curve.rate(_, \"1D\") - crv.rate(_, \"1D\")\n            for _ in [dt(2023, 1, 25), dt(2023, 3, 24), dt(2024, 11, 11), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(diff) < tol)\n    if not isinstance(result_curve.meta.index_base, NoInput):\n        projected_base = crv.index_value(dt(2023, 1, 1), crv.meta.index_lag)\n        assert abs(result_curve.meta.index_base - projected_base) < 1e-14\n\n    # test date between original initial and translated initial is zero\n    assert result_curve[dt(1900, 1, 1)] == 0.0\n    assert result_curve[dt(2022, 12, 31)] == 0.0\n\n\n@pytest.mark.parametrize(\n    \"crv\",\n    [\n        Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.988,\n                dt(2024, 1, 1): 0.975,\n                dt(2025, 1, 1): 0.965,\n                dt(2026, 1, 1): 0.955,\n                dt(2027, 1, 1): 0.9475,\n            },\n            t=[\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2026, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n            ],\n        ),\n        LineCurve(\n            nodes={\n                dt(2022, 1, 1): 1.7,\n                dt(2023, 1, 1): 1.65,\n                dt(2024, 1, 1): 1.4,\n                dt(2025, 1, 1): 1.3,\n                dt(2026, 1, 1): 1.25,\n                dt(2027, 1, 1): 1.35,\n            },\n            t=[\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2026, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n            ],\n        ),\n    ],\n)\n@pytest.mark.parametrize(\n    \"dates\",\n    [\n        (\"10d\", \"-10d\"),\n        (dt(2022, 1, 11), dt(2021, 12, 22)),\n        (10, -10),\n    ],\n)\ndef test_curve_roll(crv, dates) -> None:\n    rolled_curve = crv.roll(dates[0])\n    rolled_curve2 = crv.roll(dates[1])\n\n    expected = np.array(\n        [\n            crv.rate(_, \"1D\")\n            for _ in [dt(2023, 1, 15), dt(2023, 3, 15), dt(2024, 11, 15), dt(2026, 4, 15)]\n        ],\n    )\n    result = np.array(\n        [\n            rolled_curve.rate(_, \"1D\")\n            for _ in [dt(2023, 1, 25), dt(2023, 3, 25), dt(2024, 11, 25), dt(2026, 4, 25)]\n        ],\n    )\n    result2 = np.array(\n        [\n            rolled_curve2.rate(_, \"1D\")\n            for _ in [dt(2023, 1, 5), dt(2023, 3, 5), dt(2024, 11, 5), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(result - expected) < 1e-7)\n    assert np.all(np.abs(result2 - expected) < 1e-7)\n\n    # value prior to initial node\n    assert rolled_curve[dt(1900, 1, 1)] == 0.0\n\n\n@pytest.mark.skip(reason=\"v2.1 uses a RolledCurve and does not return a compatible object for eq\")\ndef test_curve_roll_copy(curve) -> None:\n    result = curve.roll(\"0d\")\n    assert result == curve\n\n\ndef test_curve_spline_warning() -> None:\n    curve = Curve(\n        nodes={\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 0.99,\n            dt(2025, 1, 1): 0.97,\n            dt(2026, 1, 1): 0.94,\n            dt(2027, 1, 1): 0.91,\n        },\n        t=[\n            dt(2023, 1, 1),\n            dt(2023, 1, 1),\n            dt(2023, 1, 1),\n            dt(2023, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n        ],\n    )\n    with pytest.warns(UserWarning):\n        curve[dt(2028, 1, 1)]\n\n\ndef test_index_curve_roll() -> None:\n    crv = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.988,\n            dt(2024, 1, 1): 0.975,\n            dt(2025, 1, 1): 0.965,\n            dt(2026, 1, 1): 0.955,\n            dt(2027, 1, 1): 0.9475,\n        },\n        t=[\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 1),\n            dt(2026, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n            dt(2027, 1, 1),\n        ],\n        index_base=110.0,\n        interpolation=\"log_linear\",\n    )\n    rolled_curve = crv.roll(\"10d\")\n    rolled_curve2 = crv.roll(\"-10d\")\n\n    expected = np.array(\n        [\n            crv.rate(_, \"1D\")\n            for _ in [dt(2023, 1, 15), dt(2023, 3, 15), dt(2024, 11, 15), dt(2026, 4, 15)]\n        ],\n    )\n    result = np.array(\n        [\n            rolled_curve.rate(_, \"1D\")\n            for _ in [dt(2023, 1, 25), dt(2023, 3, 25), dt(2024, 11, 25), dt(2026, 4, 25)]\n        ],\n    )\n    result2 = np.array(\n        [\n            rolled_curve2.rate(_, \"1D\")\n            for _ in [dt(2023, 1, 5), dt(2023, 3, 5), dt(2024, 11, 5), dt(2026, 4, 5)]\n        ],\n    )\n    assert np.all(np.abs(result - expected) < 1e-7)\n    assert np.all(np.abs(result2 - expected) < 1e-7)\n    assert rolled_curve.meta.index_base == crv.meta.index_base\n\n\n@pytest.mark.parametrize(\n    \"s\",\n    [\n        Series(index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)], data=[100.0, 200, 300]),\n        158.62068965517238,\n        \"KLMN\",\n    ],\n)\ndef test_index_value_series(s) -> None:\n    # test that a Series input to fixings works\n    fixings.add(\n        \"KLMN\",\n        Series(index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)], data=[100.0, 200, 300]),\n    )\n    result = index_value(\n        index_lag=1,\n        index_method=\"daily\",\n        index_fixings=s,\n        index_date=dt(2000, 2, 18),\n        index_curve=NoInput(0),\n    )\n    expected = 12 / 29 * 100.0 + 17 / 29 * 200\n    fixings.pop(\"KLMN\")\n    assert abs(result - expected) < 1e-10\n\n\ndef test_curve_translate_raises(curve) -> None:\n    with pytest.raises(ValueError, match=\"Cannot translate into the past.\"):\n        curve.translate(dt(2020, 4, 1))\n\n\ndef test_curve_zero_width_rate_raises(curve) -> None:\n    with pytest.raises(ZeroDivisionError, match=\"effective:\"):\n        curve.rate(dt(2022, 3, 10), dt(2022, 3, 10))\n\n\ndef test_set_node_vector_updates_ad_attribute(curve) -> None:\n    curve._set_node_vector([0.98], ad=2)\n    assert curve.ad == 2\n\n\n@pytest.mark.parametrize(\n    (\"convention\", \"expected\"),\n    [\n        (\"act360\", 4.3652192566314705),\n        (\"30360\", 4.372999441829487),\n        (\"act365f\", 4.372518793743008),\n        (\"bus252\", 4.354756779569957),\n    ],\n)\ndef test_average_rate(convention, expected):\n    start = dt(2000, 1, 1)\n    end = dt(2006, 1, 1)\n    rate = 5.0\n    d = dcf(start, end, convention, calendar=\"bus\")\n    result, d_, n_ = average_rate(start, end, convention, rate, d)\n\n    assert abs(result - expected) < 1e-12\n    assert abs((1 + d * rate / 100.0) - (1 + d_ * result / 100.0) ** n_) < 1e-12\n\n\n@pytest.mark.parametrize(\"curve\", [Curve, LineCurve])\ndef test_spline_interpolation_feature(curve):\n    t = [dt(2000, 1, 1)] * 4 + [dt(2001, 1, 1)] + [dt(2002, 1, 1)] * 4\n    original = curve(nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98, dt(2002, 1, 1): 0.975}, t=t)\n    feature = curve(\n        nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98, dt(2002, 1, 1): 0.975},\n        interpolation=\"spline\",\n    )\n    assert feature.interpolator.spline.t == t\n    assert feature.interpolator.spline.spline.c == original.interpolator.spline.spline.c\n\n    assert feature[dt(2000, 1, 1)] == original[dt(2000, 1, 1)]\n    assert feature[dt(1999, 1, 1)] == original[dt(1999, 1, 1)]\n    assert feature[dt(2001, 5, 1)] == original[dt(2001, 5, 1)]\n\n\ndef test_conventions_and_calendar_unnecessary():\n    # test that the calendar and the convention of a Curve is not required to forecast rates\n\n    # this test currently raises but in future versions the calendar and convention attributes\n    # of a curve may be separated from this mechanism.\n    curve = Curve({dt(2026, 4, 1): 1.0, dt(2028, 4, 1): 0.98}, calendar=\"nyc\", convention=\"act360\")\n    period = FloatPeriod(\n        start=dt(2026, 4, 1),\n        end=dt(2026, 7, 1),\n        payment=dt(2026, 7, 1),\n        frequency=\"Q\",\n        convention=\"act365f\",\n        calendar=\"osl\",\n    )\n    with pytest.raises(ValueError, match=\"A `rate_curve` and `rate_index` have been supplied with\"):\n        period.rate(rate_curve=curve)\n\n\nclass TestCurve:\n    def test_repr(self):\n        curve = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            id=\"sofr\",\n        )\n        expected = f\"<rl.Curve:{curve.id} at {hex(id(curve))}>\"\n        assert expected == curve.__repr__()\n\n    def test_cache_clear_and_defaults(self):\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99})\n        curve[dt(2001, 1, 1)]\n        assert len(curve._cache) == 1\n        curve._clear_cache()\n        assert len(curve._cache) == 0\n        v1 = curve[dt(2001, 1, 1)]\n        curve.update_node(dt(2002, 1, 1), 0.98)\n        # cache cleared by function\n        assert len(curve._cache) == 0\n        v2 = curve[dt(2001, 1, 1)]\n        assert v2 != v1\n\n        with default_context(\"curve_caching\", False):\n            curve.nodes.nodes[dt(2002, 1, 1)] = 0.90\n            # no clear cache required, but value will re-calc anyway\n            assert curve[dt(2001, 1, 1)] != v2\n\n    def test_typing_as_base_curve(self):\n        curve = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            id=\"sofr\",\n        )\n        assert isinstance(curve, _BaseCurve)\n\n    @pytest.mark.skip(reason=\"TranslatedCurve was constructed in v2.1 and bypasses this.\")\n    def test_curve_translate_knots_raises(self) -> None:\n        curve = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.988,\n                dt(2024, 1, 1): 0.975,\n                dt(2025, 1, 1): 0.965,\n                dt(2026, 1, 1): 0.955,\n                dt(2027, 1, 1): 0.9475,\n            },\n            t=[\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2022, 1, 1),\n                dt(2022, 12, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2026, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n            ],\n        )\n        with pytest.raises(ValueError, match=\"Cannot translate spline knots for given\"):\n            curve.translate(dt(2022, 12, 15))\n\n    def test_calendar_passed_to_rate_dcf(self):\n        # Holidays on which no overnight DI rate is published\n        reserve_holidays = [\n            \"2025-01-01\",\n            \"2025-03-03\",\n            \"2025-03-04\",\n            \"2025-04-18\",\n            \"2025-04-21\",\n            \"2025-05-01\",\n            \"2025-06-19\",\n            \"2025-09-07\",\n            \"2025-10-12\",\n            \"2025-11-02\",\n            \"2025-11-15\",\n            \"2025-11-20\",\n            \"2025-12-25\",\n            \"2026-01-01\",\n            \"2026-02-16\",\n            \"2026-02-17\",\n            \"2026-04-03\",\n            \"2026-04-21\",\n            \"2026-05-01\",\n            \"2026-06-04\",\n            \"2026-09-07\",\n            \"2026-10-12\",\n            \"2026-11-02\",\n            \"2026-11-15\",\n            \"2026-11-20\",\n            \"2026-12-25\",\n        ]\n        bra = Cal(holidays=[dt.strptime(h, \"%Y-%m-%d\") for h in reserve_holidays], week_mask=[5, 6])\n\n        curve = Curve(\n            nodes={\n                dt(2025, 5, 15): 1.0,\n                dt(2026, 1, 2): 0.919218,\n            },\n            convention=\"bus252\",\n            calendar=bra,\n        )\n        d = dcf(dt(2025, 5, 15), dt(2026, 1, 2), \"bus252\", calendar=bra)\n        expected = (1 + 0.14) ** -d\n        assert abs(expected - curve[dt(2026, 1, 2)]) < 5e-7\n\n        # period rate\n        result = curve.rate(dt(2025, 5, 15), dt(2026, 1, 2))\n        expected = (1 / 0.919218 - 1) * 100 / d\n        assert abs(expected - result) < 5e-7\n\n    @pytest.mark.parametrize(\"interpolation\", [\"linear\", \"log_linear\"])\n    def test_linear_bus_interpolation(self, interpolation) -> None:\n        curve = Curve(\n            nodes={dt(2000, 1, 3): 1.0, dt(2000, 1, 17): 0.9},\n            calendar=\"bus\",\n            convention=\"act365f\",\n            interpolation=interpolation,\n        )\n        curve2 = Curve(\n            nodes={dt(2000, 1, 3): 1.0, dt(2000, 1, 17): 0.9},\n            calendar=\"bus\",\n            convention=\"bus252\",\n            interpolation=interpolation,\n        )\n\n        assert curve[dt(2000, 1, 17)] == curve2[dt(2000, 1, 17)]\n        assert curve[dt(2000, 1, 3)] == curve2[dt(2000, 1, 3)]\n\n        assert curve[dt(2000, 1, 5)] != curve2[dt(2000, 1, 5)]\n        assert curve[dt(2000, 1, 10)] == curve2[dt(2000, 1, 10)]  #  half calendar and bus\n        assert curve[dt(2000, 1, 13)] != curve2[dt(2000, 1, 13)]\n\n    def test_update_meta(self, curve):\n        curve.update_meta(\"credit_discretization\", 101)\n        assert curve.meta.credit_discretization == 101\n\n    def test_no_termination(self, curve):\n        with pytest.raises(ValueError, match=\"`termination` must be supplied\"):\n            curve.rate(dt(2022, 3, 2))\n\n    def test_index_value_lag_mismatch(self, index_curve):\n        with pytest.raises(ValueError, match=\"'curve' interpolation can only be used\"):\n            index_curve.index_value(\n                index_date=dt(2022, 3, 4),\n                index_lag=22,\n                index_method=\"curve\",\n            )\n\n    def test_update_node_raises(self, curve):\n        with pytest.raises(KeyError, match=\"`key` is not in\"):\n            curve.update_node(dt(2000, 1, 1), 1.0)\n\n\nclass TestLineCurve:\n    def test_repr(self):\n        curve = LineCurve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            id=\"libor1m\",\n        )\n        expected = f\"<rl.LineCurve:{curve.id} at {hex(id(curve))}>\"\n        assert expected == curve.__repr__()\n\n    def test_typing_as_base_curve(self):\n        curve = LineCurve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            id=\"libor1m\",\n        )\n        assert isinstance(curve, _BaseCurve)\n\n    def test_index_values_raises(self, line_curve):\n        with pytest.raises(TypeError, match=\"A 'values' type Curve cannot\"):\n            line_curve.index_value(dt(2022, 3, 3), index_lag=0)\n\n\nclass TestIndexCurve:\n    def test_curve_index_linear_daily_interp(self) -> None:\n        curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 1, 5): 0.9999},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=2,\n        )\n        result = curve.index_value(dt(2022, 1, 5), 2)\n        expected = 200.020002002\n        assert abs(result - expected) < 1e-7\n\n        result = curve.index_value(dt(2022, 1, 3), 2)\n        expected = 200.010001001  # value is linearly interpolated between index values.\n        assert abs(result - expected) < 1e-7\n\n    # SKIP: with deprecation of IndexCurve errors must be deferred to price time.\n    # def test_indexcurve_raises(self) -> None:\n    #     with pytest.raises(ValueError, match=\"`index_base` must be given\"):\n    #         Curve({dt(2022, 1, 1): 1.0})\n\n    def test_index_value_raises(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0}, index_base=100.0)\n        with pytest.raises(ValueError, match=\"`index_method` as string: 'BAD' is not a v\"):\n            curve.index_value(dt(2022, 1, 1), 3, index_method=\"BAD\")\n\n    @pytest.mark.parametrize(\"ad\", [0, 1, 2])\n    def test_roll_preserves_ad(self, ad) -> None:\n        curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_base=100.0,\n            index_lag=3,\n            id=\"tags_\",\n            ad=ad,\n        )\n        new_curve = curve.roll(\"1m\")\n        assert new_curve.ad == curve.ad\n\n    def test_historic_rate_is_none(self) -> None:\n        curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_base=100.0,\n            index_lag=3,\n            id=\"tags_\",\n        )\n        assert curve.rate(dt(2021, 3, 4), \"1b\", \"f\") is None\n\n    def test_repr(self):\n        curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 1, 5): 0.9999}, index_base=200.0, id=\"us_cpi\"\n        )\n        expected = f\"<rl.Curve:us_cpi at {hex(id(curve))}>\"\n        assert expected == curve.__repr__()\n\n    def test_typing_as_base_curve(self):\n        curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 1, 5): 0.9999}, index_base=200.0, id=\"us_cpi\"\n        )\n        assert isinstance(curve, _BaseCurve)\n\n\nclass TestCompositeCurve:\n    def test_long_1day_rate_captured(self):\n        c1 = Curve({dt(2000, 1, 1): 1.0, dt(2030, 1, 1): 0.8, dt(2030, 1, 2): 0.7999})\n        c2 = Curve({dt(2000, 1, 1): 1.0, dt(2030, 1, 1): 0.7, dt(2030, 1, 2): 0.6999})\n        r1 = c1.rate(dt(2030, 1, 1), dt(2030, 1, 2))\n        r2 = c2.rate(dt(2030, 1, 1), dt(2030, 1, 2))\n        cc = CompositeCurve([c1, c2])\n        result = cc.rate(dt(2030, 1, 1), dt(2030, 1, 2))\n        assert abs(result - r1 - r2) < 5e-4\n\n    def test_curve_df_based(self) -> None:\n        curve1 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            t=[\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n            ],\n        )\n        curve2 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 6, 30): 1.0,\n                dt(2022, 7, 1): 0.999992,\n                dt(2022, 12, 31): 0.999992,\n                dt(2023, 1, 1): 0.999984,\n                dt(2023, 6, 30): 0.999984,\n                dt(2023, 7, 1): 0.999976,\n                dt(2023, 12, 31): 0.999976,\n                dt(2024, 1, 1): 0.999968,\n                dt(2024, 6, 30): 0.999968,\n                dt(2024, 7, 1): 0.999960,\n                dt(2025, 1, 1): 0.999960,\n            },\n        )\n        curve = CompositeCurve([curve1, curve2])\n\n        for date in [dt(2022, 12, 30), dt(2022, 12, 31), dt(2023, 1, 1)]:\n            result1 = curve.rate(date, \"1d\")\n            expected1 = curve1.rate(date, \"1d\") + curve2.rate(date, \"1d\")\n            assert abs(result1 - expected1) < 2e-8\n\n        result = curve.rate(dt(2022, 6, 1), \"1Y\")\n        expected = curve1.rate(dt(2022, 6, 1), \"1Y\") + curve2.rate(dt(2022, 6, 1), \"1Y\")\n        assert abs(result - expected) < 1e-4\n\n    def test_composite_curve_translate(self) -> None:\n        curve1 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            t=[\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n            ],\n        )\n        curve2 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 6, 30): 1.0,\n                dt(2022, 7, 1): 0.999992,\n                dt(2022, 12, 31): 0.999992,\n                dt(2023, 1, 1): 0.999984,\n                dt(2023, 6, 30): 0.999984,\n                dt(2023, 7, 1): 0.999976,\n                dt(2023, 12, 31): 0.999976,\n                dt(2024, 1, 1): 0.999968,\n                dt(2024, 6, 30): 0.999968,\n                dt(2024, 7, 1): 0.999960,\n                dt(2025, 1, 1): 0.999960,\n            },\n        )\n        crv = CompositeCurve([curve1, curve2])\n\n        result_curve = crv.translate(dt(2022, 3, 1))\n        diff = np.array(\n            [\n                result_curve.rate(_, \"1D\") - crv.rate(_, \"1D\")\n                for _ in [dt(2023, 1, 25), dt(2023, 3, 24), dt(2024, 11, 11)]\n            ],\n        )\n        assert np.all(np.abs(diff) < 1e-5)\n\n    def test_composite_curve_roll(self) -> None:\n        curve1 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            t=[\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n            ],\n        )\n        curve2 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 6, 30): 1.0,\n                dt(2022, 7, 1): 0.999992,\n                dt(2022, 12, 31): 0.999992,\n                dt(2023, 1, 1): 0.999984,\n                dt(2023, 6, 30): 0.999984,\n                dt(2023, 7, 1): 0.999976,\n                dt(2023, 12, 31): 0.999976,\n                dt(2024, 1, 1): 0.999968,\n                dt(2024, 6, 30): 0.999968,\n                dt(2024, 7, 1): 0.999960,\n                dt(2025, 1, 1): 0.999960,\n            },\n        )\n        crv = CompositeCurve([curve1, curve2])\n\n        rolled_curve = crv.roll(\"10d\")\n        expected = np.array(\n            [crv.rate(_, \"1D\") for _ in [dt(2023, 1, 15), dt(2023, 3, 15), dt(2024, 11, 15)]],\n        )\n        result = np.array(\n            [\n                rolled_curve.rate(_, \"1D\")\n                for _ in [dt(2023, 1, 25), dt(2023, 3, 25), dt(2024, 11, 25)]\n            ],\n        )\n\n        assert np.all(np.abs(result - expected) < 1e-7)\n\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"rate\", (dt(2022, 1, 1), \"1d\")),\n            (\"roll\", (\"10d\",)),\n            (\"translate\", (dt(2022, 1, 10),)),\n            (\"shift\", (10.0, \"id\")),\n            (\"__getitem__\", (dt(2022, 1, 10),)),\n            (\"index_value\", (dt(2022, 1, 10), 3)),\n        ],\n    )\n    def test_composite_curve_precheck_cache(self, method, args) -> None:\n        # test precache_check on shift\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}, index_base=100.0, index_lag=3)\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.998})\n        cc = CompositeCurve([c1, c2])\n        cc._cache[dt(1980, 1, 1)] = 100.0\n\n        # mutate a curve to trigger cache id clear\n        c1._set_node_vector([0.99], 0)\n        getattr(cc, method)(*args)\n        assert dt(1980, 1, 1) not in cc._cache\n\n    def test_isinstance_raises(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n        line_curve = LineCurve({dt(2022, 1, 1): 10.0, dt(2023, 1, 1): 12.0})\n        with pytest.raises(TypeError, match=\"CompositeCurve can only contain curves of the same t\"):\n            CompositeCurve([curve, line_curve])\n\n    @pytest.mark.parametrize(\n        (\"attribute\", \"val\"),\n        [\n            (\"modifier\", [\"MF\", \"MP\"]),\n            (\"calendar\", [\"ldn\", \"tgt\"]),\n            (\"convention\", [\"act360\", \"act365f\"]),\n        ],\n    )\n    def test_attribute_error_raises(self, attribute, val) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, **{attribute: val[0]})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, **{attribute: val[1]})\n        with pytest.raises(ValueError, match=\"Cannot composite curves with dif\"):\n            CompositeCurve([c1, c2])\n\n    def test_line_based(self) -> None:\n        c1 = LineCurve({dt(2022, 1, 1): 1.5, dt(2022, 1, 3): 1.0})\n        c2 = LineCurve({dt(2022, 1, 1): 2.0, dt(2022, 1, 3): 3.0})\n        cc = CompositeCurve([c1, c2])\n        expected = 3.75\n        result = cc.rate(dt(2022, 1, 2))\n        assert abs(result - expected) < 1e-8\n\n        result = cc[dt(2022, 1, 2)]\n        assert abs(result - expected) < 1e-8\n\n    def test_initial_node_raises(self) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n        c2 = Curve({dt(2022, 1, 2): 1.0, dt(2023, 1, 1): 0.99})\n        with pytest.raises(ValueError, match=\"`curves` must share the same ini\"):\n            CompositeCurve([c1, c2])\n\n    @pytest.mark.parametrize(\n        (\"lag\", \"base\"), [([2, 3], [100.0, 99.0]), ([4, NoInput(0)], [100.0, NoInput(0)])]\n    )\n    def test_index_curves_take_first_value(self, lag, base) -> None:\n        ic1 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_lag=lag[0],\n            index_base=base[0],\n        )\n        ic2 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_lag=lag[1],\n            index_base=base[1],\n        )\n        cc = CompositeCurve([ic1, ic2])\n        assert cc.meta.index_base == base[0]\n        assert cc.meta.index_lag == lag[0]\n\n    def test_index_curves_attributes_warns(self):\n        ic1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        ic2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        cc = CompositeCurve([ic1, ic2])\n\n        with pytest.warns(UserWarning):\n            result = cc.index_value(dt(1999, 1, 1), 3)\n            expected = 0.0\n            assert abs(result - expected) < 1e-5\n\n    def test_index_curves_attributes(self) -> None:\n        ic1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        ic2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        cc = CompositeCurve([ic1, ic2])\n        assert cc.meta.index_lag == 3\n        assert cc.meta.index_base == 101.1\n\n        result = cc.index_value(dt(2022, 1, 31), 3, index_method=\"monthly\")\n        expected = 101.1\n        assert abs(result - expected) < 1e-5\n\n        result = cc.index_value(dt(2022, 1, 1), 3)\n        expected = 101.1\n        assert abs(result - expected) < 1e-5\n\n    def test_index_curves_interp_raises(self) -> None:\n        ic1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        ic2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        cc = CompositeCurve([ic1, ic2])\n        with pytest.raises(ValueError, match=\"`index_method` as string: 'bad interp'\"):\n            cc.index_value(index_date=dt(2022, 1, 31), index_lag=3, index_method=\"bad interp\")\n\n    def test_composite_curve_proxies(self) -> None:\n        uu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"uu\")\n        ee = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.991}, id=\"ee\")\n        eu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.992}, id=\"eu\")\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 1)),\n            fx_curves={\n                \"usdusd\": uu,\n                \"eureur\": ee,\n                \"eurusd\": eu,\n            },\n        )\n        pc = MultiCsaCurve([uu, fxf.curve(\"usd\", \"eur\")])\n        result = pc[dt(2023, 1, 1)]\n        expected = 0.98900\n        assert abs(result - expected) < 1e-4\n\n        pc = MultiCsaCurve(\n            [\n                fxf.curve(\"usd\", \"eur\"),\n                uu,\n            ],\n        )\n        result = pc[dt(2023, 1, 1)]\n        assert abs(result - expected) < 1e-4\n\n    def test_composite_curve_no_index_value_raises(self, curve) -> None:\n        cc = CompositeCurve([curve])\n        with pytest.raises(ValueError, match=\"Curve must be initialised with an `index_base`\"):\n            cc.index_value(dt(2022, 1, 1), 3)\n\n    def test_historic_rate_is_none(self) -> None:\n        c1 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99997260,  # 1%\n                dt(2022, 1, 3): 0.99991781,  # 2%\n                dt(2022, 1, 4): 0.99983564,  # 3%\n                dt(2022, 1, 5): 0.99972608,  # 4%\n            },\n            convention=\"Act365F\",\n        )\n        c2 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99989042,  # 4%\n                dt(2022, 1, 3): 0.99980825,  # 3%\n                dt(2022, 1, 4): 0.99975347,  # 2%\n                dt(2022, 1, 5): 0.99972608,  # 1%\n            },\n            convention=\"Act365F\",\n        )\n        cc = CompositeCurve([c1, c2])\n        assert cc.rate(dt(2021, 3, 4), \"1b\", \"f\") is None\n\n    def test_repr(self):\n        curve1 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            t=[\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n            ],\n        )\n        curve2 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 6, 30): 1.0,\n                dt(2022, 7, 1): 0.999992,\n                dt(2022, 12, 31): 0.999992,\n                dt(2023, 1, 1): 0.999984,\n                dt(2023, 6, 30): 0.999984,\n                dt(2023, 7, 1): 0.999976,\n                dt(2023, 12, 31): 0.999976,\n                dt(2024, 1, 1): 0.999968,\n                dt(2024, 6, 30): 0.999968,\n                dt(2024, 7, 1): 0.999960,\n                dt(2025, 1, 1): 0.999960,\n            },\n        )\n        curve = CompositeCurve([curve1, curve2])\n        expected = f\"<rl.CompositeCurve:{curve.id} at {hex(id(curve))}>\"\n        assert expected == curve.__repr__()\n        assert isinstance(curve.id, str)\n\n    def test_typing_as_base_curve(self):\n        curve1 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n                dt(2024, 1, 1): 0.965,\n                dt(2025, 1, 1): 0.955,\n            },\n            t=[\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2023, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n                dt(2025, 1, 1),\n            ],\n        )\n        curve2 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 6, 30): 1.0,\n                dt(2022, 7, 1): 0.999992,\n                dt(2022, 12, 31): 0.999992,\n                dt(2023, 1, 1): 0.999984,\n                dt(2023, 6, 30): 0.999984,\n                dt(2023, 7, 1): 0.999976,\n                dt(2023, 12, 31): 0.999976,\n                dt(2024, 1, 1): 0.999968,\n                dt(2024, 6, 30): 0.999968,\n                dt(2024, 7, 1): 0.999960,\n                dt(2025, 1, 1): 0.999960,\n            },\n        )\n        curve = CompositeCurve([curve1, curve2])\n        assert isinstance(curve, _BaseCurve)\n\n    def test_cache(self):\n        curve1 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n            },\n        )\n        curve2 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 6, 30): 1.0,\n                dt(2022, 7, 1): 0.999992,\n                dt(2022, 12, 31): 0.999992,\n                dt(2023, 1, 1): 0.999984,\n            },\n        )\n        curve = CompositeCurve([curve1, curve2])\n        curve[dt(2022, 3, 1)]\n        assert curve._cache == {dt(2022, 3, 1): 0.9967396833121631}\n\n        # update a curve\n        curve2.update_node(dt(2022, 6, 30), 0.95)\n        curve[dt(2022, 3, 1)]\n        assert curve._cache == {dt(2022, 3, 1): 0.9801226964242061}\n\n    def test_composite_curve_of_composite_curve(self):\n        c1 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n            },\n        )\n        c2 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 30): 0.99,\n            }\n        )\n        cc1 = CompositeCurve([c1, c2])\n        cc2 = CompositeCurve([cc1, c1])\n        result = cc2.rate(dt(2022, 2, 15), \"3m\")\n        assert abs(result - 4.933123726330553) < 1e-8\n\n    def test_composite_curve_of_composite_line_curve(self):\n        c1 = LineCurve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.98,\n            },\n        )\n        c2 = LineCurve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 30): 0.99,\n            }\n        )\n        cc1 = CompositeCurve([c1, c2])\n        cc2 = CompositeCurve([cc1, c1])\n        result = cc2.rate(dt(2022, 2, 15), \"3m\")\n        assert abs(result - 2.993926361170989) < 1e-8\n\n    def test_ad_order_is_max(self):\n        c1 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99})\n        c2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99})\n        c2._set_ad_order(2)\n\n        assert CompositeCurve([c1, c2])._ad == 2\n        assert CompositeCurve([c2, c1])._ad == 2\n\n    def test_initial_df(self):\n        curve1 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99}, ad=1, id=\"v\")\n        curve2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98}, ad=1, id=\"w\")\n        cc = CompositeCurve([curve1, curve2])\n        result = cc[dt(2000, 1, 1)]\n        expected = Dual(1.0, [\"v0\", \"v1\", \"w0\", \"w1\"], [1.0, 0.0, 1.0, 0.0])\n        assert result == expected\n\n    def test_update_meta_raises(self):\n        ic1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        ic2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        cc = CompositeCurve([ic1, ic2])\n        with pytest.raises(AttributeError, match=\"'CompositeCurve' object has no attribute 'updat\"):\n            cc.update_meta(\"h\", 100.0)\n\n    def test_update_meta(self):\n        ic1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        ic2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, index_lag=3, index_base=101.1)\n        cc = CompositeCurve([ic1, ic2])\n        before = cc.meta.credit_recovery_rate\n        ic1.update_meta(\"credit_recovery_rate\", 0.88)\n        after = cc.meta.credit_recovery_rate\n        assert before != after\n        assert after == 0.88\n\n\nclass TestMultiCsaCurve:\n    def test_historic_rate_is_none(self) -> None:\n        c1 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99997260,  # 1%\n                dt(2022, 1, 3): 0.99991781,  # 2%\n                dt(2022, 1, 4): 0.99983564,  # 3%\n                dt(2022, 1, 5): 0.99972608,  # 4%\n            },\n            convention=\"Act365F\",\n        )\n        c2 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99989042,  # 4%\n                dt(2022, 1, 3): 0.99980825,  # 3%\n                dt(2022, 1, 4): 0.99975347,  # 2%\n                dt(2022, 1, 5): 0.99972608,  # 1%\n            },\n            convention=\"Act365F\",\n        )\n        cc = MultiCsaCurve([c1, c2])\n        assert cc.rate(dt(2021, 3, 4), \"1b\", \"f\") is None\n\n    def test_multi_raises(self, line_curve, curve) -> None:\n        with pytest.raises(TypeError, match=\"MultiCsaCurve must use discount factors\"):\n            MultiCsaCurve([line_curve])\n\n    def test_multi_csa_shift(self) -> None:\n        c1 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99997260,  # 1%\n                dt(2022, 1, 3): 0.99991781,  # 2%\n                dt(2022, 1, 4): 0.99983564,  # 3%\n                dt(2022, 1, 5): 0.99972608,  # 4%\n            },\n            convention=\"Act365F\",\n        )\n        c2 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99989042,  # 4%\n                dt(2022, 1, 3): 0.99980825,  # 3%\n                dt(2022, 1, 4): 0.99975347,  # 2%\n                dt(2022, 1, 5): 0.99972608,  # 1%\n            },\n            convention=\"Act365F\",\n        )\n        c3 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99989042,  # 4%\n                dt(2022, 1, 3): 0.99979455,  # 3.5%\n                dt(2022, 1, 4): 0.99969869,  # 3.5%\n                dt(2022, 1, 5): 0.99958915,  # 4%\n            },\n            convention=\"Act365F\",\n        )\n        cc = MultiCsaCurve([c1, c2, c3])\n        cc_shift = cc.shift(100)\n        with default_context(\"multi_csa_steps\", [1, 1, 1, 1, 1, 1, 1]):\n            r1 = cc_shift.rate(dt(2022, 1, 1), \"1d\")\n            r2 = cc_shift.rate(dt(2022, 1, 2), \"1d\")\n            r3 = cc_shift.rate(dt(2022, 1, 3), \"1d\")\n            r4 = cc_shift.rate(dt(2022, 1, 4), \"1d\")\n\n        assert abs(r1 - 5.0) < 1e-3\n        assert abs(r2 - 4.5) < 1e-3\n        assert abs(r3 - 4.5) < 1e-3\n        assert abs(r4 - 5.0) < 1e-3\n\n    @pytest.mark.parametrize(\"caching\", [True, False])\n    def test_multi_csa(self, caching) -> None:\n        with default_context(\"curve_caching\", caching):\n            c1 = Curve(\n                {\n                    dt(2022, 1, 1): 1.0,\n                    dt(2022, 1, 2): 0.99997260,  # 1%\n                    dt(2022, 1, 3): 0.99991781,  # 2%\n                    dt(2022, 1, 4): 0.99983564,  # 3%\n                    dt(2022, 1, 5): 0.99972608,  # 4%\n                },\n                convention=\"Act365F\",\n            )\n            c2 = Curve(\n                {\n                    dt(2022, 1, 1): 1.0,\n                    dt(2022, 1, 2): 0.99989042,  # 4%\n                    dt(2022, 1, 3): 0.99980825,  # 3%\n                    dt(2022, 1, 4): 0.99975347,  # 2%\n                    dt(2022, 1, 5): 0.99972608,  # 1%\n                },\n                convention=\"Act365F\",\n            )\n            c3 = Curve(\n                {\n                    dt(2022, 1, 1): 1.0,\n                    dt(2022, 1, 2): 0.99989042,  # 4%\n                    dt(2022, 1, 3): 0.99979455,  # 3.5%\n                    dt(2022, 1, 4): 0.99969869,  # 3.5%\n                    dt(2022, 1, 5): 0.99958915,  # 4%\n                },\n                convention=\"Act365F\",\n            )\n            cc = MultiCsaCurve([c1, c2, c3])\n            with default_context(\"multi_csa_steps\", [1, 1, 1, 1, 1, 1, 1]):\n                r1 = cc.rate(dt(2022, 1, 1), \"1d\")\n                r2 = cc.rate(dt(2022, 1, 2), \"1d\")\n                r3 = cc.rate(dt(2022, 1, 3), \"1d\")\n                r4 = cc.rate(dt(2022, 1, 4), \"1d\")\n\n            assert abs(r1 - 4.0) < 1e-3\n            assert abs(r2 - 3.5) < 1e-3\n            assert abs(r3 - 3.5) < 1e-3\n            assert abs(r4 - 4.0) < 1e-3\n\n    def test_multi_csa_granularity(self) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 0.9, dt(2072, 1, 1): 0.5})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 0.8, dt(2072, 1, 1): 0.7})\n\n        with default_context(\"multi_csa_max_step\", 182, \"multi_csa_min_step\", 182):\n            cc = MultiCsaCurve([c1, c2])\n            r1 = cc.rate(dt(2052, 5, 24), \"1d\")\n            # r2 = cc.rate(dt(2052, 5, 25), \"1d\")\n            # r3 = cc.rate(dt(2052, 5, 26), \"1d\")\n            assert abs(r1 - 1.448374) < 1e-3\n\n    def test_repr(self):\n        c1 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99997260,  # 1%\n                dt(2022, 1, 3): 0.99991781,  # 2%\n                dt(2022, 1, 4): 0.99983564,  # 3%\n                dt(2022, 1, 5): 0.99972608,  # 4%\n            },\n            convention=\"Act365F\",\n        )\n        c2 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99989042,  # 4%\n                dt(2022, 1, 3): 0.99980825,  # 3%\n                dt(2022, 1, 4): 0.99975347,  # 2%\n                dt(2022, 1, 5): 0.99972608,  # 1%\n            },\n            convention=\"Act365F\",\n        )\n        c3 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99989042,  # 4%\n                dt(2022, 1, 3): 0.99979455,  # 3.5%\n                dt(2022, 1, 4): 0.99969869,  # 3.5%\n                dt(2022, 1, 5): 0.99958915,  # 4%\n            },\n            convention=\"Act365F\",\n        )\n        curve = MultiCsaCurve([c1, c2, c3])\n        expected = f\"<rl.MultiCsaCurve:{curve.id} at {hex(id(curve))}>\"\n        assert expected == curve.__repr__()\n        assert isinstance(curve.id, str)\n\n    def test_typing_as_base_curve(self):\n        c1 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99997260,  # 1%\n                dt(2022, 1, 3): 0.99991781,  # 2%\n                dt(2022, 1, 4): 0.99983564,  # 3%\n                dt(2022, 1, 5): 0.99972608,  # 4%\n            },\n            convention=\"Act365F\",\n        )\n        c2 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99989042,  # 4%\n                dt(2022, 1, 3): 0.99980825,  # 3%\n                dt(2022, 1, 4): 0.99975347,  # 2%\n                dt(2022, 1, 5): 0.99972608,  # 1%\n            },\n            convention=\"Act365F\",\n        )\n        c3 = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 1, 2): 0.99989042,  # 4%\n                dt(2022, 1, 3): 0.99979455,  # 3.5%\n                dt(2022, 1, 4): 0.99969869,  # 3.5%\n                dt(2022, 1, 5): 0.99958915,  # 4%\n            },\n            convention=\"Act365F\",\n        )\n        curve = MultiCsaCurve([c1, c2, c3])\n        assert isinstance(curve, _BaseCurve)\n\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"rate\", (dt(2022, 1, 1), \"1d\")),\n            (\"roll\", (\"10d\",)),\n            (\"translate\", (dt(2022, 1, 10),)),\n            (\"shift\", (10.0, \"id\")),\n            (\"__getitem__\", (dt(2022, 1, 10),)),\n        ],\n    )\n    def test_multi_csa_curve_precheck_cache(self, method, args) -> None:\n        # test precache_check on shift\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.998})\n        cc = MultiCsaCurve([c1, c2])\n        cc._cache[dt(1980, 1, 1)] = 100.0\n\n        # mutate a curve to trigger cache id clear\n        c1._set_node_vector([0.99], 0)\n        getattr(cc, method)(*args)\n        assert dt(1980, 1, 1) not in cc._cache\n\n    def test_multi_csa_curve_add_to_cache(self):\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2052, 2, 1): 0.9})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2052, 2, 1): 0.8})\n        cc = MultiCsaCurve([c1, c2])\n        cc[dt(2052, 2, 1)]\n        assert len(cc._cache) == 31\n\n\nclass TestProxyCurve:\n    def test_repr(self) -> None:\n        fxr1 = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n        fxr2 = FXRates({\"usdcad\": 1.1}, dt(2022, 1, 2))\n        fxf = FXForwards(\n            [fxr1, fxr2],\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.95}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 1.0}),\n                \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.99}),\n                \"cadusd\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.97}),\n                \"cadcad\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.969}),\n            },\n        )\n        curve = fxf.curve(\"cad\", \"eur\")\n        expected = f\"<rl.ProxyCurve:{curve.id} at {hex(id(curve))}>\"\n        assert curve.__repr__() == expected\n        assert isinstance(curve.id, str)\n\n    def test_typing_as_basecurve(self):\n        fxr1 = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n        fxr2 = FXRates({\"usdcad\": 1.1}, dt(2022, 1, 2))\n        fxf = FXForwards(\n            [fxr1, fxr2],\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.95}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 1.0}),\n                \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.99}),\n                \"cadusd\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.97}),\n                \"cadcad\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.969}),\n            },\n        )\n        curve = fxf.curve(\"cad\", \"eur\")\n        assert isinstance(curve, _BaseCurve)\n\n    def test_cache_is_validated_on_getitem_and_lookup(self):\n        fxr1 = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n        fxr2 = FXRates({\"usdcad\": 1.1}, dt(2022, 1, 2))\n        fxf = FXForwards(\n            [fxr1, fxr2],\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.95}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 1.0}),\n                \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.99}),\n                \"cadusd\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.97}),\n                \"cadcad\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.969}),\n            },\n        )\n        curve = fxf.curve(\"cad\", \"eur\")\n        assert curve._state == fxf._state\n\n        fxr1.update({\"usdeur\": 100000000.0})\n        fxf.curve(\"eur\", \"eur\")._set_node_vector([0.5], 1)\n\n        state1 = fxf._state\n        # performing an action on the proxy curve will validate and update states\n        curve[dt(2022, 1, 9)]\n        state2 = fxf._state\n        assert state1 != state2\n\n        fxr1.update({\"usdeur\": 10.0})\n        fxf.curve(\"eur\", \"eur\")._set_node_vector([0.6], 1)\n        state3 = curve._state\n        assert state3 == state2  # becuase no method validation has yet occurred\n\n    def test_update(self):\n        fxr1 = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n        fxr2 = FXRates({\"usdcad\": 1.1}, dt(2022, 1, 2))\n        fxf = FXForwards(\n            [fxr1, fxr2],\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.95}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 1.0}),\n                \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.99}),\n                \"cadusd\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.97}),\n                \"cadcad\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.969}),\n            },\n        )\n        curve = fxf.curve(\"cad\", \"eur\")\n        with pytest.raises(AttributeError):\n            curve.update_meta(\"h\", 100.0)\n        with pytest.raises(AttributeError):\n            curve.update_node(\"h\", 100.0)\n        with pytest.raises(AttributeError):\n            curve.update(\"h\", 100.0)\n\n\nclass TestPlotCurve:\n    def test_plot_curve(self, curve) -> None:\n        fig, ax, lines = curve.plot(\"1d\")\n        result = lines[0].get_data()\n        assert result[0][0] == dt(2022, 3, 1)\n        assert abs(result[1][0].real - 12.004001333774994) < 1e-6\n        plt.close(\"all\")\n\n    def test_plot_linecurve(self, line_curve) -> None:\n        fig, ax, lines = line_curve.plot(\"0d\")\n        result = lines[0].get_data()\n        assert result[0][0] == dt(2022, 3, 1)\n        assert abs(result[1][0].real - 2.0) < 1e-6\n        plt.close(\"all\")\n\n    @pytest.mark.parametrize(\"left\", [\"1d\", dt(2022, 3, 2)])\n    def test_plot_curve_left(self, curve, left) -> None:\n        fig, ax, lines = curve.plot(\"1d\", left=left)\n        result = lines[0].get_data()\n        assert result[0][0] == dt(2022, 3, 2)\n        assert abs(result[1][0].real - 12.008005336896055) < 1e-6\n        plt.close(\"all\")\n\n    def test_plot_curve_left_raise(self, curve) -> None:\n        with pytest.raises(ValueError, match=\"`left` must be supplied as\"):\n            fig, ax, lines = curve.plot(\"1d\", left=100.3)\n        plt.close(\"all\")\n\n    @pytest.mark.parametrize(\"right\", [\"2d\", dt(2022, 3, 3)])\n    def test_plot_curve_right(self, curve, right) -> None:\n        fig, ax, lines = curve.plot(\"1d\", right=right)\n        result = lines[0].get_data()\n        assert result[0][-1] == dt(2022, 3, 3)\n        assert abs(result[1][-1].real - 12.012012012015738) < 1e-6\n        plt.close(\"all\")\n\n    def test_plot_curve_right_raise(self, curve) -> None:\n        with pytest.raises(ValueError, match=\"`right` must be supplied as\"):\n            fig, ax, lines = curve.plot(\"1d\", right=100.3)\n        plt.close(\"all\")\n\n    def test_plot_comparators(self, curve) -> None:\n        fig, ax, lines = curve.plot(\"1d\", comparators=[curve])\n        assert len(lines) == 2\n        res1 = lines[0].get_data()\n        res2 = lines[1].get_data()\n        assert res1[0][0] == res2[0][0]\n        assert res1[1][0] == res2[1][0]\n        plt.close(\"all\")\n\n    def test_plot_diff(self, curve) -> None:\n        fig, ax, lines = curve.plot(\"1d\", comparators=[curve], difference=True)\n        assert len(lines) == 1\n        result = lines[0].get_data()\n        assert result[0][0] == dt(2022, 3, 1)\n        assert result[1][0] == 0\n        plt.close(\"all\")\n\n    @pytest.mark.parametrize(\"left\", [NoInput(0), dt(2022, 1, 1), \"0d\"])\n    @pytest.mark.parametrize(\"right\", [NoInput(0), dt(2022, 2, 1), \"0d\"])\n    def test_plot_index(self, left, right) -> None:\n        i_curve = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 1.0}, index_base=2.0)\n        fig, ax, lines = i_curve.plot_index(left=left, right=right)\n        result = lines[0].get_data()\n        assert result[0][0] == dt(2022, 1, 1)\n        assert abs(result[1][0].real - 2.0) < 1e-6\n        plt.close(\"all\")\n\n    def test_plot_index_comparators(self) -> None:\n        i_curve = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 1.0}, index_base=2.0)\n        i_curv2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 1.0}, index_base=2.0)\n        fig, ax, lines = i_curve.plot_index(comparators=[i_curv2])\n        assert len(lines) == 2\n        res1 = lines[0].get_data()\n        res2 = lines[1].get_data()\n        assert res1[0][0] == res2[0][0]\n        assert res1[1][0] == res2[1][0]\n        plt.close(\"all\")\n\n    def test_plot_index_diff(self) -> None:\n        i_curv = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 1.0}, index_base=2.0)\n        i_curv2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 1.0}, index_base=2.0)\n        fig, ax, lines = i_curv.plot_index(\"1d\", comparators=[i_curv2], difference=True)\n        assert len(lines) == 1\n        result = lines[0].get_data()\n        assert result[0][0] == dt(2022, 1, 1)\n        assert result[1][0] == 0\n        plt.close(\"all\")\n\n    def test_plot_index_raises(self) -> None:\n        i_curve = Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 1.0}, index_base=2.0)\n        with pytest.raises(ValueError, match=\"`left` must be supplied as\"):\n            i_curve.plot_index(left=2.0)\n        with pytest.raises(ValueError, match=\"`right` must be supplied as\"):\n            i_curve.plot_index(right=2.0)\n\n    def test_composite_curve_plot(self) -> None:\n        curve1 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 12, 1): 0.95}, modifier=\"MF\", calendar=\"bus\")\n        curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 12, 1): 0.97}, modifier=\"MF\", calendar=\"bus\")\n        cc = CompositeCurve(curves=[curve1, curve2])\n        cc.plot(\"1m\")\n\n    def test_plot_a_rolled_spline_curve(self) -> None:\n        curve = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.988,\n                dt(2024, 1, 1): 0.975,\n                dt(2025, 1, 1): 0.965,\n                dt(2026, 1, 1): 0.955,\n                dt(2027, 1, 1): 0.9475,\n            },\n            t=[\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2024, 1, 1),\n                dt(2025, 1, 1),\n                dt(2026, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n                dt(2027, 1, 1),\n            ],\n        )\n        rolled_curve = curve.roll(\"6m\")\n        rolled_curve2 = curve.roll(\"-6m\")\n        curve.plot(\n            \"1d\",\n            comparators=[rolled_curve, rolled_curve2],\n            labels=[\"orig\", \"rolled\", \"rolled2\"],\n            right=dt(2026, 6, 30),\n        )\n        usd_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 0.98, dt(2023, 1, 1): 0.95},\n            calendar=\"nyc\",\n            id=\"sofr\",\n        )\n        usd_args = dict(effective=dt(2022, 1, 1), spec=\"usd_irs\", curves=\"sofr\")\n        Solver(\n            curves=[usd_curve],\n            instruments=[\n                IRS(**usd_args, termination=\"6M\"),\n                IRS(**usd_args, termination=\"1Y\"),\n            ],\n            s=[4.35, 4.85],\n            instrument_labels=[\"6M\", \"1Y\"],\n            id=\"us_rates\",\n        )\n        usd_curve.plot(\"1b\", labels=[\"SOFR o/n\"])\n\n\nclass TestStateAndCache:\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            Curve(nodes={dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99}),\n            LineCurve(nodes={dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99}),\n            Curve(\n                nodes={\n                    dt(2022, 1, 1): 1.0,\n                    dt(2023, 1, 1): 0.98,\n                },\n                index_base=200.0,\n            ),\n        ],\n    )\n    @pytest.mark.parametrize((\"method\", \"args\"), [(\"_set_ad_order\", (1,))])\n    def test_method_does_not_change_state(self, curve, method, args):\n        before = curve._state\n        getattr(curve, method)(*args)\n        after = curve._state\n        assert before == after\n\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            Curve(nodes={dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99, dt(2003, 1, 1): 0.98}),\n            LineCurve(nodes={dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99}),\n            Curve(\n                nodes={\n                    dt(2000, 1, 1): 1.0,\n                    dt(2002, 1, 1): 0.98,\n                },\n                index_base=200.0,\n            ),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98], 1)),\n            (\"update_node\", (dt(2002, 1, 1), 0.98)),\n            (\"update\", ({dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99},)),\n            (\"csolve\", tuple()),\n        ],\n    )\n    def test_method_changes_state(self, curve, method, args):\n        before = curve._state\n        getattr(curve, method)(*args)\n        after = curve._state\n        assert before != after\n\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            Curve(nodes={dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99}),\n            LineCurve(nodes={dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99}),\n            Curve(\n                nodes={\n                    dt(2000, 1, 1): 1.0,\n                    dt(2002, 1, 1): 0.98,\n                },\n                index_base=200.0,\n            ),\n        ],\n    )\n    def test_populate_cache(self, curve):\n        assert curve._cache == {}\n        curve[dt(2000, 5, 1)]\n        assert dt(2000, 5, 1) in curve._cache\n\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            Curve(nodes={dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99, dt(2003, 1, 1): 0.98}),\n            LineCurve(nodes={dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99}),\n            Curve(\n                nodes={\n                    dt(2000, 1, 1): 1.0,\n                    dt(2002, 1, 1): 0.98,\n                },\n                index_base=200.0,\n            ),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98], 1)),\n            (\"update_node\", (dt(2002, 1, 1), 0.98)),\n            (\"update\", ({dt(2000, 1, 1): 1.0, dt(2002, 1, 1): 0.99},)),\n            (\"csolve\", tuple()),\n            (\"_set_ad_order\", (1,)),\n        ],\n    )\n    def test_method_clears_cache(self, curve, method, args):\n        curve[dt(2000, 5, 1)]\n        assert dt(2000, 5, 1) in curve._cache\n        getattr(curve, method)(*args)\n        assert curve._cache == {}\n\n    @pytest.mark.parametrize(\"Klass\", [CompositeCurve, MultiCsaCurve])\n    def test_composite_curve_validation_cache_clearing_and_state(self, Klass):\n        # test that a composite curve will validate and clear its cache\n        # and following that update its own state to its composited state\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.95})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.90})\n        cc = Klass([c1, c2])\n        cc_state_pre = cc._state\n        # get a value and check the cache\n        cc_result_pre = cc[dt(2022, 6, 1)]\n        _ = cc[dt(2022, 6, 30)]\n        assert dt(2022, 6, 1) in cc._cache\n        assert dt(2022, 6, 30) in cc._cache\n\n        # update an underlying curve\n        c2.update_node(dt(2024, 1, 1), 0.85)\n        # check the cache is cleared when using a get using\n        cc_result_post = cc[dt(2022, 6, 1)]\n        assert cc_result_post < cc_result_pre\n        # check that the state of the composite curve has changed\n        cc_state_post = cc._state\n        assert cc_state_pre != cc_state_post\n        assert cc_state_post == cc._get_composited_state()\n        # check that the cache is correct\n        assert dt(2022, 6, 1) in cc._cache\n        assert dt(2022, 6, 30) not in cc._cache\n\n    def test_max_cache_size(self):\n        with default_context(\"curve_caching_max\", 3):\n            curve = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.95})\n            assert curve._cache_len == 0\n            curve[dt(2022, 2, 1)]\n            assert curve._cache_len == 1\n            curve[dt(2022, 3, 1)]\n            assert curve._cache_len == 2\n            curve[dt(2022, 4, 1)]\n            assert curve._cache_len == 3\n            curve[dt(2022, 5, 1)]\n            assert curve._cache_len == 3\n\n            assert dt(2022, 2, 1) not in curve._cache\n            assert dt(2022, 3, 1) in curve._cache\n            assert dt(2022, 4, 1) in curve._cache\n            assert dt(2022, 5, 1) in curve._cache\n\n\nclass TestIndexValue:\n    def test_dict_raise(self):\n        with pytest.raises(\n            NotImplementedError, match=\"`index_curve` cannot currently be supplied as dict\"\n        ):\n            index_value(0, \"curve\", NoInput(0), 0, {\"a\": 0, \"b\": 0})\n\n    def test_return_index_fixings_directly(self):\n        assert index_value(0, \"curve\", 2.5, NoInput(0), NoInput(0)) == 2.5\n        assert index_value(0, \"curve\", Dual(2, [\"a\"], []), NoInput(0), NoInput(0)) == Dual(\n            2, [\"a\"], []\n        )\n\n    @pytest.mark.parametrize(\"method\", [\"curve\", \"daily\"])\n    def test_forecast_from_curve_no_fixings(self, method):\n        # these methods should be identical when using \"linear_index\" interpolation directly on the\n        # curve and parametrising the curve nodes with the start of month dates. See next test.\n        curve = Curve(\n            {dt(2000, 1, 1): 1.0, dt(2000, 2, 1): 0.99},\n            index_base=100.0,\n            index_lag=0,\n            interpolation=\"linear_index\",\n        )\n        result = index_value(0, method, NoInput(0), dt(2000, 1, 15), curve)\n        expected = 100.0 / curve[dt(2000, 1, 15)]\n        assert abs(result - expected) < 1e-9\n\n    def test_forecast_from_curve_no_fixings_methods_identical(self):\n        curve = Curve(\n            {dt(2000, 1, 1): 1.0, dt(2000, 2, 1): 0.99},\n            index_base=100.0,\n            index_lag=0,\n            interpolation=\"linear_index\",\n        )\n        result1 = index_value(0, \"curve\", NoInput(0), dt(2000, 1, 15), curve)\n        result2 = index_value(0, \"daily\", NoInput(0), dt(2000, 1, 15), curve)\n        assert abs(result1 - result2) < 1e-9\n\n    @pytest.mark.parametrize(\"date\", [dt(2000, 2, 1), dt(2000, 2, 27)])\n    def test_forecast_from_curve_no_fixings_monthly(self, date):\n        # monthly interpolation should only require the date of 1st Feb from the curve\n        curve = Curve(\n            {dt(2000, 1, 1): 1.0, dt(2000, 2, 1): 0.99},\n            index_base=100.0,\n            index_lag=0,\n            interpolation=\"linear_index\",\n        )\n        result = index_value(0, \"monthly\", NoInput(0), date, curve)\n        expected = 100.0 / curve[dt(2000, 2, 1)]\n        assert abs(result - expected) < 1e-9\n\n    @pytest.mark.parametrize(\"method\", [\"curve\", \"daily\", \"monthly\"])\n    def test_no_input_return_result_err(self, method):\n        assert _try_index_value(0, method, NoInput(0), dt(2000, 1, 1), NoInput(0)).is_err\n\n    @pytest.mark.parametrize(\"method\", [\"curve\", \"daily\", \"monthly\"])\n    def test_fixings_type_raises(self, method):\n        with pytest.raises(TypeError, match=\"`index_fixings` must be of type: Str, Series, DualTy\"):\n            index_value(0, method, [1, 2], dt(2000, 1, 1), NoInput(0))\n\n    def test_no_index_date_raises(self):\n        with pytest.raises(ValueError, match=\"Must supply an `index_date` from whic\"):\n            index_value(0, \"curve\", NoInput(0), NoInput(0), NoInput(0))\n\n    def test_non_zero_index_lag_with_curve_method_raises(self):\n        ser = Series([1.0], index=[dt(2000, 1, 1)])\n        fixings.add(\"1234FGFS6\", ser)\n        with pytest.raises(ValueError, match=\"`index_lag` must be zero when using a 'Curve' `inde\"):\n            index_value(\n                index_lag=4,\n                index_method=\"curve\",\n                index_fixings=\"1234FGFS6\",\n                index_date=dt(2000, 1, 1),\n                index_curve=NoInput(0),\n            )\n        fixings.pop(\"1234FGFS6\")\n\n    def test_documentation_uk_dmo_replication(self):\n        # this is an example in the index value documentation\n        rpi_series = Series(\n            [172.2, 173.1, 174.2, 174.4],\n            index=[dt(2001, 3, 1), dt(2001, 4, 1), dt(2001, 5, 1), dt(2001, 6, 1)],\n        )\n        result = index_value(\n            index_lag=3, index_method=\"daily\", index_fixings=rpi_series, index_date=dt(2001, 7, 20)\n        )\n        expected = 173.77419\n        assert abs(result - expected) < 5e-6\n\n    def test_no_input_return_if_future_based(self):\n        # the requested date is beyond the ability of the fixings series and no curve is provided\n        rpi_series = Series([172.2, 173.1], index=[dt(2001, 3, 1), dt(2001, 4, 1)])\n\n        res1 = _try_index_value(0, \"curve\", rpi_series, dt(2001, 4, 2))\n        assert res1.is_err\n        res2 = _try_index_value(0, \"curve\", rpi_series, dt(2001, 4, 1))\n        assert res2.is_ok\n\n    def test_mixed_forecast_value_fixings_with_curve(self):\n        rpi = Series([100.0], index=[dt(2000, 1, 1)])\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2000, 4, 1): 0.99}, index_base=110.0, index_lag=0)\n        date = dt(2000, 5, 15)\n        rpi_2 = 110 * 1.0 / curve[dt(2000, 2, 1)]\n        expected = 100.0 + (14 / 31) * (rpi_2 - 100.0)\n        result = index_value(4, \"daily\", rpi, date, curve)\n        assert abs(result - expected) < 1e-9\n\n    def test_mixed_forecast_value_fixings_with_curve2(self):\n        rpi = Series([100.0], index=[dt(2000, 1, 1)])\n        curve = Curve(\n            nodes={dt(2000, 2, 1): 1.0, dt(2000, 5, 1): 0.99}, index_base=110.0, index_lag=1\n        )\n\n        date = dt(2000, 5, 15)\n        rpi_2 = 110 * 1.0 / curve[dt(2000, 3, 1)]\n        expected = 100.0 + (14 / 31) * (rpi_2 - 100.0)\n        result = index_value(4, \"daily\", rpi, date, curve)\n        assert abs(result - expected) < 1e-9\n\n    def test_keyerror_for_series_using_curve_method(self):\n        rpi = Series([9.0, 8.0], index=[dt(1999, 1, 1), dt(2000, 1, 1)])\n        with pytest.raises(FixingMissingDataError, match=\"Fixing lookup for date \"):\n            index_value(0, \"curve\", rpi, dt(1999, 12, 31), NoInput(0))\n\n    def test_daily_method_returns_directly_if_date_som(self):\n        rpi = Series([100.0], index=[dt(2000, 1, 1)])\n        assert index_value(0, \"daily\", rpi, dt(2000, 1, 1), NoInput(0)) == 100.0\n\n    def test_daily_method_returns_err_if_data_unavailable(self):\n        rpi = Series([100.0], index=[dt(2000, 1, 1)])\n        res = _try_index_value(0, \"daily\", rpi, dt(2000, 1, 2), NoInput(0))\n        assert res.is_err\n\n    def test_curve_method_from_curve_with_non_zero_index_lag(self):\n        curve = Curve(\n            nodes={dt(2000, 1, 1): 1.0, dt(2000, 2, 1): 0.99},\n            index_base=100.0,\n            index_lag=1,\n        )\n        result = index_value(1, \"curve\", NoInput(0), dt(2000, 1, 15), curve)\n        expected = 100.0 / curve[dt(2000, 1, 15)]\n        assert abs(result - expected) < 1e-9\n\n    @pytest.mark.parametrize(\n        (\"curve\", \"exp\"),\n        [\n            (NoInput(0), Err),\n            (\n                Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99}, index_base=100.0, index_lag=0),\n                Ok,\n            ),\n        ],\n    )\n    def test_series_len_zero(self, curve, exp):\n        s = Series(data=[], index=[], dtype=float)\n        result = _try_index_value(0, \"curve\", s, dt(2000, 1, 1), curve)\n        assert isinstance(result, exp)\n\n    def test_series_and_curve_aligns_with_som_date(self):\n        # the relevant value can be directly matched on the Series\n        s = Series(data=[100.0], index=[dt(2000, 1, 1)])\n        c = Curve({dt(2001, 1, 1): 1.0, dt(2002, 1, 1): 0.99}, index_base=100.0, index_lag=2)\n        result = index_value(1, \"daily\", s, dt(2000, 2, 1), c)\n        assert result == 100.0\n\n    def test_mixed_series_and_curve(self):\n        # the relevant value can be directly matched on the Series\n        s = Series(\n            data=[100.0, 200.0, 300.0], index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)]\n        )\n        c = Curve({dt(2001, 1, 1): 1.0, dt(2002, 1, 1): 0.99}, index_base=100.0, index_lag=2)\n        result = index_value(0, \"curve\", s, dt(2000, 2, 1), c)\n        assert result == 200.0\n\n    def test_mixed_series_and_curve_inside_range_raises(self):\n        s = Series(\n            data=[100.0, 200.0, 300.0], index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)]\n        )\n        c = Curve({dt(2001, 1, 1): 1.0, dt(2002, 1, 1): 0.99}, index_base=100.0, index_lag=2)\n        with pytest.raises(ValueError, match=\"The Series given for `index_fixings` requires, but\"):\n            index_value(0, \"curve\", s, dt(2000, 2, 15), c)\n\n    def test_mixed_series_and_curve_inside_range_reverts_to_curve_due_to_lag(self):\n        s = Series(\n            data=[100.0, 200.0, 300.0], index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)]\n        )\n        c = Curve({dt(2001, 1, 1): 1.0, dt(2002, 1, 1): 0.99}, index_base=100.0, index_lag=1)\n        with pytest.warns(UserWarning):\n            # this warning exists when a curve returns 0.0 and the date is prior to curve start\n            index_value(1, \"curve\", s, dt(2000, 2, 15), c)\n\n    def test_mixed_series_and_curve_outside_range(self):\n        s = Series(\n            data=[100.0, 200.0, 300.0], index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)]\n        )\n        c = Curve({dt(2001, 1, 1): 1.0, dt(2002, 1, 1): 0.99}, index_base=100.0, index_lag=2)\n        with pytest.raises(ValueError, match=\"The Series given for `index_fixings` requires, but\"):\n            index_value(0, \"curve\", s, dt(2000, 2, 15), c)\n\n    def test_mixed_series_and_curve_raises_on_lag(self):\n        s = Series(\n            data=[100.0, 200.0, 300.0], index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)]\n        )\n        c = Curve({dt(2001, 1, 1): 1.0, dt(2002, 1, 1): 0.99}, index_base=100.0, index_lag=2)\n        with pytest.raises(\n            ValueError, match=\"`index_lag` must be zero when using a 'curve' `index\"\n        ):\n            index_value(1, \"curve\", s, dt(2000, 2, 1), c)\n\n\nclass TestCurveSpline:\n    @pytest.mark.parametrize(\"endpoints\", [(\"natural\", \"natural\"), (\"not-a-knot\", \"natural\")])\n    @pytest.mark.parametrize(\"c\", [NoInput(0), [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]])\n    def test_equality(self, endpoints, c):\n        t = [\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2001, 1, 1),\n            dt(2001, 6, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n        ]\n        a = _CurveSpline(t=t, endpoints=endpoints)\n        b = _CurveSpline(t=t, endpoints=endpoints)\n\n        assert a == b\n\n    @pytest.mark.parametrize(\"differ\", [\"t\", \"end\"])\n    def test_inequality(self, differ):\n        t = [\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2001, 1, 1),\n            dt(2001, 6, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n        ]\n        t_diff = [\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2000, 1, 1),\n            dt(2001, 1, 1),\n            dt(2001, 7, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n            dt(2002, 1, 1),\n        ]\n        end = (\"natural\", \"natural\")\n        end_diff = (\"natural\", \"not-a-knot\")\n\n        a = _CurveSpline(t=t, endpoints=end)\n        if differ == \"t\":\n            b = _CurveSpline(t=t_diff, endpoints=end)\n        else:\n            b = _CurveSpline(t=t, endpoints=end_diff)\n\n        assert a != b\n        assert a != 10.0\n\n\nclass Test_CreditImpliedCurve:\n    def test_credit_implied_rates(self):\n        risk_free = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        hazard = Curve(\n            nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.95},\n            credit_recovery_rate=Variable(0.4, [\"RR\"]),\n        )\n        implied = CreditImpliedCurve(risk_free=risk_free, hazard=hazard, id=\"my-id\")\n        assert implied.id == \"my-id\"\n\n        rate1 = risk_free.rate(dt(2000, 2, 1), \"1b\")\n        rate2 = hazard.rate(dt(2000, 2, 1), \"1b\")\n\n        result = implied.rate(dt(2000, 2, 1), \"1b\")\n        approximate = rate1 + rate2 * (1 - 0.4)\n        assert abs(result - approximate) < 1e-9\n\n    def test_risk_free_rates(self):\n        credit = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        hazard = Curve(\n            nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.95},\n            credit_recovery_rate=Variable(0.4, [\"RR\"]),\n        )\n        implied = CreditImpliedCurve(credit=credit, hazard=hazard)\n\n        rate1 = credit.rate(dt(2000, 2, 1), \"1b\")\n        rate2 = hazard.rate(dt(2000, 2, 1), \"1b\")\n\n        result = implied.rate(dt(2000, 2, 1), \"1b\")\n        approximate = rate1 - rate2 * (1 - 0.4)\n        assert abs(result - approximate) < 1e-9\n\n    def test_hazard_rates(self):\n        risk_free = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        credit = Curve(\n            nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.95},\n            credit_recovery_rate=Variable(0.4, [\"RR\"]),\n        )\n        implied = CreditImpliedCurve(credit=credit, risk_free=risk_free)\n\n        rate1 = credit.rate(dt(2000, 2, 1), \"1b\")\n        rate2 = risk_free.rate(dt(2000, 2, 1), \"1b\")\n\n        result = implied.rate(dt(2000, 2, 1), \"1b\")\n        approximate = (rate1 - rate2) / (1 - 0.4)\n        assert abs(result - approximate) < 1e-9\n\n    def test_round_trip_hazard(self):\n        risk_free = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        credit = Curve(\n            nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.95},\n            credit_recovery_rate=Variable(0.4, [\"RR\"]),\n        )\n        implied = CreditImpliedCurve(credit=credit, risk_free=risk_free)\n        credit_implied = CreditImpliedCurve(hazard=implied, risk_free=risk_free)\n\n        rate1 = credit.rate(dt(2000, 2, 1), \"1b\")\n        rate2 = credit_implied.rate(dt(2000, 2, 1), \"1b\")\n\n        assert abs(rate1 - rate2) < 1e-9\n\n    def test_round_trip_credit(self):\n        risk_free = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        hazard = Curve(\n            nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.95},\n            credit_recovery_rate=Variable(0.4, [\"RR\"]),\n        )\n        implied = CreditImpliedCurve(hazard=hazard, risk_free=risk_free)\n        hazard_implied = CreditImpliedCurve(credit=implied, risk_free=risk_free)\n\n        rate1 = hazard.rate(dt(2000, 2, 1), \"1b\")\n        rate2 = hazard_implied.rate(dt(2000, 2, 1), \"1b\")\n\n        assert abs(rate1 - rate2) < 1e-9\n\n    def test_meta_dynacism(self):\n        risk_free = Curve(\n            {dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98},\n        )\n        hazard = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98}, credit_recovery_rate=0.25)\n        credit = CreditImpliedCurve(risk_free=risk_free, hazard=hazard)\n        result = credit.rate(dt(2000, 1, 10), \"10b\")\n        expected = 2.0 + 2.0 * 0.75\n        assert abs(result - expected) < 3e-2\n\n        hazard.update_meta(\"credit_recovery_rate\", 0.90)\n        result = credit.rate(dt(2000, 1, 10), \"10b\")\n        expected = 2.0 + 2.0 * 0.1\n        assert abs(result - expected) < 2e-2\n\n    def test_meta_dynacism2(self):\n        risk_free = Curve(\n            {dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98},\n        )\n        hazard = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98}, credit_recovery_rate=0.25)\n        credit = CreditImpliedCurve(risk_free=risk_free, hazard=hazard)\n        hazard.update_meta(\"credit_recovery_rate\", 0.90)\n        result = credit.meta.credit_recovery_rate\n        expected = 0.90\n        assert abs(result - expected) < 1e-12\n\n\nclass TestMeta:\n    def test_meta_mutation(self, curve, line_curve):\n        # test all the rateslib curve types metas can be mutated\n\n        curves = [curve, line_curve]\n        dependent_curves = []\n\n        dependent_curves.append(CompositeCurve([curve, curve]))\n        dependent_curves.append(curve.shift(10))\n        dependent_curves.append(curve.roll(\"10d\"))\n        dependent_curves.append(curve.translate(dt(2022, 3, 14)))\n        dependent_curves.append(MultiCsaCurve([curve, curve]))\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.10}, dt(2022, 3, 1)),\n            {\"eureur\": curve, \"eurusd\": curve, \"usdusd\": curve},\n        )\n        dependent_curves.append(fxf.curve(\"usd\", \"eur\"))\n        dependent_curves.append(CreditImpliedCurve(risk_free=curve, hazard=curve))\n\n        for c in dependent_curves + curves:\n            from random import random\n\n            x = int(random() * 100.0)\n            c.meta._credit_discretization = x\n            assert c.meta.credit_discretization == x\n\n        curve.update_meta(\"credit_recovery_rate\", 500.0)\n        for c in dependent_curves:\n            print(c)\n            assert c.meta.credit_recovery_rate == 500.0\n"
  },
  {
    "path": "python/tests/curves/test_curvesrs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport math\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.curves.rs import (\n    CurveObj,\n    CurveRs,\n    FlatBackwardInterpolator,\n    FlatForwardInterpolator,\n    LinearInterpolator,\n    LinearZeroRateInterpolator,\n    LogLinearInterpolator,\n    _get_convention,\n    _get_interpolator,\n)\nfrom rateslib.dual import Dual2\nfrom rateslib.dual.utils import ADOrder, _get_adorder\nfrom rateslib.rs import Convention, Modifier\nfrom rateslib.scheduling import get_calendar\nfrom rateslib.serialization import from_json\n\n\n@pytest.fixture\ndef curve():\n    return CurveObj(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolator=_get_interpolator(\"linear\"),\n        id=\"v\",\n        ad=_get_adorder(1),\n        convention=_get_convention(\"Act360\"),\n        modifier=Modifier.ModF,\n        calendar=get_calendar(\"all\"),\n    )\n\n\n@pytest.fixture\ndef curvers():\n    return CurveRs(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolation=\"log_linear\",\n        id=\"v\",\n        ad=1,\n    )\n\n\n@pytest.fixture\ndef indexcurvers():\n    return CurveObj(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolator=_get_interpolator(\"linear\"),\n        id=\"v\",\n        ad=_get_adorder(1),\n        convention=_get_convention(\"Act360\"),\n        modifier=Modifier.ModF,\n        calendar=get_calendar(\"all\"),\n        index_base=100.0,\n    )\n\n\n@pytest.mark.parametrize(\n    (\"name\", \"expected\"),\n    [\n        (\"linear\", LinearInterpolator),\n        (\"log_linear\", LogLinearInterpolator),\n        (\"linear_zero_rate\", LinearZeroRateInterpolator),\n        (\"flat_forward\", FlatForwardInterpolator),\n        (\"flat_backward\", FlatBackwardInterpolator),\n    ],\n)\ndef test_get_interpolator(name, expected) -> None:\n    result = _get_interpolator(name)\n    assert type(result) is expected\n\n\n@pytest.mark.parametrize(\n    \"name\",\n    [\n        \"linear\",\n        \"log_linear\",\n        \"linear_zero_rate\",\n        \"flat_forward\",\n        \"flat_backward\",\n    ],\n)\ndef test_pickle_interpolator(name) -> None:\n    import pickle\n\n    obj = _get_interpolator(name)\n    bytes_ = pickle.dumps(obj)\n    pickle.loads(bytes_)\n\n\ndef test_get_interpolation(curve) -> None:\n    result = curve.interpolation\n    assert result == \"linear\"\n\n\ndef test_get_modifier(curvers) -> None:\n    result = curvers.modifier\n    assert result == \"MF\"\n\n\ndef test_get_convention(curvers) -> None:\n    result = curvers.convention\n    assert result == \"Act360\"\n\n\ndef test_get_ad(curvers) -> None:\n    result = curvers.ad\n    assert result == 1\n\n\ndef test_get_interpolator_raises() -> None:\n    with pytest.raises(ValueError, match=\"Interpolator `name` is invalid\"):\n        _get_interpolator(\"bad\")\n\n\ndef test_get_item(curve, curvers) -> None:\n    result = curve[dt(2022, 3, 16)]\n    assert abs(result - 0.995) < 1e-14\n\n    result = curvers[dt(2022, 3, 16)]\n    expected = math.log(1.0) + (16 - 1) / (31 - 1) * (math.log(0.99) - math.log(1.0))\n    expected = math.exp(expected)\n    assert abs(result - expected) < 1e-14\n\n\ndef test_json_round_trip(curvers) -> None:\n    json = curvers.to_json()\n    curve2 = from_json(json)\n    assert curvers == curve2\n\n\n@pytest.mark.parametrize(\n    \"kind\",\n    [\n        \"linear\",\n        \"log_linear\",\n        \"linear_zero_rate\",\n        \"flat_forward\",\n        \"flat_backward\",\n    ],\n)\ndef test_interp_constructs(kind) -> None:\n    result = CurveRs(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolation=kind,\n        id=\"v\",\n        ad=1,\n    )\n    assert isinstance(result, CurveRs)\n\n\ndef test_index_value(indexcurvers) -> None:\n    result = indexcurvers.index_value(dt(2022, 3, 31))\n    assert abs(result - 100.0 / 0.99) < 1e-12\n\n\ndef test_set_ad_order(curvers) -> None:\n    curvers._set_ad_order(2)\n    assert curvers.nodes == {\n        dt(2022, 3, 1): Dual2(1.0, [\"v0\"], [], []),\n        dt(2022, 3, 31): Dual2(0.99, [\"v1\"], [], []),\n    }\n\n\ndef test_pickle(curvers) -> None:\n    import pickle\n\n    obj = pickle.dumps(curvers)\n    pickle.loads(obj)\n"
  },
  {
    "path": "python/tests/curves/test_ns.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport numpy as np\nimport pytest\nfrom rateslib.curves.academic import NelsonSiegelCurve\nfrom rateslib.dual import Dual2\nfrom rateslib.scheduling import Convention\n\n\ndef test_init():\n    ns = NelsonSiegelCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(0.01, 0.01, 0.05, 1.0),\n    )\n    result = ns.rate(dt(2001, 1, 1), \"1b\")\n    expected = 3.206911736865603\n    assert abs(result - expected) < 1e-5\n    assert ns.meta.convention == Convention.ActActISDA\n\n\ndef test_cache():\n    ns = NelsonSiegelCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(0.01, 0.01, 0.05, 1.0),\n    )\n    ns.rate(dt(2001, 1, 1), \"1b\")\n    assert dt(2001, 1, 1) in ns._cache\n\n    old_state = ns._state\n    ns._set_node_vector([1.0, 1.0, 1.0, 1.0], 0)\n    assert ns._state != old_state\n    assert dt(2001, 1, 1) not in ns._cache\n\n\ndef test_special_domain():\n    ns = NelsonSiegelCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(0.01, 0.01, 0.05, 1.0),\n    )\n    assert ns[dt(2000, 1, 1)] == 1.0\n    assert ns[dt(1999, 12, 31)] == 0.0\n\n\ndef test_getters():\n    ns = NelsonSiegelCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(1.0, 2.0, 3.0, 4.0),\n        id=\"v\",\n    )\n    assert all(ns._get_node_vector() == np.array([1.0, 2.0, 3.0, 4.0]))\n    assert ns._get_node_vars() == (\"v0\", \"v1\", \"v2\", \"v3\")\n\n\ndef test_set_ad_order():\n    ns = NelsonSiegelCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(1.0, 2.0, 3.0, 4.0),\n        id=\"v\",\n        ad=2,\n    )\n    assert isinstance(ns.params[0], Dual2)\n    ns._set_ad_order(2)  # does nothing\n    assert isinstance(ns.params[0], Dual2)\n\n    with pytest.raises(ValueError):\n        ns._set_ad_order(3)\n"
  },
  {
    "path": "python/tests/curves/test_nss.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport numpy as np\nimport pytest\nfrom rateslib.curves.academic import NelsonSiegelSvenssonCurve\nfrom rateslib.dual import Dual2\nfrom rateslib.scheduling import Convention\n\n\ndef test_init():\n    ns = NelsonSiegelSvenssonCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(0.01, 0.01, 0.05, 1.0, 0.05, 1.0),\n    )\n    result = ns.rate(dt(2001, 1, 1), \"1b\")\n    expected = 5.046514607521035\n    assert abs(result - expected) < 1e-5\n    assert ns.meta.convention == Convention.ActActISDA\n\n\ndef test_cache():\n    ns = NelsonSiegelSvenssonCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(0.01, 0.01, 0.05, 1.0, 0.05, 1.0),\n    )\n    ns.rate(dt(2001, 1, 1), \"1b\")\n    assert dt(2001, 1, 1) in ns._cache\n\n    old_state = ns._state\n    ns._set_node_vector([1.0, 1.0, 1.0, 1.0], 0)\n    assert ns._state != old_state\n    assert dt(2001, 1, 1) not in ns._cache\n\n\ndef test_special_domain():\n    ns = NelsonSiegelSvenssonCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(0.01, 0.01, 0.05, 1.0, 0.05, 1.0),\n    )\n    assert ns[dt(2000, 1, 1)] == 1.0\n    assert ns[dt(1999, 12, 31)] == 0.0\n\n\ndef test_getters():\n    ns = NelsonSiegelSvenssonCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),\n        id=\"v\",\n    )\n    assert all(ns._get_node_vector() == np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]))\n    assert ns._get_node_vars() == (\"v0\", \"v1\", \"v2\", \"v3\", \"v4\", \"v5\")\n\n\ndef test_set_ad_order():\n    ns = NelsonSiegelSvenssonCurve(\n        dates=(dt(2000, 1, 1), dt(2030, 1, 1)),\n        parameters=(0.01, 0.01, 0.05, 1.0, 0.05, 1.0),\n        id=\"v\",\n        ad=2,\n    )\n    assert isinstance(ns.params[0], Dual2)\n    ns._set_ad_order(2)  # does nothing\n    assert isinstance(ns.params[0], Dual2)\n\n    with pytest.raises(ValueError):\n        ns._set_ad_order(3)\n"
  },
  {
    "path": "python/tests/curves/test_sw.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\nfrom datetime import timedelta\n\nimport numpy as np\nimport pytest\nfrom rateslib import dual_log\nfrom rateslib.curves.academic import SmithWilsonCurve\nfrom rateslib.dual import Dual2\nfrom rateslib.scheduling import Convention\n\n\ndef test_init():\n    sw = SmithWilsonCurve(\n        nodes={dt(2000, 1, 1): 0.10, dt(2001, 1, 1): -0.1, dt(2002, 1, 1): 0.5},\n        ufr=4.2,\n    )\n    result = sw.rate(dt(2001, 1, 1), \"1b\")\n    expected = 3.3906104222626796\n    assert abs(result - expected) < 1e-5\n    assert sw.meta.convention == Convention.Act365_25\n\n\ndef test_cache():\n    sw = SmithWilsonCurve(\n        nodes={dt(2000, 1, 1): 0.10, dt(2001, 1, 1): -0.1, dt(2002, 1, 1): 0.5},\n        ufr=4.2,\n    )\n    sw.rate(dt(2001, 1, 1), \"1b\")\n    assert dt(2001, 1, 1) in sw._cache\n\n    old_state = sw._state\n    sw._set_node_vector([1.0, 1.0], 0)\n    assert sw._state != old_state\n    assert dt(2001, 1, 1) not in sw._cache\n\n\ndef test_special_domain():\n    sw = SmithWilsonCurve(\n        nodes={dt(2000, 1, 1): 0.10, dt(2001, 1, 1): -0.1, dt(2002, 1, 1): 0.5},\n        ufr=4.2,\n    )\n    assert sw[dt(2000, 1, 1)] == 1.0\n    assert sw[dt(1999, 12, 31)] == 0.0\n\n\ndef test_getters():\n    sw = SmithWilsonCurve(\n        nodes={dt(2000, 1, 1): 0.10, dt(2001, 1, 1): -0.1, dt(2002, 1, 1): 0.5},\n        ufr=4.2,\n        id=\"v\",\n    )\n    assert all(sw._get_node_vector() == np.array([-0.1, 0.5]))\n    assert sw._get_node_vars() == (\"v1\", \"v2\")\n\n    sw = SmithWilsonCurve(\n        nodes={dt(2000, 1, 1): 0.10, dt(2001, 1, 1): -0.1, dt(2002, 1, 1): 0.5},\n        ufr=4.2,\n        solve_alpha=True,\n        id=\"v\",\n    )\n    assert all(sw._get_node_vector() == np.array([0.10, -0.1, 0.5]))\n    assert sw._get_node_vars() == (\"v0\", \"v1\", \"v2\")\n\n\ndef test_set_ad_order():\n    sw = SmithWilsonCurve(\n        nodes={dt(2000, 1, 1): 0.10, dt(2001, 1, 1): -0.1, dt(2002, 1, 1): 0.5},\n        ufr=4.2,\n        id=\"v\",\n        ad=2,\n    )\n    assert isinstance(sw.nodes.values[0], Dual2)\n    sw._set_ad_order(2)  # does nothing\n    assert isinstance(sw.nodes.values[0], Dual2)\n\n    with pytest.raises(ValueError):\n        sw._set_ad_order(3)\n\n\ndef test_eiopa_example():\n    from rateslib import FixedRateBond, Solver\n\n    sw = SmithWilsonCurve(\n        nodes={dt(2000, 1, 1): 0.12376, **{dt(2000 + i, 1, 1): 0.1 for i in range(1, 21)}},\n        solve_alpha=False,\n        ufr=4.2,\n        id=\"academic_curve\",\n    )\n    coupons = [\n        0.2,\n        0.225,\n        0.3,\n        0.425,\n        0.55,\n        0.7,\n        0.85,\n        1.0,\n        1.15,\n        1.275,\n        1.4,\n        1.475,\n        1.575,\n        1.65,\n        1.7,\n        1.75,\n        1.8,\n        1.825,\n        1.85,\n        1.875,\n    ]\n    bonds = [\n        FixedRateBond(\n            effective=dt(2000, 1, 1),\n            termination=f\"{i}Y\",\n            fixed_rate=coupons[i - 1],\n            calendar=\"all\",\n            ex_div=1,\n            convention=\"actacticma\",\n            frequency=\"A\",\n            curves=\"academic_curve\",\n            metric=\"dirty_price\",\n        )\n        for i in range(1, 21)\n    ]\n    prices = [100.0] * 20\n    Solver(curves=[sw], instruments=bonds, s=prices)\n\n    assert abs(sw.k - 0.737944) < 5e-3\n\n    eiopa_u = [\n        0.00,\n        0.25,\n        0.50,\n        0.75,\n        1.00,\n        1.25,\n        1.50,\n        1.75,\n        2.00,\n        2.25,\n        2.50,\n        2.75,\n        3.00,\n        3.25,\n        3.50,\n        3.75,\n        4.00,\n        4.25,\n        4.50,\n        4.75,\n        5.00,\n        5.25,\n        5.50,\n        5.75,\n        6.00,\n        6.25,\n        6.50,\n        6.75,\n        7.00,\n        7.25,\n        7.50,\n        7.75,\n        8.00,\n        8.25,\n        8.50,\n        8.75,\n        9.00,\n        9.25,\n        9.50,\n        9.75,\n        10.00,\n        10.25,\n        10.50,\n        10.75,\n        11.00,\n        11.25,\n        11.50,\n        11.75,\n        12.00,\n        12.25,\n        12.50,\n        12.75,\n        13.00,\n        13.25,\n        13.50,\n        13.75,\n        14.00,\n        14.25,\n        14.50,\n        14.75,\n        15.00,\n        15.25,\n        15.50,\n        15.75,\n        16.00,\n        16.25,\n        16.50,\n        16.75,\n        17.00,\n        17.25,\n        17.50,\n        17.75,\n        18.00,\n        18.25,\n        18.50,\n        18.75,\n        19.00,\n        19.25,\n        19.50,\n        19.75,\n        20.00,\n        40.00,\n        60.0,\n    ]\n    eiopa_v = [\n        1.0000,\n        0.9996,\n        0.9991,\n        0.9986,\n        0.9980,\n        0.9975,\n        0.9969,\n        0.9962,\n        0.9955,\n        0.9947,\n        0.9937,\n        0.9925,\n        0.9910,\n        0.9894,\n        0.9874,\n        0.9854,\n        0.9831,\n        0.9808,\n        0.9784,\n        0.9757,\n        0.9728,\n        0.9696,\n        0.9662,\n        0.9625,\n        0.9587,\n        0.9547,\n        0.9506,\n        0.9463,\n        0.9419,\n        0.9373,\n        0.9325,\n        0.9275,\n        0.9224,\n        0.9170,\n        0.9114,\n        0.9059,\n        0.9004,\n        0.8950,\n        0.8896,\n        0.8841,\n        0.8783,\n        0.8723,\n        0.8661,\n        0.8601,\n        0.8544,\n        0.8493,\n        0.8444,\n        0.8395,\n        0.8343,\n        0.8287,\n        0.8226,\n        0.8164,\n        0.8103,\n        0.8045,\n        0.7989,\n        0.7935,\n        0.7883,\n        0.7833,\n        0.7784,\n        0.7736,\n        0.7688,\n        0.7640,\n        0.7591,\n        0.7540,\n        0.7489,\n        0.7437,\n        0.7385,\n        0.7334,\n        0.7286,\n        0.7242,\n        0.7200,\n        0.7159,\n        0.7119,\n        0.7077,\n        0.7035,\n        0.6993,\n        0.6951,\n        0.6909,\n        0.6867,\n        0.6825,\n        0.6782,\n        0.3330,\n        0.1475,\n    ]\n    for i in range(80):\n        date = dt(2000, 1, 1) + timedelta(days=round(eiopa_u[i] * 365.25, 0))\n        rateslib_v = sw[date]\n        assert abs(rateslib_v - eiopa_v[i]) < 2e-4\n\n\ndef test_2357_example():\n    from rateslib import FixedRateBond, Solver\n\n    sw = SmithWilsonCurve(\n        nodes={\n            dt(2000, 1, 1): 0.12376,\n            **{dt(2000 + i, 1, 1): 0.1 for i in [2, 3, 5, 7]},\n            # **{dt(2000+i, 1, 1): 0.1 for i in [1,2,3,4,5,6,7]}\n        },\n        solve_alpha=False,\n        ufr=4.2,\n        id=\"academic_curve\",\n    )\n    sw2 = SmithWilsonCurve(\n        nodes={\n            dt(2000, 1, 1): 0.12376,\n            # **{dt(2000+i, 1, 1): 0.1 for i in [2,3,5,7]}\n            **{dt(2000 + i, 1, 1): 0.1 for i in [1, 2, 3, 4, 5, 6, 7]},\n        },\n        solve_alpha=False,\n        ufr=4.2,\n        id=\"academic_curve\",\n    )\n    coupons = [1.5, 1.8, 2.2, 2.5]\n    bonds = [\n        FixedRateBond(\n            effective=dt(2000, 1, 1),\n            termination=f\"{i}Y\",\n            fixed_rate=coupons[idx],\n            frequency=\"A\",\n            convention=\"ActActICMA\",\n            calendar=\"all\",\n            modifier=\"F\",\n            curves=\"academic_curve\",\n            metric=\"dirty_price\",\n        )\n        for (idx, i) in enumerate([2, 3, 5, 7])\n    ]\n    prices = [100.0] * 4\n    Solver(curves=[sw], instruments=bonds, s=prices)\n    Solver(curves=[sw2], instruments=bonds, s=prices)\n\n    eiopa_u = [\n        0.10,\n        0.20,\n        0.30,\n        0.40,\n        0.50,\n        0.60,\n        0.70,\n        0.80,\n        0.90,\n        1.00,\n        1.10,\n        1.20,\n        1.30,\n        1.40,\n        1.50,\n        1.60,\n        1.70,\n        1.80,\n        1.90,\n        2.00,\n        2.10,\n        2.20,\n        2.30,\n        2.40,\n        2.50,\n        2.60,\n        2.70,\n        2.80,\n        2.90,\n        3.00,\n        3.10,\n        3.20,\n        3.30,\n        3.40,\n        3.50,\n        3.60,\n        3.70,\n        3.80,\n        3.90,\n        4.00,\n        4.10,\n        4.20,\n        4.30,\n        4.40,\n        4.50,\n        4.60,\n        4.70,\n        4.80,\n        4.90,\n        5.00,\n        5.10,\n        5.20,\n        5.30,\n        5.40,\n        5.50,\n        5.60,\n        5.70,\n        5.80,\n        5.90,\n        6.00,\n        6.10,\n        6.20,\n        6.30,\n        6.40,\n        6.50,\n        6.60,\n        6.70,\n        6.80,\n        6.90,\n        7.00,\n        7.10,\n        7.20,\n        7.30,\n        7.40,\n        7.50,\n        7.60,\n        7.70,\n        7.80,\n        7.90,\n        8.00,\n    ]\n    eiopa_v = [\n        0.9989,\n        0.9977,\n        0.9965,\n        0.9953,\n        0.9941,\n        0.9929,\n        0.9916,\n        0.9903,\n        0.9889,\n        0.9875,\n        0.9861,\n        0.9846,\n        0.9831,\n        0.9815,\n        0.9799,\n        0.9781,\n        0.9764,\n        0.9745,\n        0.9726,\n        0.9706,\n        0.9686,\n        0.9664,\n        0.9642,\n        0.9620,\n        0.9597,\n        0.9573,\n        0.9550,\n        0.9526,\n        0.9501,\n        0.9477,\n        0.9452,\n        0.9428,\n        0.9403,\n        0.9378,\n        0.9353,\n        0.9328,\n        0.9303,\n        0.9277,\n        0.9252,\n        0.9226,\n        0.9200,\n        0.9174,\n        0.9148,\n        0.9122,\n        0.9095,\n        0.9069,\n        0.9042,\n        0.9015,\n        0.8988,\n        0.8961,\n        0.8933,\n        0.8906,\n        0.8878,\n        0.8850,\n        0.8822,\n        0.8794,\n        0.8765,\n        0.8737,\n        0.8708,\n        0.8680,\n        0.8651,\n        0.8623,\n        0.8594,\n        0.8565,\n        0.8536,\n        0.8507,\n        0.8479,\n        0.8450,\n        0.8421,\n        0.8392,\n        0.8363,\n        0.8335,\n        0.8306,\n        0.8277,\n        0.8248,\n        0.8220,\n        0.8191,\n        0.8163,\n        0.8134,\n        0.8106,\n    ]\n\n    # from matplotlib import pyplot as plt\n    # fig, ax, lines = sw.plot(\"Z\", comparators=[sw2])\n    # ax.scatter(\n    #     [dt(2000, 1, 1) + timedelta(days=round(u*365.25)) for u in eiopa_u],\n    #     [100.0 * dual_log(v) / -t for v,t in zip(eiopa_v, eiopa_u)],\n    # )\n    # plt.show()\n\n    for i in range(80):\n        date = dt(2000, 1, 1) + timedelta(days=round(eiopa_u[i] * 365.25, 0))\n        rateslib_v = sw[date]\n        assert abs(rateslib_v - eiopa_v[i]) < 2e-4\n"
  },
  {
    "path": "python/tests/instruments/test_instruments_bonds_legacy.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\nfrom datetime import datetime as dt\nfrom itertools import product\n\nimport numpy as np\nimport pytest\nfrom pandas import DataFrame, Series, date_range\nfrom pandas.testing import assert_frame_equal\nfrom rateslib import defaults, fixings\nfrom rateslib.curves import Curve, LineCurve\nfrom rateslib.default import NoInput\nfrom rateslib.dual import Dual, Dual2, Variable, gradient\nfrom rateslib.enums import FloatFixingMethod\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.instruments import (\n    IRS,\n    Bill,\n    BondFuture,\n    FixedRateBond,\n    FloatRateNote,\n    IndexFixedRateBond,\n)\nfrom rateslib.instruments.bonds.conventions import US_GBB, BondCalcMode\nfrom rateslib.instruments.protocols.pricing import _Curves\nfrom rateslib.scheduling import dcf, get_calendar\nfrom rateslib.solver import Solver\n\n\n@pytest.fixture\ndef curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.99,\n        dt(2022, 7, 1): 0.98,\n        dt(2022, 10, 1): 0.97,\n    }\n    # convention = \"Act360\"\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef curve2():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.98,\n        dt(2022, 7, 1): 0.97,\n        dt(2022, 10, 1): 0.95,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\nclass TestBondCalcMode:\n    def test_custom_function(self):\n        def _my_acc(*args):\n            return 0.5\n\n        my_calc = BondCalcMode(\n            settle_accrual=_my_acc,\n            ytm_accrual=_my_acc,\n            v1=\"compounding\",\n            v2=\"regular\",\n            v3=\"compounding\",\n            c1=\"cashflow\",\n            ci=\"cashflow\",\n            cn=\"cashflow\",\n        )\n\n        bond = FixedRateBond(dt(2022, 1, 1), \"2y\", spec=\"de_gb\", fixed_rate=2.0, calc_mode=my_calc)\n        de_bond = FixedRateBond(\n            dt(2022, 1, 1),\n            \"2y\",\n            spec=\"de_gb\",\n            fixed_rate=2.0,\n        )\n\n        assert bond.accrued(dt(2022, 2, 4)) == 1.0  # 0.5 * 2.0\n        assert bond.accrued(dt(2022, 2, 4)) != de_bond.accrued(dt(2022, 2, 4))\n\n        assert bond.ytm(100.0, dt(2022, 2, 4)) != de_bond.ytm(100.0, dt(2022, 2, 4))\n\n        assert my_calc.kwargs[\"settle_accrual\"] == \"custom\"\n        assert my_calc.kwargs[\"ytm_accrual\"] == \"custom\"\n\n    def test_custom_function_affects_ytm(self):\n        def _my_acc(*args):\n            return 0.4\n\n        my_calc = BondCalcMode(\n            settle_accrual=\"linear_days\",\n            ytm_accrual=_my_acc,\n            v1=\"compounding_final_simple\",\n            v2=\"regular\",\n            v3=\"compounding\",\n            c1=\"cashflow\",\n            ci=\"cashflow\",\n            cn=\"cashflow\",\n        )\n\n        bond = FixedRateBond(dt(2022, 1, 1), \"2y\", spec=\"de_gb\", fixed_rate=2.0, calc_mode=my_calc)\n\n        v2 = 1 / (1 + 0.02)\n        v1 = v2 ** (1 - 0.4)\n        expected = 2 * v1 + 102 * v1 * v2 - 0.4 * 2\n        result = bond.price(ytm=2.00, settlement=dt(2022, 1, 1))\n\n        assert abs(result - expected) < 1e-10\n\n    def test_custom_ytm_disc_funcs(self):\n        def _my_acc(*args):\n            return 0.0\n\n        def _v(*args):\n            return 1 / (1 + 0.02)\n\n        calc_mode = BondCalcMode(\n            settle_accrual=_my_acc,\n            ytm_accrual=_my_acc,\n            v1=_v,\n            v2=_v,\n            v3=_v,\n            c1=\"cashflow\",\n            ci=\"cashflow\",\n            cn=\"cashflow\",\n        )\n\n        bond = FixedRateBond(\n            effective=dt(2000, 1, 1),\n            termination=\"2y\",\n            fixed_rate=2.00,\n            spec=\"de_gb\",\n            calc_mode=calc_mode,\n        )\n\n        # custom funcs give the same clean price of 100 for any date\n        for date in [dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 11, 1), dt(2001, 6, 1)]:\n            result = bond.price(ytm=2.0, settlement=dt(2000, 1, 1))\n            assert abs(result - 100.0) < 1e-10\n\n\nclass TestFixedRateBond:\n    def test_metric_ytm_no_fx(self) -> None:\n        # GH 193\n        usd = Curve(nodes={dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.9, dt(2010, 1, 5): 0.8})\n        gbp = Curve(nodes={dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.9, dt(2010, 1, 5): 0.8})\n        fxf = FXForwards(\n            fx_rates=FXRates({\"gbpusd\": 1.25}, settlement=dt(2000, 1, 1)),\n            fx_curves={\"gbpgbp\": gbp, \"usdusd\": usd, \"gbpusd\": gbp},\n        )\n        expected = FixedRateBond(dt(2000, 1, 1), \"10y\", spec=\"uk_gb\", fixed_rate=2.0).rate(\n            curves=gbp,\n            metric=\"ytm\",\n        )\n        result = FixedRateBond(dt(2000, 1, 1), \"10y\", spec=\"uk_gb\", fixed_rate=2.0).rate(\n            curves=gbp,\n            metric=\"ytm\",\n            fx=fxf,\n        )\n        assert abs(result - expected) < 1e-9\n\n    def test_accrued_in_text(self) -> None:\n        bond = FixedRateBond(\n            effective=dt(2022, 1, 1),\n            termination=dt(2023, 1, 1),\n            fixed_rate=5.0,\n            spec=\"ca_gb\",\n        )\n        assert abs(bond.accrued(dt(2022, 4, 15)) - 1.42465753) < 1e-8\n\n        bond = FixedRateBond(\n            effective=dt(2022, 1, 1),\n            termination=dt(2023, 1, 1),\n            fixed_rate=5.0,\n            spec=\"uk_gb\",\n        )\n        assert abs(bond.accrued(dt(2022, 4, 15)) - 1.43646409) < 1e-8\n\n    # UK Gilts Tests: Data from public DMO website.\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"exp\"),\n        [\n            (dt(1999, 5, 24), False),\n            (dt(1999, 5, 26), False),\n            (dt(1999, 5, 27), True),\n            (dt(1999, 6, 7), True),  # on payment date the\n        ],\n    )\n    def test_ex_div(self, settlement, exp) -> None:\n        ukg = FixedRateBond(\n            effective=dt(1998, 1, 1),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            fixed_rate=8.0,\n            convention=\"ActActICMA\",\n            calendar=\"ldn\",\n            ex_div=7,\n            modifier=\"NONE\",\n        )\n        assert ukg.ex_div(settlement) is exp\n\n    def test_fixed_rate_bond_price_ukg(self) -> None:\n        # test pricing functions against Gilt Example prices from UK DMO\n        bond = FixedRateBond(\n            dt(1995, 1, 1),\n            dt(2015, 12, 7),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=8,\n            ex_div=7,\n            calendar=\"ldn\",\n            modifier=\"NONE\",\n        )\n        assert abs(bond.price(4.445, dt(1999, 5, 24), True) - 145.012268) < 1e-6\n        assert abs(bond.price(4.445, dt(1999, 5, 26), True) - 145.047301) < 1e-6\n        assert abs(bond.price(4.445, dt(1999, 5, 27), True) - 141.070132) < 1e-6\n        assert abs(bond.price(4.445, dt(1999, 6, 7), True) - 141.257676) < 1e-6\n\n        bond = FixedRateBond(\n            dt(1997, 1, 1),\n            dt(2004, 11, 26),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=6.75,\n            ex_div=7,\n            calendar=\"ldn\",\n            modifier=\"F\",\n        )\n        assert abs(bond.price(4.634, dt(1999, 5, 10), True) - 113.315543) < 1e-6\n        assert abs(bond.price(4.634, dt(1999, 5, 17), True) - 113.415969) < 1e-6\n        assert abs(bond.price(4.634, dt(1999, 5, 18), True) - 110.058738) < 1e-6\n        assert abs(bond.price(4.634, dt(1999, 5, 26), True) - 110.170218) < 1e-6\n\n    def test_fixed_rate_bond_price_ukg_back_stub(self) -> None:\n        bond = FixedRateBond(\n            dt(1995, 12, 7),\n            dt(2015, 1, 23),\n            \"S\",\n            stub=\"SHORTBACK\",\n            roll=7,\n            convention=\"ActActICMA\",\n            fixed_rate=8,\n            ex_div=7,\n            calendar=\"ldn\",\n            modifier=\"NONE\",\n            calc_mode=\"ukg\",\n        )\n        result = bond.price(ytm=8.00, settlement=dt(1995, 12, 7))\n        expected = 100.00334028292  # compounded back stub does not yield par\n        assert abs(result - expected) < 1e-9\n\n    def test_fixed_rate_bond_yield_ukg(self) -> None:\n        # test pricing functions against Gilt Example prices from UK DMO\n        bond = FixedRateBond(\n            dt(1995, 1, 1),\n            dt(2015, 12, 7),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=8,\n            ex_div=7,\n            calendar=\"ldn\",\n            modifier=\"NONE\",\n        )\n        assert bond.ytm(135.0, dt(1999, 5, 24), True) - 5.1620635 < 1e-6\n        assert bond.ytm(135.0, dt(1999, 5, 26), True) - 5.1649111 < 1e-6\n        assert bond.ytm(135.0, dt(1999, 5, 27), True) - 4.871425 < 1e-6\n        assert bond.ytm(135.0, dt(1999, 6, 7), True) - 4.8856785 < 1e-6\n\n        bond = FixedRateBond(\n            dt(1997, 1, 1),\n            dt(2004, 11, 26),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=6.75,\n            ex_div=7,\n            calendar=\"ldn\",\n            modifier=\"F\",\n        )\n        assert bond.ytm(108.0, dt(1999, 5, 10), True) - 5.7009527 < 1e-6\n        assert bond.ytm(108.0, dt(1999, 5, 17), True) - 5.7253361 < 1e-6\n        assert bond.ytm(108.0, dt(1999, 5, 18), True) - 5.0413308 < 1e-6\n        assert bond.ytm(108.0, dt(1999, 5, 26), True) - 5.0652248 < 1e-6\n\n    def test_fixed_rate_bond_accrual(self) -> None:\n        # test pricing functions against Gilt Example prices from UK DMO, with stub\n        bond = FixedRateBond(\n            dt(1999, 5, 7),\n            dt(2002, 12, 7),\n            \"S\",\n            convention=\"ActActICMA\",\n            front_stub=dt(1999, 12, 7),\n            fixed_rate=6,\n            ex_div=7,\n            calendar=\"ldn\",\n            modifier=\"NONE\",\n        )\n        bond.accrued(dt(1999, 5, 8)) == 0.016484\n        bond.accrued(dt(1999, 6, 8)) == 0.527382\n        bond.accrued(dt(1999, 7, 8)) == 1.019186\n        bond.accrued(dt(1999, 11, 8)) == 3.035579\n        bond.accrued(dt(1999, 11, 26)) == 3.330661\n        bond.accrued(dt(1999, 11, 27)) == -0.16393\n        bond.accrued(dt(1999, 12, 6)) == -0.01639\n        bond.accrued(dt(1999, 12, 7)) == 0.0\n\n    def test_fixed_rate_bond_stub_ytm(self) -> None:\n        # if a regular bond is set to stub similar output should be gotten\n        bond = FixedRateBond(\n            dt(1999, 6, 7),\n            dt(2002, 12, 7),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=6,\n            ex_div=7,\n            calendar=\"ldn\",\n            modifier=\"NONE\",\n        )\n        regular_ytm = bond.ytm(101, dt(1999, 11, 8), dirty=True)\n        bond.leg1.periods[0].stub = True\n        stubbed_ytm = bond.ytm(101, dt(1999, 11, 8), dirty=True)\n        assert regular_ytm == stubbed_ytm\n\n    # US Treasury Tests. Examples from Rulebook.\n\n    @pytest.mark.parametrize(\n        (\"e\", \"t\", \"s\", \"fr\", \"ec\", \"ed\", \"y\", \"se\"),\n        [\n            (\n                dt(1990, 5, 15),\n                dt(2020, 5, 15),\n                NoInput(0),\n                8.75,\n                99.057893,\n                99.057893,\n                8.84,\n                dt(1990, 5, 15),\n            ),  # A\n            (\n                dt(1990, 4, 2),\n                dt(1992, 3, 31),\n                NoInput(0),\n                8.5,\n                99.838183,\n                99.838183,\n                8.59,\n                dt(1990, 4, 2),\n            ),  # B\n            (\n                dt(1990, 3, 1),\n                dt(1995, 5, 15),\n                dt(1990, 11, 15),\n                8.5,\n                99.805118,\n                99.805118,\n                8.53,\n                dt(1990, 3, 1),\n            ),  # C\n            (\n                dt(1985, 11, 15),\n                dt(1995, 11, 15),\n                NoInput(0),\n                9.5,\n                99.730918,\n                100.098321,\n                9.54,\n                dt(1985, 11, 29),\n            ),  # D\n            (\n                dt(1985, 7, 2),\n                dt(2005, 8, 15),\n                dt(1986, 2, 15),\n                10.75,\n                102.214586,\n                105.887384,\n                10.47,\n                dt(1985, 11, 4),\n            ),  # E\n            (\n                dt(1983, 5, 16),\n                dt(1991, 5, 15),\n                dt(1983, 11, 15),\n                10.5,\n                99.777074,\n                102.373541,\n                10.53,\n                dt(1983, 8, 15),\n            ),  # F\n            (\n                dt(1988, 10, 15),\n                dt(1994, 12, 15),\n                dt(1989, 6, 15),\n                9.75,\n                99.738045,\n                100.563865,\n                9.79,\n                dt(1988, 11, 15),\n            ),  # G\n        ],\n    )\n    def test_fixed_rate_bond_price_ust(self, e, t, s, fr, ec, ed, y, se) -> None:\n        # The UST tests are from:\n        # https://www.ecfr.gov/current/title-31/subtitle-B/chapter-II/subchapter-A/part-356/appendix-Appendix%20B%20to%20Part%20356\n        ust = FixedRateBond(\n            effective=e,\n            termination=t,\n            front_stub=s,\n            fixed_rate=fr,\n            frequency=\"S\",\n            calendar=\"nyc\",\n            convention=\"ActActICMA\",\n            calc_mode=\"ust_31Bii\",\n            ex_div=1,\n            modifier=\"NONE\",\n        )\n        res1 = ust.price(ytm=y, settlement=se, dirty=False)\n        res2 = ust.price(ytm=y, settlement=se, dirty=True)\n        assert abs(res1 - ec) < 1e-6\n        assert abs(res2 - ed) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"s\", \"exp\", \"acc\"),\n        [\n            (dt(2025, 2, 14), 99.106414, 1.926970),\n            (dt(2025, 2, 18), 99.107179, 0.032113),\n            (dt(2025, 8, 15), 99.151393, 0.0),\n        ],\n    )\n    def test_ust_price_street(self, s, exp, acc) -> None:\n        bond = FixedRateBond(\n            effective=dt(2023, 8, 15),\n            termination=dt(2033, 8, 15),\n            fixed_rate=3.875,\n            spec=\"us_gb\",\n        )\n        result = bond.price(ytm=4, settlement=s)\n        accrued = bond.accrued(settlement=s)\n        assert abs(accrued - acc) < 1e-6\n        assert abs(result - exp) < 1e-5\n\n    def test_long_stub_first_cashflow(self):\n        # test against 31.B.ii.A356.Appendix.B.I.A Example Long First\n        note = FixedRateBond(\n            effective=dt(1990, 12, 3),\n            termination=dt(1996, 2, 15),\n            stub=\"longfront\",\n            spec=\"us_gb\",\n            fixed_rate=7.875,\n            notional=-7000,\n        )\n        assert abs(note.leg1.periods[0].cashflow() - 386.474184670) < 5e-7\n\n    def test_calc_mode_ytm(self):\n        b = FixedRateBond(dt(1985, 11, 15), dt(1995, 11, 15), fixed_rate=9.5, spec=\"us_gb_tsy\")\n\n        y1 = b.ytm(price=99.730918, settlement=dt(1985, 11, 29))\n        assert abs(y1 - 9.54) < 1e-6\n\n        b2 = FixedRateBond(dt(1985, 11, 15), dt(1995, 11, 15), fixed_rate=9.5, spec=\"us_gb\")\n        exp_y2 = b2.ytm(price=99.730918, settlement=dt(1985, 11, 29))\n\n        # street convention\n        y2 = b.ytm(price=99.730918, settlement=dt(1985, 11, 29), calc_mode=\"us_gb\")\n        assert abs(y2 - 9.54) > 1e-6\n        assert abs(y2 - exp_y2) < 1e-6\n\n    def test_street_convention_simple_first_period(self):\n        # US91282CLB53\n        bond = FixedRateBond(dt(2024, 7, 31), \"2y\", spec=\"us_gb\", fixed_rate=4.375)\n        result = bond.price(ytm=4.0, settlement=dt(2026, 3, 31))\n        expected = 100.1152156\n        assert abs(result - expected) < 1e-6\n\n        result2 = bond.price(ytm=4.0, settlement=dt(2026, 1, 7))\n        expected2 = 100.205071\n        assert abs(result2 - expected2) < 1e-6\n\n    # Swedish Government Bond Tests. Data from alternative systems.\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"exp_accrued\", \"exp_price\"),\n        [\n            (dt(2024, 5, 3), 0.73125, 88.134),\n            # (dt(2024, 5, 5), 0.735417, 88.150), # ambiguous Sunday\n            (dt(2024, 5, 6), -0.0125, 88.158),\n            (dt(2024, 5, 7), -0.0104, 88.165),\n            (dt(2024, 5, 8), -0.008333, 88.173),\n            (dt(2024, 5, 12), 0.0, 88.203),\n            (dt(2024, 5, 13), 0.002083, 88.210),\n        ],\n    )\n    def test_sgb_1060s_price_and_accrued(self, settlement, exp_accrued, exp_price) -> None:\n        sgb = FixedRateBond(\n            effective=dt(2023, 5, 12),\n            termination=dt(2028, 5, 12),\n            frequency=\"A\",\n            convention=\"ActActICMA\",\n            calendar=\"stk\",\n            ex_div=5,\n            modifier=\"NONE\",\n            fixed_rate=0.75,\n            calc_mode=\"sgb\",\n        )\n        accrued = sgb.accrued(settlement)\n        assert abs(accrued - exp_accrued) < 1e-4\n        price = sgb.price(ytm=4.0, settlement=settlement, dirty=False)\n        assert abs(price - exp_price) < 1e-3\n\n    def test_sgb_ultra_short_ytm(self):\n        # SE0010469205\n        komins = FixedRateBond(\n            effective=dt(2017, 10, 2), termination=dt(2024, 10, 2), fixed_rate=1.0, spec=\"se_gb\"\n        )\n        dp = komins.price(ytm=3.42092, settlement=dt(2024, 9, 24), dirty=True)\n        cp = komins.price(ytm=3.42092, settlement=dt(2024, 9, 24), dirty=False)\n        assert abs(dp - cp - komins.accrued(settlement=dt(2024, 9, 24))) < 1e-10\n\n        assert abs(cp - 99.9455205) < 1e-4\n\n    def test_fixed_rate_bond_price_sgb_back_stub(self) -> None:\n        bond = FixedRateBond(\n            dt(1995, 12, 7),\n            dt(2015, 1, 23),\n            \"A\",\n            stub=\"SHORTBACK\",\n            roll=7,\n            convention=\"ActActICMA\",\n            fixed_rate=8,\n            ex_div=7,\n            calendar=\"ldn\",\n            modifier=\"NONE\",\n            calc_mode=\"sgb\",\n        )\n        result = bond.price(ytm=8.00, settlement=dt(1995, 12, 7))\n        expected = 100.0018153890108  # simple period back stub yields close to par\n        assert abs(result - expected) < 1e-9\n\n    # Canadian Government Bond Tests. Data from alternative systems\n    # and from https://iiac-accvm.ca/wp-content/uploads/Canadian-Conventions-in-FI-Markets-Release-1.3.pdf\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"exp\"),\n        [\n            (dt(2005, 12, 1), 1.671232),\n            (dt(2006, 1, 31), 2.486301),\n        ],\n    )\n    def test_settlement_accrued(self, settlement, exp) -> None:\n        bond = FixedRateBond(\n            effective=dt(2004, 8, 1),\n            termination=dt(2008, 2, 1),\n            fixed_rate=5.0,\n            modifier=\"NONE\",\n            frequency=\"S\",\n            convention=\"ActActICMA_stub365f\",\n            calc_mode=\"cadgb\",\n            ex_div=1,\n        )\n        result = bond.accrued(settlement=settlement)\n        assert abs(result - exp) < 1e-6\n\n    @pytest.mark.skip(reason=\"<1Y CAD bonds NotImplemented\")\n    @pytest.mark.parametrize(\n        (\"s\", \"exp\", \"acc\"),\n        [\n            (dt(2024, 8, 1), 99.839907, 0.0),\n            (dt(2024, 7, 17), 99.866051, 1.715753),\n            (dt(2024, 8, 7), 99.842641, 0.061644),\n        ],\n    )\n    def test_cadgb_price(self, s, exp, acc) -> None:\n        bond = FixedRateBond(\n            effective=dt(2022, 11, 2),\n            termination=dt(2025, 2, 1),\n            fixed_rate=3.75,\n            modifier=\"NONE\",\n            convention=\"ActActICMA_STUB365f\",\n            frequency=\"S\",\n            calc_mode=\"cadgb\",\n            roll=1,\n            stub=\"SHORTFRONT\",\n            ex_div=1,\n        )\n        result = bond.price(ytm=4.0, settlement=s)\n        accrued = bond.accrued(settlement=s)\n        assert abs(accrued - acc) < 1e-6\n        # Price fails becuase bond is <1Y from maturity needs a branched formula.\n        assert abs(result - exp) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"s\", \"exp\", \"acc\"),\n        [\n            (dt(2024, 11, 26), 91.055145, 1.341096),\n            (dt(2024, 12, 2), 91.069934, 0.007534),\n            (dt(2024, 6, 3), 90.634570, 0.015068),\n        ],\n    )\n    def test_cadgb_price2(self, s, exp, acc) -> None:\n        bond = FixedRateBond(\n            effective=dt(2023, 2, 2),\n            termination=dt(2033, 6, 1),\n            fixed_rate=2.75,\n            modifier=\"NONE\",\n            convention=\"ActActICMA_STUB365f\",\n            frequency=\"S\",\n            calc_mode=\"ca_gb\",\n            roll=1,\n            stub=\"SHORTFRONT\",\n            ex_div=1,\n        )\n        result = bond.price(ytm=4.0, settlement=s)\n        accrued = bond.accrued(settlement=s)\n        assert abs(accrued - acc) < 1e-6\n        assert abs(result - exp) < 1e-6\n\n    def test_cadgb_price3(self) -> None:\n        bond = FixedRateBond(\n            effective=dt(2018, 7, 27),\n            termination=dt(2029, 6, 1),\n            fixed_rate=2.25,\n            modifier=\"NONE\",\n            convention=\"ActActICMA_STUB365f\",\n            frequency=\"S\",\n            calc_mode=\"cadgb\",\n            roll=1,\n            stub=\"SHORTFRONT\",\n            ex_div=1,\n        )\n        result = bond.price(ytm=2.249977, settlement=dt(2018, 10, 16))\n        accrued = bond.accrued(settlement=dt(2018, 10, 16))\n        stub_cash = bond.leg1.periods[0].cashflow()\n        assert abs(accrued - 0.499315) < 1e-6\n        assert abs(result - 100.00) < 1e-5\n        assert abs(stub_cash + 7828.77) < 1e-2\n\n    def test_cadgb_ytm_dirty_calc(self) -> None:\n        # Cad GB has different Accrual function for a YTM and physical settlement.\n        # If a price is supplied dirty it is expected to be a physical settlement dirty price\n        bond = FixedRateBond(\n            effective=dt(2018, 7, 27),\n            termination=dt(2029, 6, 1),\n            fixed_rate=2.25,\n            spec=\"ca_gb\",\n        )\n\n        physical_accrued = bond._accrued(\n            dt(2019, 6, 10), bond.kwargs.meta[\"calc_mode\"]._settle_accrual\n        )\n        ytm_accrued = bond._accrued(dt(2019, 6, 10), bond.kwargs.meta[\"calc_mode\"]._ytm_accrual)\n        assert abs(physical_accrued - ytm_accrued) > 1e-4\n\n        clean_price = 101.00\n        clean_ytm = bond.ytm(clean_price, dt(2019, 6, 10))\n        dirty_ytm = bond.ytm(clean_price + physical_accrued, dt(2019, 6, 10), dirty=True)\n        assert abs(clean_ytm - dirty_ytm) < 1e-8\n\n    def test_cadgb_ytm_indexed_dirty_calc(self) -> None:\n        # Cad GB has different Accrual function for a YTM and physical settlement.\n        # If a price is supplied dirty it is expected to be a physical settlement dirty price\n        bond = IndexFixedRateBond(\n            effective=dt(2018, 7, 27),\n            termination=dt(2029, 6, 1),\n            fixed_rate=2.25,\n            spec=\"ca_gbi\",\n            index_base=90.0,\n        )\n        curve = Curve({dt(2019, 1, 1): 1.0, dt(2030, 1, 1): 1.0}, index_base=99.0).shift(100.0)\n\n        physical_indexed_accrued = bond.accrued(dt(2019, 6, 10), indexed=True, index_curve=curve)\n        ytm_indexed_accrued = bond._accrued(\n            dt(2019, 6, 10), bond.kwargs.meta[\"calc_mode\"]._ytm_accrual\n        ) * bond.index_ratio(settlement=dt(2019, 6, 18), index_curve=curve)\n        assert abs(physical_indexed_accrued - ytm_indexed_accrued) > 1e-4\n\n        clean_price = 111.00\n        clean_ytm = bond.ytm(\n            clean_price, dt(2019, 6, 10), indexed_price=True, indexed_ytm=False, index_curve=curve\n        )\n        dirty_ytm = bond.ytm(\n            clean_price + physical_indexed_accrued,\n            dt(2019, 6, 10),\n            dirty=True,\n            indexed_price=True,\n            indexed_ytm=False,\n            index_curve=curve,\n        )\n        assert abs(clean_ytm - dirty_ytm) < 1e-8\n\n    ## German gov bonds comparison with official bundesbank publications.\n\n    @pytest.mark.parametrize(\n        (\"sett\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2024, 1, 10), 105.0, 1.208836, 0.321311),\n            (\n                dt(2024, 6, 12),\n                97.180,\n                2.66368627,\n                1.204918,\n            ),  # https://www.bundesbank.de/en/service/federal-securities/prices-and-yields\n            (dt(2022, 12, 20), 99.31, 2.208075, 0.350959),\n            # (dt(2022, 12, 20), 99.31, 2.20804175, 0.3452055),  # Bundesbank official data:\n            # see link above (accrual is unexplained and does not match systems)\n            (\n                dt(2023, 11, 2),\n                97.04,\n                2.636708016,\n                2.174795,\n            ),  # Bundesbank official data: see link above (agrees with BXT)\n            (dt(2028, 11, 15), 97.5, 4.717949, 0.0),  # YAS\n        ],\n    )\n    def test_de_gb(self, sett, price, exp_ytm, exp_acc) -> None:\n        frb = FixedRateBond(  # ISIN DE0001102622\n            effective=dt(2022, 10, 20),\n            termination=dt(2029, 11, 15),\n            stub=\"LONGFRONT\",\n            fixed_rate=2.1,\n            spec=\"de_gb\",\n        )\n        result = frb.accrued(settlement=sett)\n        assert abs(result - exp_acc) < 1e-6\n\n        result = frb.ytm(price=price, settlement=sett)\n        assert abs(result - exp_ytm) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"sett\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (\n                dt(2024, 6, 12),\n                99.555,\n                3.5314195,\n                0.825137,\n            ),  # https://www.bundesbank.de/en/service/federal-securities/prices-and-yields\n        ],\n    )\n    def test_de_gb_mm(self, sett, price, exp_ytm, exp_acc) -> None:\n        # tests the MoneyMarket simple yield for the final period.\n        frb = FixedRateBond(  # ISIN DE0001102366\n            effective=dt(2014, 8, 15),\n            termination=dt(2024, 8, 15),\n            fixed_rate=1.0,\n            spec=\"de_gb\",\n        )\n        result = frb.accrued(settlement=sett)\n        assert abs(result - exp_acc) < 1e-6\n\n        result = frb.ytm(price=price, settlement=sett)\n        assert abs(result - exp_ytm) < 1e-6\n\n    def test_long_stub(self):\n        # DE000BU2Z056\n        bond = FixedRateBond(dt(2025, 7, 4), dt(2035, 8, 15), spec=\"de_gb\", fixed_rate=2.60)\n        assert bond.leg1.schedule.aschedule[0:2] == [dt(2025, 7, 4), dt(2026, 8, 15)]\n\n    def test_de_long_front_split_accrued_no_leap(self):\n        # this bond was not issued around a leap year so there is no difference between\n        # linear_days_long_front_split and linear_days\n        bond = FixedRateBond(dt(2025, 3, 12), dt(2056, 8, 15), spec=\"de_gb\", fixed_rate=2.90)\n        result1 = bond.accrued(settlement=dt(2026, 1, 16))\n        result2 = bond.accrued(settlement=dt(2026, 7, 14))\n        expected1 = (dt(2026, 1, 16) - dt(2025, 3, 12)).days / 365 * 2.9\n        expected2 = (dt(2026, 7, 14) - dt(2025, 3, 12)).days / 365 * 2.9\n        assert abs(result1 - expected1) < 1e-6\n        assert abs(result2 - expected2) < 1e-6\n\n    def test_de_long_front_split_accrued_leap(self):\n        # this bond was issued in 2024 so there is a difference between linear_days and\n        # linear_days_long_front_split: ISIN DE000BU2D004\n        bond = FixedRateBond(dt(2024, 2, 6), dt(2054, 8, 15), spec=\"de_gb\", fixed_rate=2.50)\n        result1 = bond.accrued(settlement=dt(2024, 7, 15))\n        result2 = bond.accrued(settlement=dt(2025, 7, 15))\n        expected1 = (dt(2024, 7, 15) - dt(2024, 2, 6)).days / 366 * 2.5\n        expected2 = (dt(2024, 8, 15) - dt(2024, 2, 6)).days / 366 * 2.5 + (\n            dt(2025, 7, 15) - dt(2024, 8, 15)\n        ).days / 365 * 2.5\n        assert abs(result1 - expected1) < 1e-8\n        assert abs(result2 - expected2) < 1e-8\n\n    ## French OAT\n\n    @pytest.mark.parametrize(\n        (\"sett\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2024, 6, 14), 101.0, 2.886581, 1.655738),\n            (dt(2033, 11, 25), 99.75, 3.258145, 0.0),\n            (dt(2034, 6, 13), 101.0, 0.769200, 1.643836),\n        ],\n    )\n    def test_fr_gb(self, sett, price, exp_ytm, exp_acc) -> None:\n        frb = FixedRateBond(  # ISIN FR001400QMF9\n            effective=dt(2023, 11, 25),\n            termination=dt(2034, 11, 25),\n            fixed_rate=3.0,\n            spec=\"fr_gb\",\n        )\n        result = frb.accrued(settlement=sett)\n        assert abs(result - exp_acc) < 1e-6\n\n        result = frb.ytm(price=price, settlement=sett)\n        assert abs(result - exp_ytm) < 1e-6\n\n    ## Italian BTP\n\n    @pytest.mark.parametrize(\n        (\"sett\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2024, 6, 14), 98.0, 4.730058, 0.526090),\n            (dt(2026, 4, 14), 99.0, 4.617209, 1.993370),\n        ],\n    )\n    def test_regular_it_gb(self, sett, price, exp_ytm, exp_acc) -> None:\n        frb = FixedRateBond(  # ISIN IT0005518128\n            effective=dt(2022, 11, 1),\n            termination=dt(2033, 5, 1),\n            fixed_rate=4.4,\n            spec=\"it_gb\",\n        )\n        result = frb.accrued(settlement=sett)\n        assert abs(result - exp_acc) < 5e-6\n\n        result = frb.price(ytm=exp_ytm, settlement=sett)\n        result = frb.ytm(price=price, settlement=sett)\n        assert abs(result - exp_ytm) < 2e-4\n\n    @pytest.mark.parametrize(\n        (\"sett\", \"ytm\", \"exp_price\", \"exp_acc\"),\n        [\n            (dt(2032, 11, 1), 6.5, 98.96593464, 0.0),\n            (dt(2032, 11, 2), 6.5, 98.97099073, 0.01215),\n            (dt(2033, 3, 15), 6.5, 99.69805695, 1.628730),\n            (dt(2033, 4, 29), 6.5, 99.96938727, 2.175690),\n        ],\n    )\n    def test_regular_it_gb_final_simple_vs_excel(self, sett, ytm, exp_price, exp_acc) -> None:\n        # These values are not the same as for BBG YA.\n        # BBG YA can be replicated here if the number of days between payments\n        # in the last period 1/11/32 to 2/5/33 is 184 days and the pay_adj is 1/184 instead of\n        # 1/182. Problem is, the actual number of days between payments are 182.\n        frb = FixedRateBond(  # ISIN IT0005518128\n            effective=dt(2022, 11, 1),\n            termination=dt(2033, 5, 1),\n            fixed_rate=4.4,\n            spec=\"it_gb\",\n        )\n\n        result = frb.accrued(settlement=sett)\n        assert abs(result - exp_acc) < 5e-6\n\n        result = frb.price(ytm=ytm, settlement=sett)\n        assert abs(result - exp_price) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"sett\", \"ytm\", \"exp_price\", \"exp_acc\"),\n        [\n            (dt(2032, 11, 1), 6.429702, 99.00, 0.0),  # Last coupon simple rate\n            (dt(2032, 11, 2), 6.439891, 99.00, 0.01215),  # Last coupon simple rate\n            (dt(2033, 3, 15), 6.862519, 99.65, 1.628730),  # Last coupon simple rate\n            (dt(2033, 4, 29), 6.450803, 99.97, 2.175690),  # Test accrual upto adjusted payment date\n        ],\n    )\n    def test_regular_it_gb_final_simple(self, sett, ytm, exp_price, exp_acc) -> None:\n        frb = FixedRateBond(  # ISIN IT0005518128\n            effective=dt(2022, 11, 1),\n            termination=dt(2033, 5, 1),\n            fixed_rate=4.4,\n            spec=\"it_gb\",\n        )\n\n        result = frb.accrued(settlement=sett)\n        assert abs(result - exp_acc) < 5e-6\n\n        result = frb.price(ytm=ytm, settlement=sett)\n        assert abs(result - exp_price) < 3e-3\n\n    @pytest.mark.parametrize(\n        (\"sett\", \"ytm\", \"exp_price\", \"exp_acc\"),\n        [\n            (dt(2026, 12, 13), 6.5, 98.77226353, 0.0),\n            (dt(2027, 1, 11), 6.5, 98.95139167, 0.318681),\n        ],\n    )\n    def test_regular_it_gb_final_simple_vs_excel2(self, sett, ytm, exp_price, exp_acc) -> None:\n        frb = FixedRateBond(  # ISIN IT0005547408\n            effective=dt(2023, 6, 13),\n            termination=dt(2027, 6, 13),\n            fixed_rate=4.00,\n            spec=\"it_gb\",\n        )\n\n        result = frb.accrued(settlement=sett)\n        assert abs(result - exp_acc) < 5e-6\n\n        result = frb.price(ytm=ytm, settlement=sett)\n        assert abs(result - exp_price) < 1e-6\n\n    ## Norwegian\n\n    @pytest.mark.parametrize(\n        (\"set_\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2026, 4, 13), 99.3, 3.727804, 0.0),  # YAS Coupon aligned\n            (dt(2033, 4, 13), 99.9, 3.728729, 0.0),  # Last period\n            (dt(2033, 9, 12), 99.9, 3.772713, 1.509589),  # Middle Last period\n            (dt(2024, 2, 13), 99.9, 3.638007, 0.0),  # Start of bond\n            (\n                dt(2024, 3, 13),\n                99.9,\n                3.637518,\n                0.288014,\n            ),  # Mid stub period\n        ],\n    )\n    def test_no_gb(self, set_, price, exp_ytm, exp_acc) -> None:\n        frb = FixedRateBond(  # ISIN NO0013148338\n            effective=dt(2024, 2, 13),\n            termination=dt(2034, 4, 13),\n            fixed_rate=3.625,\n            spec=\"no_gb\",\n        )\n        result = frb.accrued(settlement=set_)\n        assert abs(result - exp_acc) < 5e-6\n\n        result = frb.ytm(price=price, settlement=set_)\n        assert abs(result - exp_ytm) < 1e-5\n\n    ## Dutch\n\n    @pytest.mark.parametrize(\n        (\"set_\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2025, 6, 10), 98.0, 2.751162, 2.260274),  # YAS Coupon aligned\n            (dt(2033, 7, 15), 99.8, 2.705411, 0.0),  # Last period\n            (dt(2033, 7, 18), 99.9, 2.602897, 0.020548),  # Middle Last period\n            (dt(2024, 2, 8), 99.0, 2.611616, 0.0),  # Start of bond\n            (dt(2024, 3, 13), 99.0, 2.612194, 0.232240),  # Mid stub period\n        ],\n    )\n    def test_nl_gb(self, set_, price, exp_ytm, exp_acc) -> None:\n        frb = FixedRateBond(  # ISIN NL0015001XZ6\n            effective=dt(2024, 2, 8),\n            termination=dt(2034, 7, 15),\n            fixed_rate=2.5,\n            spec=\"nl_gb\",\n        )\n        result = frb.accrued(settlement=set_)\n        assert abs(result - exp_acc) < 5e-6\n\n        result = frb.ytm(price=price, settlement=set_)\n        assert abs(result - exp_ytm) < 1e-5\n\n    # US Corp: BNY Mello\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2025, 5, 6), 101.0, 3.493237, 0.08555556),\n            (dt(2028, 4, 3), 100.05, 3.077448, 1.65763889),\n        ],\n    )\n    def test_bny_mellon(self, settlement, price, exp_ytm, exp_acc) -> None:\n        # BNY Mellon ISIN: US06406RAH03,\n        b = FixedRateBond(\n            effective=dt(2018, 4, 30),\n            termination=dt(2028, 4, 28),\n            fixed_rate=3.85,\n            convention=\"30u360\",\n            spec=\"us_gb\",\n            calc_mode=\"us_corp\",\n        )\n        ytm = b.ytm(price, settlement)\n        acc = b.accrued(settlement)\n        assert abs(ytm - exp_ytm) < 1e-6\n        assert abs(acc - exp_acc) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2018, 5, 30), 101.0, 3.728114, 0.32083333),\n            (dt(2018, 5, 31), 101.0, 3.728114, 0.32083333),\n            (dt(2025, 5, 6), 101.0, 3.493237, 0.08555556),\n            (dt(2028, 4, 3), 100.05, 3.077448, 1.65763889),\n        ],\n    )\n    def test_bny_mellon_spec(self, settlement, price, exp_ytm, exp_acc) -> None:\n        # BNY Mellon ISIN: US06406RAH03,\n        b = FixedRateBond(\n            effective=dt(2018, 4, 30),\n            termination=dt(2028, 4, 28),\n            fixed_rate=3.85,\n            spec=\"us_corp\",\n        )\n        assert abs(b.leg1.periods[0].cashflow() + 19036.1111) < 1e-4\n        ytm = b.ytm(price, settlement)\n        acc = b.accrued(settlement)\n        assert abs(acc - exp_acc) < 1e-8\n        assert abs(ytm - exp_ytm) < 1e-6\n\n    # US MUNI: Cali State\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2025, 5, 12), 102.35, 2.879, 1.819444),\n            (dt(2025, 1, 31), 100.1, 4.923, 0.416667),\n            (dt(2026, 1, 1), 102.35, 0.293, 0.0),\n            (dt(2026, 5, 19), 100.10, 4.061, 1.916667),\n            (dt(2026, 6, 30), 100.10, -30.219, 2.486111),\n        ],\n    )\n    def test_cali_state_school(self, settlement, price, exp_ytm, exp_acc) -> None:\n        # LA Unif ISIN: US544647CW89,\n        b = FixedRateBond(\n            effective=dt(2020, 11, 10),\n            termination=dt(2026, 7, 1),\n            fixed_rate=5.0,\n            spec=\"us_muni\",\n        )\n        ytm = b.ytm(price, settlement)\n        acc = b.accrued(settlement)\n        assert abs(ytm - exp_ytm) < 1e-3\n        assert abs(acc - exp_acc) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"price\", \"exp_ytm\", \"exp_acc\"),\n        [\n            (dt(2025, 3, 31), 110.0, -3.441, 1.356800),\n            (dt(2025, 5, 12), 101.0, 3.662, 1.881600),\n            (dt(2025, 5, 30), 100.02, 4.586, 2.11200),\n            (dt(2025, 12, 15), 101.0, 2.582, 0.0),\n            (dt(2026, 3, 19), 101.0, 0.413, 1.203200),\n            (dt(2026, 6, 11), 100.02, 2.746, 2.2528),\n        ],\n    )\n    def test_new_jersey_transport(self, settlement, price, exp_ytm, exp_acc) -> None:\n        # NJ Transport ISIN: US64613CEZ77,\n        b = FixedRateBond(\n            effective=dt(2024, 10, 24),\n            termination=dt(2026, 6, 15),\n            fixed_rate=4.608,\n            spec=\"us_muni\",\n        )\n        ytm = b.ytm(price, settlement)\n        acc = b.accrued(settlement)\n        assert abs(acc - exp_acc) < 1e-6\n        assert abs(ytm - exp_ytm) < 1e-3\n\n    # Customised Thai Government Bonds\n\n    def test_thai_example_a3(self):\n        # see file in _static/thai_standard_formula.pdf\n        def _v1_thb_gb(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx):\n            r_u = (obj.leg1.schedule.uschedule[acc_idx + 1] - settlement).days\n            return v2 ** (r_u * f / 365)\n\n        def _v3_thb_gb(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx):\n            r_u = (obj.leg1.schedule.uschedule[-1] - obj.leg1.schedule.uschedule[-2]).days\n            return v2 ** (r_u * f / 365)\n\n        thai_cm = BondCalcMode(\n            settle_accrual=\"linear_days\",\n            ytm_accrual=\"linear_days\",\n            v1=_v1_thb_gb,\n            v2=\"regular\",\n            v3=_v3_thb_gb,\n            c1=\"full_coupon\",\n            ci=\"full_coupon\",\n            cn=\"cashflow\",\n        )\n\n        b = FixedRateBond(\n            effective=dt(1993, 1, 15),\n            termination=dt(1996, 4, 30),\n            stub=\"shortback\",\n            frequency=\"S\",\n            fixed_rate=11.25,\n            convention=\"act365f\",\n            modifier=\"none\",\n            currency=\"thb\",\n            calendar=\"bus\",\n            calc_mode=thai_cm,\n        )\n\n        expected_acc = 4.86986301\n        expected_clean = 103.1099263\n        result_acc = b.accrued(settlement=dt(1994, 12, 20))\n        result_clean = b.price(ytm=8.75, settlement=dt(1994, 12, 20))\n\n        assert abs(result_acc - expected_acc) < 1e-8\n        assert abs(result_clean - expected_clean) < 1e-7\n\n    def test_thai_example_a3_exdiv(self):\n        # see file in _static/thai_standard_formula.pdf\n        def _v1_thb_gb(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx):\n            r_u = (obj.leg1.schedule.uschedule[acc_idx + 1] - settlement).days\n            return v2 ** (r_u * f / 365)\n\n        def _v3_thb_gb(obj, ytm, f, settlement, acc_idx, v2, accrual, period_idx):\n            r_u = (obj.leg1.schedule.uschedule[-1] - obj.leg1.schedule.uschedule[-2]).days\n            return v2 ** (r_u * f / 365)\n\n        thai_cm = BondCalcMode(\n            settle_accrual=\"linear_days\",\n            ytm_accrual=\"linear_days\",\n            v1=_v1_thb_gb,\n            v2=\"regular\",\n            v3=_v3_thb_gb,\n            c1=\"cashflow\",\n            ci=\"full_coupon\",\n            cn=\"cashflow\",\n        )\n\n        b = FixedRateBond(\n            effective=dt(1993, 1, 15),\n            termination=dt(1996, 4, 30),\n            stub=\"shortback\",\n            frequency=\"S\",\n            fixed_rate=11.25,\n            convention=\"act365f\",\n            modifier=\"none\",\n            currency=\"thb\",\n            calendar=\"bus\",\n            calc_mode=thai_cm,\n            ex_div=21,\n        )\n\n        result_acc = b.accrued(dt(1994, 12, 20))\n        expected_acc = -0.80136986\n        assert abs(result_acc - expected_acc) < 1e-8\n\n        result_clean = b.price(ytm=8.75, settlement=dt(1994, 12, 20))\n        expected_clean = 103.19036939\n        assert abs(result_clean - expected_clean) < 1e-8\n\n    # Swiss GB\n\n    @pytest.mark.parametrize(\n        (\"ytm\", \"sett\", \"exp\"),\n        [\n            (2.01111, dt(2025, 5, 23), [92.724231, 0.095833333]),\n            (2.01111, dt(2018, 5, 29), [90.369254, 0.120833333]),  # accrued DCF\n            (2.01111, dt(2018, 5, 30), [90.370093, 0.125000000]),  # accrued DCF\n            (2.01111, dt(2018, 5, 31), [90.370093, 0.125000000]),  # accrued DCF\n            (2.01111, dt(2018, 6, 1), [90.370931, 0.129166666]),\n            (2.01111, dt(2024, 4, 29), [92.343879, 1.49583333]),  # Ex div\n            (2.01111, dt(2024, 4, 30), [92.344903, 0.000000000]),  # Ex div\n            (2.01111, dt(2042, 4, 15), [99.978326, 1.43750000]),  # Final period\n        ],\n    )\n    def test_ch_gb(self, ytm, sett, exp):\n        # ISIN: CH0127181169\n        bond = FixedRateBond(dt(2012, 4, 30), dt(2042, 4, 30), fixed_rate=1.5, spec=\"ch_gb\")\n        accrued = bond.accrued(sett)\n        assert abs(accrued - exp[1]) < 1e-8\n        price = bond.price(ytm=ytm, settlement=sett)\n        assert abs(price - exp[0]) < 1e-6\n\n    # New Zealand GB\n\n    @pytest.mark.parametrize(\n        (\"ytm\", \"sett\", \"maturity\", \"coupon\", \"exp\"),\n        [\n            (4.355, dt(2022, 11, 22), dt(2034, 5, 15), 4.25, [99.0583817412, 0.0821823204]),\n            (\n                5.348,\n                dt(2051, 4, 15),\n                dt(2051, 5, 15),\n                2.75,\n                [99.7842450753699, 1.1470994475],\n            ),  # Last period simple_act365f\n            (0.745, dt(2021, 2, 10), dt(2026, 5, 15), 0.50, [98.7384877998, 0.1201657459]),\n        ],\n    )\n    def test_nz_gb(self, ytm, sett, maturity, coupon, exp):\n        bond = FixedRateBond(dt(2020, 5, 15), maturity, fixed_rate=coupon, spec=\"nz_gb\")\n        accrued = bond.accrued(sett)\n        assert abs(accrued - exp[1]) < 1e-8\n        price = bond.price(ytm=ytm, settlement=sett)\n        assert abs(price - exp[0]) < 1e-6\n\n    # Australian GB\n\n    @pytest.mark.parametrize(\n        (\"ytm\", \"sett\", \"maturity\", \"coupon\", \"exp\"),\n        [\n            # these values are tested without australian rounding convention of 3 dp (5e-4 tol)\n            (4.0, dt(2026, 4, 8), dt(2051, 6, 21), 1.75, [64.479000, 0.5190]),\n            (4.0, dt(2050, 6, 21), dt(2051, 6, 21), 1.75, [97.816, 0.0]),\n            (4.0, dt(2051, 5, 8), dt(2051, 6, 21), 1.75, [99.727923, 0.6630]),\n            (4.0, dt(2050, 12, 21), dt(2051, 6, 21), 1.75, [98.902372, 0.0]),\n            # test ex div\n            (4.0, dt(2026, 6, 12), dt(2051, 6, 21), 1.75, [64.627, 0.832]),\n            (4.0, dt(2026, 6, 13), dt(2051, 6, 21), 1.75, [64.631, -0.038]),\n            (4.0, dt(2026, 6, 14), dt(2051, 6, 21), 1.75, [64.633, -0.034]),\n            (4.0, dt(2026, 6, 15), dt(2051, 6, 21), 1.75, [64.635, -0.029]),\n        ],\n    )\n    def test_au_gb(self, ytm, sett, maturity, coupon, exp):\n        # AU0000097495\n        bond = FixedRateBond(dt(2020, 6, 21), maturity, fixed_rate=coupon, spec=\"au_gb\")\n        accrued = bond.accrued(sett)\n        assert abs(accrued - exp[1]) < 5e-4\n        price = bond.price(ytm=ytm, settlement=sett)\n        assert abs(price - exp[0]) < 6e-4\n\n    def test_au_gb_docs_basic_formula_worked_example(self):\n        bond = FixedRateBond(dt(2018, 11, 21), dt(2029, 11, 21), fixed_rate=2.75, spec=\"au_gb\")\n        result = bond.price(ytm=1.10, settlement=dt(2019, 9, 12), dirty=True)\n        expected = 116.716\n        assert abs(round(result, 3) - expected) < 1e-4\n\n    def test_au_gb_docs_ex_interest_formula_worked_example(self):\n        bond = FixedRateBond(dt(2018, 5, 21), dt(2030, 5, 21), fixed_rate=2.50, spec=\"au_gb\")\n        result = bond.price(ytm=1.10, settlement=dt(2019, 11, 15), dirty=True)\n        expected = 113.827\n        assert abs(round(result, 3) - expected) < 1e-4\n\n    def test_au_gb_docs_near_maturing_worked_example(self):\n        bond = FixedRateBond(dt(2010, 4, 21), dt(2019, 10, 21), fixed_rate=2.75, spec=\"au_gb\")\n        result = bond.price(ytm=1.00, settlement=dt(2019, 9, 26), dirty=True)\n        expected = 101.305613\n        assert abs(round(result, 6) - expected) < 5e-6\n\n    def test_au_gb_docs_near_maturing_ex_interest_worked_example(self):\n        bond = FixedRateBond(dt(2010, 4, 21), dt(2019, 10, 21), fixed_rate=2.75, spec=\"au_gb\")\n        result = bond.price(ytm=1.00, settlement=dt(2019, 10, 16), dirty=True)\n        expected = 99.986303\n        assert abs(round(result, 6) - expected) < 5e-6\n\n    def test_au_gb_record_date_examples(self):\n        bond = FixedRateBond(dt(2023, 11, 21), dt(2028, 5, 21), fixed_rate=2.75, spec=\"au_gb\")\n        record = bond.leg1.schedule.pschedule3[1]\n        assert record == dt(2024, 5, 13)\n\n        bond = FixedRateBond(dt(2024, 4, 21), dt(2026, 4, 21), fixed_rate=4.25, spec=\"au_gb\")\n        record = bond.leg1.schedule.pschedule3[1]\n        assert record == dt(2024, 10, 11)\n\n    # Chinese GB\n\n    @pytest.mark.parametrize(\n        (\"ytm\", \"maturity\", \"coupon\", \"exp\"),\n        [\n            # gen AI cross check\n            (2.35, dt(2036, 3, 15), 2.50, [101.3230902997, 0.1290760870]),\n            (2.15, dt(2031, 6, 20), 2.20, [100.243946673846, 0.6285714286]),\n            (2.37, dt(2056, 1, 15), 2.38, [100.211057878950, 0.512707182320]),\n        ],\n    )\n    def test_cn_gb(self, ytm, maturity, coupon, exp):\n        bond = FixedRateBond(dt(2020, 6, 21), maturity, fixed_rate=coupon, spec=\"cn_gb\")\n        accrued = bond.accrued(dt(2026, 4, 3))\n        price = bond.price(ytm=ytm, settlement=dt(2026, 4, 3))\n        assert abs(accrued - exp[1]) < 1e-2\n        assert abs(price - exp[0]) < 5e-5\n\n    # General Method Coverage\n\n    def test_fixed_rate_bond_yield_domains(self) -> None:\n        bond = FixedRateBond(\n            dt(1995, 1, 1),\n            dt(2015, 12, 7),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=8,\n            ex_div=7,\n            calendar=\"ldn\",\n        )\n        assert bond.ytm(500.0, dt(1999, 5, 24), True) + 5.86484231333 < 1e-8\n        assert bond.ytm(200, dt(1999, 5, 24), True) - 1.4366895440550 < 1e-8\n        assert bond.ytm(100, dt(1999, 5, 24), True) - 8.416909601459 < 1e-8\n        assert bond.ytm(50, dt(1999, 5, 24), True) - 18.486840866431 < 1e-6\n        assert bond.ytm(1, dt(1999, 5, 24), True) - 13421775210.82037 < 1e-3\n\n    def test_fixed_rate_bond_ytm_duals(self) -> None:\n        bond = FixedRateBond(\n            dt(1995, 1, 1),\n            dt(2015, 12, 7),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=8,\n            ex_div=7,\n            calendar=\"ldn\",\n        )\n\n        dPdy = bond.duration(4, dt(1995, 1, 1))\n        P = bond.price(4, dt(1995, 1, 1))\n        result = bond.ytm(Dual(P, [\"a\", \"b\"], [1, -0.5]), dt(1995, 1, 1))\n        expected = Dual(4.00, [\"a\", \"b\"], [-1 / dPdy, 0.5 / dPdy])\n        assert abs(result - expected) < 1e-11\n        assert all(np.isclose(expected.dual, result.dual))\n\n        d2ydP2 = -bond.convexity(4, dt(1995, 1, 1)) * -(dPdy**-3)\n        result = bond.ytm(Dual2(P, [\"a\", \"b\"], [1, -0.5], []), dt(1995, 1, 1))\n        expected = Dual2(\n            4.00,\n            [\"a\", \"b\"],\n            [-1 / dPdy, 0.5 / dPdy],\n            [d2ydP2 * 0.5, d2ydP2 * -0.25, d2ydP2 * -0.25, d2ydP2 * 0.125],\n        )\n        assert abs(result - expected) < 1e-11\n        assert all(np.isclose(result.dual, expected.dual))\n        assert all(np.isclose(result.dual2, expected.dual2).flat)\n\n    @pytest.mark.skip(reason=\"Bills have Z frequency, this no longer raises\")\n    def test_fixed_rate_bond_zero_frequency_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"FixedRateBond `frequency`\"):\n            FixedRateBond(dt(1999, 5, 7), dt(2002, 12, 7), \"Z\", convention=\"ActActICMA\")\n\n    @pytest.mark.parametrize(\"metric\", [\"risk\", \"duration\", \"modified\"])\n    def test_fixed_rate_bond_duration(self, metric) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n        )\n        price0 = gilt.price(4.445, dt(1999, 5, 27), dirty=True)\n        price1 = gilt.price(4.446, dt(1999, 5, 27), dirty=True)\n        if metric == \"risk\":\n            numeric = price0 - price1\n        elif metric == \"modified\":\n            numeric = (price0 - price1) / price0 * 100\n        elif metric == \"duration\":\n            numeric = (price0 - price1) / price0 * (1 + 4.445 / (100 * 2)) * 100\n\n        result = gilt.duration(4.445, dt(1999, 5, 27), metric=metric)\n        assert (result - numeric * 1000) < 1e-1\n\n    def test_fixed_rate_bond_convexity(self) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n        )\n        numeric = gilt.duration(4.445, dt(1999, 5, 27)) - gilt.duration(4.446, dt(1999, 5, 27))\n        result = gilt.convexity(4.445, dt(1999, 5, 27))\n        assert (result - numeric * 1000) < 1e-3\n\n        price = gilt.price(4.445, dt(1999, 5, 27), dirty=True)\n        result2 = gilt.convexity(4.445, dt(1999, 5, 27), \"convexity\")\n        assert abs(result2 - result * 100.0 / price) < 1e-6\n\n    def test_convexity_traditional(self):\n        aapl_bond = FixedRateBond(dt(2013, 5, 4), dt(2043, 5, 4), fixed_rate=3.85, spec=\"us_corp\")\n        # c1 = aapl_bond.convexity(4.653674794785435, dt(2014, 3, 5))\n        c2 = aapl_bond.convexity(4.653674794785435, dt(2014, 3, 5), metric=\"convexity\")\n        assert abs(c2 - 3.803) < 1e-4\n\n    def test_fixed_rate_bond_rate(self) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            settle=0,\n        )\n        curve = Curve({dt(1998, 12, 9): 1.0, dt(2015, 12, 7): 0.50})\n        clean_price = gilt.rate(curves=curve, metric=\"clean_price\")\n        result = gilt.rate(\n            curves={\"disc_curve\": curve}, metric=\"clean_price\", settlement=dt(1998, 12, 9)\n        )\n        assert abs(result - clean_price) < 1e-8\n\n        result = gilt.rate(curves=_Curves(disc_curve=curve), metric=\"dirty_price\")\n        expected = clean_price + gilt.accrued(dt(1998, 12, 9))\n        assert result == expected\n        result = gilt.rate(curves=curve, metric=\"dirty_price\", settlement=dt(1998, 12, 9))\n        assert abs(result - clean_price - gilt.accrued(dt(1998, 12, 9))) < 1e-8\n\n        result = gilt.rate(curves=curve, metric=\"ytm\")\n        expected = gilt.ytm(clean_price, dt(1998, 12, 9), False)\n        assert abs(result - expected) < 1e-8\n\n    def test_initialisation_rate_metric(self) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            settle=0,\n            metric=\"ytm\",\n        )\n        curve = Curve({dt(1998, 12, 9): 1.0, dt(2015, 12, 7): 0.50})\n        clean_price = gilt.rate(curves=curve, metric=\"clean_price\")\n        expected = gilt.ytm(price=clean_price, settlement=dt(1998, 12, 9))\n        result = gilt.rate(curves=curve)  # default metric is \"ytm\"\n        assert abs(result - expected) < 1e-8\n\n    def test_fixed_rate_bond_npv(self) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            notional=-100,\n            settle=0,\n        )\n        curve = Curve({dt(2010, 11, 25): 1.0, dt(2015, 12, 7): 0.75})\n        result = gilt.npv(curves=curve)\n        expected = 113.22198344812742\n        assert abs(result - expected) < 1e-6\n\n        gilt.kwargs.meta[\"settle\"] = 2\n        result = gilt.npv(curves=curve)  # bond is ex div on settlement 27th Nov 2010\n        expected = 109.229489312983  # bond has dropped a coupon payment of 4.\n        assert abs(result - expected) < 1e-6\n\n        result = gilt.npv(curves=curve, local=True)\n        assert abs(result[\"gbp\"] - expected) < 1e-6\n\n    def test_fixed_rate_bond_npv_private(self) -> None:\n        # this test shadows 'fixed_rate_bond_npv' but extends it for projection on 27th Nov ex div.\n        curve = Curve({dt(2004, 11, 25): 1.0, dt(2010, 11, 25): 1.0, dt(2015, 12, 7): 0.75})\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            notional=-100,\n            settle=0,\n        )\n        result = gilt.npv(curves=curve, settlement=dt(2010, 11, 27), forward=dt(2010, 11, 25))\n        expected = 109.229489312983  # npv should match associated test\n        assert abs(result - expected) < 1e-6\n\n    def test_fixed_rate_bond_analytic_delta(self) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            notional=-1000000,\n            settle=0,\n        )\n        curve = Curve({dt(2010, 11, 25): 1.0, dt(2015, 12, 7): 1.0})\n        result = gilt.analytic_delta(curves=curve)\n        expected = -550.0\n        assert abs(result - expected) < 1e-6\n\n        gilt.kwargs.meta[\"settle\"] = 2\n        result = gilt.analytic_delta(curves=curve)  # bond is ex div on settle 27th Nov 2010\n        expected = -500.0  # bond has dropped a 6m coupon payment\n        assert abs(result - expected) < 1e-6\n\n    def test_fixed_rate_bond_cashflows(self) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            notional=-100,\n            settle=1,\n        )\n        curve = Curve({dt(2010, 11, 25): 1.0, dt(2015, 12, 7): 0.75})\n\n        flows = gilt.cashflows(curves=curve)  # bond is ex div on 26th nov 2010\n        result = flows[defaults.headers[\"npv\"]].sum()\n        expected = gilt.npv(curves=curve)\n        assert abs(result - expected) < 1e-6\n\n        gilt.settle = 0\n        flows = gilt.cashflows(curves=curve)  # settlement from curve initial node\n        result = flows[defaults.headers[\"npv\"]].sum()\n        expected = gilt.npv(curves=curve)\n        assert abs(result - expected) < 1e-6\n\n    def test_fixed_rate_bond_rate_raises(self) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            notional=-100,\n        )\n        curve = Curve({dt(1998, 12, 7): 1.0, dt(2015, 12, 7): 0.50})\n        with pytest.raises(ValueError, match=\"`metric` must be in\"):\n            gilt.rate(curves=curve, metric=\"bad_metric\")\n\n    def test_fixed_rate_bond_no_amortization(self) -> None:\n        with pytest.raises(TypeError, match=\"got an unexpected keyword argument 'amortization\"):\n            FixedRateBond(\n                effective=dt(1998, 12, 7),\n                termination=dt(2015, 12, 7),\n                frequency=\"S\",\n                calendar=\"ldn\",\n                currency=\"gbp\",\n                convention=\"ActActICMA\",\n                ex_div=7,\n                fixed_rate=8.0,\n                notional=-100,\n                amortization=100,\n            )\n\n    @pytest.mark.parametrize(\n        (\"f_s\", \"exp\"),\n        [\n            (dt(2001, 12, 31), 99.997513754),  # compounding of mid year coupon\n            (dt(2002, 1, 1), 99.9975001688),  # this is now ex div on last coupon\n        ],\n    )\n    def test_fixed_rate_bond_forward_price_analogue(self, f_s, exp) -> None:\n        gilt = FixedRateBond(\n            effective=dt(2001, 1, 1),\n            termination=dt(2002, 1, 1),\n            frequency=\"S\",\n            calendar=NoInput(0),\n            currency=\"gbp\",\n            convention=\"Act365f\",\n            ex_div=0,\n            fixed_rate=1.0,\n            notional=-100,\n            settle=0,\n        )\n        result = gilt.fwd_from_repo(100.0, dt(2001, 1, 1), f_s, 1.0, \"act365f\")\n        assert abs(result - exp) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"f_s\", \"exp\"),\n        [\n            (dt(2001, 12, 31), 100.49888361793),  # compounding of mid year coupon\n            (dt(2002, 1, 1), 99.9975001688),  # this is now ex div on last coupon\n        ],\n    )\n    def test_fixed_rate_bond_forward_price_analogue_dirty(self, f_s, exp) -> None:\n        gilt = FixedRateBond(\n            effective=dt(2001, 1, 1),\n            termination=dt(2002, 1, 1),\n            frequency=\"S\",\n            calendar=NoInput(0),\n            currency=\"gbp\",\n            convention=\"Act365f\",\n            ex_div=1,\n            fixed_rate=1.0,\n            notional=-100,\n            settle=0,\n        )\n        result = gilt.fwd_from_repo(100.0, dt(2001, 1, 1), f_s, 1.0, \"act365f\", dirty=True)\n        assert abs(result - exp) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"s\", \"f_s\", \"exp\"),\n        [\n            (dt(2010, 11, 25), dt(2011, 11, 25), 99.9975000187),  # div div\n            (dt(2010, 11, 28), dt(2011, 11, 29), 99.997471945),  # ex-div ex-div\n            (dt(2010, 11, 28), dt(2011, 11, 25), 99.997419419),  # ex-div div\n            (dt(2010, 11, 25), dt(2011, 11, 29), 99.9975516607),  # div ex-div\n        ],\n    )\n    def test_fixed_rate_bond_forward_price_analogue_ex_div(self, s, f_s, exp) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"act365f\",\n            ex_div=7,\n            fixed_rate=1.0,\n            notional=-100,\n            settle=0,\n        )\n        result = gilt.fwd_from_repo(100.0, s, f_s, 1.0, \"act365f\")\n        assert abs(result - exp) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"f_s\", \"f_p\"),\n        [\n            (dt(2001, 12, 31), 99.997513754),  # compounding of mid year coupon\n            (dt(2002, 1, 1), 99.9975001688),  # this is now ex div on last coupon\n        ],\n    )\n    def test_fixed_rate_bond_implied_repo(self, f_s, f_p) -> None:\n        gilt = FixedRateBond(\n            effective=dt(2001, 1, 1),\n            termination=dt(2002, 1, 1),\n            frequency=\"S\",\n            calendar=NoInput(0),\n            currency=\"gbp\",\n            convention=\"Act365f\",\n            ex_div=0,\n            fixed_rate=1.0,\n            notional=-100,\n            settle=0,\n        )\n        result = gilt.repo_from_fwd(100.0, dt(2001, 1, 1), f_s, f_p, \"act365f\")\n        assert abs(result - 1.00) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"f_s\", \"f_p\"),\n        [\n            (dt(2001, 12, 31), 100.49888361793),  # compounding of mid year coupon\n            (dt(2002, 1, 1), 99.9975001688),  # this is now ex div on last coupon\n        ],\n    )\n    def test_fixed_rate_bond_implied_repo_analogue_dirty(self, f_s, f_p) -> None:\n        gilt = FixedRateBond(\n            effective=dt(2001, 1, 1),\n            termination=dt(2002, 1, 1),\n            frequency=\"S\",\n            calendar=NoInput(0),\n            currency=\"gbp\",\n            convention=\"Act365f\",\n            ex_div=1,\n            fixed_rate=1.0,\n            notional=-100,\n            settle=0,\n        )\n        result = gilt.repo_from_fwd(100.0, dt(2001, 1, 1), f_s, f_p, \"act365f\", dirty=True)\n        assert abs(result - 1.0) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"price\", \"tol\"),\n        [(112.0, 5e-7), (104.0, 1e-8), (96.0, 1e-7), (91.0, 1e-6)],\n    )\n    def test_oaspread(self, price, tol) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            notional=-100,\n            settle=0,\n        )\n        curve = Curve({dt(2010, 11, 25): 1.0, dt(2015, 12, 7): 0.75})\n        # result = gilt.npv(curve) = 113.22198344812742\n        result = gilt.oaspread(curves=curve, price=price)\n        curve_z = curve.shift(result)\n        result = gilt.rate(curves=curve_z, metric=\"clean_price\")\n        assert abs(result - price) < tol\n\n    @pytest.mark.parametrize(\n        (\"price\", \"tol\"),\n        [\n            (85, 5e-8),\n            (75, 5e-8),\n            (65, 1e-7),\n            (55, 1e-7),\n            (45, 5e-8),\n            (35, 5e-8),\n        ],\n    )\n    def test_oaspread_low_price(self, price, tol) -> None:\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=1.0,\n            notional=-100,\n            settle=0,\n        )\n        curve = Curve({dt(1999, 11, 25): 1.0, dt(2015, 12, 7): 0.85})\n        # result = gilt.npv(curve) = 113.22198344812742\n        result = gilt.oaspread(curves=curve, price=price)\n        curve_z = curve.shift(result)\n        result = gilt.rate(curves=curve_z, metric=\"clean_price\")\n        assert abs(result - price) < tol\n\n    def test_oas_spread_with_solver(self):\n        gilt = FixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            spec=\"uk_gb\",\n            fixed_rate=1.0,\n        )\n        curve = Curve({dt(1999, 11, 25): 1.0, dt(2015, 12, 7): 0.85})\n        Solver(\n            curves=[curve],\n            instruments=[\n                IRS(\n                    effective=dt(1999, 12, 8),\n                    termination=\"2y\",\n                    spec=\"gbp_irs\",\n                    curves=curve,\n                    leg2_fixing_series=\"eur_rfr\",\n                )\n            ],\n            s=[2.5],\n        )\n        # gilt.rate(curve, metric=\"dirty_price\") = 80.52025551638633\n        result = gilt.oaspread(curves=curve, price=95.00)\n        curve_z = curve.shift(result)\n        result = gilt.rate(curves=curve_z, metric=\"clean_price\")\n        assert abs(result - 95.00) < 1e-8\n\n    def test_oaspread_ift_fwddiff(self):\n        bond = FixedRateBond(dt(2000, 1, 1), \"3Y\", fixed_rate=2.5, spec=\"us_gb\")\n        curve = Curve({dt(2000, 7, 1): 1.0, dt(2005, 7, 1): 0.80})\n        # Add AD variables to the curve without a Solver\n        curve._set_ad_order(1)\n\n        result = bond.oaspread(curves=curve, price=Variable(95.0, [\"price\"], []))\n        grad = gradient(result, [\"price\"])[0]\n\n        assert abs(bond.oaspread(curves=curve, price=95.01) - result - 0.01 * grad) < 1e-3\n        assert abs(bond.oaspread(curves=curve, price=94.99) - result + 0.01 * grad) < 1e-3\n\n    def test_oas_spread_metric(self):\n        gilt = FixedRateBond(dt(1998, 12, 7), dt(2015, 12, 7), spec=\"uk_gb\", fixed_rate=1.0)\n        curve = Curve({dt(1999, 11, 3): 1.0, dt(2015, 12, 7): 0.85})\n        result1 = gilt.oaspread(curves=curve, price=95.0, metric=\"clean_price\")\n        result2 = gilt.oaspread(\n            curves=curve, price=95.0 + gilt.accrued(dt(1999, 11, 4)), metric=\"dirty_price\"\n        )\n        result3 = gilt.oaspread(curves=curve, price=gilt.ytm(95.0, dt(1999, 11, 4)), metric=\"ytm\")\n        assert abs(result1 - result2) < 1e-5\n        assert abs(result1 - result3) < 1e-5\n\n    def test_cashflows_no_curve(self) -> None:\n        gilt = FixedRateBond(\n            effective=dt(2001, 1, 1), termination=\"1Y\", spec=\"uk_gb\", fixed_rate=5.0\n        )\n        result = gilt.cashflows()  # no curve argument is passed to cashflows\n        assert isinstance(result, DataFrame)\n\n    def test_schedule_start_non_business(self) -> None:\n        frb = FixedRateBond(\n            effective=dt(2000, 1, 1),\n            termination=\"1y\",\n            spec=\"us_gb\",\n            notional=5e6,\n            fixed_rate=2.0,\n        )\n        assert frb.leg1.periods[1].settlement_params.payment == dt(2001, 1, 2)\n\n    def test_random_ytm_collection(self):\n        NUMBER = 75\n        START = dt(2000, 1, 1)\n        TENORS = [\"2y\", \"3y\", \"4y\", \"5y\", \"6y\", \"7y\", \"8y\", \"9y\", \"10y\", \"15y\"]\n        COUPS = [\n            1.0,\n            2.0,\n            3.0,\n            4.0,\n        ]\n        RAND_PRICES = np.random.rand(NUMBER) * 150 + 25.0\n        BONDS = [\n            FixedRateBond(\n                effective=START,\n                termination=TENORS[i % 10],\n                spec=\"us_gb\",\n                fixed_rate=COUPS[i % 4],\n            )\n            for i in range(NUMBER)\n        ]\n        for i in range(NUMBER):\n            BONDS[i].ytm(price=RAND_PRICES[i], settlement=dt(2001, 8, 30))\n\n    def test_custom_calc_mode(self):\n        cm = BondCalcMode(\n            settle_accrual=\"linear_days\",\n            ytm_accrual=\"linear_days\",\n            v1=\"compounding\",\n            v2=\"regular\",\n            v3=\"compounding\",\n            c1=\"cashflow\",\n            ci=\"cashflow\",\n            cn=\"cashflow\",\n        )\n        bond = FixedRateBond(\n            effective=dt(2001, 1, 1),\n            termination=\"10y\",\n            frequency=\"s\",\n            calendar=\"ldn\",\n            convention=\"ActActICMA\",\n            modifier=\"none\",\n            settle=1,\n            calc_mode=cm,\n            fixed_rate=1.0,\n        )\n        bond2 = FixedRateBond(dt(2001, 1, 1), \"10y\", spec=\"uk_gb\", fixed_rate=1.0)\n        assert bond.price(3.0, dt(2002, 3, 4)) == bond2.price(3.0, dt(2002, 3, 4))\n        assert bond.accrued(dt(2002, 3, 4)) == bond2.accrued(dt(2002, 3, 4))\n\n    def test_must_have_fixed_rate(self):\n        with pytest.raises(ValueError, match=r\"`fixed_rate` must be provided for FixedRateBond.\"):\n            FixedRateBond(\n                effective=dt(2001, 1, 1),\n                termination=\"10y\",\n                frequency=\"s\",\n                calendar=\"ldn\",\n                convention=\"ActActICMA\",\n                modifier=\"none\",\n                settle=1,\n            )\n\n    def test_ytm_domains2(self):\n        # the first pass in the quadratic approximator predicts a yield outside of the\n        # interval so a bisection method is adopted instead.\n\n        frb = FixedRateBond(\n            effective=dt(2000, 1, 15),\n            termination=dt(2030, 9, 25),\n            spec=\"uk_gb\",\n            stub=\"shortfront\",\n            fixed_rate=0.57744089871129,\n        )\n        result = frb.ytm(price=173.80904334438674, settlement=dt(2000, 1, 20))\n        assert abs(result + 1.3549202231746622) < 1e-10\n\n    def test_oas_coupon_on_non_bus_day(self):\n        # coupon falls on 30th Jun (sunday) and paid on 1st July. OAS spread now handles.\n        # dev gh 17\n        bond = FixedRateBond(dt(2023, 12, 31), \"3y\", fixed_rate=0.5, spec=\"us_gb\")\n        curve = Curve({dt(2024, 6, 24): 1.0, dt(2028, 6, 25): 1.0})\n        for today in [\n            dt(2024, 6, 25),\n            dt(2024, 6, 26),\n            dt(2024, 6, 27),\n            dt(2024, 6, 28),\n            dt(2024, 6, 29),\n            dt(2024, 6, 30),\n            dt(2024, 7, 1),\n            dt(2024, 7, 2),\n            dt(2024, 7, 3),\n        ]:\n            curve_ = curve.translate(today)\n            assert 49.1 < bond.oaspread(curves=curve_, price=100.0) < 49.2\n\n    def test_dirty_price_on_non_bus_day(self):\n        # coupon falls on 30th Jun (sunday) and paid on 1st July. OAS spread now handles.\n        # dev gh 17\n        bond = FixedRateBond(dt(2023, 12, 31), \"3y\", fixed_rate=0.5, spec=\"us_gb\")\n        curve = Curve({dt(2024, 6, 24): 1.0, dt(2028, 6, 25): 1.0})\n        for today in [\n            dt(2024, 6, 25),\n            dt(2024, 6, 26),\n            dt(2024, 6, 27),\n            dt(2024, 6, 28),\n            dt(2024, 6, 29),\n            dt(2024, 6, 30),\n            dt(2024, 7, 1),\n            dt(2024, 7, 2),\n            dt(2024, 7, 3),\n        ]:\n            curve_ = curve.translate(today)\n            if today <= dt(2024, 6, 27):  # settlement Friday 28th June\n                assert bond.rate(curves=curve_, metric=\"dirty_price\") == 101.5\n            else:\n                assert bond.rate(curves=curve_, metric=\"dirty_price\") == 101.25\n\n    @pytest.mark.parametrize(\n        \"bond\",\n        [\n            FixedRateBond(dt(2023, 12, 31), dt(2025, 12, 31), fixed_rate=4.25, spec=\"us_gb\"),\n            FixedRateBond(\n                dt(2023, 12, 31), dt(2025, 12, 31), fixed_rate=4.25, spec=\"us_gb\", modifier=\"F\"\n            ),\n        ],\n    )\n    def test_npv_and_oas_with_adjusted_accrual_on_non_bus_day(self, bond):\n        curve = Curve({dt(2024, 6, 28): 1.0, dt(2026, 6, 30): 0.96})\n        result = (\n            bond.npv(curves=curve),\n            bond.oaspread(curves=curve, price=97.0),\n            bond.rate(curves=curve, metric=\"clean_price\"),\n        )\n        for date in [dt(2024, 7, 1), dt(2024, 7, 2)]:\n            curve_ = curve.translate(date)\n            assert abs(bond.npv(curves=curve_) - result[0]) < 250.0\n            assert abs(bond.oaspread(curves=curve_, price=97.0) - result[1]) < 0.75\n            assert abs(bond.rate(curves=curve_, metric=\"clean_price\") - result[2]) < 0.03\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"forward_settlement\", \"expected\"),\n        [\n            (dt(2024, 6, 27), dt(2024, 6, 28), 100.002503),\n            (dt(2024, 6, 27), dt(2024, 6, 29), 100.005596),\n            (dt(2024, 6, 27), dt(2024, 6, 30), 100.007805),\n            (dt(2024, 6, 27), dt(2024, 7, 1), 100.010140),\n            (dt(2024, 6, 27), dt(2024, 7, 2), 100.012475),\n            (dt(2024, 6, 29), dt(2024, 7, 1), 100.004550),\n        ],\n    )\n    def test_fwd_from_repo_ex_div_and_holidays(self, settlement, forward_settlement, expected):\n        bond = FixedRateBond(dt(2023, 12, 31), dt(2025, 12, 31), fixed_rate=4.25, spec=\"us_gb\")\n        result = bond.fwd_from_repo(\n            price=100.0,\n            settlement=settlement,\n            forward_settlement=forward_settlement,\n            repo_rate=5.0,\n            convention=\"Act360\",\n        )\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"forward_settlement\", \"fwd_price\"),\n        [\n            (dt(2024, 6, 27), dt(2024, 6, 28), 100.002503),\n            (dt(2024, 6, 27), dt(2024, 6, 29), 100.005596),\n            (dt(2024, 6, 27), dt(2024, 6, 30), 100.007805),\n            (dt(2024, 6, 27), dt(2024, 7, 1), 100.010140),\n            (dt(2024, 6, 27), dt(2024, 7, 2), 100.012475),\n            (dt(2024, 6, 29), dt(2024, 7, 1), 100.004550),\n        ],\n    )\n    def test_repo_from_fwd_ex_div_and_holidays(self, settlement, forward_settlement, fwd_price):\n        bond = FixedRateBond(dt(2023, 12, 31), dt(2025, 12, 31), fixed_rate=4.25, spec=\"us_gb\")\n        result = bond.repo_from_fwd(\n            price=100.0,\n            settlement=settlement,\n            forward_settlement=forward_settlement,\n            forward_price=fwd_price,\n            convention=\"Act360\",\n        )\n        assert abs(result - 5.00) < 2e-4\n\n    def test_183d_ytm(self):\n        bond_base = FixedRateBond(dt(2000, 1, 1), dt(2001, 1, 1), fixed_rate=5.0, spec=\"us_gb\")\n        bond_test = FixedRateBond(\n            dt(2000, 1, 1), dt(2001, 1, 1), fixed_rate=5.0, spec=\"us_gb\", frequency=\"183D\"\n        )\n        expected = bond_base.ytm(100, dt(2000, 1, 1))\n        result = bond_test.ytm(100, dt(2000, 1, 1))\n        assert abs(expected - result) < 1e-5\n\n    def test_long_back_stub_split_accrued(self):\n        bond = FixedRateBond(\n            dt(2000, 1, 1), dt(2001, 2, 15), fixed_rate=20.0, spec=\"us_gb\", stub=\"LongBack\"\n        )\n        accrued = bond.accrued(dt(2001, 1, 15))\n        approximation = (dt(2001, 1, 15) - dt(2000, 7, 1)).days / 365 * 20.0\n        assert abs(accrued - approximation) < 1e-1\n\n    def test_long_back_front_stubs_split_accrued(self):\n        bond = FixedRateBond(\n            dt(2000, 1, 1),\n            dt(2002, 2, 15),\n            front_stub=dt(2000, 9, 8),\n            fixed_rate=20.0,\n            spec=\"us_gb\",\n            stub=\"LongBack\",\n        )\n        accrued = bond.accrued(dt(2002, 1, 15))\n        approximation = (dt(2002, 1, 15) - dt(2001, 3, 8)).days / 365 * 20.0\n        assert abs(accrued - approximation) < 1e-1\n\n        price = bond.price(ytm=20.0, settlement=dt(2002, 1, 15))\n        assert abs(price - 100.0) < 5e-1\n\n    def test_coupon_setter(self):\n        frb = FixedRateBond(dt(2000, 1, 1), dt(2005, 1, 1), fixed_rate=2.0, spec=\"uk_gb\")\n        frb.fixed_rate = 3.0\n        assert frb.fixed_rate == 3.0\n        assert frb.kwargs.leg1[\"fixed_rate\"] == 3.0\n\n\nclass TestIndexFixedRateBond:\n    def test_fixed_rate_bond_price(self) -> None:\n        # test pricing functions against Nominal Gilt Example prices from UK DMO\n        # these prices should be equivalent for the REAL component of Index Bonds\n        bond = IndexFixedRateBond(\n            dt(1995, 1, 1),\n            dt(2015, 12, 7),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=8,\n            ex_div=7,\n            calendar=\"ldn\",\n            index_base=100.0,\n        )\n        assert abs(bond.price(4.445, dt(1999, 5, 24), True) - 145.012268) < 1e-6\n        assert abs(bond.price(4.445, dt(1999, 5, 26), True) - 145.047301) < 1e-6\n        assert abs(bond.price(4.445, dt(1999, 5, 27), True) - 141.070132) < 1e-6\n        assert abs(bond.price(4.445, dt(1999, 6, 7), True) - 141.257676) < 1e-6\n\n        bond = IndexFixedRateBond(\n            dt(1997, 1, 1),\n            dt(2004, 11, 26),\n            \"S\",\n            convention=\"ActActICMA\",\n            fixed_rate=6.75,\n            ex_div=7,\n            calendar=\"ldn\",\n            index_base=100.0,\n        )\n        assert abs(bond.price(4.634, dt(1999, 5, 10), True) - 113.315543) < 1e-6\n        assert abs(bond.price(4.634, dt(1999, 5, 17), True) - 113.415969) < 1e-6\n        assert abs(bond.price(4.634, dt(1999, 5, 18), True) - 110.058738) < 1e-6\n        assert abs(bond.price(4.634, dt(1999, 5, 26), True) - 110.170218) < 1e-6\n\n    @pytest.mark.skip(reason=\"Frequency of zero calculates but is wrong. Docs do not allow.\")\n    def test_fixed_rate_bond_zero_frequency_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`frequency` must be provided\"):\n            IndexFixedRateBond(\n                dt(1999, 5, 7),\n                dt(2002, 12, 7),\n                \"Z\",\n                convention=\"ActActICMA\",\n                fixed_rate=1.0,\n            )\n\n    def test_fixed_rate_bond_no_amortization(self) -> None:\n        with pytest.raises(TypeError, match=\"got an unexpected keyword argument 'amortization\"):\n            IndexFixedRateBond(\n                effective=dt(1998, 12, 7),\n                termination=dt(2015, 12, 7),\n                frequency=\"S\",\n                calendar=\"ldn\",\n                currency=\"gbp\",\n                convention=\"ActActICMA\",\n                ex_div=7,\n                fixed_rate=8.0,\n                notional=-100,\n                amortization=100,\n                index_base=100.0,\n            )\n\n    def test_fixed_rate_bond_rate_raises(self) -> None:\n        gilt = IndexFixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            notional=-100,\n            index_base=100.0,\n        )\n        curve = Curve({dt(1998, 12, 7): 1.0, dt(2015, 12, 7): 0.50})\n        with pytest.raises(ValueError, match=\"`metric` must be in\"):\n            gilt.rate(\n                curves=[\n                    Curve({dt(1992, 1, 1): 1.0, dt(2070, 1, 1): 0.13}, index_base=100.0),\n                    curve,\n                ],\n                metric=\"bad_metric\",\n            )\n\n    def test_initialisation_rate_metric(self) -> None:\n        gilt = IndexFixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            settle=0,\n            index_base=100.0,\n            index_lag=3,\n            metric=\"ytm\",\n        )\n        disc_curve = Curve(\n            {dt(1998, 12, 9): 1.0, dt(2015, 12, 7): 0.50}, index_base=100.0, index_lag=3\n        )\n        curve = Curve({dt(1998, 12, 1): 1.0, dt(2015, 12, 7): 0.50}, index_base=100.0, index_lag=3)\n        clean_price = gilt.rate(curves=[curve, disc_curve], metric=\"clean_price\")\n        expected = gilt.ytm(price=clean_price, settlement=dt(1998, 12, 9))\n        result = gilt.rate(curves=[curve, disc_curve])  # default metric is \"ytm\"\n        assert abs(result - expected) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"i_fixings\", \"expected\"),\n        [\n            (NoInput(0), 1.161227269),\n            (\"index_series\", (90 + 14 / 30 * 200) / 95),\n        ],\n    )\n    def test_index_ratio(self, i_fixings, expected) -> None:\n        if isinstance(i_fixings, str):\n            fixings.add(\"index_series\", Series([90.0, 290], index=[dt(2022, 1, 1), dt(2022, 2, 1)]))\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_lag=3,\n            index_base=110.0,\n            interpolation=\"linear_index\",\n        )\n        bond = IndexFixedRateBond(\n            dt(2022, 1, 1),\n            \"9m\",\n            \"Q\",\n            convention=\"ActActICMA\",\n            fixed_rate=4,\n            ex_div=0,\n            calendar=\"ldn\",\n            index_base=95.0,\n            index_fixings=i_fixings,\n            index_method=\"daily\",\n            index_lag=3,\n        )\n        result = bond.index_ratio(settlement=dt(2022, 4, 15), index_curve=i_curve)\n        if isinstance(i_fixings, str):\n            fixings.pop(\"index_series\")\n        assert abs(result - expected) < 1e-5\n\n    @pytest.mark.skip(\n        reason=\"This will calculate from the curve but will not be aligned with the specific list \"\n        \"fixings, but since list fixings are not recommended in the documentation and the\"\n        \"advice is to use a `fixings` object then this is OK.\"\n    )\n    def test_index_ratio_raises_float_index_fixings(self) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_lag=3,\n            index_base=110.0,\n            interpolation=\"linear_index\",\n        )\n        bond = IndexFixedRateBond(\n            dt(2022, 1, 1),\n            \"9m\",\n            \"Q\",\n            convention=\"ActActICMA\",\n            fixed_rate=4,\n            ex_div=0,\n            calendar=\"ldn\",\n            index_base=95.0,\n            index_fixings=[100.0, 200.0],\n            index_method=\"daily\",\n        )\n        # with pytest.raises(TypeError, match=\"`index_fixings` must be of type: Str, Series, Dual\"):\n        bond.index_ratio(settlement=dt(2022, 4, 15), curve=i_curve)\n\n    def test_fixed_rate_bond_npv_private(self) -> None:\n        # this test shadows 'fixed_rate_bond_npv' but extends it for projection\n        curve = Curve({dt(2004, 11, 25): 1.0, dt(2010, 11, 25): 1.0, dt(2015, 12, 7): 0.75})\n        index_curve = Curve(\n            {dt(2004, 11, 25): 1.0, dt(2034, 1, 1): 1.0},\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        gilt = IndexFixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            notional=-100,\n            settle=0,\n            index_base=50.0,\n            index_lag=3,\n            index_method=\"daily\",\n        )\n        with pytest.warns(UserWarning):\n            result = gilt.npv(\n                curves=[index_curve, curve], settlement=dt(2010, 11, 27), forward=dt(2010, 11, 25)\n            )\n            expected = 109.229489312983 * 2.0  # npv should match associated test\n            assert abs(result - expected) < 1e-6\n\n    def test_index_base_forecast(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_lag=3,\n            index_base=95.0,\n            interpolation=\"linear_index\",\n        )\n        bond = IndexFixedRateBond(\n            dt(2022, 1, 1),\n            \"9m\",\n            \"Q\",\n            convention=\"ActActICMA\",\n            fixed_rate=4,\n            ex_div=0,\n            calendar=NoInput(0),\n            index_method=\"daily\",\n            settle=0,\n        )\n        cashflows = bond.cashflows(curves=[i_curve, curve])\n        for i in range(4):\n            assert cashflows.iloc[i][\"Index Base\"] == 95.0\n\n        result = bond.npv(curves=[i_curve, curve])\n        expected = -1006875.3812\n        assert abs(result - expected) < 1e-4\n\n        result = bond.rate(curves=[i_curve, curve], metric=\"index_dirty_price\")\n        assert abs(result * -1e4 - expected) < 1e-4\n\n    def test_fixed_rate_bond_fwd_rate(self) -> None:\n        gilt = IndexFixedRateBond(\n            effective=dt(1998, 12, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            fixed_rate=8.0,\n            settle=0,\n            index_base=50.0,\n            index_lag=3,\n        )\n        curve = Curve({dt(1998, 12, 9): 1.0, dt(2015, 12, 7): 0.50})\n        i_curve = Curve(\n            {dt(1998, 12, 1): 1.0, dt(2015, 12, 7): 1.0},\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        clean_price = gilt.rate(curves=[i_curve, curve], metric=\"clean_price\")\n        index_clean_price = gilt.rate(curves=[i_curve, curve], metric=\"index_clean_price\")\n        assert abs(index_clean_price * 0.5 - clean_price) < 1e-3\n\n        result = gilt.rate(\n            curves=[i_curve, curve],\n            metric=\"clean_price\",\n            settlement=dt(1998, 12, 9),\n            # forward\n        )\n        assert abs(result - clean_price) < 1e-8\n        result = gilt.rate(\n            curves=[i_curve, curve],\n            metric=\"index_clean_price\",\n            settlement=dt(1998, 12, 9),\n            # forward\n        )\n        assert abs(result * 0.5 - clean_price) < 1e-8\n\n        result = gilt.rate(curves=[i_curve, curve], metric=\"dirty_price\")\n        expected = clean_price + gilt.accrued(dt(1998, 12, 9))\n        assert result == expected\n        result = gilt.rate(\n            curves=[i_curve, curve],\n            metric=\"dirty_price\",\n            settlement=dt(1998, 12, 9),\n        )\n        assert abs(result - clean_price - gilt.accrued(dt(1998, 12, 9))) < 1e-8\n        result = gilt.rate(\n            curves=[i_curve, curve],\n            metric=\"index_dirty_price\",\n            settlement=dt(1998, 12, 9),\n        )\n        assert abs(result * 0.5 - clean_price - gilt.accrued(dt(1998, 12, 9))) < 1e-8\n\n        result = gilt.rate(curves=[i_curve, curve], metric=\"ytm\")\n        expected = gilt.ytm(clean_price, dt(1998, 12, 9), False)\n        assert abs(result - expected) < 1e-8\n\n    def test_base_setting_and_index_ratio(self):\n        # GB00BMY62Z61\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name,\n            Series(\n                index=[\n                    dt(2025, 3, 1),\n                    dt(2025, 4, 1),\n                    dt(2025, 5, 1),\n                    dt(2025, 6, 1),\n                    dt(2025, 7, 1),\n                    dt(2025, 8, 1),\n                    dt(2025, 9, 1),\n                    dt(2025, 10, 1),\n                ],\n                data=[395.3, 402.2, 402.9, 404.5, 406.2, 407.7, 406.1, 407.4],\n            ),\n        )\n        gilt = IndexFixedRateBond(\n            effective=dt(2025, 6, 11),\n            termination=dt(2038, 9, 22),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            modifier=\"None\",\n            ex_div=7,\n            fixed_rate=1.75,\n            settle=0,\n            index_fixings=name,\n            index_lag=3,\n            index_method=\"daily\",\n        )\n\n        # these index base and index ratio are calculated externally and verified here\n        assert gilt.leg1.periods[0].index_params.index_base.value == 397.60\n        index_ratio = gilt.index_ratio(settlement=dt(2025, 9, 12), index_curve=NoInput(0))\n        fixings.pop(name)\n        assert abs(index_ratio - 1.018920) < 1e-5\n\n    def test_accrued_and_indexed_accrued(self):\n        # GB00BMY62Z61\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name,\n            Series(\n                index=[\n                    dt(2025, 3, 1),\n                    dt(2025, 4, 1),\n                    dt(2025, 5, 1),\n                    dt(2025, 6, 1),\n                    dt(2025, 7, 1),\n                    dt(2025, 8, 1),\n                    dt(2025, 9, 1),\n                    dt(2025, 10, 1),\n                ],\n                data=[395.3, 402.2, 402.9, 404.5, 406.2, 407.7, 406.1, 407.4],\n            ),\n        )\n        gilt = IndexFixedRateBond(\n            effective=dt(2025, 6, 11),\n            termination=dt(2038, 9, 22),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            modifier=\"None\",\n            ex_div=7,\n            fixed_rate=1.75,\n            settle=0,\n            index_fixings=name,\n            index_lag=3,\n            index_method=\"daily\",\n        )\n\n        accrued = gilt.accrued(settlement=dt(2025, 9, 12))\n        index_ratio = gilt.index_ratio(settlement=dt(2025, 9, 12), index_curve=NoInput(0))\n        indexed_accrued = accrued * index_ratio\n        # this indexed accrued is calculated externally and verified here\n        assert abs(indexed_accrued + 0.048454076) < 1e-7\n        assert abs(gilt.accrued(settlement=dt(2025, 9, 12), indexed=True) - indexed_accrued) < 1e-7\n\n        fixings.pop(name)\n\n    @pytest.mark.parametrize(\n        (\"price\", \"indexed\", \"dirty\", \"expected\"),\n        [\n            (99.423682, True, True, 100.2169930),\n            (99.1924173, True, False, 99.9007374),\n            (98.1322608, False, True, 98.0424122),\n            (97.904000, False, False, 97.733020),\n        ],\n    )\n    def test_fwd_from_repo(self, price, indexed, dirty, expected):\n        # GB00BMY62Z61\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name,\n            Series(\n                index=[\n                    dt(2025, 3, 1),\n                    dt(2025, 4, 1),\n                    dt(2025, 5, 1),\n                    dt(2025, 6, 1),\n                    dt(2025, 7, 1),\n                    dt(2025, 8, 1),\n                    dt(2025, 9, 1),\n                    dt(2025, 10, 1),\n                ],\n                data=[395.3, 402.2, 402.9, 404.5, 406.2, 407.7, 406.1, 407.4],\n            ),\n        )\n        gilt = IndexFixedRateBond(\n            effective=dt(2025, 6, 11),\n            termination=dt(2038, 9, 22),\n            fixed_rate=1.75,\n            spec=\"uk_gbi\",\n            index_fixings=name,\n        )\n        fwd = gilt.fwd_from_repo(\n            price=price,\n            settlement=dt(2025, 7, 29),\n            forward_settlement=dt(2025, 11, 25),\n            repo_rate=4.00,\n            convention=\"act365F\",\n            dirty=dirty,\n            indexed=indexed,\n        )\n        fixings.pop(name)\n        assert abs(fwd - expected) < 5e-4\n\n    @pytest.mark.parametrize(\n        (\"price\", \"indexed\", \"dirty\", \"fwd_price\"),\n        [\n            (99.423682, True, True, 100.2169930),\n            (99.1924173, True, False, 99.9007374),\n            (98.1322608, False, True, 98.0424122),\n            (97.904000, False, False, 97.733020),\n        ],\n    )\n    def test_repo_from_fwd(self, price, indexed, dirty, fwd_price):\n        # GB00BMY62Z61\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name,\n            Series(\n                index=[\n                    dt(2025, 3, 1),\n                    dt(2025, 4, 1),\n                    dt(2025, 5, 1),\n                    dt(2025, 6, 1),\n                    dt(2025, 7, 1),\n                    dt(2025, 8, 1),\n                    dt(2025, 9, 1),\n                    dt(2025, 10, 1),\n                ],\n                data=[395.3, 402.2, 402.9, 404.5, 406.2, 407.7, 406.1, 407.4],\n            ),\n        )\n        gilt = IndexFixedRateBond(\n            effective=dt(2025, 6, 11),\n            termination=dt(2038, 9, 22),\n            fixed_rate=1.75,\n            spec=\"uk_gbi\",\n            index_fixings=name,\n        )\n        repo = gilt.repo_from_fwd(\n            price=price,\n            settlement=dt(2025, 7, 29),\n            forward_settlement=dt(2025, 11, 25),\n            forward_price=fwd_price,\n            convention=\"act365F\",\n            dirty=dirty,\n            indexed=indexed,\n        )\n        fixings.pop(name)\n        assert abs(repo - 4.00) < 2e-3\n\n    @pytest.mark.parametrize(\n        (\"indexed_price\", \"indexed_ytm\"),\n        [(False, False), (False, True), (True, False), (True, True)],\n    )\n    def test_duration_index_linked_finite_diff(self, indexed_price, indexed_ytm):\n        # GB00BMY62Z61\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name,\n            Series(\n                index=[\n                    dt(2025, 3, 1),\n                    dt(2025, 4, 1),\n                    dt(2025, 5, 1),\n                    dt(2025, 6, 1),\n                    dt(2025, 7, 1),\n                    dt(2025, 8, 1),\n                    dt(2025, 9, 1),\n                    dt(2025, 10, 1),\n                ],\n                data=[395.3, 402.2, 402.9, 404.5, 406.2, 407.7, 406.1, 407.4],\n            ),\n        )\n        index_curve = Curve({dt(2025, 10, 1): 1.0, dt(2045, 10, 1): 1.0}, index_base=407.4).shift(\n            100\n        )\n        gilt = IndexFixedRateBond(\n            effective=dt(2025, 6, 11),\n            termination=dt(2038, 9, 22),\n            fixed_rate=1.75,\n            spec=\"uk_gbi\",\n            index_fixings=name,\n        )\n        value = gilt.duration(\n            ytm=2.00,\n            settlement=dt(2025, 7, 29),\n            metric=\"risk\",\n            indexed_price=indexed_price,\n            indexed_ytm=indexed_ytm,\n            index_curve=index_curve,\n        )\n\n        # finite diff test:\n        original_price = gilt.price(\n            ytm=2.00,\n            settlement=dt(2025, 7, 29),\n            indexed_price=indexed_price,\n            indexed_ytm=indexed_ytm,\n            index_curve=index_curve,\n            dirty=True,\n        )\n        bumped_price = gilt.price(\n            ytm=1.999,\n            settlement=dt(2025, 7, 29),\n            indexed_ytm=indexed_ytm,\n            indexed_price=indexed_price,\n            index_curve=index_curve,\n            dirty=True,\n        )\n        expected = (bumped_price - original_price) * 1000.0\n        assert abs(value - expected) < 1e-3\n\n        ## Test modified\n        modified = gilt.duration(\n            ytm=2.00,\n            settlement=dt(2025, 7, 29),\n            metric=\"modified\",\n            indexed_price=indexed_price,\n            indexed_ytm=indexed_ytm,\n            index_curve=index_curve,\n        )\n        assert abs(value / original_price * 100.0 - modified) < 1e-6\n\n        # Test macauley\n        macauley = gilt.duration(\n            ytm=2.00,\n            settlement=dt(2025, 7, 29),\n            metric=\"duration\",\n            indexed_price=indexed_price,\n            indexed_ytm=indexed_ytm,\n            index_curve=index_curve,\n        )\n        assert abs(modified * (1 + 0.02 / 2) - macauley) < 1e-6\n        fixings.pop(name)\n\n    # TODO: implement these tests\n    #\n    # def test_convexity(self):\n    #     assert False\n\n    def test_latest_fixing(self) -> None:\n        # this is German government inflation bond with fixings given for a specific settlement\n        # calculation\n\n        ibnd = IndexFixedRateBond(\n            effective=dt(2021, 2, 11),\n            front_stub=dt(2022, 4, 15),\n            termination=dt(2033, 4, 15),\n            convention=\"ActActICMA\",\n            calendar=\"tgt\",\n            frequency=\"A\",\n            index_lag=3,\n            index_base=124.17000 / 1.18851,  # implying from 1st Jan 2024 on webpage\n            index_method=\"daily\",\n            payment_lag=0,\n            currency=\"eur\",\n            fixed_rate=0.1,\n            ex_div=1,\n            settle=1,\n            index_fixings=Series(data=[124.17, 123.46], index=[dt(2024, 1, 1), dt(2024, 2, 1)]),\n        )\n        result = ibnd.ytm(price=100.32, settlement=dt(2024, 1, 5))\n        expected = 0.065\n        assert (result - expected) < 1e-2\n\n    def test_rate_with_fx_is_same(self) -> None:\n        usd = Curve(nodes={dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.9, dt(2010, 1, 5): 0.8})\n        gbp = Curve(nodes={dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.9, dt(2010, 1, 5): 0.8})\n        gbpi = Curve(\n            nodes={dt(2000, 1, 1): 1.0, dt(2010, 1, 1): 0.95},\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        fxf = FXForwards(\n            fx_rates=FXRates({\"gbpusd\": 1.25}, settlement=dt(2000, 1, 1)),\n            fx_curves={\"gbpgbp\": gbp, \"usdusd\": usd, \"gbpusd\": gbp},\n        )\n        result = IndexFixedRateBond(\n            dt(2000, 1, 1),\n            \"5y\",\n            index_base=100.5,\n            spec=\"uk_gbi\",\n            fixed_rate=1.0,\n        ).rate(curves=[gbpi, gbp], metric=\"clean_price\")\n        result2 = IndexFixedRateBond(\n            dt(2000, 1, 1),\n            \"5y\",\n            index_base=100.5,\n            spec=\"uk_gbi\",\n            fixed_rate=1.0,\n        ).rate(curves=[gbpi, gbp], metric=\"clean_price\", fx=fxf)\n        assert result == result2\n\n    def test_spec_kwargs(self) -> None:\n        # GH346\n        fixings = Series(data=[314.175, 314.54], index=[dt(2024, 9, 1), dt(2024, 10, 1)])\n        tii_0728 = IndexFixedRateBond(\n            effective=dt(2018, 7, 31),\n            termination=dt(2028, 7, 15),\n            spec=\"us_gb_tsy\",\n            fixed_rate=0.75,\n            notional=-100e6,\n            curves=[\"sofr\", \"sofr\"],\n            index_lag=3,\n            index_method=\"monthly\",\n            index_base=251.01658,\n            index_fixings=fixings,\n        )\n        result = tii_0728.ytm(100, dt(2024, 8, 26))\n        assert (result - 0.749935) < 1e-5\n\n    def test_custom_calc_mode(self):\n        cm = BondCalcMode(\n            settle_accrual=\"linear_days\",\n            ytm_accrual=\"linear_days\",\n            v1=\"compounding\",\n            v2=\"regular\",\n            v3=\"compounding\",\n            c1=\"cashflow\",\n            ci=\"cashflow\",\n            cn=\"cashflow\",\n        )\n        bond = IndexFixedRateBond(\n            effective=dt(2001, 1, 1),\n            termination=\"10y\",\n            frequency=\"s\",\n            calendar=\"ldn\",\n            convention=\"ActActICMA\",\n            modifier=\"none\",\n            settle=1,\n            calc_mode=cm,\n            fixed_rate=1.0,\n            index_base=100.0,\n        )\n        bond2 = IndexFixedRateBond(\n            dt(2001, 1, 1), \"10y\", spec=\"uk_gb\", fixed_rate=1.0, index_base=100.0\n        )\n        assert bond.price(3.0, dt(2002, 3, 4)) == bond2.price(3.0, dt(2002, 3, 4))\n        assert bond.accrued(dt(2002, 3, 4)) == bond2.accrued(dt(2002, 3, 4))\n\n    def test_fixed_rate_getter_and_setter(self):\n        tii_0728 = IndexFixedRateBond(\n            effective=dt(2018, 7, 31),\n            termination=dt(2028, 7, 15),\n            spec=\"us_gbi\",\n            fixed_rate=0.75,\n        )\n        assert tii_0728.fixed_rate == 0.75\n        tii_0728.fixed_rate = 1.90\n        assert tii_0728.fixed_rate == 1.90\n\n    def test_no_fixed_rate_raises(self):\n        with pytest.raises(ValueError, match=\"`fixed_rate` must be provided for IndexFixedRateBo\"):\n            IndexFixedRateBond(\n                effective=dt(2018, 7, 31),\n                termination=dt(2028, 7, 15),\n                spec=\"us_gbi\",\n            )\n\n    def test_parse_curves(self, curve):\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_lag=3,\n            index_base=95.0,\n            interpolation=\"linear_index\",\n        )\n        bond = IndexFixedRateBond(\n            dt(2022, 1, 1),\n            \"9m\",\n            \"Q\",\n            convention=\"ActActICMA\",\n            fixed_rate=4,\n            ex_div=0,\n            calendar=NoInput(0),\n            index_method=\"daily\",\n            settle=0,\n        )\n        result1 = bond.npv(curves=[i_curve, curve])\n        result2 = bond.npv(curves={\"index_curve\": i_curve, \"disc_curve\": curve})\n        expected = -1006875.3812\n        assert abs(result1 - expected) < 1e-5\n        assert abs(result1 - result2) < 1e-5\n\n    def test_rate_docs(self):\n        disc_curve = Curve(\n            nodes={dt(2025, 7, 28): 1.0, dt(2045, 7, 25): 1.0}, convention=\"act365f\"\n        ).shift(250)  # curve begins at 0% and gets shifted by 250 Act365F O/N basis points\n        index_curve = Curve(\n            nodes={dt(2025, 5, 1): 1.0, dt(2045, 5, 1): 1.0},\n            convention=\"act365f\",\n            index_lag=0,\n            index_base=402.9,\n        ).shift(100)  # curves begins at 0% and gets shifted by 100 Ac6t365f O/N basis points\n        fixings.add(\n            \"UK_RPI_987\",\n            Series(\n                index=[dt(2025, 3, 1), dt(2025, 4, 1), dt(2025, 5, 1)], data=[395.3, 402.2, 402.9]\n            ),\n        )\n        ukti = IndexFixedRateBond(  # ISIN: GB00BMY62Z61\n            effective=dt(2025, 6, 11),\n            termination=dt(2038, 9, 22),\n            fixed_rate=1.75,\n            spec=\"uk_gbi\",\n            index_fixings=\"UK_RPI_987\",\n        )\n        a1 = ukti.rate(\n            curves=[index_curve, disc_curve], metric=\"clean_price\"\n        )  # settles T+1 i.e. 29th July\n        a2 = ukti.rate(curves=[index_curve, disc_curve], metric=\"dirty_price\")\n        a3 = ukti.rate(curves=[index_curve, disc_curve], metric=\"index_clean_price\")\n        a4 = ukti.rate(curves=[index_curve, disc_curve], metric=\"index_dirty_price\")\n        a5 = ukti.rate(curves=[index_curve, disc_curve], metric=\"ytm\")\n        a6 = ukti.accrued(settlement=dt(2025, 7, 29))\n        a7 = ukti.accrued(settlement=dt(2025, 7, 29), indexed=True)\n        a8 = ukti.rate(curves=[index_curve, disc_curve], metric=\"index_ytm\")\n\n        assert abs(a1 - 102.90237315163287) < 1e-5\n        assert abs(a2 - 103.13063402119809) < 1e-5\n        assert abs(a3 - 104.25652750721756) < 1e-5\n        assert abs(a4 - 104.487792199156) < 1e-5\n        assert abs(a5 - 1.5058915118424034) < 1e-5\n        assert abs(a6 - 0.228260) < 1e-5\n        assert abs(a7 - 0.231264) < 1e-5\n        assert abs(a8 - 2.5174145913908443) < 1e-5\n\n        fixings.pop(\"UK_RPI_987\")\n\n    def test_index_ytm(self):\n        fixings.add(\n            \"UK_RPI_9843\",\n            Series(\n                index=[\n                    dt(2025, 1, 1),\n                    dt(2026, 1, 1),\n                    dt(2027, 1, 1),\n                    dt(2028, 1, 1),\n                    dt(2029, 1, 1),\n                    dt(2030, 1, 1),\n                ],\n                data=[100.0, 102, 103, 104, 105, 106],\n            ),\n        )\n        bond = IndexFixedRateBond(\n            effective=dt(2025, 1, 6),\n            termination=dt(2030, 1, 6),\n            roll=6,\n            calendar=\"bus\",\n            convention=\"actacticma\",\n            frequency=\"A\",\n            # index_base=100.0,\n            index_lag=0,\n            index_method=\"monthly\",\n            ex_div=1,\n            fixed_rate=2.0,\n            index_fixings=\"UK_RPI_9843\",\n        )\n        assert bond.leg1.periods[0].index_params.index_base.value == 100.0\n        result = bond.ytm(\n            price=101.9456166,\n            settlement=dt(2026, 1, 6),\n            indexed_price=True,\n            indexed_ytm=True,\n            dirty=True,\n        )\n        expected = 3.00\n        # 101.9456166 = 2 * 1.03/1.03 + 2 * 1.04/1.03**2 + 2 * 1.05/1.03**3 + 102 * 1.06/1.03**4\n        fixings.pop(\"UK_RPI_9843\")\n        assert abs(result - expected) < 1e-6\n\n        result = bond.ytm(\n            price=101.9456166 / 1.02,\n            settlement=dt(2026, 1, 6),\n            indexed_price=False,\n            indexed_ytm=False,\n            dirty=True,\n        )\n        expected = 2.0140070859464996\n        assert abs(result - expected) < 1e-6\n        # clean yield is approximately 1% lower than indexed yield since inflation is approx 1%\n\n    def test_index_ytm2(self):\n        index_curve = Curve(\n            nodes={dt(2025, 5, 1): 1.0, dt(2045, 5, 1): 1.0},\n            convention=\"act365f\",\n            index_lag=0,\n            index_base=402.9,\n        ).shift(100)  # curves begins at 0% and gets shifted by 100 Ac6t365f O/N basis points\n        ukti = IndexFixedRateBond(  # ISIN: GB00BMY62Z61\n            effective=dt(2025, 6, 11),\n            termination=dt(2038, 9, 22),\n            fixed_rate=1.75,\n            spec=\"uk_gbi\",\n            index_base=397.60,\n        )\n\n        prices = [104.62775438373183, 103.24009646398126, 104.3626899720302, 102.97854755093778]\n\n        for i, (dirty, indexed_price) in enumerate(product([True, False], [True, False])):\n            indexed_ytm = ukti.ytm(\n                price=prices[i],\n                settlement=dt(2025, 8, 5),\n                indexed_price=indexed_price,\n                indexed_ytm=True,\n                dirty=dirty,\n                index_curve=index_curve,\n            )\n            assert abs(indexed_ytm - 2.5100000) < 1e-8\n\n        for i, (dirty, indexed_price) in enumerate(product([True, False], [True, False])):\n            unindexed_ytm = ukti.ytm(\n                price=prices[i],\n                settlement=dt(2025, 8, 5),\n                indexed_price=indexed_price,\n                indexed_ytm=False,\n                dirty=dirty,\n                index_curve=index_curve,\n            )\n            assert abs(unindexed_ytm - 1.499260363) < 1e-8\n\n    def test_index_price(self):\n        index_curve = Curve(\n            nodes={dt(2025, 5, 1): 1.0, dt(2045, 5, 1): 1.0},\n            convention=\"act365f\",\n            index_lag=0,\n            index_base=402.9,\n        ).shift(100)  # curves begins at 0% and gets shifted by 100 Ac6t365f O/N basis points\n        ukti = IndexFixedRateBond(  # ISIN: GB00BMY62Z61\n            effective=dt(2025, 6, 11),\n            termination=dt(2038, 9, 22),\n            fixed_rate=1.75,\n            spec=\"uk_gbi\",\n            index_base=397.60,\n        )\n\n        prices_from_indexed_ytm = []\n        for dirty, indexed_price in product([True, False], [True, False]):\n            prices_from_indexed_ytm.append(\n                ukti.price(\n                    ytm=2.5100000,\n                    settlement=dt(2025, 8, 5),\n                    indexed_price=indexed_price,\n                    indexed_ytm=True,\n                    dirty=dirty,\n                    index_curve=index_curve,\n                )\n            )\n        prices_from_unindexed_ytm = []\n        for dirty, indexed_price in product([True, False], [True, False]):\n            prices_from_unindexed_ytm.append(\n                ukti.price(\n                    ytm=1.499260363,\n                    settlement=dt(2025, 8, 5),\n                    indexed_price=indexed_price,\n                    indexed_ytm=False,\n                    dirty=dirty,\n                    index_curve=index_curve,\n                )\n            )\n\n        for p1, p2 in zip(prices_from_indexed_ytm, prices_from_unindexed_ytm):\n            assert abs(p1 - p2) < 1e-8\n\n\nclass TestBill:\n    def test_bill_discount_rate(self) -> None:\n        # test pricing functions against Treasury Bill Example from US Treasury\n        bill = Bill(\n            effective=dt(2004, 1, 22),\n            termination=dt(2004, 2, 19),\n            calendar=\"nyc\",\n            currency=\"usd\",\n            convention=\"Act360\",\n            calc_mode=\"ustb\",\n        )\n\n        assert bill.discount_rate(99.93777, dt(2004, 1, 22)) == 0.8000999999999543\n        assert bill.price(0.800, dt(2004, 1, 22)) == 99.93777777777778\n\n    def test_bill_ytm(self) -> None:\n        bill = Bill(\n            effective=dt(2004, 1, 22),\n            termination=dt(2004, 2, 19),\n            calendar=\"nyc\",\n            currency=\"usd\",\n            convention=\"Act360\",\n            calc_mode=\"ustb\",\n        )\n        # this YTM is equivalent to the FixedRateBond ytm with coupon of 0.0\n        result = bill.ytm(99.937778, dt(2004, 1, 22))\n\n        # TODO this does not match US treasury example because the method is different\n        assert abs(result - 0.814) < 1e-2\n\n    def test_bill_ytm2(self) -> None:\n        # this is a longer than 6m period\n        bill = Bill(\n            effective=dt(1990, 6, 7),\n            termination=dt(1991, 6, 6),\n            convention=\"act360\",\n            calc_mode=\"ustb\",\n        )\n        price = bill.price(7.65, settlement=dt(1990, 6, 7))\n        result = bill.ytm(price, settlement=dt(1990, 6, 7))\n        assert abs(result - 8.237) < 1e-3\n\n    def test_bill_simple_rate(self) -> None:\n        bill = Bill(\n            effective=dt(2004, 1, 22),\n            termination=dt(2004, 2, 19),\n            calendar=\"nyc\",\n            currency=\"usd\",\n            convention=\"Act360\",\n            calc_mode=\"ustb\",\n        )\n        d = dcf(dt(2004, 1, 22), dt(2004, 2, 19), \"Act360\")\n        expected = 100 * (1 / (1 - 0.0080009999999 * d) - 1) / d  # floating point truncation\n        expected = 100 * (100 / 99.93777777777778 - 1) / d\n        result = bill.simple_rate(99.93777777777778, dt(2004, 1, 22))\n        assert abs(result - expected) < 1e-6\n\n    def test_bill_initialised_rate_metric(self) -> None:\n        curve = Curve({dt(2004, 1, 22): 1.00, dt(2005, 1, 22): 0.992})\n\n        bill = Bill(\n            effective=dt(2004, 1, 22),\n            termination=dt(2004, 2, 19),\n            calendar=\"nyc\",\n            currency=\"usd\",\n            convention=\"Act360\",\n            settle=0,\n            calc_mode=\"ustb\",\n            metric=\"simple_rate\",\n        )\n        price = bill.rate(curves=curve, metric=\"price\")\n        expected = bill.simple_rate(price, dt(2004, 1, 22))\n        result = bill.rate(curves=curve)\n        assert abs(result - expected) < 1e-6\n\n    def test_bill_rate(self) -> None:\n        curve = Curve({dt(2004, 1, 22): 1.00, dt(2005, 1, 22): 0.992})\n\n        bill = Bill(\n            effective=dt(2004, 1, 22),\n            termination=dt(2004, 2, 19),\n            calendar=\"nyc\",\n            currency=\"usd\",\n            convention=\"Act360\",\n            settle=0,\n            calc_mode=\"ustb\",\n        )\n\n        result = bill.rate(curves=curve, metric=\"price\")\n        expected = 99.9385705675\n        assert abs(result - expected) < 1e-6\n\n        result = bill.rate(curves=curve, metric=\"discount_rate\")\n        expected = bill.discount_rate(99.9385705675, dt(2004, 1, 22))\n        assert abs(result - expected) < 1e-6\n\n        result = bill.rate(curves=curve, metric=\"simple_rate\")\n        expected = bill.simple_rate(99.9385705675, dt(2004, 1, 22))\n        assert abs(result - expected) < 1e-6\n\n        result = bill.rate(curves=curve, metric=\"ytm\")\n        expected = bill.ytm(99.9385705675, dt(2004, 1, 22))\n        assert abs(result - expected) < 1e-6\n\n        bill.kwargs.meta[\"settle\"] = 2  # set the bill to T+2 settlement and re-run the calculations\n\n        result = bill.rate(curves=curve, metric=\"price\")\n        expected = 99.94734388985547\n        assert abs(result - expected) < 1e-6\n\n        result = bill.rate(curves=curve, metric=\"discount_rate\")\n        expected = bill.discount_rate(99.94734388985547, dt(2004, 1, 26))\n        assert abs(result - expected) < 1e-6\n\n        result = bill.rate(curves=curve, metric=\"simple_rate\")\n        expected = bill.simple_rate(99.94734388985547, dt(2004, 1, 26))\n        assert abs(result - expected) < 1e-6\n\n        result = bill.rate(curves=curve, metric=\"ytm\")\n        expected = bill.ytm(99.94734388985547, dt(2004, 1, 26))\n        assert abs(result - expected) < 1e-6\n\n    def test_bill_default_calc_mode(self) -> None:\n        bill = Bill(\n            effective=dt(2004, 1, 22),\n            termination=dt(2004, 2, 19),\n            calendar=\"nyc\",\n            currency=\"usd\",\n            convention=\"Act360\",\n            settle=0,\n        )\n        assert bill.kwargs.meta[\"calc_mode\"] == US_GBB\n\n    def test_bill_rate_raises(self) -> None:\n        curve = Curve({dt(2004, 1, 22): 1.00, dt(2005, 1, 22): 0.992})\n\n        bill = Bill(\n            effective=dt(2004, 1, 22),\n            termination=dt(2004, 2, 19),\n            calendar=\"nyc\",\n            currency=\"usd\",\n            convention=\"Act360\",\n        )\n\n        with pytest.raises(ValueError, match=\"`metric` must be in\"):\n            bill.rate(curves=curve, metric=\"bad vibes\")\n\n    def test_sgbb(self) -> None:\n        bill = Bill(\n            effective=dt(2023, 3, 15),\n            termination=dt(2024, 3, 20),\n            spec=\"se_gbb\",\n        )\n        result = bill.price(3.498, settlement=dt(2023, 3, 15))\n        expected = 96.520547\n        assert abs(result - expected) < 1e-6\n\n        ytm = bill.ytm(price=96.520547, settlement=dt(2023, 3, 15))\n        assert abs(ytm - 3.5546338) < 1e-5\n\n    # norwegian\n    @pytest.mark.parametrize(\n        (\"e\", \"t\", \"price\", \"y\"),\n        [\n            (dt(2025, 3, 19), dt(2026, 3, 18), 99.38775, 4.01095),\n            (dt(2025, 6, 18), dt(2026, 6, 17), 98.4218, 4.0012),\n            (dt(2025, 9, 17), dt(2026, 9, 16), 97.4707, 3.99),\n            (dt(2025, 12, 17), dt(2026, 12, 16), 96.5409, 3.9705),\n        ],\n    )\n    def test_nogbb(self, e, t, price, y) -> None:\n        # prices obtained from Norges Bank on Friday 16th Jan 2026, settle 20th Jan\n        bill = Bill(effective=e, termination=t, spec=\"no_gbb\")\n        ytm = bill.ytm(price=price, settlement=dt(2026, 1, 20))\n        assert abs(ytm - y) < 5e-5\n\n    def test_text_example(self) -> None:\n        bill = Bill(effective=dt(2023, 5, 17), termination=dt(2023, 9, 26), spec=\"us_gbb\")\n        result = bill.ytm(99.75, settlement=dt(2023, 9, 7))\n        bond = FixedRateBond(\n            effective=dt(2023, 3, 26),\n            termination=dt(2023, 9, 26),\n            fixed_rate=0.0,\n            spec=\"us_gb\",\n        )\n        expected = bond.ytm(99.75, settlement=dt(2023, 9, 7))\n        assert abs(result - expected) < 1e-14\n        assert abs(result - 4.854240865091567) < 1e-7\n\n    @pytest.mark.parametrize(\n        (\"price\", \"tol\"), [(96.0, 1e-6), (95.0, 1e-6), (93.0, 1e-5), (80.0, 1e-2)]\n    )\n    def test_oaspread(self, price, tol) -> None:\n        bill = Bill(\n            effective=dt(1998, 12, 7),\n            termination=dt(1999, 10, 7),\n            spec=\"us_gbb\",\n        )\n        curve = Curve({dt(1998, 12, 7): 1.0, dt(2015, 12, 7): 0.75})\n        # result = bill.rate(curve, metric=\"price\") # = 98.605\n        result = bill.oaspread(curves=curve, price=price)\n        curve_z = curve.shift(result)\n        result = bill.rate(curves=curve_z, metric=\"clean_price\")\n        assert abs(result - price) < tol\n\n    def test_with_fx_supplied(self) -> None:\n        usd = Curve(nodes={dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.9, dt(2010, 1, 5): 0.8})\n        gbp = Curve(nodes={dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.9, dt(2010, 1, 5): 0.8})\n        fxf = FXForwards(\n            fx_rates=FXRates({\"gbpusd\": 1.25}, settlement=dt(2000, 1, 1)),\n            fx_curves={\"gbpgbp\": gbp, \"usdusd\": usd, \"gbpusd\": gbp},\n        )\n        result = Bill(dt(2000, 1, 1), \"3m\", spec=\"us_gbb\").rate(curves=gbp, metric=\"discount_rate\")\n        result2 = Bill(dt(2000, 1, 1), \"3m\", spec=\"us_gbb\").rate(\n            curves=gbp,\n            metric=\"discount_rate\",\n            fx=fxf,\n        )\n        assert result == result2\n\n    def test_duration(self) -> None:\n        b = Bill(dt(2000, 1, 1), \"6m\", frequency=\"A\", spec=\"us_gbb\")\n        result = b.duration(ytm=5.0, settlement=dt(2000, 1, 10), metric=\"duration\")\n        assert result == 0.5170058346378255\n\n        b = Bill(dt(2000, 1, 1), \"6m\", spec=\"us_gbb\")\n        result = b.duration(ytm=5.0, settlement=dt(2000, 1, 10), metric=\"duration\")\n        assert result == 0.5046961719083534\n\n        b = Bill(dt(2000, 1, 1), \"6m\", frequency=\"Q\", spec=\"us_gbb\")\n        result = b.duration(ytm=5.0, settlement=dt(2000, 1, 10), metric=\"duration\")\n        assert result == 0.4985413405436174\n\n    def test_custom_calc_mode(self):\n        from rateslib.instruments.bonds import BillCalcMode\n\n        cm = BillCalcMode(price_type=\"simple\", ytm_clone_kwargs=\"uk_gb\")\n        bill = Bill(\n            effective=dt(2001, 1, 1),\n            termination=\"3m\",\n            calendar=\"ldn\",\n            convention=\"Act365f\",\n            modifier=\"none\",\n            settle=1,\n            calc_mode=cm,\n        )\n        bill2 = Bill(dt(2001, 1, 1), \"3m\", spec=\"uk_gbb\")\n        assert bill.simple_rate(99.0, dt(2001, 2, 4)) == bill2.simple_rate(99.0, dt(2001, 2, 4))\n\n    def test_us_gbb_eom(self):\n        b = Bill(dt(2023, 2, 28), \"3m\", spec=\"us_gbb\")\n        assert b.leg1._regular_periods[0].period_params.end == dt(2023, 5, 31)\n\n    def test_se_gbb_eom(self):\n        b = Bill(dt(2023, 2, 28), \"3m\", spec=\"se_gbb\")\n        assert b.leg1._regular_periods[0].period_params.end == dt(2023, 5, 28)\n\n    def test_act_act_icma(self):\n        # gh 144\n        with pytest.warns(\n            UserWarning,\n            match=\"`frequency` cannot be 'Zero' variant in combination with 'ActActICMA\",\n        ):\n            bill_actacticma = Bill(\n                effective=dt(2024, 2, 29),\n                termination=dt(2024, 5, 29),  # 90 calendar days\n                modifier=\"NONE\",\n                calendar=\"bus\",\n                payment_lag=0,\n                notional=-1000000,\n                currency=\"usd\",\n                convention=\"ACTACTICMA\",\n                settle=0,\n                calc_mode=\"us_gbb\",\n            )\n            assert bill_actacticma.leg1._regular_periods[0].period_params.dcf == 0.2465753424657534\n\n        bill_act360 = Bill(\n            effective=dt(2024, 2, 29),\n            termination=dt(2024, 5, 29),  # 90 calendar days\n            modifier=\"NONE\",\n            calendar=\"bus\",\n            payment_lag=0,\n            notional=-1000000,\n            currency=\"usd\",\n            convention=\"ACT360\",\n            settle=0,\n            calc_mode=\"us_gbb\",\n        )\n        assert bill_act360.leg1._regular_periods[0].period_params.dcf == 0.25\n\n    def test_ex_div(self):\n        b1 = Bill(dt(2000, 1, 3), \"3m\", spec=\"us_gbb\")\n        assert b1.ex_div(dt(200, 4, 3)) is False\n\n        b2 = Bill(dt(2000, 1, 3), \"3m\", ex_div=2, spec=\"us_gbb\")\n        assert b2.ex_div(dt(2000, 4, 3)) is True\n        assert b2.ex_div(dt(2000, 3, 31)) is True\n        assert b2.ex_div(dt(2000, 3, 30)) is False\n\n    def test_bill_roll(self):\n        b1 = Bill(dt(2026, 1, 30), \"6m\", spec=\"us_gbb\", roll=30)\n        b2 = Bill(dt(2026, 1, 30), \"6m\", spec=\"us_gbb\", roll=31)\n        assert b1.leg1.schedule.termination == dt(2026, 7, 30)\n        assert b2.leg1.schedule.termination == dt(2026, 7, 31)\n\n    def test_bill_eom(self):\n        b1 = Bill(dt(2026, 1, 30), \"6m\", spec=\"us_gbb\", eom=False)\n        b2 = Bill(dt(2026, 1, 30), \"6m\", spec=\"us_gbb\", eom=True)\n        assert b1.leg1.schedule.termination == dt(2026, 7, 30)\n        assert b2.leg1.schedule.termination == dt(2026, 7, 31)\n\n\nclass TestFloatRateNote:\n    @pytest.mark.parametrize(\n        (\"curve_spd\", \"method\", \"float_spd\", \"expected\"),\n        [\n            (10, NoInput(0), 0, 10.055032859883),\n            (500, NoInput(0), 0, 508.93107035125325),\n            (-200, NoInput(0), 0, -200.053341848676),\n            (10, \"isda_compounding\", 0, 10.00000120),\n            (500, \"isda_compounding\", 0, 499.9999999997),\n            (-200, \"isda_compounding\", 0, -199.99999999),\n            (10, NoInput(0), 25, 10.055032859883),\n            (500, NoInput(0), 250, 508.93107035125325),\n            (10, \"isda_compounding\", 25, 10.00000120),\n            (500, \"isda_compounding\", 250, 499.99999999975523),\n            (10, NoInput(0), -25, 10.055032859883),\n            (500, NoInput(0), -250, 508.93107035125325),\n            (10, \"isda_compounding\", -25, 10.00000120),\n            (500, \"isda_compounding\", -250, 499.9999999997),\n        ],\n    )\n    def test_float_rate_bond_rate_spread(self, curve_spd, method, float_spd, expected) -> None:\n        \"\"\"\n        When a DF curve is shifted it bumps daily rates.\n        But under the \"none_simple\" compounding method this does not compound daily\n        therefore the `float_spread` should be slightly higher than the bumped curve.\n        When the method is \"isda_compounding\" this closely matches the bumping method\n        of the curve.\n        \"\"\"\n\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=0,\n            settle=0,\n            float_spread=float_spd,\n            spread_compound_method=method,\n        )\n        curve = Curve({dt(2007, 1, 1): 1.0, dt(2017, 1, 1): 0.9}, convention=\"Act365f\")\n        disc_curve = curve.shift(curve_spd)\n        result = bond.rate(curves=[curve, disc_curve], metric=\"spread\")\n        assert abs(result - expected) < 1e-4\n\n        bond.float_spread = result\n        validate = bond.npv(curves=[curve, disc_curve])\n        assert abs(validate + bond.leg1.settlement_params.notional) < 0.30 * abs(curve_spd)\n\n    @pytest.mark.parametrize(\n        (\"curve_spd\", \"method\", \"float_spd\", \"expected\"),\n        [\n            (10, \"isda_compounding\", 0, 10.00000120),\n        ],\n    )\n    def test_float_rate_bond_rate_spread_fx(self, curve_spd, method, float_spd, expected) -> None:\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=0,\n            settle=0,\n            float_spread=float_spd,\n            spread_compound_method=method,\n        )\n        curve = Curve({dt(2007, 1, 1): 1.0, dt(2017, 1, 1): 0.9}, convention=\"Act365f\")\n        disc_curve = curve.shift(curve_spd)\n        fxr = FXRates({\"usdnok\": 10.0}, settlement=dt(2007, 1, 1))\n        result = bond.rate(\n            curves=[curve, disc_curve],\n            metric=\"spread\",\n            fx=fxr,\n        )\n        assert abs(result - expected) < 1e-4\n\n        bond.float_spread = result\n        validate = bond.npv(curves=[curve, disc_curve], fx=fxr)\n        assert abs(validate + bond.leg1.settlement_params.notional) < 0.30 * abs(curve_spd)\n\n    def test_float_rate_bond_accrued(self) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_1B\", Series(2.0, index=date_range(dt(2009, 12, 1), dt(2010, 3, 1))))\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=3,\n            float_spread=100,\n            fixing_method=FloatFixingMethod.RFRObservationShift(5),\n            rate_fixings=name,\n            spread_compound_method=\"none_simple\",\n        )\n        result = bond.accrued(dt(2010, 3, 3))\n        expected = 0.5019199020076  # 3% * 2 / 12\n        fixings.pop(name + \"_1B\")\n        assert abs(result - expected) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"metric\", \"spd\", \"exp\"),\n        [\n            (\"clean_price\", 0.0, 100.0),\n            (\"dirty_price\", 0.0, 100.0),\n            (\"clean_price\", 10.0, 99.99982764447981),  # compounding diff between shift\n            (\"dirty_price\", 10.0, 100.0165399732469),\n        ],\n    )\n    def test_float_rate_bond_rate_metric(self, metric, spd, exp) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_1B\", Series(0.0, index=date_range(dt(2009, 12, 1), dt(2010, 3, 1))))\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=3,\n            float_spread=spd,\n            fixing_method=\"rfr_observation_shift(5)\",\n            rate_fixings=name,\n            spread_compound_method=\"none_simple\",\n            settle=2,\n        )\n        curve = Curve({dt(2010, 3, 1): 1.0, dt(2017, 1, 1): 1.0}, convention=\"act365f\")\n        disc_curve = curve.shift(spd)\n\n        result = bond.rate(curves=[curve, disc_curve], metric=metric)\n        fixings.pop(name + \"_1B\")\n        assert abs(result - exp) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"metric\", \"spd\", \"exp\"),\n        [\n            (\"clean_price\", 10.0, 99.99982764447981),  # compounding diff between shift\n            (\"dirty_price\", 10.0, 100.0165399732469),\n        ],\n    )\n    def test_initialised_rate_metric(self, metric, spd, exp) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_1B\", Series(0.0, index=date_range(dt(2009, 12, 1), dt(2010, 3, 1))))\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=3,\n            float_spread=spd,\n            fixing_method=\"rfr_observation_shift(5)\",\n            rate_fixings=name,\n            spread_compound_method=\"none_simple\",\n            settle=2,\n            metric=metric,\n        )\n        curve = Curve({dt(2010, 3, 1): 1.0, dt(2017, 1, 1): 1.0}, convention=\"act365f\")\n        disc_curve = curve.shift(spd)\n\n        result = bond.rate(curves=[curve, disc_curve])\n        fixings.pop(name + \"_1B\")\n        assert abs(result - exp) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"expected\"),\n        [\n            (dt(2010, 3, 3), 0.501369863013698),\n            (dt(2010, 6, 30), -0.008219178082191761),  # ex div with fixed IBOR\n        ],\n    )\n    def test_float_rate_bond_accrued_ibor(self, settlement, expected) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_6M\", Series(2.0, index=date_range(dt(2009, 12, 1), dt(2010, 3, 1))))\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=3,\n            float_spread=100,\n            fixing_method=FloatFixingMethod.IBOR(2),\n            rate_fixings=name,\n            spread_compound_method=\"none_simple\",\n        )\n        result = bond.accrued(settlement)\n        fixings.pop(name + \"_6M\")\n        assert abs(result - expected) < 1e-8\n\n    def test_float_rate_bond_raise_frequency(self) -> None:\n        with pytest.raises(ValueError, match=\"A `FloatRateNote` cannot have a 'zero' freq\"):\n            FloatRateNote(\n                effective=dt(2007, 1, 1),\n                termination=dt(2017, 1, 1),\n                frequency=\"Z\",\n                convention=\"Act365f\",\n                ex_div=3,\n                float_spread=100,\n                fixing_method=\"rfr_observation_shift(5)\",\n                rate_fixings=NoInput(0),\n                spread_compound_method=\"none_simple\",\n            )\n\n    def test_negative_accrued_needs_forecasting(self) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_1B\", Series(2.0, index=date_range(dt(2009, 12, 1), dt(2010, 3, 8))))\n        bond = FloatRateNote(\n            effective=dt(2009, 9, 16),\n            termination=dt(2017, 3, 16),\n            frequency=\"Q\",\n            convention=\"Act365f\",\n            ex_div=6,\n            float_spread=0,\n            fixing_method=\"rfr_observation_shift(5)\",\n            rate_fixings=name,\n            spread_compound_method=\"none_simple\",\n            calendar=NoInput(0),\n        )\n        from rateslib.data.fixings import FixingMissingForecasterError\n\n        with pytest.raises(  # noqa: PT012\n            FixingMissingForecasterError,\n            match=\"A `rate_curve` is required to forecast missing RFR rates\",\n        ):\n            bond.accrued(dt(2010, 3, 11))\n            fixings.pop(name + \"_1B\")\n\n        # # approximate calculation 5 days of negative accrued at 2% = -0.027397\n        # assert abs(result + 2 * 5 / 365) < 1e-3\n\n    @pytest.mark.parametrize(\n        \"rate_fixings\",\n        [\n            NoInput(0),\n        ],\n    )\n    def test_negative_accrued_raises(self, rate_fixings) -> None:\n        bond = FloatRateNote(\n            effective=dt(2009, 9, 16),\n            termination=dt(2017, 3, 16),\n            frequency=\"Q\",\n            convention=\"Act365f\",\n            ex_div=5,\n            float_spread=0,\n            fixing_method=\"rfr_observation_shift(5)\",\n            rate_fixings=rate_fixings,\n            spread_compound_method=\"none_simple\",\n            calendar=NoInput(0),\n        )\n        from rateslib.data.fixings import FixingMissingForecasterError\n\n        with pytest.raises(\n            FixingMissingForecasterError,\n            match=\"A `rate_curve` is required to forecast missing RFR rate\",\n        ):\n            bond.accrued(dt(2010, 3, 11))\n\n    @pytest.mark.skip(reason=\"v2.5 removed these validations\")\n    def test_bad_accrued_parameter_combo_raises(self, rate_fixings) -> None:\n        with pytest.raises(ValueError, match=\"For RFR FRNs `ex_div` must be less than\"):\n            FloatRateNote(\n                effective=dt(2009, 9, 16),\n                termination=dt(2017, 3, 16),\n                frequency=\"Q\",\n                ex_div=5,\n                fixing_method=\"rfr_observation_shift(3)\",\n            )\n\n    def test_accrued_no_fixings_in_period(self) -> None:\n        bond = FloatRateNote(\n            effective=dt(2010, 3, 16),\n            termination=dt(2017, 3, 16),\n            frequency=\"Q\",\n            convention=\"Act365f\",\n            ex_div=0,\n            float_spread=0,\n            fixing_method=\"rfr_observation_shift(0)\",\n            rate_fixings=NoInput(0),\n            spread_compound_method=\"none_simple\",\n            calendar=NoInput(0),\n        )\n        result = bond.accrued(dt(2010, 3, 16))\n        assert result == 0.0\n\n    def test_float_rate_bond_analytic_delta(self) -> None:\n        frn = FloatRateNote(\n            effective=dt(2010, 6, 7),\n            termination=dt(2015, 12, 7),\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n            float_spread=100,\n            notional=-1000000,\n            settle=0,\n            fixing_method=\"ibor(2)\",\n            rate_fixings=2.0,\n        )\n        curve = Curve({dt(2010, 11, 25): 1.0, dt(2015, 12, 7): 1.0})\n        result = frn.analytic_delta(curves=curve)\n        expected = -550.0\n        assert abs(result - expected) < 1e-6\n\n        frn.kwargs.meta[\"settle\"] = 2\n        result = frn.analytic_delta(curves=curve)  # bond is ex div on settle 27th Nov 2010\n        expected = -500.0  # bond has dropped a 6m coupon payment\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"metric\", \"spd\", \"exp\"),\n        [\n            (\"clean_price\", 0.0, 100),\n            (\"dirty_price\", 0.0, 100),\n            (\"clean_price\", 50.0, 99.99601798513253),\n            (\"dirty_price\", 50.0, 100.03848373855718),\n        ],\n    )\n    def test_float_rate_bond_forward_prices(self, metric, spd, exp) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name + \"_1B\",\n            Series(\n                data=2.0,\n                index=get_calendar(\"bus\").bus_date_range(start=dt(2007, 1, 1), end=dt(2010, 2, 26)),\n            ),\n        )\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=3,\n            float_spread=spd,\n            fixing_method=\"rfr_observation_shift(5)\",\n            calendar=\"bus\",\n            rate_fixings=name,\n            spread_compound_method=\"none_simple\",\n            settle=2,\n        )\n        curve = Curve(\n            {dt(2010, 3, 1): 1.0, dt(2017, 1, 1): 1.0},\n            convention=\"act365f\",\n            calendar=\"bus\",\n        )\n        disc_curve = curve.shift(spd)\n\n        result = bond.rate(\n            curves=[curve, disc_curve],\n            metric=metric,\n            settlement=dt(2010, 8, 1),\n        )\n        fixings.pop(name + \"_1B\")\n        assert abs(result - exp) < 1e-8\n\n    def test_float_rate_bond_forward_accrued(self) -> None:\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=3,\n            float_spread=0,\n            fixing_method=\"rfr_observation_shift(5)\",\n            spread_compound_method=\"none_simple\",\n            settle=2,\n        )\n        curve = Curve({dt(2010, 3, 1): 1.0, dt(2017, 1, 1): 0.9}, convention=\"act365f\")\n        # disc_curve = curve.shift(0)\n        result = bond.accrued(dt(2010, 8, 1), rate_curve=curve)\n        expected = 0.13083715795372267\n        assert abs(result - expected) < 1e-8\n\n    def test_rate_raises(self, curve) -> None:\n        bond = FloatRateNote(\n            effective=dt(2007, 1, 1),\n            termination=dt(2017, 1, 1),\n            frequency=\"S\",\n            convention=\"Act365f\",\n            ex_div=3,\n            float_spread=0.0,\n            fixing_method=\"rfr_observation_shift(5)\",\n            spread_compound_method=\"none_simple\",\n            settle=2,\n            curves=curve,\n        )\n\n        with pytest.raises(ValueError, match=\"`metric` must be in\"):\n            bond.rate(metric=\"BAD\")\n\n    def test_forecast_ibor(self, curve) -> None:\n        f_curve = LineCurve({dt(2022, 1, 1): 3.0, dt(2022, 2, 1): 4.0})\n        frn = FloatRateNote(\n            effective=dt(2022, 2, 1),\n            termination=\"3m\",\n            frequency=\"Q\",\n            fixing_method=\"ibor(0)\",\n        )\n        result = frn.accrued(dt(2022, 2, 5), rate_curve=f_curve)\n        expected = 0.044444444\n        assert abs(result - expected) < 1e-4\n\n    @pytest.mark.parametrize(\n        (\"price\", \"tol\"), [(98.0, 1e-7), (95.0, 1e-5), (90.0, 1e-3), (80.0, 1e-2)]\n    )\n    def test_oaspread(self, price, tol) -> None:\n        bond = FloatRateNote(\n            effective=dt(1998, 12, 7),\n            termination=dt(2008, 12, 7),\n            frequency=\"q\",\n            fixing_method=\"rfr_payment_delay\",\n            rate_fixings=[4.0],\n        )\n        curve = Curve({dt(1998, 12, 7): 1.0, dt(2015, 12, 7): 0.75})\n        # result = bond.rate(curve, metric=\"clean_price\") = 99.999999999999953\n        result = bond.oaspread(curves=curve, price=price)\n        curve_z = curve.shift(result)\n        result = bond.rate(curves=[curve, curve_z], metric=\"clean_price\")\n        assert abs(result - price) < tol\n\n    def test_settle_method_param_combinations(self) -> None:\n        # for RFR when method_param is less than settle curve based pricing methods will\n        # require forecasting from RFR curve to correctly calculate the accrued.\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name + \"_1B\",\n            Series(\n                [2.0, 3.0, 4.0, 5.0, 6.0],\n                index=[\n                    dt(2022, 1, 2),\n                    dt(2022, 1, 3),\n                    dt(2022, 1, 4),\n                    dt(2022, 1, 5),\n                    dt(2022, 1, 6),\n                ],\n            ),\n        )\n        frn = FloatRateNote(\n            effective=dt(2022, 1, 5),\n            termination=\"1Y\",\n            frequency=\"Q\",\n            settle=3,\n            fixing_method=\"rfr_observation_shift(2)\",\n            rate_fixings=name,\n            convention=\"Act365F\",\n            ex_div=1,\n        )\n        curve = Curve(\n            nodes={dt(2022, 1, 7): 1.0, dt(2023, 1, 7): 0.95},\n            convention=\"act365f\",\n        )\n\n        # Case1: All fixings are known and are published\n        # in this case a Curve is not required and is not given\n        result = frn.accrued(settlement=dt(2022, 1, 9))\n        assert abs(result - 0.04932400) < 1e-6\n\n        # Case2: Some fixings are unknown and must be forecast by a curve.\n        # If a curve is not supplied this will error\n        from rateslib.data.fixings import FixingMissingForecasterError\n\n        with pytest.raises(\n            FixingMissingForecasterError, match=\"A `rate_curve` is required to forecast missing RFR\"\n        ):\n            frn.accrued(settlement=dt(2022, 1, 10))\n\n        # Case3: Some fixings are unknown and must be forecast by a curve.\n        # A curve is given so this is used to forecast the values.\n        result = frn.accrued(settlement=dt(2022, 1, 10), rate_curve=curve)\n        assert abs(result - 0.06338487826265116) < 1e-6\n\n        # Case4: The bond settles on Issue date and there is no accrued if curve supplied or not\n        result1 = frn.accrued(settlement=dt(2022, 1, 5))\n        result2 = frn.accrued(settlement=dt(2022, 1, 5), rate_curve=curve)\n        assert abs(result1) < 1e-6\n        assert abs(result2) < 1e-6\n\n        # Case5: The bond settles on a coupon date and there is no accrued if curve supplied or not\n        result1 = frn.accrued(settlement=dt(2022, 4, 5))\n        result2 = frn.accrued(settlement=dt(2022, 4, 5), rate_curve=curve)\n        assert abs(result1) < 1e-6\n        assert abs(result2) < 1e-6\n\n        # Case6: Bond settles on issue date and there is no accrued. No fixings are input\n        frn_no_fixings = FloatRateNote(\n            effective=dt(2022, 1, 5),\n            termination=\"1Y\",\n            frequency=\"Q\",\n            settle=3,\n            fixing_method=\"rfr_observation_shift(2)\",\n            convention=\"Act365F\",\n            ex_div=1,\n        )\n        result1 = frn_no_fixings.accrued(settlement=dt(2022, 1, 5))\n        result2 = frn_no_fixings.accrued(settlement=dt(2022, 1, 5), rate_curve=curve)\n        assert abs(result1) < 1e-6\n        assert abs(result2) < 1e-6\n\n        # Case7: Bond settles a few days forward(settle) no previous fixings are given, all\n        # can be forecast from curve\n        frn_no_fixings = FloatRateNote(\n            effective=dt(2022, 1, 7),\n            termination=\"1Y\",\n            frequency=\"Q\",\n            settle=3,\n            fixing_method=\"rfr_observation_shift(0)\",\n            convention=\"Act365F\",\n            ex_div=1,\n        )\n        result = frn_no_fixings.accrued(settlement=dt(2022, 1, 10), rate_curve=curve)\n        assert abs(result - 0.04216776020085078) < 1e-6\n\n        # Case8: bond settles a few days forward, no fixings are given and no curve. Must error.\n        with pytest.raises(\n            FixingMissingForecasterError,\n            match=\"A `rate_curve` is required to forecast missing RFR rates\",\n        ):\n            frn_no_fixings.accrued(settlement=dt(2022, 1, 10))\n        fixings.pop(name + \"_1B\")\n\n    def test_ibor_fixings_table_historical_before_curve(self, curve):\n        # see test FloatPeriod.test_ibor_fixings_table_historical_before_curve\n        bond = FloatRateNote(\n            effective=dt(2001, 11, 7),\n            termination=dt(2002, 8, 7),\n            frequency=\"q\",\n            fixing_method=\"ibor(2)\",\n            rate_fixings=[4.0],\n            curves=[curve],\n        )\n        result = bond.local_analytic_rate_fixings()\n        assert isinstance(result, DataFrame)\n\n    def test_ibor_fixings_table_with_fixing(self, curve):\n        # see test FloatPeriod.test_ibor_fixings_table_historical_before_curve\n        bond = FloatRateNote(\n            effective=dt(2021, 11, 7),\n            termination=dt(2022, 8, 7),\n            frequency=\"q\",\n            fixing_method=\"ibor(2)\",\n            rate_fixings=[4.0],\n            curves=[curve],\n        )\n        result = bond.local_analytic_rate_fixings()\n        assert isinstance(result, DataFrame)\n        assert result.iloc[0, 0] == 0.0\n        assert abs(result.iloc[1, 0] + 24.376897) < 1e-6\n        assert abs(result.iloc[2, 0] + 24.941351) < 1e-6\n\n    def test_ibor_ytm_rate(self, curve):\n        # test a FixedRateBond and FloatRateNote with same conventions and cashflows have same ytm\n        ibor_curve = LineCurve({dt(2021, 12, 1): 4.0, dt(2027, 12, 2): 4.0})\n        disc_curve = Curve({dt(2021, 12, 1): 1.0, dt(2027, 12, 2): 0.92})\n        frn = FloatRateNote(\n            effective=dt(2021, 11, 7),\n            termination=dt(2022, 8, 7),\n            frequency=\"q\",\n            fixing_method=\"ibor(2)\",\n            convention=\"actacticma\",\n            calendar=\"nyc\",\n            modifier=\"none\",\n            rate_fixings=[4.0],\n            curves=[ibor_curve, disc_curve],\n            calc_mode=\"us_gb\",\n            fixing_series=\"eur_ibor\",\n            settle=1,\n        )\n        frb = FixedRateBond(\n            effective=dt(2021, 11, 7),\n            termination=dt(2022, 8, 7),\n            spec=\"us_gb\",\n            frequency=\"q\",\n            fixed_rate=4.0,\n            curves=[disc_curve],\n        )\n        dp1 = frn.rate(metric=\"dirty_price\")\n        dp2 = frb.rate(metric=\"dirty_price\")\n        assert abs(dp1 - dp2) < 1e-12  # FRN and equivalent FRB have the same dirty price.\n\n        y2 = frb.rate(metric=\"ytm\")\n        y1 = frn.rate(metric=\"ytm\")\n        assert abs(y1 - y2) < 1e-12  # FRN and equivalent FRB have the same yield-to-maturity.\n\n    def test_ytm_rate_fixings_provided(self, curve):\n        # test a FixedRateBond and FloatRateNote with same conventions and cashflows have same ytm\n        disc_curve = Curve({dt(2021, 12, 1): 1.0, dt(2027, 12, 2): 0.92})\n        frn = FloatRateNote(\n            effective=dt(2021, 11, 7),\n            termination=dt(2022, 8, 7),\n            frequency=\"q\",\n            fixing_method=\"ibor(2)\",\n            convention=\"actacticma\",\n            calendar=\"nyc\",\n            modifier=\"none\",\n            rate_fixings=[4.0, 4.0, 4.0],\n            calc_mode=\"us_gb\",\n            settle=1,\n        )\n        frb = FixedRateBond(\n            effective=dt(2021, 11, 7),\n            termination=dt(2022, 8, 7),\n            spec=\"us_gb\",\n            frequency=\"q\",\n            fixed_rate=4.0,\n            curves=[disc_curve],\n        )\n        dp1 = frn.rate(metric=\"dirty_price\", curves=[None, disc_curve])\n        dp2 = frb.rate(metric=\"dirty_price\")\n        assert abs(dp1 - dp2) < 1e-12  # FRN and equivalent FRB have the same dirty price.\n\n        y2 = frb.ytm(price=dp2, dirty=True, settlement=dt(2022, 12, 1))\n        y1 = frn.ytm(price=dp1, dirty=True, settlement=dt(2022, 12, 1))\n        assert abs(y1 - y2) < 1e-12  # FRN and equivalent FRB have the same yield-to-maturity.\n\n    def test_cashflows_known_fixings(self):\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_1B\", Series(2.0, index=date_range(dt(1999, 12, 1), dt(2004, 6, 2))))\n\n        frn = FloatRateNote(\n            effective=dt(2000, 12, 7),\n            termination=dt(2001, 12, 7),\n            frequency=\"S\",\n            currency=\"gbp\",\n            convention=\"Act365F\",\n            ex_div=3,\n            rate_fixings=name,\n            fixing_method=\"rfr_observation_shift_avg(5)\",\n        )\n        result = frn.cashflows()\n        fixings.pop(name + \"_1B\")\n        assert isinstance(result, DataFrame)\n        assert abs(result[\"Cashflow\"].iloc[0] + 10000) < 50.0\n        assert abs(result[\"Cashflow\"].iloc[1] + 10000) < 50.0\n\n\nclass TestBondFuture:\n    def test_repr(self):\n        kwargs = dict(\n            effective=dt(2020, 1, 1),\n            stub=\"ShortFront\",\n            frequency=\"A\",\n            calendar=\"tgt\",\n            currency=\"eur\",\n            convention=\"ActActICMA\",\n        )\n        bond1 = FixedRateBond(termination=dt(2022, 3, 1), fixed_rate=1.5, **kwargs)\n        fut = BondFuture(delivery=dt(2021, 3, 1), coupon=6.0, basket=[bond1])\n        expected = f\"<rl.BondFuture at {hex(id(fut))}>\"\n        assert expected == fut.__repr__()\n\n    @pytest.mark.parametrize(\n        (\"delivery\", \"mat\", \"coupon\", \"exp\"),\n        [\n            (dt(2023, 6, 12), dt(2032, 2, 15), 0.0, 0.603058),\n            (dt(2023, 6, 12), dt(2032, 8, 15), 1.7, 0.703125),\n            (dt(2023, 6, 12), dt(2033, 2, 15), 2.3, 0.733943),\n            (dt(2023, 9, 11), dt(2032, 8, 15), 1.7, 0.709321),\n            (dt(2023, 9, 11), dt(2033, 2, 15), 2.3, 0.739087),\n            (dt(2023, 12, 11), dt(2032, 8, 15), 1.7, 0.715464),\n            (dt(2023, 12, 11), dt(2033, 2, 15), 2.3, 0.744390),\n        ],\n    )\n    def test_conversion_factors_eurex_bund_ytm(self, delivery, mat, coupon, exp) -> None:\n        # The expected results are downloaded from the EUREX website\n        # regarding precalculated conversion factors.\n        # this test allows for an error in the cf < 1e-4, due to YTM method\n        kwargs = dict(\n            effective=dt(2020, 1, 1),\n            stub=\"ShortFront\",\n            frequency=\"A\",\n            calendar=\"tgt\",\n            currency=\"eur\",\n            convention=\"ActActICMA\",\n        )\n        bond1 = FixedRateBond(termination=mat, fixed_rate=coupon, **kwargs)\n\n        fut = BondFuture(delivery=delivery, coupon=6.0, basket=[bond1])\n        result = fut.cfs\n        assert abs(result[0] - exp) < 1e-4\n\n    @pytest.mark.parametrize(\n        (\"delivery\", \"issue\", \"mat\", \"coupon\", \"exp\"),\n        [\n            (dt(2023, 6, 12), dt(2022, 7, 1), dt(2032, 2, 15), 0.0, 0.603058),\n            (dt(2023, 6, 12), dt(2022, 7, 8), dt(2032, 8, 15), 1.7, 0.703125),\n            (dt(2023, 6, 12), dt(2023, 1, 13), dt(2033, 2, 15), 2.3, 0.733943),\n            (dt(2023, 9, 11), dt(2022, 7, 8), dt(2032, 8, 15), 1.7, 0.709321),\n            (dt(2023, 9, 11), dt(2023, 1, 13), dt(2033, 2, 15), 2.3, 0.739087),\n            (dt(2023, 12, 11), dt(2022, 7, 8), dt(2032, 8, 15), 1.7, 0.715464),\n            (dt(2023, 12, 11), dt(2023, 1, 13), dt(2033, 2, 15), 2.3, 0.744390),\n        ],\n    )\n    def test_conversion_factors_eurex_bund_method(self, delivery, issue, mat, coupon, exp) -> None:\n        # The expected results are downloaded from the EUREX website\n        # regarding precalculated conversion factors.\n        # these should be exact due to specifically coded methods\n        kwargs = dict(\n            effective=issue,\n            stub=\"LongFront\",\n            frequency=\"A\",\n            calendar=\"tgt\",\n            currency=\"eur\",\n            convention=\"ActActICMA\",\n            modifier=\"none\",\n        )\n        bond1 = FixedRateBond(termination=mat, fixed_rate=coupon, **kwargs)\n\n        fut = BondFuture(delivery=delivery, coupon=6.0, basket=[bond1], calc_mode=\"eurex_eur\")\n        result = fut.cfs\n        assert result[0] == exp\n\n    @pytest.mark.parametrize(\n        (\"effective\", \"maturity\", \"delivery\", \"coupon\", \"exp\"),\n        [\n            # (dt(2019, 6, 26), dt(2034, 6, 26), dt(2025, 6, 10), 0.0, 0.591898),\n            # (dt(2006, 3, 8), dt(2036, 3, 8), dt(2025, 6, 10), 2.5, 0.729825),\n            # (dt(2021, 6, 23), dt(2035, 6, 23), dt(2025, 6, 10), 0.25, 0.576795),\n            (dt(2012, 6, 27), dt(2037, 6, 27), dt(2025, 6, 10), 1.25, 0.601767),\n        ],\n    )\n    def test_conversion_factors_eurex_chf_method_jun25(\n        self, effective, maturity, delivery, coupon, exp\n    ) -> None:\n        # The expected results are downloaded from the EUREX website\n        # regarding precalculated conversion factors.\n        # these should be exact due to specifically coded methods\n        bond1 = FixedRateBond(effective, maturity, fixed_rate=coupon, spec=\"ch_gb\")\n        fut = BondFuture(basket=[bond1], delivery=delivery, spec=\"ch_gb_10y\")\n        result = fut.cfs\n        assert result[0] == exp\n\n    @pytest.mark.parametrize(\n        (\"effective\", \"maturity\", \"delivery\", \"coupon\", \"exp\"),\n        [\n            (dt(2019, 1, 1), dt(2033, 4, 8), dt(2025, 3, 10), 3.5, 0.844755),\n            (dt(2019, 1, 1), dt(2034, 6, 26), dt(2025, 3, 10), 0.0, 0.583339),\n            (dt(2006, 1, 1), dt(2036, 3, 8), dt(2025, 3, 10), 2.5, 0.725400),\n            (dt(2021, 1, 1), dt(2035, 6, 23), dt(2025, 3, 10), 0.25, 0.569042),\n            (dt(2012, 1, 1), dt(2037, 6, 27), dt(2025, 3, 10), 1.25, 0.596009),\n        ],\n    )\n    def test_conversion_factors_eurex_chf_method_mar25(\n        self, effective, maturity, delivery, coupon, exp\n    ) -> None:\n        # The expected results are downloaded from the EUREX website\n        # regarding precalculated conversion factors.\n        # these should be exact due to specifically coded methods\n        bond1 = FixedRateBond(effective, maturity, fixed_rate=coupon, spec=\"ch_gb\")\n        fut = BondFuture(basket=[bond1], delivery=delivery, spec=\"ch_gb_10y\")\n        result = fut.cfs\n        assert result[0] == exp\n\n    @pytest.mark.parametrize(\n        (\"mat\", \"coupon\", \"exp\"),\n        [\n            (dt(2032, 6, 7), 4.25, 1.0187757),\n            (dt(2033, 7, 31), 0.875, 0.7410593),\n            (dt(2034, 9, 7), 4.5, 1.0449380),\n            (dt(2035, 7, 31), 0.625, 0.6773884),\n            (dt(2036, 3, 7), 4.25, 1.0247516),\n        ],\n    )\n    def test_conversion_factors_ice_gilt(self, mat, coupon, exp) -> None:\n        # The expected results are downloaded from the ICE LIFFE website\n        # regarding precalculated conversion factors.\n        # this test allows for an error in the cf < 1e-6.\n        kwargs = dict(\n            effective=dt(2020, 1, 1),\n            stub=\"ShortFront\",\n            frequency=\"S\",\n            calendar=\"ldn\",\n            currency=\"gbp\",\n            convention=\"ActActICMA\",\n            ex_div=7,\n        )\n        bond1 = FixedRateBond(termination=mat, fixed_rate=coupon, **kwargs)\n\n        fut = BondFuture(delivery=(dt(2023, 6, 1), dt(2023, 6, 30)), coupon=4.0, basket=[bond1])\n        result = fut.cfs\n        assert abs(result[0] - exp) < 1e-6\n\n    def test_conversion_factors_ice_gilt_default_spec(self) -> None:\n        # this uses the v2.5 implementation that rounds exactly to the exchange quantity\n        # this tests data directly from ice rounded to 7 dp.\n        # note this requires 'linear_days_long_front_split' on GB00BTXS1K06 which is the last bond.\n        bf = BondFuture(\n            basket=[\n                FixedRateBond(dt(1999, 1, 1), dt(2035, 7, 31), fixed_rate=0.625, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2038, 1, 29), fixed_rate=3.75, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2034, 9, 7), fixed_rate=4.5, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2035, 3, 7), fixed_rate=4.5, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2036, 3, 7), fixed_rate=4.25, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2037, 9, 7), fixed_rate=1.75, spec=\"uk_gb\"),\n                FixedRateBond(dt(2025, 9, 3), dt(2035, 10, 22), fixed_rate=4.75, spec=\"uk_gb\"),\n            ],\n            delivery=(dt(2025, 12, 1), dt(2025, 12, 31)),\n            spec=\"uk_gb_10y\",\n        )\n        expected = (\n            0.7316293,\n            0.9760712,\n            1.0366069,\n            1.0383390,\n            1.0208264,\n            0.7904642,\n            1.0606298,\n        )\n        assert bf.cfs == expected\n\n    def test_conversion_factors_ice_gilt_default_spec2(self) -> None:\n        # this test has the first calendar day of the month as a holiday\n        # this uses the v2.5 implementation that rounds exactly to the exchange quantity\n        # this tests data directly from ice rounded to 7 dp.\n        bf = BondFuture(\n            basket=[\n                FixedRateBond(dt(1999, 1, 1), dt(2034, 9, 7), fixed_rate=4.5, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2038, 1, 29), fixed_rate=3.75, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2034, 7, 31), fixed_rate=4.25, spec=\"uk_gb\"),\n                FixedRateBond(dt(2025, 2, 12), dt(2035, 3, 7), fixed_rate=4.5, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2037, 9, 7), fixed_rate=1.75, spec=\"uk_gb\"),\n                FixedRateBond(dt(1999, 1, 1), dt(2036, 3, 7), fixed_rate=4.25, spec=\"uk_gb\"),\n                FixedRateBond(dt(2020, 9, 11), dt(2035, 7, 31), fixed_rate=0.625, spec=\"uk_gb\"),\n            ],\n            delivery=(dt(2025, 6, 2), dt(2025, 6, 30)),\n            spec=\"uk_gb_10y\",\n        )\n        expected = (1.0383429, 0.9753142, 1.0189797, 1.0400109, 0.7835277, 1.0216443, 0.7203475)\n        assert bf.cfs == expected\n\n    @pytest.mark.parametrize(\n        (\"mat\", \"coupon\", \"calc_mode\", \"exp\"),\n        [\n            (dt(2010, 10, 31), 1.5, \"ust_short\", 0.9229),\n            (dt(2013, 10, 31), 2.75, \"ust_short\", 0.8653),\n            (dt(2018, 11, 15), 3.75, \"ust_long\", 0.8357),\n            (dt(2038, 5, 15), 4.5, \"ust_long\", 0.7943),\n        ],\n    )\n    def test_conversion_factors_cme_treasury(self, mat, coupon, calc_mode, exp) -> None:\n        # The expected results are downloaded from the CME website\n        # regarding precalculated conversion factors.\n        # this test allows for an error in the cf < 1e-6.\n        kwargs = dict(\n            effective=dt(2005, 1, 1),\n            spec=\"us_gb\",\n        )\n        bond1 = FixedRateBond(termination=mat, fixed_rate=coupon, **kwargs)\n\n        fut = BondFuture(\n            delivery=(dt(2008, 12, 1), dt(2008, 12, 29)),\n            coupon=6.0,\n            basket=[bond1],\n            calc_mode=calc_mode,\n        )\n        result = fut.cfs\n        assert abs(result[0] - exp) < 1e-6\n\n    def test_dlv_screen_print(self) -> None:\n        kws = dict(ex_div=7, frequency=\"S\", convention=\"ActActICMA\", calendar=NoInput(0))\n        bonds = [\n            FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, **kws),\n        ]\n        future = BondFuture(delivery=(dt(2000, 6, 1), dt(2000, 6, 30)), coupon=7.0, basket=bonds)\n        result = future.dlv(\n            future_price=112.98,\n            prices=[102.732, 131.461, 107.877, 134.455],\n            repo_rate=6.24,\n            settlement=dt(2000, 3, 16),\n            convention=\"Act365f\",\n        )\n        expected = DataFrame(\n            {\n                \"Bond\": [\n                    \"5.750% 07-12-2009\",\n                    \"9.000% 12-07-2011\",\n                    \"6.250% 25-11-2010\",\n                    \"9.000% 06-08-2012\",\n                ],\n                \"Price\": [102.732, 131.461, 107.877, 134.455],\n                \"YTM\": [5.384243, 5.273217, 5.275481, 5.193851],\n                \"C.Factor\": [0.914225, 1.152571, 0.944931, 1.161956],\n                \"Gross Basis\": [-0.557192, 1.243582, 1.118677, 3.177230],\n                \"Implied Repo\": [7.381345, 3.564685, 2.199755, -1.414670],\n                \"Actual Repo\": [6.24, 6.24, 6.24, 6.24],\n                \"Net Basis\": [-0.343654, 1.033668, 1.275866, 3.010371],\n            },\n        )\n        assert_frame_equal(result, expected)\n\n        result2 = future.dlv(\n            future_price=112.98,\n            prices=[102.732, 131.461, 107.877, 134.455],\n            repo_rate=[6.24, 6.24, 6.24, 6.24],  # test individual repo input\n            settlement=dt(2000, 3, 16),\n            convention=\"Act365f\",\n        )\n        assert_frame_equal(result2, expected)\n\n    def test_notional(self) -> None:\n        future = BondFuture(\n            coupon=0,\n            delivery=dt(2000, 6, 1),\n            basket=[],\n            nominal=100000,\n            contracts=10,\n        )\n        assert future.notional == -1e6\n\n    def test_dirty_in_methods(self) -> None:\n        kws = dict(ex_div=7, frequency=\"S\", convention=\"ActActICMA\", calendar=NoInput(0))\n        bonds = [\n            FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, **kws),\n        ]\n        future = BondFuture(delivery=(dt(2000, 6, 1), dt(2000, 6, 30)), coupon=7.0, basket=bonds)\n        prices = [102.732, 131.461, 107.877, 134.455]\n        basket = future.kwargs.meta[\"basket\"]\n        dirty_prices = [\n            price + basket[i].accrued(dt(2000, 3, 16)) for i, price in enumerate(prices)\n        ]\n        result = future.gross_basis(112.98, dirty_prices, dt(2000, 3, 16), True)\n        expected = future.gross_basis(112.98, prices, dt(2000, 3, 16), False)\n        assert result == expected\n\n    def test_delivery_in_methods(self) -> None:\n        kws = dict(ex_div=7, frequency=\"S\", convention=\"ActActICMA\", calendar=NoInput(0))\n        bonds = [\n            FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, **kws),\n        ]\n        future = BondFuture(delivery=(dt(2000, 6, 1), dt(2000, 6, 30)), coupon=7.0, basket=bonds)\n        prices = [102.732, 131.461, 107.877, 134.455]\n        expected = future.net_basis(112.98, prices, 6.24, dt(2000, 3, 16))\n        result = future.net_basis(112.98, prices, 6.24, dt(2000, 3, 16), delivery=dt(2000, 6, 30))\n        assert result == expected\n\n        expected = future.implied_repo(112.98, prices, dt(2000, 3, 16))\n        result = future.implied_repo(112.98, prices, dt(2000, 3, 16), delivery=dt(2000, 6, 30))\n        assert result == expected\n\n        expected = future.ytm(112.98)\n        result = future.ytm(112.98, delivery=dt(2000, 6, 30))\n        assert result == expected\n\n        expected = future.duration(112.98)\n        result = future.duration(112.98, delivery=dt(2000, 6, 30))\n        assert result == expected\n\n    def test_ctd_index(self) -> None:\n        kws = dict(ex_div=7, frequency=\"S\", convention=\"ActActICMA\", calendar=NoInput(0))\n        bonds = [\n            FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, **kws),\n        ]\n        future = BondFuture(delivery=(dt(2000, 6, 1), dt(2000, 6, 30)), coupon=7.0, basket=bonds)\n        prices = [102.732, 131.461, 107.877, 134.455]\n        assert future.ctd_index(112.98, prices, dt(2000, 3, 16)) == 0\n\n    @pytest.mark.parametrize((\"metric\", \"expected\"), [(\"future_price\", 112.98), (\"ytm\", 5.301975)])\n    @pytest.mark.parametrize(\"delivery\", [NoInput(0), dt(2000, 6, 30)])\n    def test_futures_rates(self, metric, expected, delivery) -> None:\n        curve = Curve(\n            nodes={\n                dt(2000, 3, 15): 1.0,\n                dt(2000, 6, 30): 1.0,\n                dt(2009, 12, 7): 1.0,\n                dt(2010, 11, 25): 1.0,\n                dt(2011, 7, 12): 1.0,\n                dt(2012, 8, 6): 1.0,\n            },\n            id=\"gilt_curve\",\n            convention=\"act365f\",\n        )\n        kws = dict(\n            ex_div=7,\n            frequency=\"S\",\n            convention=\"ActActICMA\",\n            calendar=NoInput(0),\n            settle=1,\n            curves=\"gilt_curve\",\n        )\n        bonds = [\n            FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, **kws),\n        ]\n        solver = Solver(\n            curves=[curve],\n            instruments=[\n                IRS(\n                    dt(2000, 3, 15),\n                    dt(2000, 6, 30),\n                    \"A\",\n                    convention=\"act365f\",\n                    curves=\"gilt_curve\",\n                ),\n            ]\n            + bonds,\n            s=[7.381345, 102.732, 131.461, 107.877, 134.455],\n        )  # note the repo rate as defined by 'gilt_curve' is set to analogue implied\n        future = BondFuture(\n            coupon=7.0,\n            delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n            basket=bonds,\n        )\n        result = future.rate(\n            solver=solver,\n            metric=metric,\n            settlement=delivery,\n        )\n        assert abs(result - expected) < 1e-3\n\n    def test_future_rate_raises(self) -> None:\n        kws = dict(\n            ex_div=7,\n            frequency=\"S\",\n            convention=\"ActActICMA\",\n            calendar=NoInput(0),\n            settle=1,\n            curves=\"gilt_curve\",\n        )\n        bonds = [\n            FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, **kws),\n        ]\n        future = BondFuture(\n            coupon=7.0,\n            delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n            basket=bonds,\n        )\n        with pytest.raises(ValueError, match=\"`metric`\"):\n            future.rate(metric=\"badstr\")\n\n    def test_futures_npv(self) -> None:\n        curve = Curve(\n            nodes={\n                dt(2000, 3, 15): 1.0,\n                dt(2000, 6, 30): 1.0,\n                dt(2009, 12, 7): 1.0,\n                dt(2010, 11, 25): 1.0,\n                dt(2011, 7, 12): 1.0,\n                dt(2012, 8, 6): 1.0,\n            },\n            id=\"gilt_curve\",\n            convention=\"act365f\",\n        )\n        kws = dict(\n            ex_div=7,\n            frequency=\"S\",\n            convention=\"ActActICMA\",\n            calendar=NoInput(0),\n            settle=1,\n            curves=\"gilt_curve\",\n            currency=\"gbp\",\n        )\n        bonds = [\n            FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, **kws),\n        ]\n        solver = Solver(\n            curves=[curve],\n            instruments=[\n                IRS(\n                    dt(2000, 3, 15),\n                    dt(2000, 6, 30),\n                    \"A\",\n                    convention=\"act365f\",\n                    curves=\"gilt_curve\",\n                ),\n            ]\n            + bonds,\n            s=[7.381345, 102.732, 131.461, 107.877, 134.455],\n            algorithm=\"gauss_newton\",\n        )  # note the repo rate as defined by 'gilt_curve' is set to analogue implied\n        future = BondFuture(\n            coupon=7.0,\n            delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n            basket=bonds,\n            nominal=100000,\n            contracts=10,\n            currency=\"gbp\",\n        )\n        result = future.npv(solver=solver, local=False)\n        expected = 1129798.770872\n        assert abs(result - expected) < 1e-5\n\n        result2 = future.npv(solver=solver, local=True)\n        assert abs(result2[\"gbp\"] - expected) < 1e-5\n\n    @pytest.mark.parametrize(\"delivery\", [NoInput(0), dt(2000, 6, 30)])\n    def test_futures_duration_and_convexity(self, delivery) -> None:\n        kws = dict(\n            ex_div=7,\n            frequency=\"S\",\n            convention=\"ActActICMA\",\n            calendar=NoInput(0),\n            settle=1,\n            curves=\"gilt_curve\",\n        )\n        bonds = [\n            FixedRateBond(dt(1999, 1, 1), dt(2009, 12, 7), fixed_rate=5.75, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2011, 7, 12), fixed_rate=9.00, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2010, 11, 25), fixed_rate=6.25, **kws),\n            FixedRateBond(dt(1999, 1, 1), dt(2012, 8, 6), fixed_rate=9.00, **kws),\n        ]\n        future = BondFuture(\n            coupon=7.0,\n            delivery=(dt(2000, 6, 1), dt(2000, 6, 30)),\n            basket=bonds,\n        )\n        result = future.duration(112.98, delivery=delivery)[0]\n        expected = 8.20178546111\n        assert abs(result - expected) < 1e-3\n\n        expected = (\n            future.duration(112.98, delivery=delivery)[0]\n            - future.duration(112.98 - result / 100, delivery=delivery)[0]\n        )\n        result2 = future.convexity(112.98, delivery=delivery)[0]\n        assert abs(result2 - expected * 100) < 1e-3\n\n        # Bond future duration which is not risk is not adjusted by CFs\n        result = future.duration(112.98, delivery=delivery, metric=\"modified\")[0]\n        expected = 7.23419455163\n        assert abs(result - expected) < 1e-3\n\n    def test_cms(self):\n        data = DataFrame(\n            data=[\n                [\n                    FixedRateBond(\n                        dt(2022, 1, 1),\n                        dt(2039, 8, 15),\n                        fixed_rate=4.5,\n                        spec=\"us_gb\",\n                        curves=\"bcurve\",\n                    ),\n                    98.6641,\n                ],\n                [\n                    FixedRateBond(\n                        dt(2022, 1, 1),\n                        dt(2040, 2, 15),\n                        fixed_rate=4.625,\n                        spec=\"us_gb\",\n                        curves=\"bcurve\",\n                    ),\n                    99.8203,\n                ],\n                [\n                    FixedRateBond(\n                        dt(2022, 1, 1),\n                        dt(2041, 2, 15),\n                        fixed_rate=4.75,\n                        spec=\"us_gb\",\n                        curves=\"bcurve\",\n                    ),\n                    100.7734,\n                ],\n                [\n                    FixedRateBond(\n                        dt(2022, 1, 1),\n                        dt(2040, 5, 15),\n                        fixed_rate=4.375,\n                        spec=\"us_gb\",\n                        curves=\"bcurve\",\n                    ),\n                    96.6953,\n                ],\n                [\n                    FixedRateBond(\n                        dt(2022, 1, 1),\n                        dt(2042, 11, 15),\n                        fixed_rate=4.00,\n                        spec=\"us_gb\",\n                        curves=\"bcurve\",\n                    ),\n                    90.4766,\n                ],\n            ],\n            columns=[\"bonds\", \"prices\"],\n        )\n        usz3 = BondFuture(  # Construct the BondFuture Instrument\n            coupon=6.0,\n            delivery=(dt(2023, 12, 1), dt(2023, 12, 29)),\n            basket=data[\"bonds\"],\n            nominal=100e3,\n            calendar=\"nyc\",\n            currency=\"usd\",\n            calc_mode=\"ust_long\",\n        )\n        result = usz3.cms(prices=data[\"prices\"], settlement=dt(2023, 11, 22), shifts=[-50, 0, 50])\n        expected = DataFrame(\n            data={\n                \"Bond\": [\n                    \"4.500% 15-08-2039\",\n                    \"4.625% 15-02-2040\",\n                    \"4.750% 15-02-2041\",\n                    \"4.375% 15-05-2040\",\n                    \"4.000% 15-11-2042\",\n                ],\n                -50: [\n                    0.0,\n                    0.10938764224876252,\n                    0.32693578691382186,\n                    0.24721845093496597,\n                    1.1960030963801813,\n                ],\n                0: [\n                    0.0,\n                    0.01148721023514554,\n                    0.016282194434154462,\n                    0.032902987886402,\n                    0.33598669301149187,\n                ],\n                50: [\n                    0.43066112621522734,\n                    0.3653207547713322,\n                    0.19632745772335625,\n                    0.27120849999053576,\n                    0.0,\n                ],\n            }\n        )\n        assert_frame_equal(result, expected)\n\n    def test_curves_on_individual_bonds(self):\n        # no curves are supplied to BondFuture meta or method an local instrument meta are used\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2042, 1, 1): 1.0})\n        c2 = Curve({dt(2022, 1, 2): 1.0, dt(2042, 1, 1): 0.5})\n        usz3 = BondFuture(  # Construct the BondFuture Instrument\n            coupon=6.0,\n            delivery=(dt(2023, 12, 1), dt(2023, 12, 29)),\n            basket=[\n                FixedRateBond(dt(2022, 1, 1), \"10y\", fixed_rate=4.5, spec=\"us_gb\", curves=c1),\n                FixedRateBond(dt(2022, 1, 1), \"10Y\", fixed_rate=4.5, spec=\"us_gb\", curves=c2),\n            ],\n            nominal=100e3,\n            calendar=\"nyc\",\n            currency=\"usd\",\n            calc_mode=\"ust_long\",\n        )\n        cfs = usz3.cfs\n        assert cfs[0] == cfs[1]\n        result = usz3.rate()\n        assert abs(result - 118.06972328) < 1e-7\n\n    def test_curves_supplied_to_rate_method(self):\n        # the curves supplied to the method overrides the local instruments meta\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2042, 1, 1): 1.0})\n        c2 = Curve({dt(2022, 1, 2): 1.0, dt(2042, 1, 1): 0.5})\n        usz3 = BondFuture(  # Construct the BondFuture Instrument\n            coupon=6.0,\n            delivery=(dt(2023, 12, 1), dt(2023, 12, 29)),\n            basket=[\n                FixedRateBond(dt(2022, 1, 1), \"10y\", fixed_rate=4.5, spec=\"us_gb\", curves=c1),\n                FixedRateBond(dt(2022, 1, 1), \"10Y\", fixed_rate=4.5, spec=\"us_gb\", curves=c2),\n            ],\n            nominal=100e3,\n            calendar=\"nyc\",\n            currency=\"usd\",\n            calc_mode=\"ust_long\",\n        )\n        cfs = usz3.cfs\n        assert cfs[0] == cfs[1]\n        # in this c1 is used as an override so both bonds are priced expensively - price is higher\n        result = usz3.rate(curves=c1)\n        assert abs(result - 150.184019411) < 1e-7\n\n    def test_curves_supplied_as_future_meta(self):\n        # the curves supplied to the BondFuture.curves meta override local instruments.\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2042, 1, 1): 1.0})\n        c2 = Curve({dt(2022, 1, 2): 1.0, dt(2042, 1, 1): 0.5})\n        usz3 = BondFuture(  # Construct the BondFuture Instrument\n            coupon=6.0,\n            delivery=(dt(2023, 12, 1), dt(2023, 12, 29)),\n            basket=[\n                FixedRateBond(dt(2022, 1, 1), \"10y\", fixed_rate=4.5, spec=\"us_gb\", curves=c1),\n                FixedRateBond(dt(2022, 1, 1), \"10Y\", fixed_rate=4.5, spec=\"us_gb\", curves=c2),\n            ],\n            nominal=100e3,\n            calendar=\"nyc\",\n            currency=\"usd\",\n            calc_mode=\"ust_long\",\n            curves=c1,\n        )\n        cfs = usz3.cfs\n        assert cfs[0] == cfs[1]\n        # in this c1 is used as an override via the bond future metric so both bonds are priced\n        # expensively - price is higher\n        result = usz3.rate()\n        assert abs(result - 150.184019411) < 1e-7\n"
  },
  {
    "path": "python/tests/instruments/test_instruments_legacy.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\nfrom datetime import datetime as dt\n\nimport numpy as np\nimport pytest\nfrom pandas import DataFrame, Index, MultiIndex, Series, isna\nfrom pandas.testing import assert_frame_equal\nfrom rateslib import default_context, defaults, fixings\nfrom rateslib.curves import CompositeCurve, Curve, LineCurve, MultiCsaCurve\nfrom rateslib.curves._parsers import _map_curve_from_solver\nfrom rateslib.data.fixings import FloatRateSeries, FXIndex, IBORStubFixing\nfrom rateslib.default import NoInput\nfrom rateslib.dual import Dual, Dual2, Variable, dual_exp, dual_log, gradient\nfrom rateslib.enums.parameters import FloatFixingMethod, LegMtm\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.instruments import (\n    CDS,\n    FRA,\n    IIRS,\n    IRS,\n    NDF,\n    NDXCS,\n    SBS,\n    XCS,\n    ZCIS,\n    ZCS,\n    Bill,\n    Fee,\n    FixedRateBond,\n    FloatRateNote,\n    Fly,\n    FXBrokerFly,\n    FXCall,\n    FXForward,\n    FXPut,\n    FXRiskReversal,\n    FXStraddle,\n    FXStrangle,\n    FXSwap,\n    FXVolValue,\n    IndexFixedRateBond,\n    IRSCall,\n    IRSPut,\n    IRSStraddle,\n    IRVolValue,\n    Loan,\n    Portfolio,\n    Spread,\n    STIRFuture,\n    Value,\n    YoYIS,\n)\nfrom rateslib.instruments.bonds.conventions import US_GB\nfrom rateslib.instruments.protocols.kwargs import (\n    _KWArgs,\n)\nfrom rateslib.instruments.protocols.pricing import (\n    _Curves,\n    _Vol,\n)\nfrom rateslib.legs import Amortization\nfrom rateslib.periods import Cashflow, ZeroFloatPeriod\nfrom rateslib.scheduling import Adjuster, NamedCal, Schedule, add_tenor, get_imm\nfrom rateslib.solver import Solver\nfrom rateslib.volatility import (\n    FXDeltaVolSmile,\n    FXDeltaVolSurface,\n    FXSabrSmile,\n    FXSabrSurface,\n    IRSabrCube,\n    IRSabrSmile,\n    IRSplineSmile,\n)\n\n\n@pytest.fixture\ndef curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.99,\n        dt(2022, 7, 1): 0.98,\n        dt(2022, 10, 1): 0.97,\n    }\n    # convention = \"Act360\"\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef curve2():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.98,\n        dt(2022, 7, 1): 0.97,\n        dt(2022, 10, 1): 0.95,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\", index_base=100.0)\n\n\n@pytest.fixture\ndef usdusd():\n    nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.99}\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef eureur():\n    nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.997}\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef usdeur():\n    nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.996}\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef simple_solver():\n    curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0}, id=\"curve\")\n    solver = Solver(\n        curves=[curve],\n        instruments=[\n            IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n            IRS(dt(2022, 1, 1), \"2Y\", \"A\", curves=\"curve\"),\n        ],\n        s=[2.5, 3.0],\n        id=\"solver\",\n        instrument_labels=[\"1Y\", \"2Y\"],\n    )\n    return solver\n\n\n@pytest.mark.parametrize(\n    \"inst\",\n    [\n        IRS(dt(2022, 7, 1), \"3M\", \"A\", curves=\"eureur\", notional=1e6),\n        STIRFuture(\n            dt(2022, 3, 16),\n            dt(2022, 6, 15),\n            \"Q\",\n            curves=\"eureur\",\n            spec=\"eur_stir\",\n            contracts=-1,\n        ),\n        FRA(dt(2022, 7, 1), \"3M\", \"A\", curves=\"eureur\", notional=1e6),\n        SBS(\n            dt(2022, 7, 1),\n            \"3M\",\n            \"A\",\n            curves=[\"eureur\", \"eureur\", \"eurusd\", \"eureur\"],\n            notional=-1e6,\n        ),\n        ZCS(dt(2022, 7, 1), \"3M\", \"A\", curves=\"eureur\", notional=1e6),\n        ZCIS(dt(2022, 1, 1), \"1Y\", \"A\", curves=[\"usdusd\", \"usdusd\", \"eu_cpi\", \"usdusd\"]),\n        IIRS(\n            dt(2022, 7, 1),\n            \"3M\",\n            \"A\",\n            curves=[\"eu_cpi\", \"eureur\", \"eureur\", \"eureur\"],\n            notional=1e6,\n        ),\n        XCS(  # XCS - FloatFloat\n            dt(2022, 7, 1),\n            \"3M\",\n            \"A\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            curves=[\"usdusd\", \"usdusd\", \"eureur\", \"eurusd\"],\n            notional=1e6,\n        ),\n        FXSwap(\n            dt(2022, 7, 1),\n            \"3M\",\n            pair=\"usdeur\",\n            curves=[\"usdusd\", \"usdusd\", \"eureur\", \"eureur\"],\n            notional=-1e6,\n        ),\n        FXForward(\n            settlement=dt(2022, 10, 1),\n            pair=\"eurusd\",\n            curves=[None, \"eureur\", None, \"usdusd\"],\n            notional=-1e6 * 25 / 74.27,\n        ),\n    ],\n)\ndef test_instrument_repr(inst):\n    result = inst.__repr__()\n    expected = f\"<rl.{type(inst).__name__} at {hex(id(inst))}>\"\n    assert result == expected\n\n\nclass TestCurvesandSolver:\n    def test_get_curve_from_solver(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"tagged\")\n        inst = [(Value(dt(2023, 1, 1)), {\"curves\": \"tagged\"})]\n        solver = Solver([curve], [], inst, [0.975])\n\n        result = _map_curve_from_solver(\"tagged\", solver)\n        assert result == curve\n\n        result = _map_curve_from_solver(curve, solver)\n        assert result == curve\n\n        no_curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"not in solver\")\n\n        with default_context(\"curve_not_in_solver\", \"ignore\"):\n            result = _map_curve_from_solver(no_curve, solver)\n            assert result == no_curve\n\n        with pytest.warns(), default_context(\"curve_not_in_solver\", \"warn\"):\n            result = _map_curve_from_solver(no_curve, solver)\n            assert result == no_curve\n\n        with (\n            pytest.raises(ValueError, match=\"`curve` must be in `solver`\"),\n            default_context(\"curve_not_in_solver\", \"raise\"),\n        ):\n            _map_curve_from_solver(no_curve, solver)\n\n        with pytest.raises(AttributeError, match=\"`curve` has no attribute `id`, likely it not\"):\n            _map_curve_from_solver(100.0, solver)\n\n    # @pytest.mark.parametrize(\"solver\", [True, False])\n    # @pytest.mark.parametrize(\"fxf\", [True, False])\n    # @pytest.mark.parametrize(\"fx\", [NoInput(0), 2.0])\n    # @pytest.mark.parametrize(\"crv\", [True, False])\n    # def test_get_curves_and_fx_from_solver(\n    #     self,\n    #     usdusd,\n    #     usdeur,\n    #     eureur,\n    #     solver,\n    #     fxf,\n    #     fx,\n    #     crv,\n    # ) -> None:\n    #     curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"tagged\")\n    #     inst = [Value(dt(2023, 1, 1), curves=\"tagged\")]\n    #     fxfs = FXForwards(\n    #         FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3)),\n    #         {\"usdusd\": usdusd, \"usdeur\": usdeur, \"eureur\": eureur},\n    #     )\n    #     solver = (\n    #         Solver([curve], [], inst, [0.975], fx=fxfs if fxf else NoInput(0))\n    #         if solver\n    #         else NoInput(0)\n    #     )\n    #     curve = curve if crv else NoInput(0)\n    #\n    #     if solver is not NoInput(0) and fxf and fx is not NoInput(0):\n    #         with pytest.warns(UserWarning):\n    #             #  Solver contains an `fx` attribute but an `fx` argument has been supplied\n    #             crv_result, fx_result, _ = _get_curves_fx_and_base_maybe_from_solver(\n    #                 NoInput(0),\n    #                 solver,\n    #                 curve,\n    #                 fx,\n    #                 NoInput(0),\n    #                 \"usd\",\n    #             )\n    #     else:\n    #         crv_result, fx_result, _ = _get_curves_fx_and_base_maybe_from_solver(\n    #             NoInput(0),\n    #             solver,\n    #             curve,\n    #             fx,\n    #             NoInput(0),\n    #             \"usd\",\n    #         )\n    #\n    #     # check the fx results. If fx is specified directly it is returned\n    #     # otherwsie it is returned from a solver object if it is available.\n    #     if fx is not NoInput(0):\n    #         assert fx_result == 2.0\n    #     elif solver is NoInput(0):\n    #         assert fx_result is NoInput(0)\n    #     else:\n    #         if fxf:\n    #             assert fx_result == fxfs\n    #         else:\n    #             assert fx_result is NoInput(0)\n    #\n    #     assert crv_result == (curve, curve, curve, curve)\n\n    # @pytest.mark.parametrize(\n    #     \"obj\",\n    #     [\n    #         (Curve({dt(2000, 1, 1): 1.0})),\n    #         (LineCurve({dt(2000, 1, 1): 1.0})),\n    #         (Curve({dt(2000, 1, 1): 1.0}, index_base=100.0)),\n    #         (CompositeCurve([Curve({dt(2000, 1, 1): 1.0})])),\n    #         (MultiCsaCurve([Curve({dt(2000, 1, 1): 1.0})])),\n    #         (\n    #             FXDeltaVolSmile(\n    #                 {0.1: 1.0, 0.2: 2.0, 0.5: 3.0, 0.7: 4.0, 0.9: 5.0},\n    #                 dt(2023, 3, 16),\n    #                 dt(2023, 6, 16),\n    #                 \"forward\",\n    #             )\n    #         ),\n    #     ],\n    # )\n    # def test_get_curves_fx_and_base_maybe_from_solver_object_types(self, obj) -> None:\n    #     crv_result, _, _ = _get_curves_fx_and_base_maybe_from_solver(\n    #         obj,\n    #         NoInput(0),\n    #         NoInput(0),\n    #         NoInput(0),\n    #         NoInput(0),\n    #         NoInput(0),\n    #     )\n    #     assert crv_result == (obj,) * 4\n\n    # def test_get_curves_and_fx_from_solver_raises(self) -> None:\n    #     from rateslib.solver import Solver\n    #\n    #     curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"tagged\")\n    #     inst = [Value(dt(2023, 1, 1), curves=\"tagged\")]\n    #     solver = Solver([curve], [], inst, [0.975])\n    #\n    #     with pytest.raises(ValueError, match=\"`curves` must contain Curve, not str, if\"):\n    #         _get_curves_fx_and_base_maybe_from_solver(\n    #             NoInput(0),\n    #             NoInput(0),\n    #             \"tagged\",\n    #             NoInput(0),\n    #             NoInput(0),\n    #             \"\",\n    #         )\n    #\n    #     with pytest.raises(ValueError, match=\"`curves` must contain str curve `id` s\"):\n    #         _get_curves_fx_and_base_maybe_from_solver(\n    #             NoInput(0),\n    #             solver,\n    #             \"bad_id\",\n    #             NoInput(0),\n    #             NoInput(0),\n    #             \"\",\n    #         )\n    #\n    #     with pytest.raises(ValueError, match=\"Can only supply a maximum of 4 `curves`\"):\n    #         _get_curves_fx_and_base_maybe_from_solver(\n    #             NoInput(0),\n    #             solver,\n    #             [\"tagged\"] * 5,\n    #             NoInput(0),\n    #             NoInput(0),\n    #             \"\",\n    #         )\n\n    # @pytest.mark.parametrize(\"num\", [1, 2, 3, 4])\n    # def test_get_curves_from_solver_multiply(self, num) -> None:\n    #     from rateslib.solver import Solver\n    #\n    #     curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"tagged\")\n    #     inst = [Value(dt(2023, 1, 1), curves=\"tagged\")]\n    #     solver = Solver([curve], [], inst, [0.975])\n    #     result, _, _ = _get_curves_fx_and_base_maybe_from_solver(\n    #         NoInput(0),\n    #         solver,\n    #         [\"tagged\"] * num,\n    #         NoInput(0),\n    #         NoInput(0),\n    #         \"\",\n    #     )\n    #     assert result == (curve, curve, curve, curve)\n\n    def test_get_proxy_curve_from_solver(self, usdusd, usdeur, eureur) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"tagged\")\n        inst = [Value(dt(2023, 1, 1), curves=\"tagged\")]\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": usdusd, \"usdeur\": usdeur, \"eureur\": eureur},\n        )\n        solver = Solver([curve], [], inst, [0.975], fx=fxf)\n        curve = fxf.curve(\"eur\", \"usd\")\n        irs = IRS(dt(2022, 1, 1), \"3m\", \"Q\")\n\n        # test the curve will return even though it is not included within the solver\n        # because it is a proxy curve.\n        irs.npv(curves=curve, solver=solver)\n\n    def test_ambiguous_curve_in_out_id_solver_raises(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0}, id=\"cloned-id\")\n        curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"cloned-id\")\n        solver = Solver(\n            curves=[curve2],\n            instruments=[IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"cloned-id\")],\n            s=[5.0],\n        )\n        irs = IRS(dt(2022, 1, 1), \"1y\", \"A\", fixed_rate=2.0)\n        with pytest.raises(ValueError, match=\"A curve has been supplied, as part of ``curves``,\"):\n            irs.npv(curves=curve, solver=solver)\n\n    def test_get_multicsa_curve_from_solver(self, usdusd, usdeur, eureur) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"tagged\")\n        inst = [Value(dt(2023, 1, 1), curves=\"tagged\")]\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": usdusd, \"usdeur\": usdeur, \"eureur\": eureur},\n        )\n        solver = Solver([curve], [], inst, [0.975], fx=fxf)\n        curve = fxf.curve(\"eur\", (\"usd\", \"eur\"))\n        irs = IRS(dt(2022, 1, 1), \"3m\", \"Q\")\n\n        # test the curve will return even though it is not included within the solver\n        # because it is a proxy curve.\n        irs.npv(curves=curve, solver=solver)\n\n\nclass TestSolverFXandBase:\n    \"\"\"\n    Test the npv method with combinations of solver fx and base args.\n    \"\"\"\n\n    @classmethod\n    def setup_class(cls):\n        \"\"\"setup any state specific to the execution of the given class (which\n        usually contains tests).\n        \"\"\"\n        cls.curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.96}, id=\"curve\")\n        cls.fxr = FXRates({\"eurusd\": 1.1, \"gbpusd\": 1.25}, base=\"gbp\")\n        cls.irs = IRS(dt(2022, 2, 1), \"6M\", \"A\", curves=cls.curve, fixed_rate=4.0)\n        cls.solver = Solver(\n            curves=[cls.curve],\n            instruments=[IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=cls.curve)],\n            s=[4.109589041095898],\n            id=\"Solver\",\n        )\n        cls.nxcs = XCS(\n            dt(2022, 2, 1),\n            \"6M\",\n            \"A\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            curves=[cls.curve] * 4,\n            currency=\"eur\",\n            pair=\"eurusd\",\n            float_spread=2.0,\n        )\n\n    @classmethod\n    def teardown_class(cls):\n        \"\"\"teardown any state that was previously setup with a call to\n        setup_class.\n        \"\"\"\n        pass\n\n    # ``base`` is explcit\n\n    def test_base_and_fx(self) -> None:\n        # calculable since base aligns with local currency\n        result = self.irs.npv(fx=self.fxr, base=\"eur\")\n        expected = 330.4051154763001 / 1.1\n        assert abs(result - expected) < 1e-4\n\n        with pytest.warns(\n            DeprecationWarning,\n            match=r\"Supplying `fx` as numeric is ambiguous, particularly with multi-curr\",\n        ):\n            # warn about numeric\n            self.irs.npv(fx=1 / 1.1, base=\"eur\")\n\n        # raises because no FX data to calculate a conversion\n        with pytest.raises(KeyError, match=\"'usd'\"):\n            self.irs.npv(fx=FXRates({\"eurgbp\": 1.1}), base=\"eur\")\n\n    def test_base_and_solverfx(self) -> None:\n        # should take fx from solver and calculated\n        self.solver.fx = FXRates({\"eurusd\": 1.1})\n        self.solver._set_new_state()\n        result = self.irs.npv(solver=self.solver, base=\"eur\")\n        expected = 330.4051154763001 / 1.1\n        assert abs(result - expected) < 1e-4\n        self.solver.fx = NoInput(0)\n\n    def test_base_and_fx_and_solverfx(self) -> None:\n        # should take fx and ignore solver.fx\n        fxr = FXRates({\"eurusd\": 1.2})\n        self.solver.fx = fxr\n        self.solver._set_new_state()\n\n        # no warning becuase objects are the same\n        result = self.irs.npv(solver=self.solver, base=\"eur\", fx=fxr)\n        expected = 330.4051154763001 / 1.2\n        assert abs(result - expected) < 1e-4\n\n        # should give warning because obj id are different\n        with pytest.warns(UserWarning):\n            result = self.irs.npv(solver=self.solver, base=\"eur\", fx=self.fxr)\n            expected = 330.4051154763001 / 1.1\n            assert abs(result - expected) < 1e-4\n\n        self.solver.fx = NoInput(0)\n\n    def test_base_only(self) -> None:\n        # calculable since base aligns with local currency\n        result = self.irs.npv(base=\"usd\")\n        expected = 330.4051154763001\n        assert abs(result - expected) < 1e-4\n\n        # raises becuase no FX data to calculate a conversion\n        with pytest.raises(ValueError, match=\"`base` \"):\n            result = self.irs.npv(base=\"eur\")\n\n    def test_base_solvernofx(self) -> None:\n        # calculable since base aligns with local currency\n        result = self.irs.npv(base=\"usd\", solver=self.solver)\n        expected = 330.4051154763001\n        assert abs(result - expected) < 1e-4\n\n        # raises becuase no FX data to calculate a conversion\n        with pytest.raises(ValueError, match=\"`base` \"):\n            result = self.irs.npv(base=\"eur\", solver=self.solver)\n\n    # ``base`` is inferred\n\n    def test_no_args(self) -> None:\n        # should result in a local NPV calculation\n        result = self.irs.npv()\n        expected = 330.4051154763001\n        assert abs(result - expected) < 1e-4\n\n    def test_fx(self) -> None:\n        # this was amended by v2.5. `base` must now be explicit and is not inherited.\n        result = self.irs.npv(fx=self.fxr)\n        expected = 330.4051154763001  # / 1.25\n        assert abs(result - expected) < 1e-4\n\n    def test_fx_solverfx(self) -> None:\n        # this was amended by v2.5. `base` must now be explicit and is not inherited.\n        fxr = FXRates({\"eurusd\": 1.2}, base=\"eur\")\n        self.solver.fx = fxr\n        self.solver._set_new_state()\n\n        # no warning because objects are the same\n        result = self.irs.npv(solver=self.solver, fx=fxr)\n        expected = 330.4051154763001  # / 1.2\n        assert abs(result - expected) < 1e-4\n\n        # should give warning because obj id are different\n        with pytest.warns(UserWarning, match=\"Solver contains an `fx` attribute but an `fx` ar\"):\n            result = self.irs.npv(solver=self.solver, fx=self.fxr)\n            expected = 330.4051154763001  # / 1.25\n            assert abs(result - expected) < 1e-4\n\n        self.solver.fx = NoInput(0)\n\n    def test_solverfx(self) -> None:\n        fxr = FXRates({\"eurusd\": 1.2}, base=\"eur\")\n        self.solver.fx = fxr\n        self.solver._set_new_state()\n\n        # no warning becuase objects are the same\n        result = self.irs.npv(solver=self.solver)\n        expected = 330.4051154763001  # base in this should be local currency not eur.\n        assert abs(result - expected) < 1e-4\n\n        self.solver.fx = NoInput(0)\n\n\nclass TestNullPricing:\n    # test instruments can be priced without defining a pricing parameter.\n\n    @pytest.mark.parametrize(\n        \"inst\",\n        [\n            CDS(\n                dt(2022, 7, 1), \"3M\", \"Q\", curves=[\"eureur\", \"usdusd\"], notional=1e6 * 25 / 14.91357\n            ),\n            IRS(dt(2022, 7, 1), \"3M\", \"A\", curves=\"eureur\", notional=1e6),\n            STIRFuture(\n                dt(2022, 3, 16),\n                dt(2022, 6, 15),\n                \"Q\",\n                curves=\"eureur\",\n                spec=\"usd_stir\",\n                contracts=-1,\n            ),\n            FRA(dt(2022, 7, 1), \"3M\", \"A\", curves=\"eureur\", notional=1e6),\n            SBS(\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                curves=[\"eureur\", \"eureur\", \"eurusd\", \"eureur\"],\n                notional=-1e6,\n            ),\n            ZCS(dt(2022, 7, 1), \"3M\", \"A\", curves=\"eureur\", notional=1e6),\n            IIRS(\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                curves=[\"eu_cpi\", \"eureur\", \"eureur\", \"eureur\"],\n                notional=1e6,\n            ),\n            IIRS(\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                curves=[\"eu_cpi\", \"eureur\", \"eureur\", \"eureur\"],\n                notional=1e6,\n                notional_exchange=True,\n            ),\n            # TODO add a null price test for ZCIS\n            XCS(  # XCS-FixedFloatNonMtm\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=True,\n                leg2_fixed=False,\n                leg2_mtm=False,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                curves=[\"eureur\", \"eureur\", \"usdusd\", \"usdusd\"],\n                notional=1e6,\n            ),\n            XCS(  # XCS-FixedFixedNonMtm\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=True,\n                leg2_fixed=True,\n                leg2_mtm=False,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                fixed_rate=1.2,\n                curves=[\"eureur\", \"eureur\", \"usdusd\", \"usdusd\"],\n                notional=1e6,\n            ),\n            XCS(  # XCS - FixedFloat\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=True,\n                leg2_fixed=False,\n                leg2_mtm=True,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                curves=[\"eureur\", \"eureur\", \"usdusd\", \"usdusd\"],\n                notional=1e6,\n            ),\n            XCS(  # XCS-FixedFixed\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=True,\n                leg2_fixed=True,\n                leg2_mtm=True,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                leg2_fixed_rate=1.3,\n                curves=[\"eureur\", \"eureur\", \"usdusd\", \"usdusd\"],\n                notional=1e6,\n            ),\n            XCS(  # XCS - FloatFixed\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=False,\n                leg2_fixed=True,\n                leg2_mtm=True,\n                currency=\"usd\",\n                pair=\"usdeur\",\n                curves=[\"usdusd\", \"usdusd\", \"eureur\", \"eureur\"],\n                notional=-1e6,\n            ),\n        ],\n    )\n    def test_null_priced_delta(self, inst) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"eureur\")\n        c3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.982}, id=\"eurusd\")\n        c4 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995},\n            id=\"eu_cpi\",\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            {\"usdusd\": c1, \"eureur\": c2, \"eurusd\": c3},\n        )\n        ins = [\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eureur\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"usdusd\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eurusd\"),\n            ZCIS(dt(2022, 1, 1), \"1y\", \"A\", curves=[\"eureur\", \"eureur\", \"eu_cpi\", \"eureur\"]),\n        ]\n        solver = Solver(\n            curves=[c1, c2, c3, c4, fxf.curve(\"usd\", \"eur\", \"usdeur\")],\n            instruments=ins,\n            s=[1.2, 1.3, 1.33, 0.5],\n            id=\"solver\",\n            instrument_labels=[\"eur 1y\", \"usd 1y\", \"eur 1y xcs adj.\", \"1y cpi\"],\n            fx=fxf,\n        )\n        result = inst.delta(solver=solver)\n        assert abs(result.iloc[0, 0] - 25.0) < 1.0\n        result2 = inst.npv(solver=solver)\n        assert abs(result2) < 1e-3\n\n        # test that instruments have not been set by the previous pricing action\n        solver.s = [1.3, 1.4, 1.36, 0.55]\n        solver.iterate()\n        result3 = inst.npv(solver=solver)\n        assert abs(result3) < 1e-3\n\n    @pytest.mark.parametrize(\n        \"inst\",\n        [\n            FXSwap(\n                dt(2022, 7, 1),\n                \"3M\",\n                pair=\"eurusd\",\n                curves=[\"eureur\", \"usdeur\"],\n                notional=-1e6,\n                fx_rate=0.999851,\n                split_notional=-1003052.812,\n                points=-0.756443,\n            ),\n            FXForward(\n                settlement=dt(2022, 10, 1),\n                pair=\"eurusd\",\n                curves=[\"eureur\", \"usdeur\"],\n                notional=-1e6 * 25 / 74.27,\n            ),\n        ],\n    )\n    def test_instruments_that_cannot_be_set_to_mid_market_if_null_priced(self, inst) -> None:\n        # These instruments behave differently when they have no pricing parameters with regards\n        # to risk becuase they cannot have FXFixings set, otherwise it breaks the fixings\n        # calculation (or each call manually requires a reset fixing process)\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"eureur\")\n        c3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.982}, id=\"eurusd\")\n        c4 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995},\n            id=\"eu_cpi\",\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            {\"usdusd\": c1, \"eureur\": c2, \"eurusd\": c3},\n        )\n        ins = [\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eureur\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"usdusd\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eurusd\"),\n            ZCIS(dt(2022, 1, 1), \"1y\", \"A\", curves=[\"eureur\", \"eureur\", \"eu_cpi\", \"eureur\"]),\n        ]\n        solver = Solver(\n            curves=[c1, c2, c3, c4, fxf.curve(\"usd\", \"eur\", \"usdeur\")],\n            instruments=ins,\n            s=[1.2, 1.3, 1.33, 0.5],\n            id=\"solver\",\n            instrument_labels=[\"eur 1y\", \"usd 1y\", \"eur 1y xcs adj.\", \"1y cpi\"],\n            fx=fxf,\n        )\n        result = inst.delta(solver=solver)\n        assert abs(result.iloc[0, 0] - 25.0) < 1.0\n        result2 = inst.npv(solver=solver)\n        assert abs(result2) < 1e-3\n\n        # # test that instruments have not been set by the previous pricing action\n        # solver.s = [1.3, 1.4, 1.36, 0.55]\n        # solver.iterate()\n        # result3 = inst.npv(solver=solver)\n        # assert abs(result3) < 1e-3\n\n    @pytest.mark.parametrize(\n        \"inst\",\n        [\n            XCS(  # XCS - FloatFloat\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                currency=\"usd\",\n                pair=\"usdeur\",\n                curves=[\"usdusd\", \"usdusd\", \"eureur\", \"eurusd\"],\n                notional=1e6,\n                float_spread=-12.876098007605556,\n            ),\n            XCS(  # XCS-FloatFloatNonMtm\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=False,\n                leg2_fixed=False,\n                leg2_mtm=False,\n                currency=\"usd\",\n                pair=\"usdeur\",\n                curves=[\"usdusd\", \"usdusd\", \"eureur\", \"eurusd\"],\n                notional=1e6,\n                metric=\"leg2\",\n                leg2_float_spread=12.877093409125974,\n            ),\n        ],\n    )\n    def test_null_priced_delta_xcs_float_spread(self, inst) -> None:\n        # all float spreads are defaulted to 0.0 so are artificially priced contracts\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"eureur\")\n        c3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.982}, id=\"eurusd\")\n        c4 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995},\n            id=\"eu_cpi\",\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            {\"usdusd\": c1, \"eureur\": c2, \"eurusd\": c3},\n        )\n        ins = [\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eureur\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"usdusd\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eurusd\"),\n            ZCIS(dt(2022, 1, 1), \"1y\", \"A\", curves=[\"eureur\", \"eureur\", \"eu_cpi\", \"eureur\"]),\n        ]\n        solver = Solver(\n            curves=[c1, c2, c3, c4],\n            instruments=ins,\n            s=[1.2, 1.3, 1.33, 0.5],\n            id=\"solver\",\n            instrument_labels=[\"eur 1y\", \"usd 1y\", \"eur 1y xcs adj.\", \"1y cpi\"],\n            fx=fxf,\n        )\n        result = inst.delta(solver=solver)\n        # rate = inst.rate(solver=solver)\n        assert abs(result.iloc[0, 0] - 25.0) < 1.0\n        result2 = inst.npv(solver=solver)\n        assert abs(result2) < 1e-3\n\n        # test that instruments have not been set by the previous pricing action\n        solver.s = [1.3, 1.4, 1.36, 0.55]\n        solver.iterate()\n        result3 = inst.npv(solver=solver)\n        assert abs(result3) > 175  # becuase XCS is priced so its value has changed\n\n    @pytest.mark.parametrize(\n        \"inst\",\n        [\n            NDF(\n                pair=\"eurusd\",\n                notional=1e6 * 0.333,\n                settlement=dt(2022, 10, 1),\n                curves=\"usdusd\",\n            )\n        ],\n    )\n    def test_null_priced_delta2(self, inst) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"eureur\")\n        c3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.982}, id=\"eurusd\")\n        c4 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995},\n            id=\"eu_cpi\",\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            {\"usdusd\": c1, \"eureur\": c2, \"eurusd\": c3},\n        )\n        ins = [\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eureur\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"usdusd\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eurusd\"),\n            ZCIS(dt(2022, 1, 1), \"1y\", \"A\", curves=[\"eureur\", \"eureur\", \"eu_cpi\", \"eureur\"]),\n        ]\n        solver = Solver(\n            curves=[c1, c2, c3, c4],\n            instruments=ins,\n            s=[1.2, 1.3, 1.33, 0.5],\n            id=\"solver\",\n            instrument_labels=[\"eur 1y\", \"usd 1y\", \"eur 1y xcs adj.\", \"1y cpi\"],\n            fx=fxf,\n        )\n        result = inst.delta(solver=solver)\n        assert abs(result.iloc[1, 0] - 25.0) < 1.0\n        result2 = inst.npv(solver=solver)\n        assert abs(result2) < 1e-3\n\n        # test that instruments have not been set by the previous pricing action\n        solver.s = [1.3, 1.4, 1.36, 0.55]\n        solver.iterate()\n        result3 = inst.npv(solver=solver)\n        assert abs(result3) < 1e-3\n\n    @pytest.mark.parametrize(\n        \"inst\",\n        [\n            NDF(\n                pair=\"eurusd\",\n                notional=1e6 * 0.333,\n                settlement=dt(2022, 10, 1),\n                curves=\"usdusd\",\n            )\n        ],\n    )\n    def test_null_priced_gamma2(self, inst) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"eureur\")\n        c3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.982}, id=\"eurusd\")\n        c4 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995},\n            id=\"eu_cpi\",\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            {\"usdusd\": c1, \"eureur\": c2, \"eurusd\": c3},\n        )\n        ins = [\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eureur\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"usdusd\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eurusd\"),\n            ZCIS(dt(2022, 1, 1), \"1y\", \"A\", curves=[\"eureur\", \"eureur\", \"eu_cpi\", \"eureur\"]),\n        ]\n        solver = Solver(\n            curves=[c1, c2, c3, c4],\n            instruments=ins,\n            s=[1.2, 1.3, 1.33, 0.5],\n            id=\"solver\",\n            instrument_labels=[\"eur 1y\", \"usd 1y\", \"eur 1y xcs adj.\", \"1y cpi\"],\n            fx=fxf,\n        )\n        result = inst.gamma(solver=solver)\n        assert isinstance(result, DataFrame)\n\n    @pytest.mark.parametrize(\n        (\"inst\", \"param\"),\n        [\n            (IRS(dt(2022, 7, 1), \"3M\", \"A\", curves=\"usdusd\"), \"fixed_rate\"),\n            (FRA(dt(2022, 7, 1), \"3M\", \"Q\", curves=\"usdusd\"), \"fixed_rate\"),\n            (\n                SBS(dt(2022, 7, 1), \"3M\", \"Q\", curves=[\"usdusd\", \"usdusd\", \"eureur\", \"usdusd\"]),\n                \"float_spread\",\n            ),\n            (ZCS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=[\"usdusd\"]), \"fixed_rate\"),\n            (\n                ZCIS(dt(2022, 1, 1), \"1Y\", \"A\", curves=[\"usdusd\", \"usdusd\", \"eu_cpi\", \"usdusd\"]),\n                \"fixed_rate\",\n            ),\n            (\n                IIRS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=[\"eu_cpi\", \"usdusd\", \"usdusd\", \"usdusd\"]),\n                \"fixed_rate\",\n            ),\n            (\n                FXForward(\n                    dt(2022, 3, 1),\n                    pair=\"usdeur\",\n                    curves=[NoInput(0), \"usdusd\", NoInput(0), \"eurusd\"],\n                ),\n                \"fx_rate\",\n            ),\n        ],\n    )\n    def test_null_priced_delta_round_trip_one_pricing_param(self, inst, param) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"eureur\")\n        c3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.982}, id=\"eurusd\")\n        c4 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995},\n            id=\"eu_cpi\",\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            {\"usdusd\": c1, \"eureur\": c2, \"eurusd\": c3},\n        )\n        ins = [\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eureur\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"usdusd\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eurusd\"),\n            ZCIS(dt(2022, 1, 1), \"1y\", \"A\", curves=[\"eureur\", \"eureur\", \"eu_cpi\", \"eureur\"]),\n        ]\n        solver = Solver(\n            curves=[c1, c2, c3, c4],\n            instruments=ins,\n            s=[1.2, 1.3, 1.33, 0.5],\n            id=\"solver\",\n            instrument_labels=[\"eur 1y\", \"usd 1y\", \"eur 1y xcs adj.\", \"1y cpi\"],\n            fx=fxf,\n        )\n\n        unpriced_delta = inst.delta(solver=solver)\n        mid_market_price = inst.rate(solver=solver)\n        setattr(inst, param, float(mid_market_price))\n        priced_delta = inst.delta(solver=solver)\n\n        assert_frame_equal(unpriced_delta, priced_delta)\n\n    @pytest.mark.parametrize(\n        (\"inst\", \"param\"),\n        [\n            (\n                FXSwap(\n                    dt(2022, 2, 1),\n                    \"3M\",\n                    pair=\"eurusd\",\n                    curves=[NoInput(0), \"eurusd\", NoInput(0), \"usdusd\"],\n                ),\n                \"points\",\n            ),\n        ],\n    )\n    def test_null_priced_delta_round_trip_one_pricing_param_fx_fix(self, inst, param) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"eureur\")\n        c3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.982}, id=\"eurusd\")\n        c4 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995},\n            id=\"eu_cpi\",\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            {\"usdusd\": c1, \"eureur\": c2, \"eurusd\": c3},\n        )\n        ins = [\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eureur\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"usdusd\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"eurusd\"),\n            ZCIS(dt(2022, 1, 1), \"1y\", \"A\", curves=[\"eureur\", \"eureur\", \"eu_cpi\", \"eureur\"]),\n        ]\n        solver = Solver(\n            curves=[c1, c2, c3, c4],\n            instruments=ins,\n            s=[1.2, 1.3, 1.33, 0.5],\n            id=\"solver\",\n            instrument_labels=[\"eur 1y\", \"usd 1y\", \"eur 1y xcs adj.\", \"1y cpi\"],\n            fx=fxf,\n        )\n\n        unpriced_delta = inst.delta(solver=solver, fx=fxf)\n        mid_market_price = inst.rate(solver=solver, fx=fxf)\n        setattr(inst, param, float(mid_market_price))\n        priced_delta = inst.delta(solver=solver, fx=fxf)\n\n        assert_frame_equal(unpriced_delta, priced_delta)\n\n    @pytest.mark.parametrize(\n        \"inst\",\n        [\n            CDS(dt(2022, 7, 1), \"3M\", \"Q\", notional=1e6 * 25 / 14.91357),\n            IRS(dt(2022, 7, 1), \"3M\", \"A\", notional=1e6),\n            FRA(dt(2022, 7, 1), \"3M\", \"A\", notional=1e6),\n            SBS(\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                notional=-1e6,\n            ),\n            ZCS(dt(2022, 7, 1), \"3M\", \"A\", notional=1e6),\n            IIRS(\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                notional=1e6,\n            ),\n            IIRS(\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                notional=1e6,\n                notional_exchange=True,\n            ),\n            # # TODO add a null price test for ZCIS\n            XCS(  # XCS - FloatFloat\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                currency=\"usd\",\n                pair=\"usdeur\",\n                notional=1e6,\n            ),\n            XCS(  # XCS-FloatFloatNonMtm\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=False,\n                leg2_fixed=False,\n                leg2_mtm=False,\n                currency=\"usd\",\n                pair=\"usdeur\",\n                notional=1e6,\n            ),\n            XCS(  # XCS-FixedFloatNonMtm\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=True,\n                leg2_fixed=False,\n                leg2_mtm=False,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                notional=1e6,\n            ),\n            XCS(  # XCS-FixedFixedNonMtm\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=True,\n                leg2_fixed=True,\n                leg2_mtm=False,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                leg2_fixed_rate=1.2,\n                notional=1e6,\n            ),\n            XCS(  # XCS - FixedFloat\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=True,\n                leg2_fixed=False,\n                leg2_mtm=True,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                notional=1e6,\n            ),\n            XCS(  # XCS-FixedFixed\n                dt(2022, 7, 1),\n                \"3M\",\n                \"A\",\n                fixed=True,\n                leg2_fixed=True,\n                leg2_mtm=True,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                leg2_fixed_rate=1.3,\n                notional=1e6,\n            ),\n            FXSwap(\n                dt(2022, 7, 1),\n                \"3M\",\n                pair=\"usdeur\",\n                notional=-1e6,\n                # fx_fixing=0.999851,\n                # split_notional=1003052.812,\n                # points=2.523505,\n            ),\n            FXForward(\n                settlement=dt(2022, 10, 1),\n                pair=\"usdeur\",\n                notional=-1e6 * 25 / 74.27,\n            ),\n            # NDF(\n            #     pair=\"eurusd\",  # settlement currency defaults to right hand side: usd\n            #     settlement=dt(2022, 10, 1),\n            # ),\n        ],\n    )\n    def test_set_pricing_does_not_overwrite_unpriced_status(self, inst):\n        # unpriced instruments run a `set_pricing_mid` method\n        # this test ensures that after that run the price is not permanently set and\n        # will reset when priced from an alternative set of curves.\n        # test is slightly different to null_priced_delta: uses fx and includes rate call\n        curve1 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.99}, index_base=66)\n        curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.98}, index_base=66)\n        curve3 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.97})\n        curve4 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.96}, index_base=80)\n        curve5 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.95}, index_base=80)\n        curve6 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.94})\n        fxr1 = FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1))\n        fxr2 = FXRates({\"eurusd\": 1.5}, settlement=dt(2022, 1, 1))\n        fxf1 = FXForwards(fxr1, {\"usdusd\": curve1, \"eureur\": curve2, \"eurusd\": curve3})\n        fxf2 = FXForwards(fxr2, {\"usdusd\": curve4, \"eureur\": curve5, \"eurusd\": curve6})\n\n        rate1 = inst.rate(\n            curves=dict(\n                rate_curve=curve1,\n                disc_curve=curve1,\n                index_curve=curve1,\n                leg2_rate_curve=curve2,\n                leg2_disc_curve=curve3,\n                leg2_index_curve=curve2,\n            ),\n            fx=fxf1,\n        )\n        npv1 = inst.npv(\n            curves=dict(\n                rate_curve=curve1,\n                disc_curve=curve1,\n                index_curve=curve1,\n                leg2_rate_curve=curve2,\n                leg2_disc_curve=curve3,\n                leg2_index_curve=curve2,\n            ),\n            fx=fxf1,\n        )\n        assert abs(npv1) < 1e-8\n\n        rate2 = inst.rate(\n            curves=dict(\n                rate_curve=curve4,\n                disc_curve=curve4,\n                index_curve=curve4,\n                leg2_rate_curve=curve5,\n                leg2_disc_curve=curve6,\n                leg2_index_curve=curve5,\n            ),\n            fx=fxf2,\n        )\n        npv2 = inst.npv(\n            curves=dict(\n                rate_curve=curve4,\n                disc_curve=curve4,\n                index_curve=curve4,\n                leg2_rate_curve=curve5,\n                leg2_disc_curve=curve6,\n                leg2_index_curve=curve5,\n            ),\n            fx=fxf2,\n        )\n        assert rate1 != rate2\n        assert abs(npv2) < 1e-8\n\n    @pytest.mark.parametrize(\n        \"inst\",\n        [\n            STIRFuture(\n                dt(2022, 3, 16),\n                dt(2022, 6, 15),\n                \"Q\",\n                spec=\"usd_stir\",\n                contracts=-1,\n            ),\n            # # TODO add a null price test for ZCIS\n        ],\n    )\n    def test_set_pricing_does_not_overwrite_unpriced_status_single_currency_inst(self, inst):\n        # unpriced instruments run a `set_pricing_mid` method\n        # this test ensures that after that run the price is not permanently set and\n        # will reset when priced from an alternative set of curves.\n        # test is slightly different to null_priced_delta: uses fx and includes rate call\n        curve1 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.99}, index_base=66)\n        curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.98}, index_base=66)\n        curve3 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.97})\n        curve4 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.96}, index_base=80)\n        curve5 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.95}, index_base=80)\n        curve6 = Curve({dt(2022, 1, 1): 1.0, dt(2024, 1, 1): 0.94})\n        fxr1 = FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1))\n        fxr2 = FXRates({\"eurusd\": 1.5}, settlement=dt(2022, 1, 1))\n        fxf1 = FXForwards(fxr1, {\"usdusd\": curve1, \"eureur\": curve2, \"eurusd\": curve3})\n        fxf2 = FXForwards(fxr2, {\"usdusd\": curve4, \"eureur\": curve5, \"eurusd\": curve6})\n\n        rate1 = inst.rate(\n            curves=dict(\n                rate_curve=curve1,\n                disc_curve=curve1,\n                index_curve=curve1,\n                leg2_rate_curve=curve2,\n                leg2_disc_curve=curve1,\n                leg2_index_curve=curve2,\n            ),\n            fx=fxf1,\n        )\n        npv1 = inst.npv(\n            curves=dict(\n                rate_curve=curve1,\n                disc_curve=curve1,\n                index_curve=curve1,\n                leg2_rate_curve=curve2,\n                leg2_disc_curve=curve1,\n                leg2_index_curve=curve2,\n            ),\n            fx=fxf1,\n        )\n        assert abs(npv1) < 1e-8\n\n        rate2 = inst.rate(\n            curves=dict(\n                rate_curve=curve4,\n                disc_curve=curve4,\n                index_curve=curve4,\n                leg2_rate_curve=curve5,\n                leg2_disc_curve=curve4,\n                leg2_index_curve=curve5,\n            ),\n            fx=fxf2,\n        )\n        npv2 = inst.npv(\n            curves=dict(\n                rate_curve=curve4,\n                disc_curve=curve4,\n                index_curve=curve4,\n                leg2_rate_curve=curve5,\n                leg2_disc_curve=curve4,\n                leg2_index_curve=curve5,\n            ),\n            fx=fxf2,\n        )\n        assert rate1 != rate2\n        assert abs(npv2) < 1e-8\n\n\nclass TestIRS:\n    @pytest.mark.parametrize(\n        (\"float_spread\", \"fixed_rate\", \"expected\"),\n        [\n            (0, 4.03, 4.03637780),\n            (3, 4.03, 4.06637780),\n            (0, 5.10, 4.03637780),\n        ],\n    )\n    def test_irs_rate(self, curve, float_spread, fixed_rate, expected) -> None:\n        # test the mid-market rate ignores the given fixed_rate and reacts to float_spread\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            fixed_rate=fixed_rate,\n            stub=\"ShortFront\",\n            leg2_float_spread=float_spread,\n        )\n        result = irs.rate(curves=curve)\n        assert abs(result - expected) < 1e-7\n\n    @pytest.mark.parametrize(\n        (\"float_spread\", \"fixed_rate\", \"expected\"),\n        [\n            (0, 4.03, -0.63777963),\n            (200, 4.03, -0.63777963),\n            (500, 4.03, -0.63777963),\n            (0, 4.01, -2.63777963),\n        ],\n    )\n    def test_irs_spread_none_simple(self, curve, float_spread, fixed_rate, expected) -> None:\n        # test the mid-market float spread ignores the given float_spread and react to fixed\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            fixed_rate=fixed_rate,\n            leg2_float_spread=float_spread,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            leg2_spread_compound_method=\"none_simple\",\n            stub=\"ShortFront\",\n        )\n        result = irs.spread(curves=curve)\n        assert abs(result - expected) < 1e-7\n\n        irs.leg2_float_spread = result\n        validate = irs.npv(curves=curve)\n        assert abs(validate) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"float_spread\", \"fixed_rate\", \"expected\"),\n        [\n            (0, 4.03, -0.6322524949759807),\n            (200, 4.03, -0.6322524951743129),\n            (500, 4.03, -0.6322524951743129),\n            (0, 4.01, -2.61497625534),\n        ],\n    )\n    def test_irs_spread_isda_compound(self, curve, float_spread, fixed_rate, expected) -> None:\n        # test the mid-market float spread ignores the given float_spread and react to fixed\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            fixed_rate=fixed_rate,\n            leg2_float_spread=float_spread,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            leg2_spread_compound_method=\"isda_compounding\",\n            stub=\"ShortFront\",\n        )\n        result = irs.spread(curves=curve)\n        assert abs(result - expected) < 1e-7\n\n        irs.leg2_float_spread = result\n        validate = irs.npv(curves=curve)\n        assert abs(validate) < 5e2\n\n    @pytest.mark.parametrize(\n        (\"float_spread\", \"fixed_rate\", \"expected\"),\n        [\n            (0, 4.03, -0.63500600),\n            (200, 4.03, -0.6348797243),\n            (500, 4.03, -0.6346903026),\n            (0, 4.01, -2.626308241),\n        ],\n    )\n    def test_irs_spread_isda_flat_compound(self, curve, float_spread, fixed_rate, expected) -> None:\n        # test the mid-market float spread ignores the given float_spread and react to fixed\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            fixed_rate=fixed_rate,\n            leg2_float_spread=float_spread,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            leg2_spread_compound_method=\"isda_flat_compounding\",\n            stub=\"ShortFront\",\n        )\n        result = irs.spread(curves=curve)\n        assert abs(result - expected) < 1e-2\n\n        irs.leg2_float_spread = result\n        validate = irs.npv(curves=curve)\n        assert abs(validate) < 20\n\n    def test_irs_npv(self, curve) -> None:\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            fixed_rate=4.035,\n            stub=\"ShortFront\",\n            leg2_float_spread=0,\n        )\n        result = irs.npv(curves=curve)\n        expected = irs.analytic_delta(curves=curve) * (4.035 - irs.rate(curves=curve)) * -100\n        assert abs(result - expected) < 1e-7\n        assert abs(result - 5704.13604352) < 1e-7\n\n    def test_irs_cashflows(self, curve) -> None:\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            fixed_rate=4.035,\n            leg2_float_spread=NoInput(0),\n            stub=\"ShortFront\",\n        )\n        result = irs.cashflows(curves=curve)\n        assert isinstance(result, DataFrame)\n        assert result.index.nlevels == 2\n\n    def test_irs_npv_mid_mkt_zero(self, curve) -> None:\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            stub=\"ShortFront\",\n        )\n        result = irs.npv(curves=curve)\n        assert abs(result) < 1e-8\n\n        irs.fixed_rate = 1.0  # pay fixed low rate implies positive NPV\n        assert irs.npv(curves=curve) > 1\n\n        irs.fixed_rate = NoInput(0)  # fixed rate set back to initial\n        assert abs(irs.npv(curves=curve)) < 1e-8\n\n        irs.fixed_rate = float(irs.rate(curves=curve))\n        irs.leg2_float_spread = 100\n        assert irs.npv(curves=curve) > 1\n\n        # irs.leg2_float_spread = NoInput(0)\n        # assert abs(irs.npv(curves=curve)) < 1e-8\n\n    @pytest.mark.skip(reason=\"unexpected attribute no longer raise exceptions\")\n    def test_sbs_float_spread_raises(self, curve) -> None:\n        irs = IRS(dt(2022, 1, 1), \"9M\", \"Q\")\n        with pytest.raises(AttributeError, match=\"property 'float_spread' of 'IRS' object has no \"):\n            irs.float_spread = 1.0\n\n    @pytest.mark.skip(reason=\"attribute mutation is not exhaustively blocked\")\n    def test_index_base_raises(self) -> None:\n        irs = IRS(dt(2022, 1, 1), \"9M\", \"Q\")\n        with pytest.raises(AttributeError, match=\"property 'float_spread' of 'IRS' object has no\"):\n            irs.index_base = 1.0\n\n        with pytest.raises(AttributeError, match=\"property 'float_spread' of 'IRS' object has no\"):\n            irs.leg2_index_base = 1.0\n\n    def test_irs_interpolated_stubs(self, curve) -> None:\n        curve6 = LineCurve({dt(2022, 1, 1): 4.0, dt(2023, 2, 1): 4.0})\n        curve3 = LineCurve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0})\n        curve1 = LineCurve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        irs = IRS(\n            effective=dt(2022, 1, 3),\n            termination=dt(2023, 1, 3),\n            front_stub=dt(2022, 2, 10),\n            back_stub=dt(2022, 8, 10),\n            frequency=\"Q\",\n            convention=\"act360\",\n            curves=[{\"3m\": curve3, \"1m\": curve1, \"6M\": curve6}, curve],\n            leg2_fixing_method=\"ibor(2)\",\n        )\n        cashflows = irs.cashflows()\n        assert (cashflows.loc[(\"leg2\", 0), \"Rate\"] - 1.23729) < 1e-4\n        assert (cashflows.loc[(\"leg2\", 3), \"Rate\"] - 3.58696) < 1e-4\n\n    def test_irs_interpolated_stubs_solver(self) -> None:\n        curve6 = Curve({dt(2022, 1, 1): 4.0, dt(2023, 2, 1): 4.0}, id=\"6m\")\n        curve3 = Curve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0}, id=\"3m\")\n        solver = Solver(\n            curves=[curve6, curve3],\n            instruments=[\n                IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=curve6),\n                IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=curve3),\n            ],\n            s=[6.0, 3.0],\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 3),\n            termination=dt(2022, 11, 3),\n            front_stub=dt(2022, 5, 3),\n            stub=\"Front\",\n            frequency=\"Q\",\n            convention=\"act360\",\n            curves=[{\"3m\": \"3m\", \"6m\": \"6m\"}, \"3m\"],\n            leg2_fixing_method=\"ibor(2)\",\n        )\n        cashflows = irs.cashflows(solver=solver)\n        assert (cashflows.loc[(\"leg2\", 0), \"Rate\"] - 3.93693) < 1e-4\n\n    def test_no_rfr_fixings_raises(self) -> None:\n        # GH 170\n        T_irs = IRS(\n            effective=dt(2020, 12, 15),\n            termination=dt(2037, 12, 15),\n            notional=-600e6,\n            frequency=\"A\",\n            leg2_frequency=\"A\",\n            fixed_rate=4.5,\n            curves=\"curve\",\n        )\n        par_curve = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 1.0,\n                dt(2024, 1, 1): 1.0,\n                dt(2025, 1, 1): 1.0,\n            },\n            id=\"curve\",\n        )\n        with pytest.raises(ValueError, match=\"`effective` date for rate period is before the init\"):\n            T_irs.npv(curves=par_curve)\n\n    def test_no_rfr_fixings_raises2(self) -> None:\n        # GH 357\n        sofr = Curve(\n            id=\"sofr\",\n            convention=\"Act360\",\n            calendar=\"nyc\",\n            modifier=\"MF\",\n            interpolation=\"log_linear\",\n            nodes={\n                dt(2023, 8, 21): 1.0,\n                dt(2026, 8, 25): 0.97,\n            },\n        )\n        irs = IRS(\n            effective=dt(2023, 8, 18),\n            termination=dt(2025, 8, 18),\n            notional=1e6,\n            curves=sofr,\n            fixed_rate=4.86,\n            spec=\"usd_irs\",\n        )\n        with pytest.raises(ValueError, match=\"`effective` date for rate period is before the init\"):\n            irs.npv()\n\n    def test_1b_tenor_swaps(self):\n        irs = IRS(dt(2024, 12, 30), \"1b\", spec=\"sek_irs\")  # 31st is a holiday.\n        assert irs.leg1.schedule.uschedule == [dt(2024, 12, 30), dt(2025, 1, 2)]\n\n    def test_1d_tenor_swaps(self):\n        irs = IRS(dt(2024, 12, 30), \"1d\", spec=\"sek_irs\")  # 31st is a holiday.\n        assert irs.leg1.schedule.uschedule == [dt(2024, 12, 30), dt(2025, 1, 2)]\n\n    def test_fixings_table(self, curve):\n        irs = IRS(dt(2022, 1, 15), \"6m\", spec=\"usd_irs\", curves=curve)\n        result = irs.local_analytic_rate_fixings()\n        assert isinstance(result, DataFrame)\n\n    def test_1d_instruments(self):\n        # GH484\n        with pytest.raises(ValueError, match=\"A Schedule could not be generated from the pa\"):\n            IRS(dt(2025, 1, 1), \"1d\", spec=\"sek_irs\")\n\n    def test_custom_amortization_raises(self):\n        with pytest.raises(ValueError, match=\"Custom amortisation schedules must have `n-1` amort\"):\n            IRS(dt(2000, 1, 1), dt(2000, 4, 1), \"M\", notional=1000, amortization=[100, 400, 50])\n\n    def test_custom_amortization(self):\n        irs = IRS(dt(2000, 1, 1), dt(2000, 5, 1), \"M\", notional=1000, amortization=[100, 400, 50])\n        assert irs.leg1.amortization.outstanding == (1000.0, 900.0, 500.0, 450.0)\n        assert irs.leg1.amortization.amortization == (100.0, 400.0, 50.0)\n        assert irs.leg2.amortization.outstanding == (-1000.0, -900.0, -500.0, -450.0)\n        assert irs.leg2.amortization.amortization == (-100.0, -400.0, -50.0)\n\n    def test_custom_amortization_as_object(self):\n        # test an Amortization object can be passed and is negated correctly\n        amort = Amortization(4, 1000.0, [100.0, 400.0, 50.0])\n        irs = IRS(dt(2000, 1, 1), dt(2000, 5, 1), \"M\", notional=1000, amortization=amort)\n        assert irs.leg1.amortization.outstanding == (1000.0, 900.0, 500.0, 450.0)\n        assert irs.leg1.amortization.amortization == (100.0, 400.0, 50.0)\n        assert irs.leg2.amortization.outstanding == (-1000.0, -900.0, -500.0, -450.0)\n        assert irs.leg2.amortization.amortization == (-100.0, -400.0, -50.0)\n\n    @pytest.mark.skip(reason=\"unexpected attribute no longer raise exceptions\")\n    def test_irs_attributes(self):\n        irs = IRS(dt(2000, 1, 1), dt(2000, 5, 1), \"M\", fixed_rate=2.0)\n        assert irs.fixed_rate == 2.0\n        with pytest.raises(AttributeError, match=\"Attribute not available on IRS\"):\n            irs.float_spread\n        with pytest.raises(AttributeError, match=\"Attribute not available on IRS\"):\n            irs.leg2_fixed_rate\n        assert irs.leg2_float_spread == 0.0\n\n    def test_irs_parse_curves(self, curve):\n        irs = IRS(dt(2000, 1, 1), dt(2000, 5, 1), \"M\", fixed_rate=2.0)\n        r1 = irs.npv(curves=[curve])\n        r2 = irs.npv(curves={\"rate_curve\": curve, \"disc_curve\": curve})\n        assert r1 == r2\n\n    def test_modifier_as_adjuster(self):\n        irs = IRS(dt(2000, 1, 1), \"1y\", \"S\", modifier=Adjuster.CalDaysLagSettle(10), calendar=\"ldn\")\n        assert irs.leg1.schedule.uschedule[0] == dt(2000, 1, 1)\n        assert irs.leg1.schedule.aschedule[0] == dt(2000, 1, 11)\n\n    def test_cny_zero_periods(self):\n        irs = IRS(\n            effective=dt(2026, 2, 4),\n            termination=dt(2031, 2, 4),\n            frequency=\"Q\",\n            calendar=\"bjs\",\n            modifier=\"F\",\n            payment_lag=0,\n            leg2_fixing_method=\"ibor(1)\",\n            convention=\"act365F\",\n            leg2_fixing_frequency=\"7D\",\n            leg2_fixing_series=FloatRateSeries(\n                lag=1,\n                convention=\"act365f\",\n                calendar=\"bjs\",\n                modifier=\"f\",\n                tenors=[\"7D\"],\n                zero_period_stub=\"shortback\",\n                eom=False,\n            ),\n            leg2_zero_periods=True,\n        )\n\n        assert isinstance(irs.leg2.periods[0], ZeroFloatPeriod)\n        fixing_dates = [\n            dt(2026, 2, 3),\n            dt(2026, 2, 10),\n            dt(2026, 2, 14),\n            dt(2026, 2, 24),\n            dt(2026, 3, 3),\n            dt(2026, 3, 10),\n            dt(2026, 3, 17),\n            dt(2026, 3, 24),\n            dt(2026, 3, 31),\n            dt(2026, 4, 7),\n            dt(2026, 4, 14),\n            dt(2026, 4, 21),\n            dt(2026, 4, 28),\n        ]\n        for i, float_period in enumerate(irs.leg2.periods[0].float_periods):\n            assert float_period.rate_params.rate_fixing.date == fixing_dates[i]\n\n        # test even stub sub-periods are fixed veruss \"7D\"\n        assert isinstance(\n            irs.leg2.periods[1].float_periods[-1].rate_params.rate_fixing, IBORStubFixing\n        )\n        assert isinstance(\n            irs.leg2.periods[1].float_periods[-1].rate_params.rate_fixing.fixing2, NoInput\n        )\n\n    def test_cny_golden_week_npv(self):\n        fixings.add(\n            \"CNR7_1W\",\n            Series(\n                index=[dt(2025, 9, 23), dt(2025, 9, 30), dt(2025, 10, 14)],\n                data=[1.53, 1.65, 1.48],\n            ),\n        )\n        irs = IRS(\n            effective=dt(2025, 9, 24),\n            termination=dt(2025, 10, 22),\n            frequency=\"Q\",\n            payment_lag=0,\n            leg2_fixing_method=\"ibor(1)\",\n            leg2_fixing_frequency=\"7D\",\n            leg2_zero_periods=True,\n            leg2_rate_fixings=\"CNR7\",\n            leg2_fixing_series=FloatRateSeries(\n                lag=1,\n                calendar=\"bjs\",\n                convention=\"act365f\",\n                tenors=[\"7D\"],\n                zero_period_stub=\"shortback\",\n                modifier=\"F\",\n                eom=False,\n            ),\n            fixed_rate=3.0,\n            calendar=\"bjs\",\n            convention=\"act365F\",\n            notional=-100e6,\n        )\n        curve = Curve(\n            {dt(2025, 10, 21): 1.0, dt(2026, 10, 21): 0.99}, calendar=\"bjs\", convention=\"act365f\"\n        )\n        _npv = irs.npv(curves=curve)\n        cf = irs.cashflows(curves=curve)\n\n        assert abs(cf.loc[(\"leg1\", 0), \"Cashflow\"] - 230136.99) < 1e-2\n        assert abs(cf.loc[(\"leg2\", 0), \"Cashflow\"] + 118426.165) < 1e-2\n\n        expected_rate = (\n            ((1 + 1.53 * 15 / 36500) * (1 + 1.65 * 6 / 36500) * (1 + 1.48 * 7 / 36500) - 1)\n            * 36500\n            / 28\n        )\n        assert abs(cf.loc[(\"leg2\", 0), \"Rate\"] - expected_rate) < 1e-4\n        fixings.pop(\"CNR7_1W\")\n\n\nclass TestNDIRS:\n    def test_irs_analytic_dv01(self, eureur, usdeur, usdusd) -> None:\n        # test the mid-market rate ignores the given fixed_rate and reacts to float_spread\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            frequency=\"Q\",\n            fixed_rate=1.05,\n            stub=\"ShortFront\",\n            leg2_float_spread=1.0,\n        )\n        anal_delta_l1_local = irs.analytic_delta(curves=[eureur, usdusd], fx=fxf)\n        anal_delta_l1_eur = irs.analytic_delta(curves=[eureur, usdusd], fx=fxf, base=\"eur\")\n        anal_delta_l2_local = irs.analytic_delta(curves=[eureur, usdusd], fx=fxf, leg=2)\n        anal_delta_l2_eur = irs.analytic_delta(curves=[eureur, usdusd], fx=fxf, base=\"eur\", leg=2)\n\n        assert abs(anal_delta_l1_local + anal_delta_l2_local) < 1e-8\n        assert abs(anal_delta_l1_eur + anal_delta_l2_eur) < 1e-8\n        assert abs(anal_delta_l1_local - anal_delta_l1_eur) > 4000\n        assert abs(anal_delta_l1_eur - 5 / 12 * 1e5) < 213\n\n    @pytest.mark.parametrize(\n        (\"float_spread\", \"fixed_rate\", \"expected\"),\n        [\n            (0, 1.05, 1.2033904812590062),\n            (3, 1.05, 1.2333904812590062),\n            (0, 1.25, 1.2033904812590062),\n        ],\n    )\n    def test_irs_rate(self, float_spread, fixed_rate, expected, eureur, usdeur, usdusd) -> None:\n        # test the mid-market rate ignores the given fixed_rate and reacts to float_spread\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            frequency=\"Q\",\n            fixed_rate=fixed_rate,\n            stub=\"ShortFront\",\n            leg2_float_spread=float_spread,\n        )\n        result = irs.rate(curves=[eureur, usdusd], fx=fxf)\n        assert abs(result - expected) < 1e-7\n\n    @pytest.mark.parametrize(\n        (\"float_spread\", \"fixed_rate\", \"expected\"),\n        [\n            (0, 1.05, -15.33904812590061),\n            (200, 1.05, -15.33904812590061),\n            (500, 1.05, -15.33904812590061),\n            (0, 1.02, -18.33904812590061),\n        ],\n    )\n    def test_irs_spread_none_simple(\n        self, curve, float_spread, fixed_rate, expected, eureur, usdeur, usdusd\n    ) -> None:\n        # test the mid-market float spread ignores the given float_spread and react to fixed\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fixed_rate=fixed_rate,\n            leg2_float_spread=float_spread,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            leg2_spread_compound_method=\"none_simple\",\n            stub=\"ShortFront\",\n        )\n        result = irs.spread(curves=[eureur, usdusd], fx=fxf)\n        assert abs(result - expected) < 1e-7\n\n        irs.leg2_float_spread = result\n        validate = irs.npv(curves=[eureur, usdusd], fx=fxf)\n        assert abs(validate) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"float_spread\", \"fixed_rate\", \"expected\"),\n        [\n            (0, 1.05, -15.301676945861795),\n            (200, 1.05, -15.301676945861795),\n            (500, 1.05, -15.301676945861795),\n            (0, 1.02, -18.294961421921645),\n        ],\n    )\n    def test_irs_spread_isda_compound(\n        self, float_spread, fixed_rate, expected, eureur, usdeur, usdusd\n    ) -> None:\n        # test the mid-market float spread ignores the given float_spread and react to fixed\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fixed_rate=fixed_rate,\n            leg2_float_spread=float_spread,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            leg2_spread_compound_method=\"isda_compounding\",\n            stub=\"ShortFront\",\n        )\n        result = irs.spread(curves=[eureur, usdusd], fx=fxf)\n        assert abs(result - expected) < 1e-7\n\n        irs.leg2_float_spread = result\n        validate = irs.npv(curves=[eureur, usdusd], fx=fxf)\n        assert abs(validate) < 5e-2\n\n    @pytest.mark.parametrize(\n        (\"float_spread\", \"fixed_rate\", \"expected\"),\n        [\n            (0, 1.05, -15.319076706336164),\n            (200, 1.05, -15.319076706336164),\n            (500, 1.05, -15.319076706336164),\n            (0, 1.02, -18.315170710463878),\n        ],\n    )\n    def test_irs_spread_isda_flat_compound(\n        self, float_spread, fixed_rate, expected, eureur, usdeur, usdusd\n    ) -> None:\n        # test the mid-market float spread ignores the given float_spread and react to fixed\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fixed_rate=fixed_rate,\n            leg2_float_spread=float_spread,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            leg2_spread_compound_method=\"isda_flat_compounding\",\n            stub=\"ShortFront\",\n        )\n        result = irs.spread(curves=[eureur, usdusd], fx=fxf)\n        assert abs(result - expected) < 1e-7\n\n        irs.leg2_float_spread = result\n        validate = irs.npv(curves=[eureur, usdusd], fx=fxf)\n        assert abs(validate) < 5e-2\n\n    def test_irs_npv(self, eureur, usdeur, usdusd) -> None:\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fixed_rate=1.24,\n            leg2_float_spread=3.0,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            stub=\"ShortFront\",\n            curves=[eureur, usdusd],\n        )\n\n        result = irs.npv(fx=fxf)\n        expected = irs.analytic_delta(fx=fxf) * (1.24 - irs.rate(fx=fxf)) * -100\n        assert abs(result - expected) < 1e-7\n        assert abs(result + 30138.5056568) < 1e-7\n\n    def test_irs_cashflows(self, eureur, usdeur, usdusd) -> None:\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fixed_rate=1.24,\n            leg2_float_spread=3.0,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            stub=\"ShortFront\",\n            curves=[eureur, usdusd],\n        )\n\n        result = irs.cashflows(fx=fxf)\n        assert isinstance(result, DataFrame)\n        assert all(result[defaults.headers[\"reference_currency\"]] == [\"EUR\", \"EUR\", \"EUR\", \"EUR\"])\n        assert irs.kwargs.leg1[\"mtm\"]\n        assert irs.kwargs.leg2[\"mtm\"]\n\n    def test_irs_npv_mid_mkt_zero(self, eureur, usdeur, usdusd) -> None:\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            leg2_float_spread=3.0,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            stub=\"ShortFront\",\n            curves=[eureur, usdusd],\n        )\n\n        result = irs.npv(fx=fxf)\n        assert abs(result) < 1e-8\n\n        irs.fixed_rate = 1.0  # pay fixed low rate implies positive NPV\n        assert irs.npv(fx=fxf) > 1\n\n        irs.fixed_rate = NoInput(0)  # fixed rate set back to initial\n        assert abs(irs.npv(fx=fxf)) < 1e-8\n\n        irs.fixed_rate = float(irs.rate(fx=fxf))\n        irs.leg2_float_spread = 100\n        assert irs.npv(fx=fxf) > 1\n\n    def test_fixings_table(self, eureur, usdeur, usdusd):\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2022, 1, 3)),\n            fx_curves={\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 6, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            leg2_float_spread=3.0,\n            leg2_fixing_method=\"rfr_payment_delay\",\n            stub=\"ShortFront\",\n            curves=[eureur, usdusd],\n        )\n        result = irs.local_analytic_rate_fixings(fx=fxf)\n        assert isinstance(result, DataFrame)\n        assert isinstance(result.iloc[0, 0], Dual)\n        assert abs(result.iloc[0, 0] - 304.26949) < 1e-5\n        assert abs(gradient(result.iloc[0, 0], vars=[\"fx_eurusd\"])[0] - 276.6) < 1e-1\n\n    def test_irs_parse_curves(self, curve):\n        irs = IRS(dt(2000, 1, 1), dt(2000, 5, 1), \"M\", fixed_rate=2.0)\n        r1 = irs.npv(curves=[curve])\n        r2 = irs.npv(curves={\"rate_curve\": curve, \"disc_curve\": curve})\n        assert r1 == r2\n\n    def test_spec_ndset(self):\n        irs = IRS(effective=dt(2022, 1, 1), termination=\"1y\", spec=\"inr_ndirs\")\n        assert irs.kwargs.leg1[\"pair\"] == \"usdinr\"\n        assert irs.kwargs.leg1[\"mtm\"]\n        assert irs.kwargs.leg2[\"mtm\"]\n\n    def test_real_mkt_example(self):\n        # An INRUSD NDIRS with market pricing\n        fxr = FXRates({\"usdinr\": 90.38}, settlement=dt(2025, 12, 19))\n        usdusd = Curve({dt(2025, 12, 17): 1.0, dt(2030, 12, 20): 0.9})\n        inrinr = Curve({dt(2025, 12, 17): 1.0, dt(2030, 12, 20): 0.9}, convention=\"act365F\")\n        inrusd = Curve({dt(2025, 12, 17): 1.0, dt(2030, 12, 20): 0.9}, convention=\"act365F\")\n        fxf = FXForwards(\n            fx_rates=fxr, fx_curves={\"usdusd\": usdusd, \"inrinr\": inrinr, \"inrusd\": inrusd}\n        )\n\n        Solver(\n            curves=[usdusd, inrinr, inrusd],\n            instruments=[\n                IRS(dt(2025, 12, 19), \"5y\", spec=\"usd_irs\", curves=[usdusd]),\n                FXSwap(dt(2025, 12, 19), \"5y\", \"usdinr\", curves=[usdusd, inrusd]),\n                IRS(dt(2025, 12, 18), \"5Y\", spec=\"inr_ndirs\", curves=[inrinr, usdusd]),\n            ],\n            s=[3.447, 148300.0, 5.9075],\n            fx=fxf,\n        )\n\n        ndirs = IRS(dt(2025, 12, 18), \"5y\", spec=\"inr_ndirs\", fixed_rate=5.8775, notional=250e6)\n        npv = ndirs.npv(fx=fxf, curves=[inrinr, usdusd])\n        assert abs(npv - 3489.2) < 1e-1\n        a_delta = ndirs.analytic_delta(fx=fxf, curves=[inrinr, usdusd])\n        assert abs(a_delta - 1163.1) < 1e-1\n        df = ndirs.cashflows(fx=fxf, curves=[inrinr, usdusd])\n        assert isinstance(df, DataFrame)\n\n\nclass TestIIRS:\n    @pytest.mark.skip(reason=\"v2.5 new IndexFixing handles setting and updating\")\n    def test_index_base_none_populated(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.5, dt(2034, 1, 1): 0.4},\n            index_lag=3,\n            index_base=100.0,\n            interpolation_method=\"linear_index\",\n        )\n        iirs = IIRS(\n            effective=dt(2022, 2, 1),\n            termination=\"1y\",\n            frequency=\"Q\",\n            index_lag=3,\n            notional_exchange=False,\n        )\n        for period in iirs.leg1.periods:\n            assert period.index_params.index_base.value is NoInput(0)\n        iirs.rate(curves=[i_curve, curve])\n        for period in iirs.leg1.periods:\n            assert period.index_params.index_base.value is NoInput(0)\n\n    def test_iirs_npv_mid_mkt_zero(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.5, dt(2034, 1, 1): 0.4},\n            index_lag=3,\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        name = str(hash(os.urandom(8)))\n        fixings.add(name=name, series=Series(index=[dt(2000, 1, 1)], data=[1.00]))\n        iirs = IIRS(\n            effective=dt(2022, 2, 1),\n            termination=dt(2022, 7, 1),\n            payment_lag=0,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            stub=\"ShortFront\",\n            index_lag=3,\n            index_fixings=name,\n        )\n        initial_mid = iirs.rate(curves=[i_curve, curve, curve])\n        result = iirs.npv(curves=[i_curve, curve, curve, curve])\n        assert abs(result) < 1e-8\n\n        iirs.fixed_rate = iirs.rate(curves=[i_curve, curve, curve])\n        fixings.pop(name)\n        fixings.add(name=name, series=Series(index=[dt(2021, 11, 1)], data=[500.0]))\n        result2 = iirs.npv(curves=[i_curve, curve, curve])\n        assert result2 > 1\n        assert iirs.leg1._regular_periods[0].index_params.index_base.value == 500.0\n\n        new_mid = iirs.rate(curves=[i_curve, curve, curve])\n        assert abs(new_mid - initial_mid) > 5.00\n\n    def test_cashflows(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 0.99},\n            index_lag=3,\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        iirs = IIRS(\n            effective=dt(2022, 2, 1),\n            termination=\"9M\",\n            frequency=\"Q\",\n            index_base=Series([100.0], index=[dt(2021, 11, 1)]),\n            index_fixings=Series([110.0, 115], index=[dt(2022, 2, 1), dt(2022, 5, 1)]),\n            index_lag=3,\n            index_method=\"monthly\",\n            fixed_rate=1.0,\n        )\n        result = iirs.cashflows(curves=[i_curve, curve, curve, curve])\n        expected = DataFrame(\n            {\n                \"Index Val\": [110.0, 115.0, 100.7754, np.nan, np.nan, np.nan],\n                \"Index Ratio\": [1.10, 1.15, 1.00775, np.nan, np.nan, np.nan],\n                \"NPV\": [-2682.655, -2869.534, -2488.937, 9849.93, 10070.85, 9963.277],\n                \"Type\": [\"FixedPeriod\"] * 3 + [\"FloatPeriod\"] * 3,\n            },\n            index=MultiIndex.from_tuples(\n                [(\"leg1\", 0), (\"leg1\", 1), (\"leg1\", 2), (\"leg2\", 0), (\"leg2\", 1), (\"leg2\", 2)],\n            ),\n        )\n        assert_frame_equal(\n            expected,\n            result[[\"Index Val\", \"Index Ratio\", \"NPV\", \"Type\"]],\n            rtol=1e-3,\n        )\n\n    def test_npv_no_index_base(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.5, dt(2034, 1, 1): 0.4},\n            index_lag=3,\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        iirs = IIRS(\n            effective=dt(2022, 2, 1),\n            termination=\"1y\",\n            frequency=\"Q\",\n            fixed_rate=2.0,\n            index_lag=3,\n            notional_exchange=False,\n        )\n        result = iirs.npv(curves=[i_curve, curve, curve, curve])\n        expected = 19792.08369745\n        assert abs(result - expected) < 1e-6\n\n    def test_cashflows_no_index_base(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.5, dt(2034, 1, 1): 0.4},\n            index_lag=3,\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        iirs = IIRS(\n            effective=dt(2022, 2, 1),\n            termination=\"1y\",\n            frequency=\"Q\",\n            fixed_rate=2.0,\n            index_lag=3,\n            notional_exchange=False,\n        )\n        result = iirs.cashflows(curves=[i_curve, curve, curve, curve])\n        for i in range(4):\n            assert result.iloc[i][\"Index Base\"] == 200.0\n\n    def test_fixings_table(self, curve):\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.5, dt(2034, 1, 1): 0.4},\n            index_lag=3,\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        iirs = IIRS(dt(2022, 1, 15), \"6m\", \"Q\", curves=[i_curve, curve, curve])\n        result = iirs.local_analytic_rate_fixings()\n        assert isinstance(result, DataFrame)\n\n    def test_fixing_in_the_past(self):\n        # this test will also initialise `index_base` from the provided `index_fixings`\n        discount = Curve({dt(2025, 5, 15): 1.0, dt(2027, 5, 15): 0.96})\n        inflation = Curve(\n            {dt(2025, 4, 1): 1.0, dt(2027, 5, 1): 0.98}, index_base=100.0, index_lag=0\n        )\n        fixings = Series(\n            [97, 98, 99, 100.0],\n            index=[dt(2025, 1, 1), dt(2025, 2, 1), dt(2025, 3, 1), dt(2025, 4, 1)],\n        )\n        iirs = IIRS(\n            dt(2025, 5, 15),\n            \"1y\",\n            \"Q\",\n            index_fixings=fixings,\n            curves=[inflation, discount, discount],\n        )\n        result = iirs.rate()\n        assert abs(result - 1.9775254614497422) < 1e-8\n\n\nclass TestYoYIS:\n    def test_index_fixings(self, curve) -> None:\n        name = str(hash(os.urandom(2)))\n        fixings.add(\n            name,\n            Series(\n                index=[\n                    dt(2025, 11, 1),\n                    dt(2026, 11, 1),\n                    dt(2027, 11, 1),\n                    dt(2028, 11, 1),\n                    dt(2029, 11, 1),\n                    dt(2030, 11, 1),\n                ],\n                data=[324.09771, 332.32169, 340.43872, 348.73351, 357.21860, 366.05583],\n            ),\n        )\n        yoyis = YoYIS(\n            effective=dt(2026, 2, 11),\n            termination=\"5y\",\n            frequency=\"A\",\n            fixed_rate=2.473874,\n            convention=\"ActActIsda\",\n            leg2_index_lag=3,\n            leg2_index_method=\"monthly\",\n            leg2_index_fixings=name,\n            notional=10e6,\n            calendar=\"nyc\",\n        )\n        expected_cashflows = [253750.018, 244177.225, 244392.329, 242644.969, 247389.973]\n        cashflows = yoyis.cashflows(curves=[NoInput(0), curve])\n        for i in range(5):\n            value = cashflows.loc[\"leg2\", \"Cashflow\"].iloc[i]\n            assert abs(value - expected_cashflows[i]) < 1e-2\n\n        expected_cashflows = [\n            -247387.40,\n            -247311.47,\n            -248141.10,\n            -246709.63,\n            -247387.40,\n        ]\n        cashflows = yoyis.cashflows(curves=[NoInput(0), curve])\n        for i in range(5):\n            value = cashflows.loc[\"leg1\", \"Cashflow\"].iloc[i]\n            assert abs(value - expected_cashflows[i]) < 1e-2\n\n        npv = yoyis.npv(curves=[NoInput(0), curve])\n        assert abs(npv + 3002.4397) < 1e-3\n\n        rate = yoyis.rate(curves=[NoInput(0), curve])\n        analytic_delta = yoyis.analytic_delta(curves=[NoInput(0), curve])\n\n        assert abs((2.473874 - rate) * analytic_delta * 100.0 - 3002.4397) < 1e-3\n\n    def test_cashflows_no_index_base(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.5, dt(2034, 1, 1): 0.4},\n            index_lag=3,\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        yoyis = YoYIS(\n            effective=dt(2022, 2, 1),\n            termination=\"3y\",\n            frequency=\"A\",\n            fixed_rate=2.0,\n            convention=\"One\",\n            leg2_index_lag=3,\n        )\n        result = yoyis.cashflows(curves=[i_curve, curve])\n        expected = [200.0, 204.193474, 208.386949]\n        for i in range(3):\n            assert abs(result.loc[\"leg2\", \"Index Base\"].iloc[i] - expected[i]) < 1e-6\n\n        expected_cashflows = [204.193474 / 200.0, 208.386949 / 204.193474]\n        for i in range(2):\n            expected = 1e6 * (expected_cashflows[i] - 1)\n            assert abs(result.loc[\"leg2\", \"Cashflow\"].iloc[i] - expected) < 1e-2\n\n    def test_yoyis_npv_mid_mkt_zero(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.5, dt(2034, 1, 1): 0.4},\n            index_lag=3,\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        name = str(hash(os.urandom(8)))\n        fixings.add(name=name, series=Series(index=[dt(2000, 1, 1)], data=[1.00]))\n        yoyis = YoYIS(\n            effective=dt(2022, 2, 1),\n            termination=\"3y\",\n            frequency=\"A\",\n            convention=\"One\",\n            leg2_index_lag=3,\n            leg2_index_fixings=name,\n            leg2_index_method=\"monthly\",\n        )\n\n        initial_mid = yoyis.rate(curves=[i_curve, curve])\n        result = yoyis.npv(curves=[i_curve, curve])\n        assert abs(result) < 1e-8\n\n        yoyis.fixed_rate = initial_mid\n        fixings.pop(name)\n        fixings.add(name=name, series=Series(index=[dt(2021, 11, 1)], data=[500.0]))\n        result2 = yoyis.npv(curves=[i_curve, curve])\n        assert result2 < 500000\n        assert yoyis.leg2._regular_periods[0].index_params.index_base.value == 500.0\n\n        new_mid = yoyis.rate(curves=[i_curve, curve])\n        assert new_mid - initial_mid < -20.0\n\n    def test_fixings_table(self, curve):\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.5, dt(2034, 1, 1): 0.4},\n            index_lag=3,\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        yoyis = YoYIS(dt(2022, 1, 15), \"6m\", \"Q\", curves=[i_curve, curve])\n        result = yoyis.local_analytic_rate_fixings()\n        assert result.empty\n\n\nclass TestSBS:\n    def test_sbs_npv(self, curve) -> None:\n        sbs = SBS(dt(2022, 1, 1), \"9M\", \"Q\", float_spread=3.0)\n        a_delta = sbs.analytic_delta(curves=[curve, curve, curve], leg=1)\n        npv = sbs.npv(curves=[curve, curve, curve])\n        assert abs(npv + 3.0 * a_delta) < 1e-9\n\n        sbs.leg2_float_spread = 4.5\n        npv = sbs.npv(curves=[curve, curve, curve])\n        assert abs(npv - 1.5 * a_delta) < 1e-9\n\n    def test_sbs_rate(self, curve) -> None:\n        sbs = SBS(dt(2022, 1, 1), \"9M\", \"Q\", float_spread=3.0)\n        result = sbs.rate(curves=[curve] * 3)\n        alias = sbs.spread(curves=[curve] * 3)\n        assert abs(result - 0) < 1e-8\n        assert abs(alias - 0) < 1e-8\n\n        result = sbs.rate(curves=[curve] * 3, metric=\"leg2_float_spread\")\n        alias = sbs.rate(curves=[curve] * 3, metric=\"leg2_float_spread\")\n        assert abs(result - 3.0) < 1e-8\n        assert abs(alias - 3.0) < 1e-8\n\n    def test_sbs_cashflows(self, curve) -> None:\n        sbs = SBS(dt(2022, 1, 1), \"9M\", \"Q\", float_spread=3.0)\n        result = sbs.cashflows(curves=[curve] * 3)\n        expected = DataFrame(\n            {\n                \"Type\": [\"FloatPeriod\", \"FloatPeriod\"],\n                \"Period\": [\"Regular\", \"Regular\"],\n                \"Spread\": [3.0, 0.0],\n            },\n            index=MultiIndex.from_tuples([(\"leg1\", 0), (\"leg2\", 2)]),\n        )\n        assert_frame_equal(\n            result.loc[[(\"leg1\", 0), (\"leg2\", 2)], [\"Type\", \"Period\", \"Spread\"]],\n            expected,\n        )\n\n    @pytest.mark.skip(reason=\"exceptions are no longer raised for unexpected attributes.\")\n    def test_sbs_fixed_rate_raises(self, curve) -> None:\n        sbs = SBS(dt(2022, 1, 1), \"9M\", \"Q\", float_spread=3.0)\n        with pytest.raises(AttributeError, match=\"property 'fixed_rate' of 'SBS' object has no se\"):\n            sbs.fixed_rate = 1.0\n\n        with pytest.raises(AttributeError, match=\"property 'leg2_fixed_rate' of 'SBS' object has\"):\n            sbs.leg2_fixed_rate = 1.0\n\n    def test_fixings_table(self, curve):\n        inst = SBS(dt(2022, 1, 15), \"6m\", spec=\"usd_irs\", curves=[curve] * 3)\n        result = inst.local_analytic_rate_fixings()\n        assert isinstance(result, DataFrame)\n\n    def test_fixings_table_3s1s(self, curve, curve2):\n        inst = SBS(\n            dt(2022, 1, 15),\n            \"6m\",\n            fixing_method=\"ibor(0)\",\n            leg2_fixing_method=\"ibor(1)\",\n            frequency=\"Q\",\n            leg2_frequency=\"m\",\n            curves=[curve, curve, curve2, curve],\n        )\n        result = inst.local_analytic_rate_fixings()\n        assert isinstance(result, DataFrame)\n        assert len(result.columns) == 2\n        assert len(result.index) == 8\n\n\nclass TestFRA:\n    def test_fra_rate(self, curve) -> None:\n        # test the mid-market rate ignores the given fixed_rate and reacts to float_spread\n        fra = FRA(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 7, 1),\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"S\",\n            fixed_rate=4.00,\n        )\n        result = fra.rate(curves=curve)\n        expected = 4.0590821964144\n        assert abs(result - expected) < 1e-7\n\n    def test_fra_rate_with_spec(self):\n        curve = Curve(\n            {dt(2026, 1, 14): 1.0, dt(2027, 1, 14): 0.98},\n            calendar=\"stk\",\n            convention=\"act360\",\n        )\n        fra = FRA(get_imm(code=\"H26\"), get_imm(code=\"M26\"), spec=\"sek_fra3\", curves=\"sek_3m\")\n        result = fra.rate(curves=curve)\n        expected = 1.9976777500828364\n\n        assert fra.leg1.settlement_params.notional == 1e6\n        assert fra.leg2.settlement_params.notional == -1e6\n\n        assert abs(result - expected) < 1e-5\n\n    def test_negated_notional(self):\n        fra = FRA(\n            get_imm(code=\"H26\"), get_imm(code=\"M26\"), spec=\"sek_fra3\", curves=\"sek_3m\", notional=7.0\n        )\n        assert fra.leg1.settlement_params.notional == 7.0\n        assert fra.leg2.settlement_params.notional == -7.0\n\n    def test_fra_npv(self, curve) -> None:\n        fra = FRA(\n            effective=dt(2022, 1, 1),\n            termination=\"6m\",\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            modifier=\"mf\",\n            frequency=\"S\",\n            fixed_rate=4.035,\n        )\n        result = fra.npv(curves=curve)\n        expected = fra.analytic_delta(curves=curve) * (4.035 - fra.rate(curves=curve)) * -100\n        assert abs(result - expected) < 1e-8\n        assert abs(result - 118631.8350458332) < 1e-7\n\n    def test_fra_cashflows(self, curve) -> None:\n        fra = FRA(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 7, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"s\",\n            fixed_rate=4.035,\n        )\n        result = fra.cashflows(curves=curve)\n        assert isinstance(result, DataFrame)\n        assert result.index.nlevels == 2\n\n    def test_fra_cashflows_with_rate_fixing(self) -> None:\n        fra = FRA(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 7, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"s\",\n            fixed_rate=4.035,\n            leg2_rate_fixings=2.99,\n        )\n        result = fra.cashflows()\n        assert isinstance(result, DataFrame)\n        subsample = result.loc[:, \"Cashflow\"]\n\n        d = 181.0 / 360.0\n        x = 0.04035 * d / (1 + d * 0.0299)\n        y = 0.0299 * d / (1 + d * 0.0299)\n\n        for a, b in zip(subsample, [-1e9 * x, 1e9 * y]):\n            assert abs(a - b) < 1e-4\n\n    def test_irs_npv_mid_mkt_zero(self, curve) -> None:\n        fra = FRA(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 7, 1),\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            frequency=\"S\",\n        )\n        result = fra.npv(curves=curve)\n        assert abs(result) < 1e-9\n\n        fra.fixed_rate = 1.0  # pay fixed low rate implies positive NPV\n        assert fra.npv(curves=curve) > 1\n\n        fra.fixed_rate = NoInput(0)  # fixed rate set back to initial\n        assert abs(fra.npv(curves=curve)) < 1e-9\n\n    @pytest.mark.parametrize((\"eom\", \"exp\"), [(True, dt(2021, 5, 31)), (False, dt(2021, 5, 26))])\n    def test_fra_roll_inferral(self, eom, exp) -> None:\n        fra = FRA(\n            effective=dt(2021, 2, 26),\n            termination=\"3m\",\n            frequency=\"Q\",\n            eom=eom,\n            calendar=\"bus\",\n        )\n        assert fra.leg1.schedule.termination == exp\n\n    def test_imm_dated(self):\n        FRA(effective=dt(2024, 12, 18), termination=dt(2025, 3, 19), spec=\"sek_fra3\", roll=\"imm\")\n\n    def test_fra_fixings_table(self, curve) -> None:\n        fra = FRA(\n            effective=dt(2022, 1, 1),\n            termination=\"6m\",\n            payment_lag=2,\n            notional=1e9,\n            convention=\"Act360\",\n            modifier=\"mf\",\n            frequency=\"S\",\n            fixed_rate=4.035,\n            curves=curve,\n        )\n        result = fra.local_analytic_rate_fixings(curves=curve)\n        assert isinstance(result, DataFrame)\n\n    def test_imm_dated_fixings_table(self, curve):\n        # This is an IMM FRA: the DCF is different to standard tenor.\n        fra = FRA(\n            effective=dt(2024, 12, 18),\n            termination=dt(2025, 3, 19),\n            spec=\"sek_fra3\",\n            roll=\"imm\",\n            curves=curve,\n            notional=1e9,\n        )\n        result = fra.local_analytic_rate_fixings()\n        analytic_delta = fra.analytic_delta()\n        assert isinstance(result, DataFrame)\n        assert abs(result.iloc[0, 0] - analytic_delta) < 1\n\n    def test_fra_ex_div_and_payment(self):\n        fra = FRA(\n            effective=dt(2024, 12, 18),\n            termination=dt(2025, 3, 19),\n            spec=\"sek_fra3\",\n            roll=\"imm\",\n            curves=curve,\n            notional=1e9,\n            payment_lag=2,\n            ex_div=-1,\n        )\n        assert fra.leg1.periods[0].period_params.start == dt(2024, 12, 18)\n        assert fra.leg1.periods[0].settlement_params.payment == dt(2024, 12, 20)\n        assert fra.leg1.periods[0].settlement_params.ex_dividend == dt(2024, 12, 19)\n\n    def test_fra_cashflows_no_curve(self):\n        fra = FRA(\n            effective=dt(2000, 1, 1),\n            termination=\"6m\",\n            spec=\"eur_fra6\",\n            fixed_rate=2.0,\n        )\n        assert isinstance(fra.cashflows(), DataFrame)\n\n\nclass TestZCS:\n    @pytest.mark.parametrize((\"freq\", \"exp\"), [(\"Q\", 3.53163356950), (\"S\", 3.54722411409218)])\n    def test_zcs_rate(self, freq, exp) -> None:\n        usd = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2027, 1, 1): 0.85, dt(2032, 1, 1): 0.70},\n            id=\"usd\",\n            calendar=\"bus\",\n        )\n        zcs = ZCS(\n            effective=dt(2022, 1, 1),\n            termination=\"10Y\",\n            frequency=freq,\n            leg2_frequency=\"Q\",\n            calendar=\"bus\",\n            modifier=\"MF\",\n            currency=\"usd\",\n            fixed_rate=4.0,\n            convention=\"Act360\",\n            notional=100e6,\n            curves=[\"usd\"],\n        )\n        result = zcs.rate(curves=usd)\n        assert abs(result - exp) < 1e-7\n\n    def test_zcs_analytic_delta(self) -> None:\n        usd = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2027, 1, 1): 0.85, dt(2032, 1, 1): 0.70},\n            id=\"usd\",\n        )\n        zcs = ZCS(\n            effective=dt(2022, 1, 1),\n            termination=\"10Y\",\n            frequency=\"Q\",\n            leg2_frequency=\"Q\",\n            calendar=\"nyc\",\n            currency=\"usd\",\n            fixed_rate=4.0,\n            convention=\"Act360\",\n            notional=100e6,\n            curves=[\"usd\"],\n        )\n        result = zcs.analytic_delta(curves=usd)\n        expected = 105186.21760654295\n        assert abs(result - expected) < 1e-7\n\n    def test_zcs_raise_frequency(self) -> None:\n        with pytest.raises(ValueError, match=\"`frequency` for a ZeroFixedLeg should not be 'Z'.\"):\n            ZCS(\n                effective=dt(2022, 1, 5),\n                termination=\"10Y\",\n                modifier=\"mf\",\n                frequency=\"Z\",\n                fixed_rate=4.22566695954813,\n            )\n\n    def test_fixings_table(self, curve):\n        zcs = ZCS(\n            effective=dt(2022, 1, 15),\n            termination=\"2y\",\n            frequency=\"Q\",\n            leg2_fixing_method=\"ibor(2)\",\n            calendar=\"all\",\n            convention=\"30e360\",\n            leg2_convention=\"30e360\",\n            leg2_fixing_series=\"eur_ibor\",\n            curves=curve,\n        )\n        result = zcs.local_analytic_rate_fixings()\n        assert isinstance(result, DataFrame)\n        for i in range(8):\n            abs(result.iloc[i, 0] - 24.678) < 1e-3\n\n\nclass TestZCIS:\n    def test_leg2_index_base(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        zcis = ZCIS(\n            effective=dt(2022, 1, 1),\n            termination=\"9m\",\n            frequency=\"Q\",\n        )\n        prior = zcis.rate(curves=[curve, curve, i_curve, curve])\n\n        zcis = ZCIS(\n            effective=dt(2022, 1, 1),\n            termination=\"9m\",\n            frequency=\"Q\",\n            leg2_index_base=100.0,\n        )\n        result = zcis.rate(curves=[curve, curve, i_curve, curve])\n        assert result > (prior + 100)\n\n    def test_solver_failure_unspecified_index_base(self, curve) -> None:\n        # GH 349\n        curve = Curve({dt(2022, 1, 15): 1.0, dt(2023, 1, 1): 0.98})\n        i_curve = Curve(\n            {dt(2022, 1, 15): 1.0, dt(2023, 1, 1): 0.99},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n        )\n        zcis = ZCIS(\n            effective=dt(2022, 1, 15),\n            termination=\"9m\",\n            frequency=\"A\",\n            convention=\"1+\",\n            calendar=\"nyc\",\n            leg2_index_method=\"monthly\",\n            currency=\"usd\",\n            curves=[curve, curve, i_curve, curve],\n            leg2_index_lag=3,\n        )\n        with pytest.raises(ZeroDivisionError):  # noqa: SIM117\n            with pytest.warns(\n                UserWarning, match=\"The date queried on the Curve for an `index_value` is prior\"\n            ):\n                zcis.rate()\n\n    def test_fixing_in_the_past(self):\n        # this test will also initialise `index_base` from the provided `index_fixings`\n        discount = Curve({dt(2025, 5, 15): 1.0, dt(2027, 5, 15): 0.96})\n        inflation = Curve(\n            {dt(2025, 4, 1): 1.0, dt(2027, 5, 1): 0.98}, index_base=100.0, index_lag=0\n        )\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name,\n            Series(\n                [97, 98, 99, 100.0],\n                index=[dt(2025, 1, 1), dt(2025, 2, 1), dt(2025, 3, 1), dt(2025, 4, 1)],\n            ),\n        )\n        zcis = ZCIS(dt(2025, 5, 15), \"1y\", spec=\"eur_zcis\", leg2_index_fixings=name)\n        result = zcis.rate(curves=[inflation, discount])\n        assert abs(result - 2.8742266148532813) < 1e-8\n\n\nclass TestValue:\n    def test_npv_adelta_cashflows_raises(self) -> None:\n        value = Value(dt(2022, 1, 1))\n        with pytest.raises(NotImplementedError):\n            value.npv()\n\n        with pytest.raises(NotImplementedError):\n            value.cashflows()\n\n        with pytest.raises(NotImplementedError):\n            value.analytic_delta()\n\n    def test_cc_zero_rate(self, curve) -> None:\n        v = Value(effective=dt(2022, 7, 1), metric=\"cc_zero_rate\")\n        result = v.rate(curves=curve)\n        t = (dt(2022, 7, 1) - dt(2022, 1, 1)).days / 360\n        expected = 100 * dual_log(curve[dt(2022, 7, 1)]) / -t\n        assert abs(result - expected) < 1e-12\n\n    def test_on_rate(self, curve) -> None:\n        c = Curve({dt(2000, 1, 1): 1.0, dt(2000, 7, 1): 1.0})\n        v = Value(effective=dt(2000, 2, 1), metric=\"o/n_rate\")\n        result = v.rate(curves=c)\n        expected = 0.0\n        assert abs(result - expected) < 1e-8\n\n    def test_index_value(self) -> None:\n        curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.995},\n            id=\"eu_cpi\",\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        v = Value(effective=dt(2022, 7, 1), metric=\"index_value\")\n        result = v.rate(curves=curve)\n        expected = 100.24919116128588\n        assert result == expected\n\n    def test_value_raise(self, curve) -> None:\n        with pytest.raises(ValueError):\n            Value(effective=dt(2022, 7, 1), metric=\"bad\").rate(curves=curve)\n\n\nclass TestFXForward:\n    def test_cashflows(self) -> None:\n        fxe = FXForward(\n            settlement=dt(2022, 10, 1),\n            pair=\"eurusd\",\n            notional=-1e6,\n            fx_rate=2.05,\n        )\n        result = fxe.cashflows()\n        expected = DataFrame(\n            {\n                \"Type\": [\"Cashflow\", \"Cashflow\"],\n                \"Ccy\": [\"EUR\", \"USD\"],\n                \"Payment\": [dt(2022, 10, 1), dt(2022, 10, 1)],\n                \"Notional\": [1e6, -1e6],\n                \"FX Fixing\": [None, 2.05],\n                \"Cashflow\": [-1e6, 2050000.0],\n            },\n            index=MultiIndex.from_tuples([(\"leg1\", 0), (\"leg2\", 0)]),\n        )\n        result = result[[\"Type\", \"Ccy\", \"Payment\", \"Notional\", \"FX Fixing\", \"Cashflow\"]]\n        assert_frame_equal(result, expected, rtol=1e-6)\n\n    @pytest.mark.parametrize(\n        (\"base\", \"fx\"),\n        [\n            (\"usd\", FXRates({\"eurusd\": 1.20})),\n            (\"eur\", FXRates({\"eurusd\": 1.20})),\n        ],\n    )\n    def test_npv_at_mid_market(self, curve, curve2, base, fx) -> None:\n        fxe = FXForward(\n            settlement=dt(2022, 3, 1),\n            pair=\"eurusd\",\n            fx_rate=1.2080131682341035,\n        )\n        result = fxe.npv(\n            curves=[NoInput(0), curve, NoInput(0), curve2],\n            fx=fx,\n            base=base,\n            local=False,\n        )\n        assert abs(result - 0.0) < 1e-8\n\n    def test_rate(self, curve, curve2) -> None:\n        fxe = FXForward(\n            settlement=dt(2022, 3, 1),\n            pair=\"eurusd\",\n            fx_rate=1.2080131682341035,\n        )\n        result = fxe.rate(\n            curves=[NoInput(0), curve, NoInput(0), curve2], fx=FXRates({\"eurusd\": 1.20})\n        )\n        expected = 1.2080131682341035\n        assert abs(result - expected) < 1e-7\n\n    def test_npv_fx_numeric(self, curve) -> None:\n        # This demonstrates the ambiguity and poor practice of\n        # using numeric fx as pricing input, although it will return.\n        fxe = FXForward(\n            settlement=dt(2022, 3, 1),\n            pair=\"eurusd\",\n            fx_rate=1.2080131682341035,\n            notional=-1e6,\n        )\n        # # real_result_ = fxe.npv(curves=[curve] * 4, fx=FXRates({\"eurusd\": 2.0}), local=True)\n        with pytest.warns(\n            DeprecationWarning,\n            match=\"Supplying `fx` as numeric is ambiguous, particularly with multi-curre\",\n        ):\n            fxe.npv(curves=[curve] * 4, fx=2.0, base=\"bad\")\n\n    def test_npv_no_fx_raises(self, curve) -> None:\n        fxe = FXForward(\n            settlement=dt(2022, 3, 1),\n            pair=\"eurusd\",\n            fx_rate=1.2080131682341035,\n        )\n        with pytest.raises(\n            ValueError,\n            match=r\"`base` \\(eur\\) cannot be requested without supplying `fx` as a valid FXRates\",\n        ):\n            fxe.npv(curves=[curve, curve])\n\n    def test_notional_direction(self, curve, curve2) -> None:\n        fx1 = FXForward(notional=1e6, pair=\"eurusd\", settlement=dt(2022, 1, 1), fx_rate=1.20)\n        fx2 = FXForward(notional=-1e6, pair=\"eurusd\", settlement=dt(2022, 1, 1), fx_rate=1.30)\n        pf = Portfolio([fx1, fx2])\n        fx = FXRates({\"eurusd\": 1.30}, base=\"usd\")\n        result = pf.npv(curves=[None, curve, None, curve2], fx=fx)\n        expected = 100000.0 / 1.30\n        assert abs(result - expected) < 1e-8\n        result = pf.npv(curves=[None, curve, None, curve2], fx=fx, base=\"usd\")\n        expected = 100000.0\n        assert abs(result - expected) < 1e-8\n\n    def test_analytic_delta_is_zero(self, curve, curve2) -> None:\n        result = FXForward(\n            settlement=dt(2022, 3, 1),\n            pair=\"eurusd\",\n            fx_rate=1.2080131682341035,\n        ).analytic_delta(curves=[curve, curve2])\n        assert abs(result - 0.0) < 1e-8\n\n    def test_error_msg_for_no_fx(self) -> None:\n        eur = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 30): 1.0}, calendar=\"tgt\")\n        usd = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 30): 1.0}, calendar=\"nyc\")\n        eurusd = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 30): 1.0})\n        with pytest.raises(ValueError, match=\"`fx` must be supplied to price FXExchange\"):\n            Solver(\n                curves=[eur, usd, eurusd],\n                instruments=[\n                    IRS(dt(2024, 6, 24), \"3m\", spec=\"eur_irs\", curves=eur),\n                    IRS(dt(2024, 6, 24), \"3m\", spec=\"usd_irs\", curves=usd),\n                    FXForward(\n                        pair=\"eurusd\",\n                        settlement=dt(2024, 9, 24),\n                        curves=[None, eurusd, None, usd],\n                    ),\n                ],\n                s=[3.77, 5.51, 1.0775],\n            )\n\n    def test_leg2_notional(self, curve, curve2) -> None:\n        fx1 = FXForward(\n            leg2_notional=-1.2e6, pair=\"eurusd\", settlement=dt(2022, 1, 1), fx_rate=1.20\n        )\n        fx2 = FXForward(leg2_notional=1.3e6, pair=\"eurusd\", settlement=dt(2022, 1, 1), fx_rate=1.30)\n        pf = Portfolio([fx1, fx2])\n        fx = FXRates({\"eurusd\": 1.30}, base=\"usd\")\n        result = pf.npv(curves=[None, curve, None, curve2], fx=fx)\n        expected = 100000.0 / 1.30\n        assert abs(result - expected) < 1e-8\n        result = pf.npv(curves=[None, curve, None, curve2], fx=fx, base=\"usd\")\n        expected = 100000.0\n        assert abs(result - expected) < 1e-8\n\n\nclass TestNDF:\n    def test_2ccy_constructions(self):\n        # no notionals or fx_rate, notional=1mm by default, pricing set at price time.\n        a1 = NDF(pair=\"eurusd\", currency=\"eur\", settlement=dt(2000, 1, 1))\n        a2 = NDF(pair=\"eurusd\", currency=\"eur\", notional=1e6, settlement=dt(2000, 1, 1))\n\n        assert a1.kwargs.leg1[\"notional\"] == 1e6\n        assert a2.kwargs.leg1[\"notional\"] == 1e6\n        assert a1.kwargs.leg2[\"notional\"] == NoInput(0)\n        assert a2.kwargs.leg2[\"notional\"] == NoInput(0)\n        assert a1.kwargs.meta[\"fx_rate\"] == NoInput(0)\n        assert a2.kwargs.meta[\"fx_rate\"] == NoInput(0)\n\n        # no notional with fx_rate, notional=1mm by default and ==> leg2_notional\n        b1 = NDF(pair=\"eurusd\", currency=\"eur\", fx_rate=2.0, settlement=dt(2000, 1, 1))\n        b2 = NDF(\n            pair=\"eurusd\", currency=\"eur\", fx_rate=2.0, notional=1e6, settlement=dt(2000, 1, 1)\n        )\n        b3 = NDF(\n            pair=\"eurusd\",\n            currency=\"eur\",\n            fx_rate=2.0,\n            leg2_notional=-2e6,\n            settlement=dt(2000, 1, 1),\n        )\n\n        assert b1.kwargs.leg1[\"notional\"] == 1e6\n        assert b2.kwargs.leg1[\"notional\"] == 1e6\n        assert b3.kwargs.leg1[\"notional\"] == 1e6\n        assert b1.kwargs.leg2[\"notional\"] == -2e6\n        assert b2.kwargs.leg2[\"notional\"] == -2e6\n        assert b3.kwargs.leg2[\"notional\"] == -2e6\n        assert b1.kwargs.meta[\"fx_rate\"] == 2.0\n        assert b2.kwargs.meta[\"fx_rate\"] == 2.0\n        assert b3.kwargs.meta[\"fx_rate\"] == 2.0\n\n        # reversed pair\n        c1 = NDF(\n            pair=\"usdeur\", currency=\"eur\", fx_rate=0.5, notional=-2e6, settlement=dt(2000, 1, 1)\n        )\n        c2 = NDF(\n            pair=\"usdeur\", currency=\"eur\", fx_rate=0.5, leg2_notional=1e6, settlement=dt(2000, 1, 1)\n        )\n\n        assert c1.kwargs.leg1[\"notional\"] == -2e6\n        assert c2.kwargs.leg1[\"notional\"] == -2e6\n        assert c1.kwargs.leg2[\"notional\"] == 1e6\n        assert c2.kwargs.leg2[\"notional\"] == 1e6\n        assert c1.kwargs.meta[\"fx_rate\"] == 0.5\n        assert c2.kwargs.meta[\"fx_rate\"] == 0.5\n\n        # 2 notionals imply fx rate\n        d1 = NDF(\n            pair=\"eurusd\",\n            currency=\"eur\",\n            notional=-1e6,\n            leg2_notional=2e6,\n            settlement=dt(2000, 1, 1),\n        )\n        d2 = NDF(\n            pair=\"usdeur\",\n            currency=\"eur\",\n            notional=2e6,\n            leg2_notional=-1e6,\n            settlement=dt(2000, 1, 1),\n        )\n\n        assert d1.kwargs.meta[\"fx_rate\"] == 2.0\n        assert d2.kwargs.meta[\"fx_rate\"] == 0.5\n\n    def test_construction(self) -> None:\n        ndf = NDF(\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            settlement=dt(2022, 1, 1),\n            fx_rate=1.20,\n            fx_fixings=2.25,\n            notional=1e6,  # <- should be expressed in BRL\n            currency=\"usd\",\n        )\n        assert ndf.leg1.periods[0].settlement_params.currency == \"usd\"\n        assert ndf.leg1.periods[0].non_deliverable_params.reference_currency == \"brl\"\n        assert ndf.leg1.periods[0].non_deliverable_params.fx_reversed is False\n\n        # value is 1.05mm usd\n        c = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0})\n        fxf = FXForwards(\n            fx_rates=FXRates({\"brlusd\": 2.20}, settlement=dt(2022, 1, 1)),\n            fx_curves={\"brlbrl\": c2, \"brlusd\": c2, \"usdusd\": c},\n        )\n        result = ndf.npv(curves=[c], fx=fxf)  # value should be expressed in USD\n        assert abs(result - 1000000.0 * (2.25 - 1.2)) < 1e-8\n\n    def test_construction_opposite(self) -> None:\n        ndf = NDF(\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            settlement=dt(2022, 1, 1),\n            fx_rate=1.20,\n            leg2_fx_fixings=2.25,\n            notional=1e6,  # <- should be expressed in BRL\n            currency=\"brl\",\n        )\n        assert ndf.leg2.periods[0].settlement_params.currency == \"brl\"\n        assert ndf.leg2.periods[0].non_deliverable_params.reference_currency == \"usd\"\n        assert ndf.leg2.periods[0].non_deliverable_params.fx_reversed is True\n\n        # value is 1.05mm usd\n        c = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0})\n        fxf = FXForwards(\n            fx_rates=FXRates({\"brlusd\": 2.20}, settlement=dt(2022, 1, 1)),\n            fx_curves={\"brlbrl\": c2, \"brlusd\": c2, \"usdusd\": c},\n        )\n        result = ndf.npv(curves=[c], fx=fxf)  # value should be expressed in BRL\n        assert abs(result - 1000000.0 * (2.25 - 1.2) / 2.25) < 1e-8\n\n    def test_construction_reversed(self) -> None:\n        ndf = NDF(\n            pair=FXIndex(\"usdbrl\", \"all\", 0),\n            settlement=dt(2022, 1, 1),\n            currency=\"usd\",\n            fx_rate=1.20,\n            leg2_fx_fixings=2.25,\n            notional=1e6,  # <- should be expressed in USD\n        )\n        assert ndf.leg1.periods[0].settlement_params.currency == \"usd\"\n        assert ndf.leg2.periods[0].non_deliverable_params.reference_currency == \"brl\"\n        assert ndf.leg2.periods[0].non_deliverable_params.fx_reversed is True\n\n        # value is 1.05mm BRL\n        c = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0})\n        fxf = FXForwards(\n            fx_rates=FXRates({\"brlusd\": 2.20}, settlement=dt(2022, 1, 1)),\n            fx_curves={\"brlbrl\": c2, \"brlusd\": c2, \"usdusd\": c},\n        )\n        result = ndf.npv(curves=[c], fx=fxf)  # value should be expressed in USD\n        assert abs(result - 1000000.0 * (2.25 - 1.2) / 2.25) < 1e-8\n\n    def test_construction_reversed_opposite(self) -> None:\n        ndf = NDF(\n            pair=FXIndex(\"usdbrl\", \"all\", 0),\n            settlement=dt(2022, 1, 1),\n            currency=\"brl\",\n            fx_rate=1.20,\n            fx_fixings=2.25,\n            notional=1e6,  # <- should be expressed in USD\n        )\n        assert ndf.leg1.periods[0].settlement_params.currency == \"brl\"\n        assert ndf.leg1.periods[0].non_deliverable_params.reference_currency == \"usd\"\n        assert ndf.leg1.periods[0].non_deliverable_params.fx_reversed is False\n\n        # value is 1.05mm BRL\n        c = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0})\n        fxf = FXForwards(\n            fx_rates=FXRates({\"brlusd\": 2.20}, settlement=dt(2022, 1, 1)),\n            fx_curves={\"brlbrl\": c2, \"brlusd\": c2, \"usdusd\": c},\n        )\n        result = ndf.npv(curves=[c], fx=fxf)  # value should be expressed in USD\n        assert abs(result - 1000000.0 * (2.25 - 1.2)) < 1e-8\n\n    def test_3ccy_constructions(self):\n        # no notionals or fx_rate, notional=1mm by default, pricing set at price time.\n        a1 = NDF(\n            pair=\"seknok\", currency=\"usd\", notional=1e6, fx_rate=2.0, settlement=dt(2000, 1, 1)\n        )\n        a2 = NDF(\n            pair=\"seknok\",\n            currency=\"usd\",\n            notional=1e6,\n            fx_rate=2.0,\n            reversed=True,\n            settlement=dt(2000, 1, 1),\n        )\n        a3 = NDF(\n            pair=\"seknok\",\n            currency=\"usd\",\n            notional=1e6,\n            fx_rate=2.0,\n            leg2_reversed=True,\n            settlement=dt(2000, 1, 1),\n        )\n        a4 = NDF(\n            pair=\"seknok\",\n            currency=\"usd\",\n            notional=1e6,\n            fx_rate=2.0,\n            reversed=True,\n            leg2_reversed=True,\n            settlement=dt(2000, 1, 1),\n        )\n\n        assert a1.leg1.periods[0].non_deliverable_params.fx_index.pair == \"usdsek\"\n        assert a1.leg2.periods[0].non_deliverable_params.fx_index.pair == \"usdnok\"\n        assert a2.leg1.periods[0].non_deliverable_params.fx_index.pair == \"sekusd\"\n        assert a2.leg2.periods[0].non_deliverable_params.fx_index.pair == \"usdnok\"\n        assert a3.leg1.periods[0].non_deliverable_params.fx_index.pair == \"usdsek\"\n        assert a3.leg2.periods[0].non_deliverable_params.fx_index.pair == \"nokusd\"\n        assert a4.leg1.periods[0].non_deliverable_params.fx_index.pair == \"sekusd\"\n        assert a4.leg2.periods[0].non_deliverable_params.fx_index.pair == \"nokusd\"\n\n    @pytest.mark.parametrize(\n        (\"lag\", \"eval1\", \"exp2\"),\n        [\n            (2, dt(2009, 8, 11), dt(2009, 11, 13)),\n            (3, dt(2009, 8, 10), dt(2009, 11, 13)),\n        ],\n    )\n    def test_dates(self, lag, eval1, exp2):\n        ndf = NDF(\n            pair=FXIndex(\"eurusd\", \"tgt|fed\", lag),\n            settlement=\"3m\",\n            eval_date=eval1,\n            currency=\"usd\",\n        )\n        assert ndf.leg1.periods[0].settlement_params.payment == exp2\n\n    @pytest.mark.parametrize(\n        (\"eom\", \"exp\"),\n        [\n            (True, dt(2025, 5, 30)),\n            (False, dt(2025, 5, 28)),\n        ],\n    )\n    def test_roll(self, eom, exp):\n        ndf = NDF(\n            pair=\"eurusd\",\n            settlement=\"3m\",\n            eval_date=dt(2025, 2, 26),\n            currency=\"usd\",\n            eom=eom,\n        )\n        assert ndf.leg1.periods[0].settlement_params.payment == exp\n\n    def test_zero_analytic_delta(self):\n        curve = Curve({dt(2009, 1, 1): 1.0, dt(2020, 1, 1): 1.0})\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2009, 1, 1)),\n            fx_curves={\"eureur\": curve, \"eurusd\": curve, \"usdusd\": curve},\n        )\n        ndf = NDF(\n            pair=\"eurusd\",\n            settlement=\"3m\",\n            eval_date=dt(2009, 8, 13),\n            currency=\"usd\",\n        )\n        assert ndf.analytic_delta(curves=curve, fx=fxf) == 0.0\n\n    @pytest.mark.skip(reason=\"v2.5 allows third currency settlement currency.\")\n    def test_bad_currency_raises(self):\n        with pytest.raises(ValueError, match=\"`currency` must be one of the currencies in `pair`.\"):\n            NDF(\n                pair=\"eurusd\",\n                currency=\"jpy\",\n                settlement=\"3m\",\n                eval_date=dt(2009, 8, 13),\n            )\n\n    def test_cashflows(self, usdusd, usdeur, eureur):\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.02}, settlement=dt(2022, 1, 3)),\n            {\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        ndf = NDF(\n            pair=\"eurusd\",\n            settlement=\"3m\",\n            eval_date=dt(2022, 1, 1),\n            currency=\"usd\",\n            fx_rate=1.05,\n        )\n        result = ndf.cashflows(curves=usdusd, fx=fxf)\n        assert result.loc[(\"leg1\", 0), \"Type\"] == \"Cashflow\"\n        assert result.loc[(\"leg1\", 0), \"Notional\"] == -1e6\n        assert result.loc[(\"leg1\", 0), \"Ccy\"] == \"USD\"\n        assert result.loc[(\"leg1\", 0), \"Reference Ccy\"] == \"EUR\"\n        assert result.loc[(\"leg1\", 0), \"Payment\"] == dt(2022, 4, 4)\n        assert result.loc[(\"leg1\", 0), \"FX Fixing\"] == 1.0210354810081033\n        assert result.loc[(\"leg2\", 0), \"Notional\"] == 1050000.0\n        assert result.loc[(\"leg2\", 0), \"Ccy\"] == \"USD\"\n\n    @pytest.mark.parametrize((\"base\", \"expected\"), [(\"eur\", -28103.831), (\"usd\", -28665.269)])\n    def test_npv(self, usdusd, usdeur, eureur, base, expected):\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.02}, settlement=dt(2022, 1, 3)),\n            {\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        ndf = NDF(\n            pair=\"eurusd\",\n            settlement=\"3m\",\n            eval_date=dt(2022, 1, 1),\n            currency=\"usd\",\n            fx_rate=1.05,\n            notional=1e6,\n        )\n        result = ndf.npv(curves=usdusd, fx=fxf, base=base)\n        assert abs(result - expected) < 1e-3\n\n        expected = {\"usd\": -28665.269}\n        local_result = ndf.npv(curves=usdusd, fx=fxf, base=base, local=True)\n        assert len(local_result.keys()) == 1\n        assert abs(local_result[\"usd\"] - expected[\"usd\"]) < 1e-3\n\n    @pytest.mark.parametrize((\"base\", \"expected\"), [(\"eur\", -28103.831), (\"usd\", -28665.269)])\n    def test_npv_leg2_notional(self, usdusd, usdeur, eureur, base, expected):\n        # same test as above expressed with leg2 notional\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.02}, settlement=dt(2022, 1, 3)),\n            {\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        ndf = NDF(\n            pair=\"eurusd\",\n            settlement=\"3m\",\n            eval_date=dt(2022, 1, 1),\n            currency=\"usd\",\n            fx_rate=1.05,\n            # notional=1e6,\n            leg2_notional=-1.05e6,\n        )\n        result = ndf.npv(curves=usdusd, fx=fxf, base=base)\n        assert abs(result - expected) < 1e-3\n\n        expected = {\"usd\": -28665.269}\n        local_result = ndf.npv(curves=usdusd, fx=fxf, base=base, local=True)\n        assert len(local_result.keys()) == 1\n        assert abs(local_result[\"usd\"] - expected[\"usd\"]) < 1e-3\n\n    @pytest.mark.parametrize((\"pair\", \"rate\"), [(\"eurusd\", 1.05), (\"usdeur\", 0.952380952)])\n    def test_npv_direction(self, usdusd, usdeur, eureur, pair, rate):\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.02}, settlement=dt(2022, 1, 3)),\n            {\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        ndf = NDF(\n            pair=pair,\n            settlement=\"3m\",\n            eval_date=dt(2022, 1, 1),\n            currency=\"usd\",\n            fx_rate=rate,\n            notional=1e6 if pair[:3] == \"eur\" else -1e6 / rate,\n        )\n        result = ndf.npv(curves=usdusd, fx=fxf)\n        expected = -28665.26900\n        assert abs(result - expected) < 1e-3\n\n    @pytest.mark.parametrize((\"base\", \"expected\"), [(\"eur\", 0.0), (\"usd\", 0.0)])\n    def test_npv_unpriced(self, usdusd, usdeur, eureur, base, expected):\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.02}, settlement=dt(2022, 1, 3)),\n            {\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        ndf = NDF(\n            pair=\"eurusd\",\n            settlement=\"3m\",\n            eval_date=dt(2022, 1, 1),\n            currency=\"usd\",\n        )\n        result = ndf.npv(curves=usdusd, fx=fxf, base=base)\n        assert abs(result - expected) < 1e-3\n\n        local_result = ndf.npv(curves=usdusd, fx=fxf, base=base, local=True)\n        expected = {\"usd\": 0.0}\n        assert len(local_result.keys()) == 1\n        assert abs(local_result[\"usd\"] - expected[\"usd\"]) < 1e-3\n\n    @pytest.mark.parametrize((\"base\", \"expected\"), [(\"eur\", 0.0), (\"usd\", 0.0)])\n    def test_npv_unpriced_leg2_notional(self, usdusd, usdeur, eureur, base, expected):\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.02}, settlement=dt(2022, 1, 3)),\n            {\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        ndf = NDF(\n            pair=\"eurusd\",\n            settlement=\"3m\",\n            eval_date=dt(2022, 1, 1),\n            currency=\"usd\",\n            leg2_notional=-1e6,\n        )\n        result = ndf.npv(curves=usdusd, fx=fxf, base=base)\n        assert abs(result - expected) < 1e-3\n\n        local_result = ndf.npv(curves=usdusd, fx=fxf, base=base, local=True)\n        expected = {\"usd\": 0.0}\n        assert len(local_result.keys()) == 1\n        assert abs(local_result[\"usd\"] - expected[\"usd\"]) < 1e-3\n\n    def test_rate(self, usdusd, usdeur, eureur):\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.02}, settlement=dt(2022, 1, 3)),\n            {\"eureur\": eureur, \"usdeur\": usdeur, \"usdusd\": usdusd},\n        )\n        ndf = NDF(\n            pair=\"eurusd\",\n            settlement=\"3m\",\n            eval_date=dt(2022, 1, 1),\n            currency=\"usd\",\n        )\n        result = ndf.rate(curves=usdusd, fx=fxf)\n        expected = 1.021035\n        assert abs(result - expected) < 1e-6\n\n    def test_raising(self):\n        with pytest.raises(ValueError, match=\"`notional`, `leg2_notional` and `fx_rate` cannot\"):\n            NDF(\n                pair=\"eurusd\",\n                settlement=dt(2000, 1, 1),\n                notional=1e6,\n                leg2_notional=-1e6,\n                fx_rate=1.05,\n            )\n\n        with pytest.raises(ValueError, match=\"Leg1 of NDF is directly deliverable\"):\n            NDF(pair=\"eurusd\", settlement=dt(2000, 1, 1), notional=1e6, fx_fixings=1.0)\n\n        with pytest.raises(ValueError, match=\"Leg2 of NDF is directly deliverable\"):\n            NDF(\n                pair=\"eurusd\",\n                currency=\"usd\",\n                settlement=dt(2000, 1, 1),\n                notional=1e6,\n                leg2_fx_fixings=1.0,\n            )\n\n        with pytest.raises(ValueError, match=\"When providing `notional` and `leg2_notional` on an\"):\n            NDF(pair=\"eurusd\", settlement=dt(2000, 1, 1), notional=1e6, leg2_notional=1.2e6)\n\n\n# test the commented out FXSwap variant\n# def test_fx_swap(curve, curve2):\n#     fxs = FXSwap(dt(2022, 1, 15), \"3M\", notional=1000, fx_fixing_points=(10.1, 105),\n#                  currency=\"eur\", leg2_currency=\"sek\")\n#     assert len(fxs.leg1.periods) == 2\n#     assert len(fxs.leg2.periods) == 2\n#\n#     assert fxs.leg1.periods[0].notional == 1000\n#     assert fxs.leg1.periods[0].payment == dt(2022, 1, 15)\n#     assert fxs.leg1.periods[1].notional == -1000\n#     assert fxs.leg1.periods[1].payment == dt(2022, 4, 15)\n#\n#     assert fxs.leg2.periods[0].notional == -10100\n#     assert fxs.leg2.periods[0].payment == dt(2022, 1, 15)\n#     assert fxs.leg2.periods[1].notional == 10110.5\n#     assert fxs.leg2.periods[1].payment == dt(2022, 4, 15)\n#\n#     fxs.fx_fixing_points = NoInput(0)\n#     points = fxs._rate_alt(curve, curve2, 10.0)\n#     npv = fxs._npv_alt(curve, curve2, 10.0)\n#     assert abs(npv) < 1e-9\n#\n#     fxf = FXForwards(\n#         FXRates({\"eursek\": 10.0}, dt(2022, 1, 1)),\n#         {\"eureur\": curve, \"seksek\": curve2, \"sekeur\": curve2}\n#     )\n#     points2 = fxs.rate(fxf)\n#     npv2 = fxs.npv(fxf, NoInput(0), \"eur\")\n#     assert abs(npv2) < 1e-9\n\n\nclass TestNonMtmXCS:\n    def test_nonmtmxcs_npv(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"eurusd\": curve2, \"eureur\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"eur\",\n            pair=\"eurusd\",\n            payment_lag_exchange=0,\n        )\n        # npv2 = xcs._npv2(curve2, curve2, curve, curve, 1.10)\n        npv = xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)\n        assert abs(npv) < 1e-9\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            amortization=100e3,\n            currency=\"eur\",\n            pair=\"eurusd\",\n            payment_lag_exchange=0,\n        )\n        # npv2 = xcs._npv2(curve2, curve2, curve, curve, 1.10)\n        npv = xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)\n        assert abs(npv) < 1e-9\n\n    def test_nonmtmxcs_fx_notional(self) -> None:\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"eur\",\n            pair=\"eurusd\",\n            payment_lag_exchange=0,\n            leg2_fx_fixings=2.0,\n            notional=1e6,\n        )\n        for period in xcs.leg2.periods:\n            assert period.non_deliverable_params.fx_fixing.value == 2.0\n\n    @pytest.mark.parametrize(\n        (\"float_spd\", \"compound\", \"expected\"),\n        [\n            (10, \"none_simple\", 10.160794),\n            (100, \"none_simple\", 101.60794),\n            (100, \"isda_compounding\", 101.023590),\n            (100, \"isda_flat_compounding\", 101.336040),\n        ],\n    )\n    def test_nonmtmxcs_spread(self, curve, curve2, float_spd, compound, expected) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            float_spread=float_spd,\n            leg2_spread_compound_method=compound,\n        )\n\n        result = xcs.rate(curves=[curve, curve, curve2, curve2], fx=fxf, metric=\"leg2\")\n        assert abs(result - expected) < 1e-4\n        alias = xcs.spread(curves=[curve, curve, curve2, curve2], fx=fxf, metric=\"leg2\")\n        assert alias == result\n\n        xcs.leg2_float_spread = result\n        validate = xcs.npv(curves=[curve, curve, curve2, curve2], fx=fxf)\n        assert abs(validate) < 1e-2\n        result2 = xcs.rate(curves=[curve, curve, curve2, curve2], fx=fxf, metric=\"leg2\")\n        assert abs(result - result2) < 1e-3\n\n        # reverse legs\n        xcs_reverse = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"usd\",\n            pair=\"usdnok\",\n            payment_lag_exchange=0,\n            notional=1e6,\n            leg2_float_spread=float_spd,\n            spread_compound_method=compound,\n        )\n        result = xcs_reverse.rate(curves=[curve2, curve2, curve, curve], fx=fxf)\n        assert abs(result - expected) < 1e-4\n\n    def test_no_fx_raises(self, curve, curve2) -> None:\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            float_spread=0.0,\n        )\n\n        with (\n            pytest.raises(ValueError, match=\"Must provide `fx` argument to forecast FXFixing\"),\n            default_context(\"no_fx_fixings_for_xcs\", \"raise\"),\n        ):\n            xcs.npv(curves=[curve, curve, curve2, curve2])\n\n        # no error\n        xcs.cashflows(curves=[curve, curve, curve2, curve2])\n\n        # with pytest.warns():\n        #     with default_context(\"no_fx_fixings_for_xcs\", \"warn\"):\n        #         xcs.npv([curve, curve, curve2, curve2])\n\n    def test_nonmtmxcs_cashflows(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n\n        result = xcs.cashflows(\n            curves=[curve, curve, curve2, curve2],\n            fx=fxf,\n            base=\"usd\",\n        )\n        expected = DataFrame(\n            {\n                \"Type\": [\"Cashflow\", \"FloatPeriod\"],\n                \"Period\": [np.nan, \"Regular\"],\n                \"Ccy\": [\"NOK\", \"USD\"],\n                \"Notional\": [-10000000, -10000000.0],\n                \"FX Rate\": [0.10002256337062124, 1.0],\n                \"FX Fixing\": [np.nan, 0.09967340252423884],\n            },\n            index=MultiIndex.from_tuples([(\"leg1\", 0), (\"leg2\", 8)]),\n        )\n        assert_frame_equal(\n            result.loc[\n                [(\"leg1\", 0), (\"leg2\", 8)],\n                [\"Type\", \"Period\", \"Ccy\", \"Notional\", \"FX Rate\", \"FX Fixing\"],\n            ],\n            expected,\n        )\n\n    @pytest.mark.parametrize(\"fix\", [\"float\", \"dual\", \"variable\"])\n    def test_nonmtm_fx_fixing(self, curve, curve2, fix) -> None:\n        fxr = FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 1))\n        fxf = FXForwards(fxr, {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2})\n        mapping = {\n            \"float\": 1 / 10.0,\n            \"dual\": Dual(1 / 10.0, [\"x\"], []),\n            \"variable\": Variable(1 / 10.0, [\"x\"], []),\n        }\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_fx_fixings=mapping[fix],\n        )\n        assert abs(xcs.npv(curves=[curve, curve, curve2, curve2], fx=fxf)) < 1e-7\n\n    def test_nonmtm_fx_fixing_raises_type_crossing(self, curve, curve2):\n        fxr = FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 1))\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_fx_fixings=Dual2(10.0, [\"x\"], [], []),\n        )\n        # the given fixing is not downcast to Float because it is a specific user provided value.\n        # Users should technically use a Variable.\n        with pytest.raises(TypeError, match=r\"Dual2 operation with incompatible type \\(Dual\\)\"):\n            xcs.npv(curves=[curve, curve, curve2, curve2], fx=fxr) < 1e-7\n\n    def test_is_priced(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            leg2_float_spread=1.0,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            metric=\"leg2\",\n        )\n        result = xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf, base=\"usd\")\n        assert abs(result - 65.766356) < 1e-5\n\n    @pytest.mark.skip(reason=\"no fx fixings no longer allows warnings\")\n    def test_no_fx_warns(self, curve, curve2) -> None:\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            leg2_float_spread=1.0,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n        with default_context(\"no_fx_fixings_for_xcs\", \"warn\"), pytest.warns(UserWarning):\n            xcs.npv(curves=[curve2, curve2, curve, curve], local=True)\n\n    def test_npv_fx_as_float_raises(self) -> None:\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n        curve = Curve({dt(2022, 2, 1): 1.0, dt(2024, 2, 1): 0.9})\n        with pytest.raises(AttributeError, match=\"'float' object has no attribute 'rate'\"):\n            xcs.npv(curves=[curve] * 4, fx=10.0)\n\n    @pytest.mark.skip(reason=\"v2.5 uses FXForwards as a more explicit input type.\")\n    def test_npv_fx_as_rates_valid(self) -> None:\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n        curve = Curve({dt(2022, 2, 1): 1.0, dt(2024, 2, 1): 0.9})\n        result = xcs.npv(curves=[curve] * 2, fx=FXRates({\"usdnok\": 10.0}))\n        assert abs(result) < 1e-6\n\n    def test_setting_fx_fixing_no_input(self):\n        # Define the interest rate curves for EUR, USD and X-Ccy basis\n        usdusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 11, 7): 0.98}, calendar=\"nyc\", id=\"usdusd\")\n        eureur = Curve({dt(2024, 5, 7): 1.0, dt(2024, 11, 7): 0.99}, calendar=\"tgt\", id=\"eureur\")\n        eurusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 11, 7): 0.992}, id=\"eurusd\")\n\n        # Create an FX Forward market with spot FX rate data\n        fxr = FXRates({\"eurusd\": 1.0760}, settlement=dt(2024, 5, 9))\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\"eureur\": eureur, \"usdusd\": usdusd, \"eurusd\": eurusd},\n        )\n\n        xcs = XCS(\n            dt(2024, 5, 9),\n            \"6M\",\n            \"Q\",\n            fixed=False,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"eur\",\n            pair=\"eurusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n        xcs.npv(curves=[eureur, eurusd, usdusd, usdusd], fx=fxf)\n        xcs.leg2.periods[0].non_deliverable_params.fx_fixing.value_or_forecast(fx=fxf) == Dual(\n            1.0760, [\"fx_eurusd\"], []\n        )\n\n\nclass TestNonMtmFixedFloatXCS:\n    @pytest.mark.parametrize(\n        (\"float_spd\", \"compound\", \"expected\"),\n        [\n            (10, \"none_simple\", 6.70955968),\n            (100, \"isda_compounding\", 7.62137047),\n        ],\n    )\n    def test_nonmtmfixxcs_rate_npv(self, curve, curve2, float_spd, compound, expected) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            fixed=True,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_spread_compound_method=compound,\n            leg2_float_spread=float_spd,\n        )\n\n        result = xcs.rate(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg1\")\n        assert abs(result - expected) < 1e-4\n        npv = xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)\n        assert abs(npv) < 1e-6\n\n        xcs.fixed_rate = result  # set the fixed rate and check revalues to zero\n        assert abs(xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)) < 1e-6\n\n        irs = IRS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"nok\",\n            leg2_spread_compound_method=compound,\n            leg2_float_spread=float_spd,\n        )\n        validate = irs.rate(curves=curve2)\n        assert abs(result - validate) < 1e-2\n\n    def test_nonmtmfixxcs_fx_notional(self) -> None:\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"eur\",\n            pair=\"eurusd\",\n            payment_lag_exchange=0,\n            leg2_fx_fixings=2.0,\n            notional=1e6,\n        )\n        for period in xcs.leg2.periods:\n            assert period.non_deliverable_params.fx_fixing.value == 2.0\n\n    def test_nonmtmfixxcs_no_fx_raises(self, curve, curve2) -> None:\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n\n        with (\n            pytest.raises(ValueError, match=\"Must provide `fx` argument to forecast FXFixing\"),\n            default_context(\"no_fx_fixings_for_xcs\", \"raise\"),\n        ):\n            xcs.npv(curves=[curve, curve, curve2, curve2])\n\n        xcs.cashflows(curves=[curve, curve, curve2, curve2])\n\n    def test_nonmtmfixxcs_cashflows(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n\n        result = xcs.cashflows(curves=[curve, curve, curve2, curve2], fx=fxf, base=\"usd\")\n        expected = DataFrame(\n            {\n                \"Type\": [\"Cashflow\", \"FloatPeriod\"],\n                \"Period\": [np.nan, \"Regular\"],\n                \"Ccy\": [\"NOK\", \"USD\"],\n                \"Notional\": [-10000000, -10000000.0],\n                \"FX Rate\": [0.10002256337062124, 1.0],\n                \"FX Fixing\": [np.nan, 0.09967340252423884],\n            },\n            index=MultiIndex.from_tuples([(\"leg1\", 0), (\"leg2\", 8)]),\n        )\n        assert_frame_equal(\n            result.loc[\n                [(\"leg1\", 0), (\"leg2\", 8)],\n                [\"Type\", \"Period\", \"Ccy\", \"Notional\", \"FX Rate\", \"FX Fixing\"],\n            ],\n            expected,\n        )\n\n    @pytest.mark.parametrize(\"fix\", [\"float\", \"dual\", \"variable\"])\n    def test_nonmtmfixxcs_fx_fixing(self, curve, curve2, fix) -> None:\n        fxr = FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 1))\n        fxf = FXForwards(fxr, {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2})\n        mapping = {\n            \"float\": 10.0,\n            \"dual\": Dual(10.0, [\"x\"], []),\n            \"variable\": Variable(10.0, [\"x\"], []),\n        }\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_fx_fixings=mapping[fix],\n            leg2_float_spread=10.0,\n        )\n        assert abs(xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)) < 1e-7\n\n    def test_nonmtmfixxcs_fx_fixing_raises_type_crossing(self, curve, curve2) -> None:\n        fxr = FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 1))\n        fxf = FXForwards(fxr, {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2})\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_fx_fixings=Dual2(2.0, [\"c\"], [], []),\n            leg2_float_spread=10.0,\n        )\n        with pytest.raises(TypeError, match=r\"Dual2 operation with incompatible type \\(Dual\\).\"):\n            xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)\n\n    def test_nonmtmfixxcs_raises(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=False,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n\n        with pytest.raises(ValueError, match=\"A `fixed_rate` must be set for a cashflow to be\"):\n            xcs.rate(curves=[curve, curve, curve2, curve2], fx=fxf, metric=\"leg2\")\n\n\nclass TestNonMtmFixedFixedXCS:\n    # @pytest.mark.parametrize(\"float_spd, compound, expected\",[\n    #     (10, \"none_simple\", 6.70955968),\n    #     (100, \"isda_compounding\", 7.62137047),\n    # ])\n    # def test_nonmtmfixxcs_rate_npv(self, curve, curve2, float_spd, compound, expected):\n    #     fxf = FXForwards(\n    #         FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n    #         {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2}\n    #     )\n    #     xcs = NonMtmFixedFloatXCS(dt(2022, 2, 1), \"8M\", \"M\",\n    #         payment_lag=0, currency=\"nok\", leg2_currency=\"usd\",\n    #         payment_lag_exchange=0, notional=10e6,\n    #         leg2_spread_compound_method=compound, leg2_float_spread=float_spd\n    #      )\n    #\n    #     result = xcs.rate([curve2, curve2, curve, curve], NoInput(0), fxf, 1)\n    #     assert abs(result - expected) < 1e-4\n    #     assert abs(xcs.npv([curve2, curve2, curve, curve], NoInput(0), fxf)) < 1e-6\n    #\n    #     xcs.fixed_rate = result  # set the fixed rate and check revalues to zero\n    #     assert abs(xcs.npv([curve2, curve2, curve, curve], NoInput(0), fxf)) < 1e-6\n    #\n    #     irs = IRS(dt(2022, 2, 1), \"8M\", \"M\",\n    #         payment_lag=0, currency=\"nok\",\n    #         leg2_spread_compound_method=compound, leg2_float_spread=float_spd)\n    #     validate = irs.rate(curve2)\n    #     assert abs(result - validate) < 1e-2\n    #\n    # def test_nonmtmfixxcs_fx_notional(self):\n    #     xcs = NonMtmFixedFloatXCS(dt(2022, 2, 1), \"8M\", \"M\",\n    #                     payment_lag=0, currency=\"eur\", leg2_currency=\"usd\",\n    #                     payment_lag_exchange=0, fx_fixing=2.0, notional=1e6)\n    #     assert xcs.leg2_notional == -2e6\n    #\n    # def test_nonmtmfixxcs_no_fx_raises(self, curve, curve2):\n    #     xcs = NonMtmFixedFloatXCS(dt(2022, 2, 1), \"8M\", \"M\",\n    #                     payment_lag=0, currency=\"nok\", leg2_currency=\"usd\",\n    #                     payment_lag_exchange=0, notional=10e6)\n    #\n    #     with pytest.raises(ValueError, match=\"`fx` is required when `fx_fixing` is\"):\n    #         xcs.npv([curve, curve, curve2, curve2])\n    #\n    #     with pytest.raises(ValueError, match=\"`fx` is required when `fx_fixing` is\"):\n    #         xcs.cashflows([curve, curve, curve2, curve2])\n    #\n    # def test_nonmtmfixxcs_cashflows(self, curve, curve2):\n    #     fxf = FXForwards(\n    #         FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n    #         {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2}\n    #     )\n    #\n    #     xcs = NonMtmFixedFloatXCS(dt(2022, 2, 1), \"8M\", \"M\",\n    #                     payment_lag=0, currency=\"nok\", leg2_currency=\"usd\",\n    #                     payment_lag_exchange=0, notional=10e6)\n    #\n    #     result = xcs.cashflows([curve, curve, curve2, curve2], NoInput(0), fxf)\n    #     expected = DataFrame({\n    #         \"Type\": [\"Cashflow\", \"FloatPeriod\"],\n    #         \"Period\": [\"Exchange\", \"Regular\"],\n    #         \"Ccy\": [\"NOK\", \"USD\"],\n    #         \"Notional\": [-10000000, -996734.0252423884],\n    #         \"FX Rate\": [0.10002256337062124, 1.0],\n    #     }, index=MultiIndex.from_tuples([(\"leg1\", 0), (\"leg2\", 8)]))\n    #     assert_frame_equal(\n    #         result.loc[[(\"leg1\", 0), (\"leg2\", 8)],\n    #                    [\"Type\", \"Period\", \"Ccy\", \"Notional\", \"FX Rate\"]],\n    #         expected,\n    #     )\n\n    @pytest.mark.parametrize(\"fix\", [\"float\", \"dual\", \"variable\"])\n    def test_nonmtmfixxcs_fx_fixing(self, curve, curve2, fix) -> None:\n        fxr = FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 1))\n        fxf = FXForwards(fxr, {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2})\n        mapping = {\n            \"float\": 10.0,\n            \"dual\": Dual(10.0, [\"x\"], []),\n            \"variable\": Variable(10.0, [\"x\"], []),\n        }\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=True,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_fx_fixings=mapping[fix],\n            leg2_fixed_rate=2.0,\n        )\n        assert abs(xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)) < 1e-7\n\n    def test_nonmtmfixxcs_fx_fixing_type_crossing_raises(self, curve, curve2) -> None:\n        fxr = FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 1))\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=True,\n            leg2_mtm=False,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_fx_fixings=Dual2(10.0, [\"s\"], [], []),\n            leg2_fixed_rate=2.0,\n        )\n        with pytest.raises(TypeError, match=r\"Dual2 operation with incompatible type \\(Dual\\).\"):\n            xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxr)\n\n    def test_nonmtmfixfixxcs_raises(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            fixed=True,\n            leg2_fixed=True,\n            leg2_mtm=False,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n\n        with pytest.raises(ValueError, match=\"A `fixed_rate` must be set for a cashflow to be det\"):\n            xcs.rate(curves=[curve, curve, curve2, curve2], fx=fxf, metric=\"leg2\")\n\n        with pytest.raises(AttributeError, match=\"Leg2 is of type\"):\n            xcs.leg2_float_spread = 2.0\n\n\n@pytest.fixture\ndef isda_credit_curves_40rr_20quote():\n    # https://www.cdsmodel.com/rfr-test-grids.html?\n    # USD 22 June 2022\n\n    # from rateslib.scheduling import get_calendar\n    # trade = dt(2022, 6, 22)\n    # spot = get_calendar(\"nyc\").add_bus_days(trade, 2, False)\n    # tenors = [\"1m\", \"2m\", \"3m\", \"6m\", \"1y\", \"2y\", \"3y\", \"4y\", \"5y\", \"6y\", \"7y\", \"8y\", \"9y\"]\n    # tenors += [\"10y\", \"12y\", \"15y\", \"20y\", \"25y\", \"30y\"]\n    # curve = Curve(\n    #     nodes={\n    #         trade: 1.0,\n    #         **{add_tenor(spot, _, \"f\", \"nyc\"): 1.0 for _ in tenors},\n    #     },\n    #     interpolation=\"log_linear\",\n    # )\n    # solver = Solver(\n    #     curves=[curve],\n    #     instruments=[IRS(spot, _, spec=\"usd_irs\", curves=curve) for _ in tenors],\n    #     s=[1.5088, 1.8228, 1.9729, 2.5640, 3.1620, 3.3169, 3.2441, 3.1771, 3.1371, 3.1131, 3.0951,\n    #        3.0841, 3.0811, 3.0871, 3.1061, 3.1201, 3.0601, 2.9381, 2.8221]\n    # )\n    #\n    # credit_curve = Curve(\n    #     nodes={trade: 1.0, dt(2055, 1, 1): 1.0}, credit_recovery_rate=0.4\n    # )\n    # solver2 = Solver(\n    #     curves=[credit_curve],\n    #     pre_solvers=[solver],\n    #     instruments=[\n    #         CDS(dt(2022, 6, 20), dt(2023, 6, 20), spec=\"us_ig_cds\", curves=[credit_curve, curve])], #noqa: E501\n    #     s=[0.20]\n    # )\n\n    curve = Curve(\n        {\n            dt(2022, 6, 22, 0, 0): 1.0,\n            dt(2022, 7, 25, 0, 0): 0.9986187857823194,\n            dt(2022, 8, 24, 0, 0): 0.9968373705612348,\n            dt(2022, 9, 26, 0, 0): 0.994791605422867,\n            dt(2022, 12, 27, 0, 0): 0.9868431949407511,\n            dt(2023, 6, 26, 0, 0): 0.9686906539113461,\n            dt(2024, 6, 24, 0, 0): 0.9357773336285784,\n            dt(2025, 6, 24, 0, 0): 0.9073411683282268,\n            dt(2026, 6, 24, 0, 0): 0.8808780124060293,\n            dt(2027, 6, 24, 0, 0): 0.8551765951547667,\n            dt(2028, 6, 26, 0, 0): 0.8298749243478529,\n            dt(2029, 6, 25, 0, 0): 0.8056454824131845,\n            dt(2030, 6, 24, 0, 0): 0.7819517736960135,\n            dt(2031, 6, 24, 0, 0): 0.7584699996495646,\n            dt(2032, 6, 24, 0, 0): 0.7349334728363958,\n            dt(2034, 6, 26, 0, 0): 0.6890701260967745,\n            dt(2037, 6, 24, 0, 0): 0.62634116393611,\n            dt(2042, 6, 24, 0, 0): 0.5441094046550682,\n            dt(2047, 6, 24, 0, 0): 0.4864281755586489,\n            dt(2052, 6, 24, 0, 0): 0.4409891618081753,\n        }\n    )\n\n    return (None, curve)\n\n\nclass TestCDS:\n    def okane_curve(self):\n        today = dt(2019, 8, 12)\n        spot = dt(2019, 8, 14)\n        tenors = [\n            \"1b\",\n            \"1m\",\n            \"2m\",\n            \"3m\",\n            \"6m\",\n            \"12M\",\n            \"2y\",\n            \"3y\",\n            \"4y\",\n            \"5y\",\n            \"6y\",\n            \"7y\",\n            \"8y\",\n            \"9y\",\n            \"10y\",\n        ]\n        ibor = Curve(\n            nodes={today: 1.0, **{add_tenor(spot, _, \"mf\", \"nyc\"): 1.0 for _ in tenors}},\n            convention=\"act360\",\n            calendar=\"nyc\",\n            id=\"ibor\",\n        )\n        rates = [\n            2.2,\n            2.2009,\n            2.2138,\n            2.1810,\n            2.0503,\n            1.9930,\n            1.591,\n            1.499,\n            1.4725,\n            1.4664,\n            1.48,\n            1.4995,\n            1.5118,\n            1.5610,\n            1.6430,\n        ]\n        ib_sv = Solver(\n            curves=[ibor],\n            instruments=[\n                IRS(\n                    spot,\n                    _,\n                    leg2_fixing_method=\"ibor(2)\",\n                    calendar=\"nyc\",\n                    payment_lag=0,\n                    convention=\"30e360\",\n                    leg2_convention=\"act360\",\n                    frequency=\"s\",\n                    curves=ibor,\n                )\n                for _ in tenors\n            ],\n            s=rates,\n        )\n        cds_tenor = [\"6m\", \"12m\", \"2y\", \"3y\", \"4y\", \"5y\", \"7y\", \"10y\"]\n        credit_curve = Curve(\n            nodes={today: 1.0, **{add_tenor(today, _, \"mf\", \"nyc\"): 1.0 for _ in cds_tenor}},\n            convention=\"act365f\",\n            calendar=\"all\",\n            id=\"credit\",\n            credit_discretization=5,\n        )\n        cc_sv = Solver(\n            curves=[credit_curve],\n            pre_solvers=[ib_sv],\n            instruments=[\n                CDS(\n                    today,\n                    add_tenor(dt(2019, 9, 20), _, \"mf\", \"nyc\"),\n                    front_stub=dt(2019, 9, 20),\n                    frequency=\"q\",\n                    convention=\"act360\",\n                    payment_lag=0,\n                    curves=[\"credit\", \"ibor\"],\n                    fixed_rate=4.00,\n                    premium_accrued=True,\n                    calendar=\"nyc\",\n                )\n                for _ in cds_tenor\n            ],\n            s=[4.00, 4.00, 4.00, 4.00, 4.00, 4.00, 4.00, 4.00],\n        )\n        return credit_curve, ibor, cc_sv\n\n    def test_okane_values(self):\n        # These values are validated against finance Py. Not identical but within tolerance.\n        cds = CDS(\n            dt(2019, 8, 12),\n            dt(2029, 6, 20),\n            front_stub=dt(2019, 9, 20),\n            frequency=\"q\",\n            fixed_rate=1.50,\n            curves=[\"credit\", \"ibor\"],\n            calendar=\"nyc\",\n        )\n        c1, c2, solver = self.okane_curve()\n        result1 = cds.rate(solver=solver)\n        assert abs(result1 - 3.9999960) < 5e-5\n\n        result2 = cds.npv(solver=solver)\n        assert abs(result2 - 170739.5956) < 180\n\n        result3 = cds.leg1.npv(rate_curve=c1, disc_curve=c2)\n        assert abs(result3 + 104508.9265 - 2125) < 50\n\n        result4 = cds.leg2.npv(rate_curve=c1, disc_curve=c2)\n        assert abs(result4 - 273023.5221) < 110\n\n    def test_unpriced_npv(self, curve, curve2) -> None:\n        cds = CDS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"eur\",\n        )\n\n        npv = cds.npv(curves=[curve2, curve], solver=NoInput(0))\n        assert abs(npv) < 1e-9\n\n    def test_rate(self, curve, curve2) -> None:\n        hazard_curve = curve\n        disc_curve = curve2\n\n        cds = CDS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"eur\",\n        )\n\n        rate = cds.rate(curves=[hazard_curve, disc_curve])\n        expected = 2.4164004881061285\n        assert abs(rate - expected) < 1e-9\n\n    def test_npv(self, curve, curve2) -> None:\n        hazard_curve = curve\n        disc_curve = curve2\n\n        cds = CDS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"eur\",\n            fixed_rate=1.00,\n        )\n\n        npv = cds.npv(curves=[hazard_curve, disc_curve])\n        expected = 9075.835204292109  # uses cds_discretization = 23 as default\n        assert abs(npv - expected) < 1e-5\n\n    def test_analytic_delta(self, curve, curve2) -> None:\n        hazard_curve = curve\n        disc_curve = curve2\n\n        cds = CDS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"eur\",\n        )\n\n        result = cds.analytic_delta(curves=[hazard_curve, disc_curve], leg=1)\n        expected = 64.07675851924779\n        assert abs(result - expected) < 1e-7\n\n        result = cds.analytic_delta(curves=[hazard_curve, disc_curve], leg=2)\n        expected = 0.0\n        assert abs(result - expected) < 1e-7\n\n    def test_cds_cashflows(self, curve, curve2) -> None:\n        hazard_curve = curve\n        disc_curve = curve2\n\n        cds = CDS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"eur\",\n        )\n        result = cds.cashflows(curves=[hazard_curve, disc_curve])\n        assert isinstance(result, DataFrame)\n        assert result.index.nlevels == 2\n\n    def test_solver(self, curve2):\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"disc\")\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 0.99, dt(2023, 1, 1): 0.98}, id=\"haz\")\n\n        solver = Solver(\n            curves=[c2],\n            instruments=[\n                CDS(dt(2022, 1, 1), \"6m\", frequency=\"Q\", curves=[\"haz\", c1]),\n                CDS(dt(2022, 1, 1), \"12m\", frequency=\"Q\", curves=[\"haz\", c1]),\n            ],\n            s=[0.30, 0.40],\n            instrument_labels=[\"6m\", \"12m\"],\n        )\n        inst = CDS(dt(2022, 7, 1), \"3M\", \"Q\", curves=[\"haz\", c1], notional=1e6)\n        result = inst.delta(solver=solver)\n        assert abs(result.sum().iloc[0] - 25.294894375736) < 1e-6\n\n    def test_okane_paper(self):\n        # Figure 12 of Turnbull and O'Kane 2003 Valuation of CDS\n        usd_libor = Curve(\n            nodes={\n                dt(2003, 6, 19): 1.0,\n                dt(2003, 12, 23): 1.0,\n                dt(2004, 6, 23): 1.0,\n                dt(2005, 6, 23): 1.0,\n                dt(2006, 6, 23): 1.0,\n                dt(2007, 6, 23): 1.0,\n                dt(2008, 6, 23): 1.0,\n            },\n            convention=\"act360\",\n            calendar=\"nyc\",\n            id=\"libor\",\n        )\n        args = dict(spec=\"eur_irs6\", frequency=\"s\", calendar=\"nyc\", curves=\"libor\", currency=\"usd\")\n        solver = Solver(\n            curves=[usd_libor],\n            instruments=[\n                IRS(dt(2003, 6, 23), \"6m\", **args),\n                IRS(dt(2003, 6, 23), \"1y\", **args),\n                IRS(dt(2003, 6, 23), \"2y\", **args),\n                IRS(dt(2003, 6, 23), \"3y\", **args),\n                IRS(dt(2003, 6, 23), \"4y\", **args),\n                IRS(dt(2003, 6, 23), \"5y\", **args),\n            ],\n            s=[1.35, 1.43, 1.90, 2.47, 2.936, 3.311],\n        )\n        haz_curve = Curve(\n            nodes={\n                dt(2003, 6, 19): 1.0,\n                dt(2004, 6, 20): 1.0,\n                dt(2005, 6, 20): 1.0,\n                dt(2006, 6, 20): 1.0,\n                dt(2007, 6, 20): 1.0,\n                dt(2008, 6, 20): 1.0,\n            },\n            convention=\"act365f\",\n            calendar=\"all\",\n            id=\"hazard\",\n        )\n        args = dict(\n            calendar=\"nyc\", frequency=\"q\", roll=20, curves=[\"hazard\", \"libor\"], convention=\"act360\"\n        )\n        solver = Solver(\n            curves=[haz_curve],\n            pre_solvers=[solver],\n            instruments=[\n                CDS(dt(2003, 6, 20), \"1y\", **args),\n                CDS(dt(2003, 6, 20), \"2y\", **args),\n                CDS(dt(2003, 6, 20), \"3y\", **args),\n                CDS(dt(2003, 6, 20), \"4y\", **args),\n                CDS(dt(2003, 6, 20), \"5y\", **args),\n            ],\n            s=[1.10, 1.20, 1.30, 1.40, 1.50],\n        )\n        cds = CDS(dt(2003, 6, 20), dt(2007, 9, 20), fixed_rate=2.00, notional=10e6, **args)\n        result = cds.rate(solver=solver)\n        assert abs(result - 1.427) < 0.0030\n\n        _table = cds.cashflows(solver=solver)\n        leg1_npv = cds.leg1.npv(rate_curve=haz_curve, disc_curve=usd_libor)\n        leg2_npv = cds.leg2.npv(rate_curve=haz_curve, disc_curve=usd_libor)\n        assert abs(leg1_npv + 781388) < 250\n        assert abs(leg2_npv - 557872) < 900\n\n        a_delta = cds.analytic_delta(curves=[haz_curve, usd_libor])\n        assert abs(a_delta - 3899) < 10\n\n        npv = cds.npv(solver=solver)\n        assert abs(npv + 223516) < 670\n\n    def test_accrued(self):\n        cds = CDS(\n            dt(2022, 1, 1), \"6M\", \"Q\", payment_lag=0, currency=\"eur\", notional=1e9, fixed_rate=2.0\n        )\n        result = cds.accrued(dt(2022, 2, 1))\n        assert abs(result + 0.25 * 1e9 * 0.02 * 31 / 90) < 1e-6\n\n    def test_accrued_raises(self):\n        cds = CDS(dt(2022, 1, 1), \"6M\", \"Q\", payment_lag=0, currency=\"eur\", notional=1e9)\n        with pytest.raises(ValueError, match=\"A `fixed_rate` must be set for a cashflo\"):\n            cds.accrued(dt(2022, 2, 1))\n\n    @pytest.mark.parametrize(\n        (\"cash\", \"tenor\", \"quote\"),\n        [\n            (-79690.03, \"1y\", 0.20),\n            (-156453.96, \"2y\", 0.20),\n            (-230320.76, \"3y\", 0.20),\n            (-370875.32, \"5y\", 0.20),\n            (-502612.64, \"7y\", 0.20),\n            (-684299.75, \"10y\", 0.20),\n            (116199.85, \"1y\", 2.20),\n            (225715.34, \"2y\", 2.20),\n            (327602.22, \"3y\", 2.20),\n            (512001.20, \"5y\", 2.20),\n            (673570.58, \"7y\", 2.20),\n            (878545.53, \"10y\", 2.20),\n        ],\n    )\n    def test_standard_model_test_grid(self, cash, tenor, quote, isda_credit_curves_40rr_20quote):\n        # https://www.cdsmodel.com/rfr-test-grids.html?\n        # USD 22 June 2022\n        credit_curve, curve = isda_credit_curves_40rr_20quote\n\n        credit_curve = Curve({dt(2022, 6, 22): 1.0, dt(2052, 6, 30): 1.0}, credit_recovery_rate=0.4)\n        Solver(\n            curves=[credit_curve],\n            instruments=[\n                CDS(dt(2022, 6, 20), tenor, spec=\"us_ig_cds\", curves=[credit_curve, curve])\n            ],\n            s=[quote],\n        )\n\n        cds = CDS(\n            dt(2022, 6, 20), tenor, spec=\"us_ig_cds\", curves=[credit_curve, curve], notional=10e6\n        )\n        result = cds.npv()\n        assert abs(result - cash) < 875\n\n    def test_cds_attributes(self):\n        cds = CDS(\n            dt(2022, 1, 1), \"6M\", \"Q\", payment_lag=0, currency=\"eur\", notional=1e9, fixed_rate=2.0\n        )\n        assert cds.fixed_rate == 2.0\n        cds.fixed_rate = 1.0\n        assert cds.fixed_rate == 1.0\n\n    def test_cds_parse_curves(self, curve, curve2):\n        cds = CDS(\n            dt(2022, 1, 1), \"6M\", \"Q\", payment_lag=0, currency=\"eur\", notional=1e9, fixed_rate=2.0\n        )\n        r1 = cds.npv(curves={\"rate_curve\": curve, \"disc_curve\": curve2})\n        r2 = cds.npv(curves=[curve, curve2])\n        assert r1 == r2\n\n        with pytest.raises(ValueError, match=\"CDS requires 2\"):\n            cds.npv(curves=curve)\n\n    def test_analytic_rec_risk(self):\n        irs_tenor = [\n            \"1m\",\n            \"2m\",\n            \"3m\",\n            \"6m\",\n            \"12m\",\n            \"2y\",\n            \"3y\",\n            \"4y\",\n            \"5y\",\n            \"6y\",\n            \"7y\",\n            \"8y\",\n            \"9y\",\n            \"10y\",\n            \"12y\",\n        ]\n        irs_rates = [\n            4.8457,\n            4.7002,\n            4.5924,\n            4.3019,\n            3.8992,\n            3.5032,\n            3.3763,\n            3.3295,\n            3.3165,\n            3.3195,\n            3.3305,\n            3.3450,\n            3.3635,\n            3.3830,\n            3.4245,\n        ]\n        cds_tenor = [\"6m\", \"12m\", \"2y\", \"3y\", \"4y\", \"5y\", \"7y\", \"10y\"]\n        cds_rates = [0.11011, 0.14189, 0.20750, 0.26859, 0.32862, 0.37861, 0.51068, 0.66891]\n\n        today = dt(2024, 10, 4)  # Friday 4th October 2024\n        spot = dt(2024, 10, 8)  # Tuesday 8th October 2024\n\n        disc_curve = Curve(\n            nodes={today: 1.0, **{add_tenor(spot, _, \"mf\", \"nyc\"): 1.0 for _ in irs_tenor}},\n            calendar=\"nyc\",\n            convention=\"act360\",\n            interpolation=\"log_linear\",\n            id=\"sofr\",\n        )\n\n        us_rates_sv = Solver(\n            curves=[disc_curve],\n            instruments=[IRS(spot, _, spec=\"usd_irs\", curves=\"sofr\") for _ in irs_tenor],\n            s=irs_rates,\n            instrument_labels=irs_tenor,\n            id=\"us_rates\",\n        )\n\n        cds_eff = dt(2024, 9, 20)\n        cds_mats = [add_tenor(dt(2024, 12, 20), _, \"mf\", \"all\") for _ in cds_tenor]\n\n        hazard_curve = Curve(\n            nodes={today: 1.0, **{add_tenor(spot, _, \"mf\", \"nyc\"): 1.0 for _ in cds_tenor}},\n            calendar=\"all\",\n            convention=\"act365f\",\n            interpolation=\"log_linear\",\n            id=\"pfizer\",\n        )\n\n        pfizer_sv = Solver(\n            curves=[hazard_curve],\n            pre_solvers=[us_rates_sv],\n            instruments=[\n                CDS(cds_eff, _, spec=\"us_ig_cds\", curves=[\"pfizer\", \"sofr\"]) for _ in cds_mats\n            ],\n            s=cds_rates,\n            instrument_labels=cds_tenor,\n            id=\"pfizer_cds\",\n        )\n        cds = CDS(\n            effective=dt(2024, 9, 20),\n            termination=dt(2029, 12, 20),\n            spec=\"us_ig_cds\",\n            curves=[\"pfizer\", \"sofr\"],\n            notional=10e6,\n        )\n\n        result = cds.analytic_rec_risk(solver=pfizer_sv)\n        assert abs(result + 3031.0076128941) < 1e-8\n\n\nclass TestXCS:\n    def test_mtmxcs_npv(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"eurusd\": curve2, \"eureur\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"eur\",\n            pair=\"eurusd\",\n            payment_lag_exchange=0,\n            leg2_mtm=True,\n        )\n\n        npv = xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)\n        assert abs(npv) < 1e-9\n\n    def test_mtmxcs_cashflows(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_mtm=True,\n        )\n\n        result = xcs.cashflows(curves=[curve, curve, curve2, curve2], fx=fxf, base=\"usd\")\n        expected = DataFrame(\n            {\n                \"Type\": [\"Cashflow\", \"FloatPeriod\", \"MtmCashflow\"],\n                \"Period\": [np.nan, \"Regular\", np.nan],\n                \"Ccy\": [\"NOK\", \"USD\", \"USD\"],\n                \"Notional\": [-10000000, -10000000.0, 10000000.0],\n                \"Rate\": [np.nan, 8.181151773810475, np.nan],\n                \"FX Rate\": [0.10002256337062124, 1.0, 1.0],\n                \"FX Fixing\": [np.nan, 0.0990019249688802, 0.09829871161519926],\n            },\n            index=MultiIndex.from_tuples([(\"leg1\", 0), (\"leg2\", 11), (\"leg2\", 14)]),\n        )\n        assert_frame_equal(\n            result.loc[\n                [(\"leg1\", 0), (\"leg2\", 11), (\"leg2\", 14)],\n                [\"Type\", \"Period\", \"Ccy\", \"Notional\", \"Rate\", \"FX Rate\", \"FX Fixing\"],\n            ],\n            expected,\n        )\n\n    @pytest.mark.skip(\n        reason=\"After merging all XCS to one class inputting `fx_fixings` as list was changed.\",\n    )\n    def test_mtmxcs_fx_fixings_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`fx_fixings` for MTM XCS should\"):\n            _ = XCS(\n                dt(2022, 2, 1),\n                \"8M\",\n                \"M\",\n                fx_fixings=NoInput(0),\n                currency=\"usd\",\n                pair=\"eurusd\",\n            )\n\n        with pytest.raises(ValueError, match=\"`fx_fixings` for MTM XCS should\"):\n            _ = XCS(\n                dt(2022, 2, 1),\n                \"8M\",\n                \"M\",\n                fx_fixings=NoInput(0),\n                fixed=True,\n                leg2_fixed=False,\n                leg2_mtm=True,\n                currency=\"usd\",\n                pair=\"eurusd\",\n            )\n\n        with pytest.raises(ValueError, match=\"`fx_fixings` for MTM XCS should\"):\n            _ = XCS(\n                dt(2022, 2, 1),\n                \"8M\",\n                \"M\",\n                fx_fixings=NoInput(0),\n                fixed=True,\n                leg2_fixed=True,\n                leg2_mtm=True,\n                currency=\"usd\",\n                pair=\"eurusd\",\n            )\n\n        with pytest.raises(ValueError, match=\"`fx_fixings` for MTM XCS should\"):\n            _ = XCS(\n                dt(2022, 2, 1),\n                \"8M\",\n                \"M\",\n                fx_fixings=NoInput(0),\n                fixed=False,\n                leg2_fixed=True,\n                leg2_mtm=True,\n                currency=\"usd\",\n                pair=\"eurusd\",\n            )\n\n    @pytest.mark.parametrize(\n        (\"float_spd\", \"compound\", \"expected\"),\n        [\n            (10, \"none_simple\", 9.97839804),\n            (100, \"none_simple\", 99.78398037),\n            (100, \"isda_compounding\", 99.418428),\n            (100, \"isda_flat_compounding\", 99.621117),\n        ],\n    )\n    def test_mtmxcs_rate(self, float_spd, compound, expected, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            float_spread=float_spd,\n            leg2_spread_compound_method=compound,\n            leg2_mtm=True,\n        )\n\n        result = xcs.rate(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg2\")\n        assert abs(result - expected) < 1e-4\n        alias = xcs.spread(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg2\")\n        assert alias == result\n\n        xcs.leg2_float_spread = result\n        validate = xcs.npv(curves=[curve2, curve2, curve, curve], fx=fxf)\n        assert abs(validate) < 1e-2\n        result2 = xcs.rate(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg2\")\n        assert abs(result - result2) < 1e-3\n\n    def test_fx_fixings_2_tuple(self) -> None:\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            leg2_fx_fixings=(1.25, Series([1.5, 1.75], index=[dt(2022, 2, 25), dt(2022, 3, 30)])),\n            leg2_mtm=True,\n        )\n        assert xcs.leg2._regular_periods[0].non_deliverable_params.fx_fixing.value == 1.25\n        assert xcs.leg2._regular_periods[1].non_deliverable_params.fx_fixing.value == 1.50\n        assert xcs.leg2._regular_periods[2].non_deliverable_params.fx_fixing.value == 1.75\n        assert xcs.leg2._regular_periods[3].non_deliverable_params.fx_fixing.value == NoInput(0)\n\n    def test_initialisation_nonmtm_xcs_notional_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"The `notional` can only be provided on one leg\"):\n            XCS(\n                effective=dt(2000, 1, 1),\n                termination=\"1y\",\n                frequency=\"q\",\n                notional=135e6,\n                fx_fixings=0.7407407407407407,\n                leg2_notional=20e6,\n                currency=\"cad\",\n                pair=\"cadusd\",\n                leg2_mtm=False,\n            )\n\n    @pytest.mark.parametrize(\"fixed1\", [True, False])\n    @pytest.mark.parametrize(\"fixed2\", [True, False])\n    @pytest.mark.parametrize(\"mtm\", [True, False])\n    def test_fixings_table(self, curve, curve2, fixed1, fixed2, mtm):\n        curve._id = \"c1\"\n        curve2._id = \"c2\"\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"eurusd\": curve2, \"eureur\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            frequency=\"M\",\n            payment_lag=0,\n            currency=\"eur\",\n            pair=\"eurusd\",\n            payment_lag_exchange=0,\n            fixed=fixed1,\n            leg2_fixed=fixed2,\n            leg2_mtm=mtm,\n            fixing_method=\"ibor(2)\",\n            leg2_fixing_method=FloatFixingMethod.IBOR(2),\n        )\n        result = xcs.local_analytic_rate_fixings(curves=[curve, curve, curve2, curve2], fx=fxf)\n        assert isinstance(result, DataFrame)\n\n    def test_initialisation_bug(self):\n        XCS(\n            dt(2000, 1, 7),\n            \"9m\",\n            spec=\"eurusd_xcs\",\n            leg2_fixed=True,\n            leg2_mtm=False,\n            fixing_method=\"ibor(2)\",\n            leg2_fixed_rate=2.4,\n        )\n\n        XCS(dt(2000, 1, 7), \"9m\", spec=\"eurusd_xcs\", fixed=True, fixed_rate=3.0)\n\n    def test_fixing_doc(self):\n        # tests a series as string can be provided to XCS in tuple\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name + \"_GBPUSD\",\n            Series(\n                index=[dt(2023, 1, 13), dt(2023, 4, 13), dt(2023, 7, 13)],\n                data=[1.19, 1.21, 1.24],\n            ),\n        )\n        xcs = XCS(\n            effective=dt(2023, 1, 15),\n            termination=\"9M\",\n            spec=\"gbpusd_xcs\",\n            leg2_fx_fixings=(1.20, name),\n        )\n        result = xcs.cashflows()\n        assert isinstance(result, DataFrame)\n        expected = [1.20, 1.21, 1.24]\n        for i, period in enumerate(xcs.leg2._regular_periods):\n            period.non_deliverable_params.fx_fixing.value == expected[i]\n\n    def test_notional_on_mtm_leg_raises(self):\n        with pytest.raises(ValueError, match=\"Setting `mtm` on a Leg requires a non-deli\"):\n            XCS(\n                effective=dt(2025, 1, 8),\n                termination=\"6m\",\n                frequency=\"Q\",\n                currency=\"usd\",\n                mtm=True,\n                leg2_fx_fixings=155.0,\n                pair=\"usdjpy\",\n                notional=1e9,\n            )\n\n    def test_attributes_get(self):\n        xcs = XCS(dt(2000, 1, 1), \"6m\", \"Q\", fixed=True, leg2_fixed=True, pair=\"eurusd\")\n        assert xcs.fixed_rate == NoInput(0)\n        assert xcs.leg2_fixed_rate == NoInput(0)\n        with pytest.raises(AttributeError, match=\"Leg1 is of type\"):\n            xcs.float_spread\n        with pytest.raises(AttributeError, match=\"Leg2 is of type\"):\n            xcs.leg2_float_spread\n\n    def test_attributes_get_float(self):\n        xcs = XCS(dt(2000, 1, 1), \"6m\", \"Q\", pair=\"eurusd\")\n        assert xcs.float_spread == 0.0\n        assert xcs.leg2_float_spread == 0.0\n        with pytest.raises(AttributeError, match=\"Leg1 is of type\"):\n            xcs.fixed_rate\n        with pytest.raises(AttributeError, match=\"Leg2 is of type\"):\n            xcs.leg2_fixed_rate\n\n    def test_attributes_set(self):\n        xcs = XCS(dt(2000, 1, 1), \"6m\", \"Q\", fixed=True, leg2_fixed=True, pair=\"eurusd\")\n        xcs.fixed_rate = 2.0\n        xcs.leg2_fixed_rate = 1.5\n        with pytest.raises(AttributeError, match=\"Leg1 is of type\"):\n            xcs.float_spread = 2.0\n        with pytest.raises(AttributeError, match=\"Leg2 is of type\"):\n            xcs.leg2_float_spread = 1.5\n\n    def test_attributes_set_float(self):\n        xcs = XCS(dt(2000, 1, 1), \"6m\", \"Q\", pair=\"eurusd\")\n        xcs.float_spread = 2.0\n        xcs.leg2_float_spread = 1.5\n        with pytest.raises(AttributeError, match=\"Leg1 is of type\"):\n            xcs.fixed_rate = 2.0\n        with pytest.raises(AttributeError, match=\"Leg2 is of type\"):\n            xcs.leg2_fixed_rate = 1.5\n\n    def test_mtm_dual_validation_raises(self):\n        with pytest.raises(ValueError, match=\"`mtm` and `leg2_mtm` must def\"):\n            XCS(\n                dt(2000, 1, 1),\n                \"6m\",\n                \"Q\",\n                pair=\"eurusd\",\n                mtm=True,\n                leg2_mtm=True,\n            )\n\n    def test_notional_fixings_mismatch_raises(self):\n        with pytest.raises(ValueError, match=\"When `notional` is given, that leg is assumed to be\"):\n            XCS(dt(2000, 1, 1), \"6m\", \"Q\", pair=\"eurusd\", mtm=True, fx_fixings=1.10)\n\n        with pytest.raises(\n            ValueError, match=\"When `leg2_notional` is given, that leg is assumed to be\"\n        ):\n            XCS(\n                dt(2000, 1, 1),\n                \"6m\",\n                \"Q\",\n                pair=\"usdeur\",\n                mtm=True,\n                leg2_fx_fixings=1.10,\n                leg2_notional=10.0,\n            )\n\n    @pytest.mark.parametrize(\"curves\", [\"bad-value\", [\"1\", \"2\", \"3\"]])\n    def test_parse_curves_failures(self, curves):\n        with pytest.raises(ValueError, match=\"XCS requires 4 curve type\"):\n            XCS(\n                dt(2000, 1, 1),\n                \"6m\",\n                \"Q\",\n                pair=\"usdeur\",\n                mtm=True,\n                fx_fixings=1.10,\n                leg2_notional=10.0,\n                curves=curves,\n            )\n\n    def test_must_set_one_fixed_rate(self):\n        with pytest.raises(ValueError, match=\"At least one leg must have a de\"):\n            XCS(\n                dt(2000, 1, 1),\n                \"6m\",\n                \"Q\",\n                pair=\"usdeur\",\n                mtm=True,\n                fx_fixings=1.10,\n                leg2_notional=10.0,\n                fixed=True,\n                leg2_fixed=True,\n            ).npv()\n\n    def test_bad_metric_raises(self):\n        with pytest.raises(ValueError, match=\"`metric` must be in {'leg1', 'leg2'}\"):\n            XCS(\n                dt(2000, 1, 1),\n                \"6m\",\n                \"Q\",\n                pair=\"eurusd\",\n                mtm=True,\n                fx_fixings=1.10,\n                leg2_notional=10.0,\n                fixed=True,\n                leg2_fixed=True,\n            ).rate(metric=\"bad\")\n\n    def test_leg1_mtm(self):\n        # if notional given on leg1 this will error with nd `pair` not given.\n        XCS(\n            effective=dt(2000, 1, 1),\n            termination=\"6m\",\n            frequency=\"Q\",\n            currency=\"eur\",\n            pair=\"eurusd\",\n            mtm=True,\n            leg2_notional=10.0,\n        )\n\n\nclass TestNDXCS:\n    @pytest.mark.parametrize(\n        (\"leg1\", \"curves\"),\n        [\n            (True, [\"c2\", \"c\", \"c\", \"c\"]),\n            (False, [\"c\", \"c\", \"c2\", \"c\"]),\n        ],\n    )\n    def test_2c_ndxcs_npv(self, curve, curve2, leg1, curves) -> None:\n        # the EUR reference leg is leg1 if leg1 is True\n        map_ = {\n            True: LegMtm.Payment,\n            False: LegMtm.Initial,\n        }\n\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"eurusd\": curve2, \"eureur\": curve2},\n        )\n        ndxcs = NDXCS(\n            dt(2022, 2, 1),\n            \"3M\",\n            \"M\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            notional=1e6 if leg1 else NoInput(0),\n            leg2_notional=1e6 if not leg1 else NoInput(0),\n            curves=[curve if _ == \"c\" else curve2 for _ in curves],\n        )\n\n        assert ndxcs.kwargs.leg1[\"mtm\"] is map_[leg1]\n        assert ndxcs.kwargs.leg2[\"mtm\"] is map_[not leg1]\n\n        npv = ndxcs.npv(fx=fxf)\n        assert abs(npv) < 1e-9\n\n    def test_3c_ndxcs_npv(self, curve, curve2) -> None:\n        curve3 = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.00,\n                dt(2022, 4, 1): 0.981,\n                dt(2022, 7, 1): 0.973,\n                dt(2022, 10, 1): 0.955,\n            },\n            interpolation=\"log_linear\",\n            index_base=100.0,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.1, \"gbpusd\": 1.25}, settlement=dt(2022, 1, 3)),\n            {\n                \"usdusd\": curve,\n                \"eurusd\": curve2,\n                \"eureur\": curve2,\n                \"gbpgbp\": curve3,\n                \"gbpusd\": curve3,\n            },\n        )\n        ndxcs = NDXCS(\n            dt(2022, 2, 1),\n            \"3M\",\n            \"M\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            notional=1e6,\n            leg2_notional=-1e6 * float(fxf.rate(\"eurgbp\")),\n            leg2_pair=\"gbpusd\",\n            curves=[curve2, curve, curve3, curve],\n        )\n\n        assert ndxcs.kwargs.leg1[\"mtm\"] == LegMtm.Payment\n        assert ndxcs.kwargs.leg2[\"mtm\"] == LegMtm.Payment\n\n        npv = ndxcs.npv(fx=fxf)\n        rate = ndxcs.rate(fx=fxf)\n        df = ndxcs.cashflows(fx=fxf)\n        assert abs(npv) < 1e-9\n        ndxcs.float_spread = float(rate) + 0.1\n        assert abs(ndxcs.npv(fx=fxf) + ndxcs.analytic_delta(fx=fxf, leg=1) * 0.1) < 1e-8\n        for a, b in zip(\n            df[\"FX Fix Date\"],\n            [dt(2022, 1, 28), dt(2022, 3, 1), dt(2022, 3, 31), dt(2022, 4, 28), dt(2022, 4, 28)]\n            * 2,\n        ):\n            assert a == b\n\n    def test_init_default_ccy(self):\n        defaults.base_currency = \"gbp\"\n        ndxcs = NDXCS(dt(2000, 1, 1), \"1y\", spec=\"inrusd_ndxcs\")\n        assert ndxcs.leg1.settlement_params.currency == \"usd\"\n        assert ndxcs.leg2.settlement_params.currency == \"usd\"\n        defaults.reset_defaults()\n        assert defaults.base_currency == \"usd\"\n\n\nclass TestFixedFloatXCS:\n    def test_mtmfixxcs_rate(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=False,\n            leg2_mtm=True,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n\n        result = xcs.rate(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg1\")\n\n        irs = IRS(dt(2022, 2, 1), \"8M\", \"M\", currency=\"nok\", payment_lag=0)\n        validate = irs.rate(curves=curve2)\n        assert abs(result - validate) < 1e-4\n        # alias = xcs.spread([curve2, curve2, curve, curve], NoInput(0), fxf, 2)\n\n    def test_mtmfixxcs_rate_reversed(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=False,\n            leg2_fixed=True,\n            leg2_mtm=True,\n            payment_lag=0,\n            currency=\"usd\",\n            pair=\"usdnok\",\n            payment_lag_exchange=0,\n            notional=10e6,\n        )\n\n        result = xcs.rate(curves=[curve, curve, curve2, curve2], fx=fxf, metric=\"leg2\")\n\n        irs = IRS(dt(2022, 2, 1), \"8M\", \"M\", currency=\"nok\", payment_lag=0)\n        validate = irs.rate(curves=curve2)\n        assert abs(result - validate) < 1e-2\n        alias = xcs.spread(curves=[curve, curve, curve2, curve2], fx=fxf, metric=\"leg2\")\n        assert abs(result - alias) < 1e-4\n\n\nclass TestFixedFixedXCS:\n    def test_mtmfixfixxcs_rate(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n\n        irs = IRS(dt(2022, 2, 1), \"8M\", \"M\", payment_lag=0)\n        nok_rate = float(irs.rate(curves=curve2))\n        xcs = XCS(\n            dt(2022, 2, 1),\n            \"8M\",\n            \"M\",\n            fixed=True,\n            leg2_fixed=True,\n            leg2_mtm=True,\n            payment_lag=0,\n            currency=\"nok\",\n            pair=\"nokusd\",\n            payment_lag_exchange=0,\n            notional=10e6,\n            fixed_rate=nok_rate,\n        )\n        result = xcs.rate(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg2\")\n        validate = irs.rate(curves=curve)\n        assert abs(result - validate) < 1e-4\n        alias = xcs.spread(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg2\")\n        assert abs(result - alias) < 1e-8\n\n        # test reverse\n        usd_rate = float(irs.rate(curves=curve))\n        xcs.fixed_rate = NoInput(0)\n        xcs.leg2_fixed_rate = usd_rate\n        result = xcs.rate(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg1\")\n        validate = irs.rate(curves=curve2)\n        assert abs(result - validate) < 1e-4\n        alias = xcs.spread(curves=[curve2, curve2, curve, curve], fx=fxf, metric=\"leg1\")\n        assert abs(result - alias) < 1e-8\n\n\nclass TestFXSwap:\n    def test_fxswap_rate(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        fxs = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            pair=\"usdnok\",\n            notional=1e6,\n        )\n        expected = fxf.swap(\"usdnok\", [dt(2022, 2, 1), dt(2022, 10, 1)])\n        result = fxs.rate(curves=[NoInput(0), curve, NoInput(0), curve2], fx=fxf)\n        assert abs(result - expected) < 1e-10\n        assert np.isclose(result.dual, expected.dual)\n\n    def test_fxswap_pair_arg(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        fxs = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            pair=\"usdnok\",\n            notional=1e6,\n        )\n        expected = fxf.swap(\"usdnok\", [dt(2022, 2, 1), dt(2022, 10, 1)])\n        result = fxs.rate(curves=[NoInput(0), curve, NoInput(0), curve2], fx=fxf)\n        assert abs(result - expected) < 1e-10\n        assert np.isclose(result.dual, expected.dual)\n\n    def test_currency_arg_pair_overlap(self) -> None:\n        with pytest.raises(TypeError, match=\"unexpected keyword argument 'currency'\"):\n            FXSwap(\n                dt(2022, 2, 1),\n                \"8M\",\n                pair=\"usdnok\",\n                currency=\"jpy\",\n            )\n\n    def test_fxswap_npv(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        fxs = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            pair=\"usdnok\",\n            notional=1e6,\n        )\n\n        assert abs(fxs.npv(curves=[NoInput(0), curve, NoInput(0), curve2], fx=fxf)) < 1e-7\n\n        result = fxs.rate(curves=[NoInput(0), curve, NoInput(0), curve2], fx=fxf)\n        fxs.leg2_fixed_rate = result\n        assert abs(fxs.npv(curves=[NoInput(0), curve, NoInput(0), curve2], fx=fxf)) < 1e-7\n\n    def test_fxswap_points_raises(self) -> None:\n        msg = \"For an FXSwap transaction both `fx_rate` and `points` must be given\"\n        with pytest.raises(ValueError, match=msg):\n            FXSwap(\n                dt(2022, 2, 1),\n                \"8M\",\n                pair=\"usdnok\",\n                notional=1e6,\n                points=100.0,\n            )\n\n    def test_fxswap_points_warns(self) -> None:\n        with pytest.raises(\n            ValueError, match=\"For an FXSwap transaction both `fx_rate` and `points` must be given\"\n        ):\n            FXSwap(\n                dt(2022, 2, 1),\n                \"8M\",\n                fx_rate=11.0,\n                pair=\"usdnok\",\n                notional=1e6,\n            )\n\n        FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            fx_rate=11.0,\n            points=1000.0,\n            pair=\"usdnok\",\n            notional=1e6,\n            split_notional=1e6,\n        )\n\n    @pytest.mark.parametrize(\n        (\"fx_rate\", \"points\", \"split_notional\", \"expected\"),\n        [\n            (NoInput(0), NoInput(0), NoInput(0), Dual(0, [\"fx_usdnok\"], [0.0])),\n            (11.0, 1800.0, NoInput(0), Dual(3734.617680, [\"fx_usdnok\"], [-3027.88203904])),\n            (\n                11.0,\n                1754.5623360395632,\n                NoInput(0),\n                Dual(4166.37288388, [\"fx_usdnok\"], [-3071.05755945]),\n            ),\n            (\n                10.032766762996951,\n                1754.5623360395632,\n                NoInput(0),\n                Dual(0, [\"fx_usdnok\"], [-2654.42027107]),\n            ),\n            (\n                10.032766762996951,\n                1754.5623360395632,\n                1027365.1574336714,\n                Dual(0, [\"fx_usdnok\"], [0.0]),\n            ),\n        ],\n    )\n    def test_fxswap_parameter_combinations_off_mids_given(\n        self,\n        curve,\n        curve2,\n        fx_rate,\n        points,\n        split_notional,\n        expected,\n    ) -> None:\n        # curve._set_ad_order(1)\n        # curve2._set_ad_order(1)\n        # risk sensitivity to curve is checked in:\n        # test_null_priced_delta_round_trip_one_pricing_param_fx_fix\n\n        # the exact values of relevance here are:\n        # usdnok: 10.032766762996951,\n        # points:  1754.5623360395632\n        # split_notional: 1027365.1574336714\n\n        # the first test which results in a zero gradient is explained on the documentation pages\n        # from an FXSwap in the notes section.\n\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        fxs = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            fx_rate=fx_rate,\n            points=points,\n            split_notional=split_notional,\n            pair=\"usdnok\",\n            notional=1e6,\n        )\n        assert fxs.kwargs.meta[\"points\"] == points\n        result = fxs.npv(curves=[NoInput(0), curve, NoInput(0), curve2], fx=fxf, base=\"usd\")\n        # rate = fxs.rate(curves=[curve, curve2], fx=fxf)\n\n        assert abs(result - expected) < 1e-6\n        assert np.isclose(result.dual, expected.dual)\n\n    def test_direction_fx_swap_notional(self, curve, curve2):\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        fxs = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            fx_rate=10.0,\n            points=1000.0,\n            pair=\"usdnok\",\n            notional=1e6,\n        )\n        fxs2 = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            fx_rate=10.0,\n            points=1500.0,\n            pair=\"usdnok\",\n            notional=-1e6,\n        )\n        npv1 = fxs.npv(curves=[curve, curve2], fx=fxf)\n        npv2 = fxs2.npv(curves=[curve, curve2], fx=fxf)\n        assert (npv1 - npv2) > 0\n\n    @pytest.mark.parametrize(\"leg\", [1, 2])\n    def test_notional_directions_with_split_notional(self, leg):\n        fxs = FXSwap(\n            **{\n                \"effective\": dt(2022, 2, 1),\n                \"termination\": dt(2022, 4, 1),\n                f\"{'leg2_' if leg == 2 else ''}notional\": 1e6,\n                \"split_notional\": 1e6,\n                \"pair\": \"usdnok\",\n            }\n        )\n        l1c1_sign = fxs.leg1.periods[0].settlement_params.notional < 0\n        l1c2_sign = fxs.leg1.periods[1].settlement_params.notional < 0\n        l2c1_sign = fxs.leg2.periods[0].settlement_params.notional < 0\n        l2c2_sign = fxs.leg2.periods[1].settlement_params.notional < 0\n        assert l1c1_sign != l1c2_sign\n        assert l2c1_sign != l2c2_sign\n        assert l1c1_sign != l2c1_sign\n\n    def test_rate_with_fixed_parameters(self, curve, curve2) -> None:\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        fxs = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            fx_rate=10.01,\n            points=1765,\n            split_notional=1.01e6,\n            pair=\"usdnok\",\n            notional=1e6,\n        )\n        result = fxs.rate(curves=[NoInput(0), curve, NoInput(0), curve2], fx=fxf)\n        expected = 1746.59802\n        assert abs(result - expected) < 1e-4\n\n    # def test_proxy_curve_from_fxf(self, curve, curve2):\n    #     # TODO this needs a solver from which to test the proxy curve (line 92)\n    #     fxf = FXForwards(\n    #         FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n    #         {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2}\n    #     )\n    #     fxs = FXSwap(dt(2022, 2, 1), \"8M\", \"M\",\n    #                         currency=\"usd\", leg2_currency=\"nok\",\n    #                         payment_lag_exchange=0, notional=1e6,\n    #                         leg2_fixed_rate=-1.0)\n    #     npv_nok =\n    #         fxs.npv([NoInput(0), fxf.curve(\"usd\", \"nok\"), NoInput(0), curve2], NoInput(0), fxf)\n    #     npv_usd =\n    #         fxs.npv([NoInput(0), curve, NoInput(0), fxf.curve(\"nok\", \"usd\")], NoInput(0), fxf)\n    #     assert abs(npv_nok-npv_usd) < 1e-7  # npvs are equivalent becasue xcs basis =0\n\n    def test_transition_from_dual_to_dual2(self, curve, curve2) -> None:\n        # Test added for BUG, see PR: XXX\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        fxf._set_ad_order(1)\n        fxs = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            pair=\"usdnok\",\n            notional=1e6,\n        )\n        result = fxs.npv(\n            curves=[None, fxf.curve(\"usd\", \"usd\"), None, fxf.curve(\"nok\", \"usd\")], fx=fxf\n        )\n        assert isinstance(result, Dual)\n        fxf._set_ad_order(2)\n        result2 = fxs.npv(\n            curves=[None, fxf.curve(\"usd\", \"usd\"), None, fxf.curve(\"nok\", \"usd\")], fx=fxf\n        )\n        assert isinstance(result2, Dual2)\n\n    def test_transition_from_dual_to_dual2_rate(self, curve, curve2) -> None:\n        # Test added for BUG, see PR: XXX\n        fxf = FXForwards(\n            FXRates({\"usdnok\": 10}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"nokusd\": curve2, \"noknok\": curve2},\n        )\n        fxf._set_ad_order(1)\n        fxs = FXSwap(\n            dt(2022, 2, 1),\n            \"8M\",\n            pair=\"usdnok\",\n            notional=1e6,\n        )\n        result = fxs.rate(\n            curves=[None, fxf.curve(\"usd\", \"usd\"), None, fxf.curve(\"nok\", \"usd\")], fx=fxf\n        )\n        assert isinstance(result, Dual)\n        fxf._set_ad_order(2)\n        result = fxs.rate(\n            curves=[None, fxf.curve(\"usd\", \"usd\"), None, fxf.curve(\"nok\", \"usd\")], fx=fxf\n        )\n        assert isinstance(result, Dual2)\n\n    @pytest.mark.skip(reason=\"in v2.5 split notional is not the default and must be set directly\")\n    def test_split_notional_raises(self):\n        # this is an unpriced FXswap with split notional\n        fxs = FXSwap(effective=dt(2022, 2, 1), termination=\"3m\", pair=\"eurusd\")\n        with pytest.raises(\n            TypeError, match=\"`curves` have not been supplied correctly. A `disc_curve` is required\"\n        ):\n            fxs.rate()\n\n    @pytest.mark.parametrize(\n        (\"eom\", \"expected\"),\n        [\n            (False, dt(2022, 5, 28)),\n            (True, dt(2022, 5, 31)),\n        ],\n    )\n    def test_eom_dates(self, eom, expected):\n        fxs = FXSwap(\n            effective=dt(2022, 2, 28),\n            termination=\"3m\",\n            pair=\"eurusd\",\n            calendar=\"all\",\n            modifier=\"mf\",\n            eom=eom,\n        )\n        assert fxs.kwargs.leg1[\"termination\"] == expected\n\n    @pytest.mark.parametrize(\n        (\"roll\", \"expected\"),\n        [\n            (\"imm\", dt(2022, 4, 20)),\n            (19, dt(2022, 4, 19)),\n        ],\n    )\n    def test_roll_dates(self, roll, expected):\n        fxs = FXSwap(\n            effective=dt(2022, 1, 19),\n            termination=\"3m\",\n            pair=\"eurusd\",\n            calendar=\"all\",\n            modifier=\"mf\",\n            roll=roll,\n        )\n        assert fxs.kwargs.leg1[\"termination\"] == expected\n\n\nclass TestSTIRFuture:\n    def test_stir_rate(self, curve, curve2) -> None:\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n        )\n        expected = 95.96254344884888\n        result = stir.rate(curves=curve, metric=\"price\")\n        assert abs(100 - result - stir.rate(curves=curve)) < 1e-8\n        assert abs(result - expected) < 1e-8\n\n    def test_stir_no_gamma(self, curve) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        ins = [\n            IRS(dt(2022, 3, 16), dt(2022, 6, 15), \"Q\", curves=\"usdusd\"),\n        ]\n        solver = Solver(\n            curves=[c1],\n            instruments=ins,\n            s=[1.2],\n            id=\"solver\",\n            instrument_labels=[\"usd fut\"],\n        )\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n            curves=\"usdusd\",\n        )\n        result = stir.delta(solver=solver).sum().sum()\n        assert abs(result + 25.0) < 1e-7\n\n        result = stir.gamma(solver=solver).sum().sum()\n        assert abs(result) < 1e-7\n\n    def test_stir_npv(self) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        # irs = IRS(dt(2022, 3, 16), dt(2022, 6, 15), \"Q\", curves=\"usdusd\")\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n            curves=\"usdusd\",\n            price=99.50,\n        )\n        result = stir.npv(curves=c1)\n        expected = (99.5 - (100 - 0.99250894761)) * 2500 * -1.0\n        assert abs(result - expected) < 1e-7\n\n    def test_stir_npv_currency_bug(self) -> None:\n        # GH653: instantiation without a currency failed to NPV when an fx object provided.\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n        c2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98})\n        c3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.97})\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.1}, dt(2022, 1, 1)), {\"eureur\": c1, \"eurusd\": c2, \"usdusd\": c3}\n        )\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            frequency=\"Q\",\n            spec=\"usd_stir\",\n            contracts=-1,\n        )\n        result = stir.npv(curves=[None, c1, c2, c1], fx=fxf)\n        assert abs(result) < 1e-7\n\n    def test_stir_npv_fx(self) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        # irs = IRS(dt(2022, 3, 16), dt(2022, 6, 15), \"Q\", curves=\"usdusd\")\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n            curves=\"usdusd\",\n            price=99.50,\n        )\n\n        fxr = FXRates({\"usdeur\": 0.85})\n        result = stir.npv(curves=c1, fx=fxr, base=\"eur\")\n        expected = ((99.5 - (100 - 0.99250894761)) * 2500 * -1.0) * 0.85\n\n        assert abs(result - expected) < 1e-7\n\n    def test_stir_raises(self) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        # irs = IRS(dt(2022, 3, 16), dt(2022, 6, 15), \"Q\", curves=\"usdusd\")\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n            curves=\"usdusd\",\n            price=99.50,\n        )\n        with pytest.raises(ValueError, match=\"`metric` must be in\"):\n            stir.rate(curves=c1, metric=\"bad\")\n\n    def test_analytic_delta(self) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n            curves=c1,\n            price=99.50,\n            contracts=100,\n        )\n        expected = -2500.0\n        result = stir.analytic_delta()\n        assert abs(result - expected) < 1e-10\n\n    def test_analytic_delta_fx(self) -> None:\n        c1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"usdusd\")\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n            curves=c1,\n            price=99.50,\n            contracts=100,\n        )\n        expected = -2500.0 * 0.85\n        fxr = FXRates({\"usdeur\": 0.85})\n        result = stir.analytic_delta(fx=fxr, base=\"eur\")\n        assert abs(result - expected) < 1e-10\n\n    def test_fixings_table(self, curve):\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=\"3m\",\n            spec=\"eur_stir3\",\n            contracts=100,\n            curves=curve,\n        )\n        result = stir.local_analytic_rate_fixings()\n        assert isinstance(result, DataFrame)\n        value = result.loc[dt(2022, 3, 14), slice(None)].iloc[0]\n        assert abs(value + 2500.0) < 1e-9\n\n    @pytest.mark.parametrize(\n        (\"spec\", \"expected\"),\n        [\n            (\"usd_stir\", -25.0),\n            (\"eur_stir\", -25.0),\n            (\"eur_stir3\", -25.0),\n            (\"gbp_stir\", -25.0),\n        ],\n    )\n    def test_3m_spec_contracts(self, spec, expected, curve):\n        stir = STIRFuture(get_imm(3, 2022), get_imm(6, 2022), spec=spec)\n        for leg in [1, 2]:\n            result = stir.analytic_delta(curves=curve, leg=leg)\n            assert abs(result - expected * (1.5 - leg) * 2) < 1e-10\n\n    @pytest.mark.parametrize(\n        (\"spec\", \"expected\"),\n        [\n            (\"usd_stir1\", -41.670),\n            (\"eur_stir1\", -25.0),\n        ],\n    )\n    def test_1m_spec_contracts(self, spec, expected, curve):\n        stir = STIRFuture(dt(2022, 4, 1), dt(2022, 5, 1), spec=spec)\n        for leg in [1, 2]:\n            result = stir.analytic_delta(curves=curve, leg=leg)\n            assert abs(result - expected * (1.5 - leg) * 2) < 1e-10\n\n    def test_cashflows(self, curve):\n        stir = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n            price=99.50,\n            contracts=10,\n        )\n        result = stir.cashflows()\n        assert result[\"Payment\"].iloc[0] is None\n        result2 = stir.cashflows(curves=curve)\n        assert result2[\"Payment\"].iloc[0] == dt(2022, 1, 1)\n        assert result2[\"DF\"].iloc[0] == 1.0\n\n    def test_edsp_check(self):\n        # https://www.fmxfutures.com/wp-content/uploads/2025/09/SOFR-Final-Settlement-Report-9_17_2025.pdf\n        name = str(hash(os.urandom(3)))\n        fixings.add(\n            name + \"_1B\",\n            Series(\n                index=[\n                    dt.strptime(_, \"%m/%d/%Y\")\n                    for _ in [\n                        \"06/30/2025\",\n                        \"06/27/2025\",\n                        \"06/26/2025\",\n                        \"06/25/2025\",\n                        \"06/24/2025\",\n                        \"06/23/2025\",\n                        \"06/20/2025\",\n                        \"06/18/2025\",\n                        \"06/17/2025\",\n                        \"06/16/2025\",\n                        \"06/13/2025\",\n                        \"06/12/2025\",\n                        \"06/11/2025\",\n                        \"06/10/2025\",\n                        \"06/09/2025\",\n                        \"06/06/2025\",\n                        \"06/05/2025\",\n                        \"06/04/2025\",\n                        \"06/03/2025\",\n                        \"06/02/2025\",\n                        \"05/30/2025\",\n                        \"05/29/2025\",\n                        \"05/28/2025\",\n                        \"05/27/2025\",\n                        \"05/23/2025\",\n                        \"05/22/2025\",\n                        \"05/21/2025\",\n                        \"05/20/2025\",\n                        \"05/19/2025\",\n                        \"05/16/2025\",\n                        \"05/15/2025\",\n                        \"05/14/2025\",\n                        \"05/13/2025\",\n                        \"05/12/2025\",\n                        \"05/09/2025\",\n                        \"05/08/2025\",\n                        \"05/07/2025\",\n                        \"05/06/2025\",\n                        \"05/05/2025\",\n                        \"05/02/2025\",\n                        \"05/01/2025\",\n                        \"04/30/2025\",\n                        \"04/29/2025\",\n                        \"04/28/2025\",\n                        \"04/25/2025\",\n                        \"04/24/2025\",\n                        \"04/23/2025\",\n                        \"04/22/2025\",\n                        \"04/21/2025\",\n                        \"04/17/2025\",\n                        \"04/16/2025\",\n                        \"04/15/2025\",\n                        \"04/14/2025\",\n                        \"04/11/2025\",\n                        \"04/10/2025\",\n                        \"04/09/2025\",\n                        \"04/08/2025\",\n                        \"04/07/2025\",\n                        \"04/04/2025\",\n                        \"04/03/2025\",\n                        \"04/02/2025\",\n                        \"04/01/2025\",\n                        \"03/31/2025\",\n                        \"03/28/2025\",\n                        \"03/27/2025\",\n                        \"03/26/2025\",\n                        \"03/25/2025\",\n                        \"03/24/2025\",\n                        \"03/21/2025\",\n                        \"03/20/2025\",\n                        \"03/19/2025\",\n                        \"03/18/2025\",\n                        \"03/17/2025\",\n                        \"03/14/2025\",\n                        \"03/13/2025\",\n                        \"03/12/2025\",\n                        \"03/11/2025\",\n                        \"03/10/2025\",\n                        \"03/07/2025\",\n                        \"03/06/2025\",\n                        \"03/05/2025\",\n                        \"03/04/2025\",\n                        \"03/03/2025\",\n                    ]\n                ],\n                data=[\n                    4.45,\n                    4.39,\n                    4.4,\n                    4.36,\n                    4.3,\n                    4.29,\n                    4.29,\n                    4.28,\n                    4.31,\n                    4.32,\n                    4.28,\n                    4.28,\n                    4.28,\n                    4.28,\n                    4.29,\n                    4.29,\n                    4.29,\n                    4.28,\n                    4.32,\n                    4.35,\n                    4.35,\n                    4.33,\n                    4.33,\n                    4.31,\n                    4.26,\n                    4.26,\n                    4.26,\n                    4.27,\n                    4.29,\n                    4.3,\n                    4.31,\n                    4.29,\n                    4.3,\n                    4.28,\n                    4.28,\n                    4.29,\n                    4.3,\n                    4.32,\n                    4.33,\n                    4.36,\n                    4.39,\n                    4.41,\n                    4.36,\n                    4.36,\n                    4.33,\n                    4.29,\n                    4.28,\n                    4.3,\n                    4.32,\n                    4.32,\n                    4.31,\n                    4.36,\n                    4.33,\n                    4.33,\n                    4.37,\n                    4.42,\n                    4.4,\n                    4.33,\n                    4.35,\n                    4.39,\n                    4.37,\n                    4.39,\n                    4.41,\n                    4.34,\n                    4.36,\n                    4.35,\n                    4.33,\n                    4.31,\n                    4.3,\n                    4.29,\n                    4.29,\n                    4.31,\n                    4.32,\n                    4.3,\n                    4.3,\n                    4.31,\n                    4.32,\n                    4.33,\n                    4.34,\n                    4.35,\n                    4.34,\n                    4.33,\n                    4.33,\n                ],\n            ),\n        )\n        inst = STIRFuture(\n            get_imm(code=\"H25\"),\n            get_imm(code=\"M25\"),\n            spec=\"usd_stir\",\n            leg2_rate_fixings=name,\n        )\n        result = inst.rate()\n        edsp = 100 - round(result, 4)\n        fixings.pop(name + \"_1B\")\n        assert edsp == 95.6577\n\n    def test_edsp_check2(self):\n        # https://www.cmegroup.com/content/dam/cmegroup/education/files/sofr-futures-settlement-calculation-methodologies.pdf\n        name = str(hash(os.urandom(3)))\n        fixings.add(\n            name + \"_1B\",\n            Series(\n                index=[\n                    dt.strptime(_, \"%Y-%m-%d\")\n                    for _ in [\n                        \"2017-06-21\",\n                        \"2017-06-22\",\n                        \"2017-06-23\",\n                        \"2017-06-26\",\n                        \"2017-06-27\",\n                        \"2017-06-28\",\n                        \"2017-06-29\",\n                        \"2017-06-30\",\n                        \"2017-07-03\",\n                        \"2017-07-05\",\n                        \"2017-07-06\",\n                        \"2017-07-07\",\n                        \"2017-07-10\",\n                        \"2017-07-11\",\n                        \"2017-07-12\",\n                        \"2017-07-13\",\n                        \"2017-07-14\",\n                        \"2017-07-17\",\n                        \"2017-07-18\",\n                        \"2017-07-19\",\n                        \"2017-07-20\",\n                        \"2017-07-21\",\n                        \"2017-07-24\",\n                        \"2017-07-25\",\n                        \"2017-07-26\",\n                        \"2017-07-27\",\n                        \"2017-07-28\",\n                        \"2017-07-31\",\n                        \"2017-08-01\",\n                        \"2017-08-02\",\n                        \"2017-08-03\",\n                        \"2017-08-04\",\n                        \"2017-08-07\",\n                        \"2017-08-08\",\n                        \"2017-08-09\",\n                        \"2017-08-10\",\n                        \"2017-08-11\",\n                        \"2017-08-14\",\n                        \"2017-08-15\",\n                        \"2017-08-16\",\n                        \"2017-08-17\",\n                        \"2017-08-18\",\n                        \"2017-08-21\",\n                        \"2017-08-22\",\n                        \"2017-08-23\",\n                        \"2017-08-24\",\n                        \"2017-08-25\",\n                        \"2017-08-28\",\n                        \"2017-08-29\",\n                        \"2017-08-30\",\n                        \"2017-08-31\",\n                        \"2017-09-01\",\n                        \"2017-09-05\",\n                        \"2017-09-06\",\n                        \"2017-09-07\",\n                        \"2017-09-08\",\n                        \"2017-09-11\",\n                        \"2017-09-12\",\n                        \"2017-09-13\",\n                        \"2017-09-14\",\n                        \"2017-09-15\",\n                        \"2017-09-18\",\n                        \"2017-09-19\",\n                    ]\n                ],\n                data=[\n                    1.02,\n                    1.02,\n                    1.06,\n                    1.05,\n                    1.03,\n                    1.04,\n                    1.07,\n                    1.2,\n                    1.1,\n                    1.05,\n                    1.03,\n                    1.01,\n                    1.01,\n                    1.01,\n                    1.01,\n                    1.02,\n                    1.02,\n                    1.04,\n                    1.02,\n                    1.01,\n                    1.02,\n                    1.01,\n                    1.05,\n                    1.04,\n                    1.04,\n                    1.04,\n                    1.03,\n                    1.08,\n                    1.03,\n                    1.01,\n                    1.01,\n                    1.01,\n                    1.01,\n                    1.01,\n                    1.01,\n                    1.03,\n                    1.05,\n                    1.08,\n                    1.11,\n                    1.09,\n                    1.09,\n                    1.06,\n                    1.04,\n                    1.02,\n                    1.02,\n                    1.08,\n                    1.05,\n                    1.05,\n                    1.03,\n                    1.03,\n                    1.14,\n                    1.09,\n                    1.05,\n                    1.03,\n                    1.04,\n                    1.04,\n                    1.04,\n                    1.05,\n                    1.05,\n                    1.09,\n                    1.1,\n                    1.04,\n                    1.01,\n                ],\n            ),\n        )\n        inst = STIRFuture(\n            get_imm(code=\"M17\"),\n            get_imm(code=\"U17\"),\n            spec=\"usd_stir\",\n            leg2_rate_fixings=name,\n        )\n        result = inst.rate()\n        edsp = 100 - round(result, 4)\n        fixings.pop(name + \"_1B\")\n        assert edsp == 98.9495\n\n\nclass TestPricingMechanism:\n    def test_value(self, curve) -> None:\n        ob = Value(dt(2022, 1, 28), curves=curve)\n        ob.rate()\n\n    def test_irs(self, curve) -> None:\n        ob = IRS(dt(2022, 1, 28), \"6m\", \"Q\", curves=curve)\n        ob.rate()\n        ob.npv()\n        ob.cashflows()\n        ob.spread()\n\n    def test_iirs(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        ob = IIRS(dt(2022, 1, 28), \"6m\", \"Q\", curves=[i_curve, curve, curve, curve])\n        ob.rate()\n        ob.npv()\n        ob.cashflows()\n        ob.spread()\n\n    def test_sbs(self, curve) -> None:\n        ob = SBS(dt(2022, 1, 28), \"6m\", \"Q\", curves=[curve] * 3)\n        ob.rate()\n        ob.npv()\n        ob.cashflows()\n        ob.spread()\n\n    def test_fra(self, curve) -> None:\n        ob = FRA(dt(2022, 1, 28), \"6m\", \"S\", curves=curve)\n        ob.rate()\n        ob.npv()\n        ob.cashflows()\n\n    @pytest.mark.parametrize(\n        (\"klass\", \"kwargs\"),\n        [\n            (XCS, {\"fixed\": False, \"leg2_fixed\": False, \"leg2_mtm\": False}),\n            (XCS, {\"fixed\": True, \"leg2_fixed\": False, \"leg2_mtm\": False, \"fixed_rate\": 2.0}),\n            (XCS, {\"fixed\": True, \"leg2_fixed\": True, \"leg2_mtm\": False, \"fixed_rate\": 2.0}),\n            (XCS, {}),  # defaults to fixed:False, leg2_fixed: False, leg2_mtm: True\n            (XCS, {\"fixed\": True, \"leg2_fixed\": False, \"leg2_mtm\": True, \"fixed_rate\": 2.0}),\n            (XCS, {\"fixed\": False, \"leg2_fixed\": True, \"leg2_mtm\": True}),\n            (XCS, {\"fixed\": True, \"leg2_fixed\": True, \"leg2_mtm\": True, \"fixed_rate\": 2.0}),\n        ],\n    )\n    def test_allxcs(self, klass, kwargs, curve, curve2) -> None:\n        ob = klass(\n            dt(2022, 1, 28),\n            \"6m\",\n            \"S\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            curves=[curve, NoInput(0), curve2, NoInput(0)],\n            **kwargs,\n        )\n        fxf = FXForwards(\n            FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": curve, \"eurusd\": curve2, \"eureur\": curve2},\n        )\n        ob.rate(metric=\"leg2\", fx=fxf)\n        ob.npv(fx=fxf)\n        ob.cashflows(fx=fxf)\n\n    def test_zcs(self, curve) -> None:\n        ob = ZCS(dt(2022, 1, 28), \"6m\", \"S\", curves=curve)\n        ob.rate()\n        ob.npv()\n        ob.cashflows()\n\n    def test_zcis(self, curve) -> None:\n        i_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        ob = ZCIS(dt(2022, 1, 28), \"6m\", \"S\", curves=[curve, curve, i_curve, curve])\n        ob.rate()\n        ob.npv()\n        ob.cashflows()\n\n    # TODO FXEXchange and FXSwap\n\n\nclass TestPortfolio:\n    def test_portfolio_npv(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"6m\", \"Q\", fixed_rate=1.0, curves=curve)\n        irs2 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=2.0, curves=curve)\n        pf = Portfolio([irs1, irs2])\n        assert pf.npv(base=\"usd\") == irs1.npv() + irs2.npv()\n\n        pf = Portfolio([irs1] * 5)\n        assert pf.npv(base=\"usd\") == irs1.npv() * 5\n\n    def test_portoflio_npv_pool(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"6m\", \"Q\", fixed_rate=1.0, curves=curve)\n        pf = Portfolio([irs1] * 5)\n        with default_context(\"pool\", 2):  # also test parallel processing\n            result = pf.npv(base=\"usd\")\n            assert result == irs1.npv() * 5\n\n    def test_portfolio_npv_local(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"6m\", \"Q\", fixed_rate=1.0, curves=curve, currency=\"usd\")\n        irs2 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=2.0, curves=curve, currency=\"eur\")\n        irs3 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=2.0, curves=curve, currency=\"usd\")\n        pf = Portfolio([irs1, irs2, irs3])\n\n        result = pf.npv(local=True)\n        expected = {\n            \"usd\": 20093.295095887483,\n            \"eur\": 5048.87332403382,\n        }\n        assert result == expected\n\n    def test_portfolio_local_parallel(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"6m\", \"Q\", fixed_rate=1.0, curves=curve, currency=\"usd\")\n        irs2 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=2.0, curves=curve, currency=\"eur\")\n        irs3 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=2.0, curves=curve, currency=\"usd\")\n        pf = Portfolio([irs1, irs2, irs3])\n\n        expected = {\n            \"usd\": 20093.295095887483,\n            \"eur\": 5048.87332403382,\n        }\n        with default_context(\"pool\", 2):  # also test parallel processing\n            result = pf.npv(local=True)\n            assert result == expected\n\n    def test_portfolio_mixed_currencies(self) -> None:\n        ll_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 5, 1): 1.0, dt(2022, 9, 3): 1.0},\n            interpolation=\"log_linear\",\n            id=\"sofr\",\n        )\n        ll_solver = Solver(\n            curves=[ll_curve],\n            instruments=[\n                IRS(dt(2022, 1, 1), \"4m\", \"Q\", curves=\"sofr\"),\n                IRS(dt(2022, 1, 1), \"8m\", \"Q\", curves=\"sofr\"),\n            ],\n            s=[1.85, 2.10],\n            instrument_labels=[\"4m\", \"8m\"],\n            id=\"sofr\",\n        )\n\n        ll_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 1): 1.0, dt(2022, 10, 1): 1.0},\n            interpolation=\"log_linear\",\n            id=\"estr\",\n        )\n        combined_solver = Solver(\n            curves=[ll_curve],\n            instruments=[\n                IRS(dt(2022, 1, 1), \"3m\", \"Q\", curves=\"estr\"),\n                IRS(dt(2022, 1, 1), \"9m\", \"Q\", curves=\"estr\"),\n            ],\n            s=[0.75, 1.65],\n            instrument_labels=[\"3m\", \"9m\"],\n            pre_solvers=[ll_solver],\n            id=\"estr\",\n        )\n\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=\"6m\",\n            frequency=\"Q\",\n            currency=\"usd\",\n            notional=500e6,\n            fixed_rate=2.0,\n            curves=\"sofr\",  # or [\"sofr\", \"sofr\"] for forecasting and discounting\n        )\n        irs2 = IRS(\n            effective=dt(2022, 1, 1),\n            termination=\"6m\",\n            frequency=\"Q\",\n            currency=\"eur\",\n            notional=-300e6,\n            fixed_rate=1.0,\n            curves=\"estr\",\n        )\n        pf = Portfolio([irs, irs2])\n        result = pf.npv(solver=combined_solver, local=True)\n        assert \"eur\" in result\n        assert \"usd\" in result\n\n        # the following should execute without warnings\n        pf.delta(solver=combined_solver)\n        pf.gamma(solver=combined_solver)\n\n    def test_repr(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"6m\", \"Q\", fixed_rate=1.0, curves=curve)\n        irs2 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=2.0, curves=curve)\n        pf = Portfolio([irs1, irs2])\n        expected = f\"<rl.Portfolio at {hex(id(pf))}>\"\n        assert pf.__repr__() == expected\n\n    def test_fixings_table(self, curve, curve2):\n        curve._id = \"c1\"\n        curve2._id = \"c2\"\n        irs1 = IRS(dt(2022, 1, 17), \"6m\", spec=\"eur_irs3\", curves=curve, notional=3e6)\n        irs2 = IRS(dt(2022, 1, 23), \"6m\", spec=\"eur_irs6\", curves=curve2, notional=1e6)\n        irs3 = IRS(dt(2022, 1, 17), \"6m\", spec=\"eur_irs3\", curves=curve, notional=-2e6)\n        pf = Portfolio([irs1, irs2, irs3])\n        result = pf.local_analytic_rate_fixings()\n\n        # # irs1 and irs3 are summed over curve c1 notional\n        # assert abs(result[\"c1\", \"notional\"][dt(2022, 1, 15)] - 1021994.16) < 1e-2\n        # irs1 and irs3 are summed over curve c1 risk\n        assert abs(result[\"c1\", \"eur\", \"eur\", \"3M\"][dt(2022, 1, 13)] - 25.249) < 1e-2\n        # c1 has no exposure to 22nd Jan\n        assert isna(result[\"c1\", \"eur\", \"eur\", \"3M\"][dt(2022, 1, 20)])\n        # # c1 dcf is not summed\n        # assert abs(result[\"c1\", \"dcf\"][dt(2022, 1, 15)] - 0.25) < 1e-3\n\n        # # irs2 is included\n        # assert abs(result[\"c2\", \"notional\"][dt(2022, 1, 22)] - 1005297.17) < 1e-2\n        # irs1 and irs3 are summed over curve c1 risk\n        assert abs(result[\"c2\", \"eur\", \"eur\", \"6M\"][dt(2022, 1, 20)] - 48.773) < 1e-3\n        # c2 has no exposure to 15 Jan\n        assert isna(result[\"c2\", \"eur\", \"eur\", \"6M\"][dt(2022, 1, 13)])\n        # # c2 has DCF\n        # assert abs(result[\"c2\", \"dcf\"][dt(2022, 1, 22)] - 0.50277) < 1e-3\n\n    def test_fixings_table_null_inst(self, curve):\n        irs = IRS(dt(2022, 1, 15), \"6m\", spec=\"eur_irs3\", curves=curve)\n        frb = FixedRateBond(dt(2022, 1, 1), \"5y\", \"A\", fixed_rate=2.0, curves=curve)\n        pf = Portfolio([irs, frb])\n        table = pf.local_analytic_rate_fixings()\n        assert isinstance(table, DataFrame)\n\n\nclass TestFly:\n    @pytest.mark.parametrize(\"mechanism\", [False, True])\n    def test_fly_npv(self, curve, mechanism) -> None:\n        mechanism = curve if mechanism else NoInput(0)\n        inverse = curve if mechanism is NoInput(0) else NoInput(0)\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=mechanism)\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0, curves=mechanism)\n        irs3 = IRS(dt(2022, 1, 1), \"5m\", \"Q\", fixed_rate=1.0, curves=mechanism)\n        fly = Fly(irs1, irs2, irs3)\n        assert fly.npv(curves=inverse) == irs1.npv(curves=inverse) + irs2.npv(\n            curves=inverse\n        ) + irs3.npv(curves=inverse)\n\n    @pytest.mark.parametrize(\"mechanism\", [False, True])\n    def test_fly_rate(self, curve, mechanism) -> None:\n        mechanism = curve if mechanism else NoInput(0)\n        inv = curve if mechanism is NoInput(0) else NoInput(0)\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=mechanism)\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0, curves=mechanism)\n        irs3 = IRS(dt(2022, 1, 1), \"5m\", \"Q\", fixed_rate=1.0, curves=mechanism)\n        fly = Fly(irs1, irs2, irs3)\n        assert (\n            fly.rate(curves=inv)\n            == (-irs1.rate(curves=inv) + 2 * irs2.rate(curves=inv) - irs3.rate(curves=inv)) * 100.0\n        )\n\n    def test_fly_cashflows_executes(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=curve)\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0, curves=curve)\n        irs3 = IRS(dt(2022, 1, 1), \"5m\", \"Q\", fixed_rate=1.0, curves=curve)\n        fly = Fly(irs1, irs2, irs3)\n        fly.cashflows()\n\n    def test_local_npv(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=curve, currency=\"eur\")\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0, curves=curve, currency=\"usd\")\n        irs3 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=curve, currency=\"gbp\")\n        fly = Fly(irs1, irs2, irs3)\n        result = fly.npv(local=True)\n        expected = {\n            \"eur\": 7523.321141258284,\n            \"usd\": 6711.514715925333,\n            \"gbp\": 7523.321141258284,\n        }\n        assert result == expected\n\n    def test_delta(self, simple_solver) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"6m\", \"A\", fixed_rate=1.0, notional=-3e6, curves=\"curve\")\n        irs2 = IRS(dt(2022, 1, 1), \"1Y\", \"A\", fixed_rate=2.0, notional=3e6, curves=\"curve\")\n        irs3 = IRS(dt(2022, 1, 1), \"18m\", \"A\", fixed_rate=1.0, notional=-1e6, curves=\"curve\")\n        fly = Fly(irs1, irs2, irs3)\n        result = fly.delta(solver=simple_solver).to_numpy()\n        expected = np.array([[102.08919479], [-96.14488074]])\n        assert np.all(np.isclose(result, expected))\n\n    def test_gamma(self, simple_solver) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"6m\", \"A\", fixed_rate=1.0, notional=-3e6, curves=\"curve\")\n        irs2 = IRS(dt(2022, 1, 1), \"1Y\", \"A\", fixed_rate=2.0, notional=3e6, curves=\"curve\")\n        irs3 = IRS(dt(2022, 1, 1), \"18m\", \"A\", fixed_rate=1.0, notional=-1e6, curves=\"curve\")\n        fly = Fly(irs1, irs2, irs3)\n        result = fly.gamma(solver=simple_solver).to_numpy()\n        expected = np.array([[-0.02944899, 0.009254014565], [0.009254014565, 0.0094239781314]])\n        assert np.all(np.isclose(result, expected))\n\n    def test_repr(self):\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0)\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0)\n        spd = Spread(irs1, irs2)\n        expected = f\"<rl.Spread at {hex(id(spd))}>\"\n        assert expected == spd.__repr__()\n\n    def test_fixings_table(self, curve, curve2):\n        curve._id = \"c1\"\n        curve2._id = \"c2\"\n        irs1 = IRS(dt(2022, 1, 17), \"6m\", spec=\"eur_irs3\", curves=curve, notional=3e6)\n        irs2 = IRS(dt(2022, 1, 23), \"6m\", spec=\"eur_irs6\", curves=curve2, notional=1e6)\n        irs3 = IRS(dt(2022, 1, 17), \"6m\", spec=\"eur_irs3\", curves=curve, notional=-2e6)\n        fly = Fly(irs1, irs2, irs3)\n        result = fly.local_analytic_rate_fixings()\n\n        # irs1 and irs3 are summed over curve c1 risk\n        assert abs(result[(\"c1\", \"eur\", \"eur\", \"3M\")][dt(2022, 1, 13)] - 25.249) < 1e-2\n        # c1 has no exposure to 22nd Jan\n        assert isna(result[(\"c1\", \"eur\", \"eur\", \"3M\")][dt(2022, 1, 20)])\n\n        # irs1 and irs3 are summed over curve c1 risk\n        assert abs(result[(\"c2\", \"eur\", \"eur\", \"6M\")][dt(2022, 1, 20)] - 48.773) < 1e-3\n        # c2 has no exposure to 15 Jan\n        assert isna(result[(\"c2\", \"eur\", \"eur\", \"6M\")][dt(2022, 1, 13)])\n\n    def test_fixings_table_null_inst(self, curve):\n        irs = IRS(dt(2022, 1, 15), \"6m\", spec=\"eur_irs3\", curves=curve)\n        frb = FixedRateBond(dt(2022, 1, 1), \"5y\", \"A\", fixed_rate=2.0, curves=curve)\n        fly = Fly(irs, frb, irs)\n        assert isinstance(fly.local_analytic_rate_fixings(), DataFrame)\n\n\nclass TestSpread:\n    @pytest.mark.parametrize(\"mechanism\", [False, True])\n    def test_spread_npv(self, curve, mechanism) -> None:\n        mechanism = curve if mechanism else NoInput(0)\n        inverse = curve if mechanism is NoInput(0) else NoInput(0)\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=mechanism)\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0, curves=mechanism)\n        spd = Spread(irs1, irs2)\n        assert spd.npv(curves=inverse) == irs1.npv(curves=inverse) + irs2.npv(curves=inverse)\n\n    @pytest.mark.parametrize(\"mechanism\", [False, True])\n    def test_spread_rate(self, curve, mechanism) -> None:\n        mechanism = curve if mechanism else NoInput(0)\n        inverse = curve if mechanism is NoInput(0) else NoInput(0)\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=mechanism)\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0, curves=mechanism)\n        spd = Spread(irs1, irs2)\n        assert (\n            spd.rate(curves=inverse)\n            == (-irs1.rate(curves=inverse) + irs2.rate(curves=inverse)) * 100.0\n        )\n\n    def test_spread_cashflows_executes(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=curve)\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0, curves=curve)\n        spd = Spread(irs1, irs2)\n        spd.cashflows()\n\n    def test_local_npv(self, curve) -> None:\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0, curves=curve, currency=\"eur\")\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0, curves=curve, currency=\"usd\")\n        spd = Spread(irs1, irs2)\n        result = spd.npv(local=True)\n        expected = {\n            \"eur\": 7523.321141258284,\n            \"usd\": 6711.514715925333,\n        }\n        assert result == expected\n\n    def test_repr(self):\n        irs1 = IRS(dt(2022, 1, 1), \"3m\", \"Q\", fixed_rate=1.0)\n        irs2 = IRS(dt(2022, 1, 1), \"4m\", \"Q\", fixed_rate=2.0)\n        irs3 = IRS(dt(2022, 1, 1), \"5m\", \"Q\", fixed_rate=1.0)\n        fly = Fly(irs1, irs2, irs3)\n        expected = f\"<rl.Fly at {hex(id(fly))}>\"\n        assert expected == fly.__repr__()\n\n    def test_fixings_table(self, curve, curve2):\n        curve._id = \"c1\"\n        curve2._id = \"c2\"\n        irs1 = IRS(dt(2022, 1, 17), \"6m\", spec=\"eur_irs3\", curves=curve, notional=3e6)\n        irs2 = IRS(dt(2022, 1, 23), \"6m\", spec=\"eur_irs6\", curves=curve2, notional=1e6)\n        irs3 = IRS(dt(2022, 1, 17), \"6m\", spec=\"eur_irs3\", curves=curve, notional=-2e6)\n        spd = Spread(irs1, Spread(irs2, irs3))\n        result = spd.local_analytic_rate_fixings()\n\n        # irs1 and irs3 are summed over curve c1 risk\n        assert abs(result[(\"c1\", \"eur\", \"eur\", \"3M\")][dt(2022, 1, 13)] - 25.249) < 1e-2\n        # c1 has no exposure to 22nd Jan\n        assert isna(result[(\"c1\", \"eur\", \"eur\", \"3M\")][dt(2022, 1, 20)])\n\n        # irs1 and irs3 are summed over curve c1 risk\n        assert abs(result[(\"c2\", \"eur\", \"eur\", \"6M\")][dt(2022, 1, 20)] - 48.773) < 1e-3\n        # c2 has no exposure to 15 Jan\n        assert isna(result[\"c2\", \"eur\", \"eur\", \"6M\"][dt(2022, 1, 13)])\n\n    def test_fixings_table_null_inst(self, curve):\n        irs = IRS(dt(2022, 1, 15), \"6m\", spec=\"eur_irs3\", curves=curve)\n        frb = FixedRateBond(dt(2022, 1, 1), \"5y\", \"A\", fixed_rate=2.0, curves=curve)\n        spd = Spread(irs, frb)\n        table = spd.local_analytic_rate_fixings()\n        assert isinstance(table, DataFrame)\n\n    def test_cashflows_curve_strings(self):\n        irs = IRS(dt(2025, 12, 1), dt(2030, 12, 7), spec=\"gbp_irs\", curves=[\"uk_sonia\"])\n        ukt = FixedRateBond(\n            dt(2024, 12, 7),\n            dt(2030, 12, 7),\n            fixed_rate=4.75,\n            spec=\"uk_gb\",\n            curves=[\"uk_gb\"],\n            metric=\"ytm\",\n        )\n        asw = Spread(ukt, irs)\n        result = asw.cashflows()\n        assert isinstance(result, DataFrame)\n\n\nclass TestSensitivities:\n    def test_sensitivity_raises(self) -> None:\n        irs = IRS(dt(2022, 1, 1), \"6m\", \"Q\")\n        with pytest.raises(ValueError, match=\"`solver` is required\"):\n            irs.delta()\n\n        with pytest.raises(ValueError, match=\"`solver` is required\"):\n            irs.gamma()\n\n\nclass TestSpec:\n    def test_spec_overwrites(self) -> None:\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=dt(2024, 2, 26),\n            calendar=\"tgt\",\n            frequency=\"Q\",\n            notional=250.0,\n            spec=\"test\",\n            curves=\"test\",\n        )\n        expected = dict(\n            initial_exchange=False,\n            final_exchange=False,\n            leg2_initial_exchange=False,\n            leg2_final_exchange=False,\n            pair=NoInput(0),\n            leg2_pair=NoInput(1),\n            fx_fixings=NoInput(0),\n            leg2_fx_fixings=NoInput(1),\n            leg2_zero_periods=NoInput(0),\n            mtm=LegMtm.Payment,\n            leg2_mtm=LegMtm.Payment,\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2024, 2, 26),\n                frequency=\"Q\",\n                stub=\"longfront\",\n                front_stub=NoInput(0),\n                back_stub=NoInput(0),\n                roll=NoInput(0),\n                eom=False,\n                modifier=\"p\",\n                calendar=\"tgt\",\n                payment_lag=4,\n                payment_lag_exchange=0,\n            ),\n            leg2_schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2024, 2, 26),\n                frequency=\"m\",\n                stub=\"longback\",\n                front_stub=NoInput(0),\n                back_stub=NoInput(0),\n                roll=1,\n                eom=False,\n                modifier=\"mp\",\n                calendar=\"nyc,tgt,ldn\",\n                payment_lag=3,\n                payment_lag_exchange=0,\n            ),\n            notional=250.0,\n            currency=\"tes\",\n            amortization=NoInput(0),\n            convention=\"yearsmonths\",\n            leg2_notional=-250.0,\n            leg2_currency=\"tes\",\n            leg2_convention=\"one\",\n            leg2_amortization=NoInput(0),\n            fixed_rate=NoInput(0),\n            leg2_fixing_method=NoInput(0),\n            leg2_spread_compound_method=NoInput(0),\n            leg2_rate_fixings=NoInput(0),\n            leg2_float_spread=NoInput(0),\n            leg2_fixing_series=NoInput(0),\n            leg2_fixing_frequency=NoInput(0),\n            curves=_Curves(disc_curve=\"test\", leg2_rate_curve=\"test\", leg2_disc_curve=\"test\"),\n            vol=_Vol(),\n        )\n        kwargs = _KWArgs(\n            user_args=expected,\n            meta_args=[\"curves\", \"vol\"],\n        )\n        assert irs.kwargs.meta == kwargs.meta\n        assert irs.kwargs.leg1 == kwargs.leg1\n        assert irs.kwargs.leg2 == kwargs.leg2\n        assert irs.kwargs == kwargs\n\n    def test_irs(self) -> None:\n        irs = IRS(\n            effective=dt(2022, 1, 1),\n            termination=\"1Y\",\n            spec=\"usd_irs\",\n            convention=\"30e360\",\n            fixed_rate=2.0,\n        )\n        assert irs.kwargs.leg1[\"convention\"] == \"30e360\"\n        assert irs.kwargs.leg2[\"convention\"] == \"30e360\"\n        assert irs.kwargs.leg1[\"currency\"] == \"usd\"\n        assert irs.kwargs.leg1[\"fixed_rate\"] == 2.0\n\n    def test_stir(self) -> None:\n        irs = STIRFuture(\n            effective=dt(2022, 3, 16),\n            termination=dt(2022, 6, 15),\n            spec=\"usd_stir\",\n            convention=\"30e360\",\n        )\n        assert irs.kwargs.leg1[\"convention\"] == \"30e360\"\n        assert irs.kwargs.leg2[\"convention\"] == \"30e360\"\n        assert irs.kwargs.leg1[\"currency\"] == \"usd\"\n        assert irs.kwargs.leg1[\"schedule\"].roll == \"IMM\"\n\n    def test_sbs(self) -> None:\n        inst = SBS(\n            effective=dt(2022, 1, 1),\n            termination=\"1Y\",\n            spec=\"eur_sbs36\",\n            convention=\"30e360\",\n            frequency=\"A\",\n        )\n        assert inst.kwargs.leg1[\"convention\"] == \"30e360\"\n        assert inst.kwargs.leg2[\"convention\"] == \"30e360\"\n        assert inst.kwargs.leg1[\"currency\"] == \"eur\"\n        assert inst.kwargs.leg2[\"fixing_method\"] == \"ibor(2)\"\n        assert inst.kwargs.leg1[\"schedule\"].frequency == \"A\"\n        assert inst.kwargs.leg2[\"schedule\"].frequency == \"S\"\n\n    def test_zcis(self) -> None:\n        inst = ZCIS(\n            effective=dt(2022, 1, 1),\n            termination=\"1Y\",\n            spec=\"eur_zcis\",\n            leg2_calendar=\"nyc,tgt\",\n            calendar=\"nyc,tgt\",\n        )\n        assert inst.kwargs.leg1[\"convention\"] == \"1+\"\n        assert inst.kwargs.leg2[\"convention\"] == \"1+\"\n        assert inst.kwargs.leg1[\"currency\"] == \"eur\"\n        assert inst.kwargs.leg2[\"index_method\"] == \"monthly\"\n        assert inst.kwargs.leg2[\"schedule\"].calendar == NamedCal(\"nyc,tgt\")\n\n    def test_zcs(self) -> None:\n        inst = ZCS(\n            effective=dt(2022, 1, 1),\n            termination=\"5Y\",\n            spec=\"gbp_zcs\",\n            leg2_calendar=\"nyc,tgt\",\n            calendar=\"nyc,tgt\",\n            fixed_rate=3.0,\n            curves=\"ish\",\n        )\n        assert inst.kwargs.leg1[\"convention\"] == \"act365f\"\n        assert inst.kwargs.leg1[\"currency\"] == \"gbp\"\n        assert inst.kwargs.leg2[\"schedule\"].calendar == NamedCal(\"nyc,tgt\")\n        assert inst.kwargs.leg2[\"schedule\"].frequency == \"A\"\n        assert inst.kwargs.leg1[\"fixed_rate\"] == 3.0\n        assert inst.kwargs.leg2[\"spread_compound_method\"] == \"none_simple\"\n        assert isinstance(inst.kwargs.meta[\"curves\"], _Curves)\n\n    def test_iirs(self) -> None:\n        inst = IIRS(\n            effective=dt(2022, 1, 1),\n            termination=\"1Y\",\n            spec=\"sek_iirs\",\n            leg2_calendar=\"nyc,tgt\",\n            calendar=\"nyc,tgt\",\n            fixed_rate=3.0,\n        )\n        assert inst.kwargs.leg1[\"convention\"] == \"actacticma\"\n        assert inst.kwargs.leg2[\"schedule\"].frequency == \"Q\"\n        assert inst.kwargs.leg1[\"currency\"] == \"sek\"\n        assert inst.kwargs.leg2[\"schedule\"].calendar == NamedCal(\"nyc,tgt\")\n        assert inst.kwargs.leg1[\"fixed_rate\"] == 3.0\n        assert inst.kwargs.leg2[\"spread_compound_method\"] == \"none_simple\"\n\n    def test_fixedratebond(self) -> None:\n        bond = FixedRateBond(\n            effective=dt(2022, 1, 1),\n            termination=\"1Y\",\n            spec=\"us_gb\",\n            calc_mode=\"ust_31bii\",\n            fixed_rate=2.0,\n        )\n        from rateslib.instruments.bonds.conventions import US_GB_TSY\n\n        assert bond.kwargs.meta[\"calc_mode\"].kwargs == US_GB_TSY.kwargs\n        assert bond.kwargs.leg1[\"convention\"] == \"actacticma\"\n        assert bond.kwargs.leg1[\"currency\"] == \"usd\"\n        assert bond.kwargs.leg1[\"fixed_rate\"] == 2.0\n        assert bond.kwargs.leg1[\"schedule\"].payment_adjuster3 == Adjuster.BusDaysLagSettle(-1)\n\n    def test_indexfixedratebond(self) -> None:\n        bond = IndexFixedRateBond(\n            effective=dt(2022, 1, 1),\n            termination=\"1Y\",\n            spec=\"uk_gbi\",\n            calc_mode=\"ust\",\n            fixed_rate=2.0,\n        )\n\n        assert bond.kwargs.leg1[\"convention\"] == \"actacticma\"\n        assert bond.kwargs.leg1[\"currency\"] == \"gbp\"\n        assert bond.kwargs.leg1[\"fixed_rate\"] == 2.0\n        assert bond.kwargs.leg1[\"schedule\"].payment_adjuster3 == Adjuster.BusDaysLagSettle(-7)\n        assert bond.kwargs.meta[\"calc_mode\"] == US_GB\n\n    def test_bill(self) -> None:\n        bill = Bill(\n            effective=dt(2022, 1, 1),\n            termination=\"3m\",\n            spec=\"us_gbb\",\n            convention=\"act365f\",\n        )\n        from rateslib.instruments.bonds.conventions import US_GBB\n\n        assert bill.kwargs.meta[\"calc_mode\"] == US_GBB\n        assert bill.kwargs.leg1[\"convention\"] == \"act365f\"\n        assert bill.kwargs.leg1[\"currency\"] == \"usd\"\n        assert bill.kwargs.leg1[\"fixed_rate\"] == 0.0\n\n    def test_fra(self) -> None:\n        from rateslib.enums.parameters import FloatFixingMethod\n\n        fra = FRA(\n            effective=dt(2022, 1, 1),\n            termination=\"3m\",\n            spec=\"eur_fra3\",\n            payment_lag=5,\n            modifier=\"F\",\n            fixed_rate=2.0,\n        )\n        assert fra.kwargs.leg2[\"fixing_method\"] == \"ibor(2)\"\n        assert fra.kwargs.leg1[\"convention\"] == \"act360\"\n        assert fra.kwargs.leg1[\"currency\"] == \"eur\"\n        assert fra.kwargs.leg2[\"currency\"] == \"eur\"\n        assert fra.kwargs.leg1[\"fixed_rate\"] == 2.0\n        assert fra.kwargs.leg2[\"schedule\"].payment_adjuster == Adjuster.BusDaysLagSettleInAdvance(5)\n        assert fra.kwargs.leg2[\"schedule\"].modifier == Adjuster.Following()\n\n    def test_frn(self) -> None:\n        frn = FloatRateNote(\n            effective=dt(2022, 1, 1),\n            termination=\"3y\",\n            spec=\"usd_frn5\",\n            payment_lag=5,\n        )\n        assert frn.kwargs.leg1[\"fixing_method\"] == \"rfr_observation_shift(5)\"\n        assert frn.kwargs.leg1[\"convention\"] == \"act360\"\n        assert frn.kwargs.leg1[\"currency\"] == \"usd\"\n        assert frn.kwargs.leg1[\"schedule\"].payment_adjuster == Adjuster.BusDaysLagSettle(5)\n        assert frn.kwargs.leg1[\"schedule\"].modifier == Adjuster.ModifiedFollowing()\n\n    def test_xcs(self) -> None:\n        xcs = XCS(\n            effective=dt(2022, 1, 1),\n            termination=\"3y\",\n            spec=\"eurusd_xcs\",\n            payment_lag=5,\n            calendar=\"ldn,tgt,nyc\",\n        )\n        assert xcs.kwargs.leg1[\"fixing_method\"] == \"rfr_payment_delay\"\n        assert xcs.kwargs.leg1[\"convention\"] == \"act360\"\n        assert xcs.kwargs.leg1[\"currency\"] == \"eur\"\n        assert xcs.kwargs.leg1[\"schedule\"].calendar == NamedCal(\"ldn,tgt,nyc\")\n        assert xcs.kwargs.leg2[\"schedule\"].calendar == NamedCal(\"ldn,tgt,nyc\")\n        assert xcs.kwargs.leg1[\"schedule\"].payment_adjuster == Adjuster.BusDaysLagSettle(5)\n        assert xcs.kwargs.leg2[\"schedule\"].payment_adjuster == Adjuster.BusDaysLagSettle(5)\n\n\n@pytest.mark.parametrize(\n    (\"inst\", \"expected\"),\n    [\n        (\n            IRS(\n                dt(2022, 1, 1),\n                \"9M\",\n                \"Q\",\n                currency=\"eur\",\n                curves=[\"eureur\", \"eur_eurusd\"],\n                fixed_rate=4.0,\n            ),\n            DataFrame(\n                data=[-3808.80973, -3850.91496, -3893.01546],\n                index=Index([dt(2022, 4, 3), dt(2022, 7, 3), dt(2022, 10, 3)], name=\"payment\"),\n                columns=MultiIndex.from_tuples(\n                    tuples=[(\"EUR\", \"usd,eur\")],\n                    names=[\"local_ccy\", \"collateral_ccy\"],\n                ),\n            ),\n        ),\n        (\n            SBS(\n                dt(2022, 1, 1),\n                \"9M\",\n                \"Q\",\n                leg2_frequency=\"S\",\n                currency=\"eur\",\n                curves=[\"eureur\", \"eurusd\", \"eureur\"],\n            ),\n            DataFrame(\n                [-0.00, -6260.19615, 6299.81823],\n                index=Index([dt(2022, 4, 3), dt(2022, 7, 3), dt(2022, 10, 3)], name=\"payment\"),\n                columns=MultiIndex.from_tuples(\n                    [(\"EUR\", \"usd\")],\n                    names=[\"local_ccy\", \"collateral_ccy\"],\n                ),\n            ),\n        ),\n        (\n            FRA(\n                dt(2022, 1, 15),\n                \"3M\",\n                \"Q\",\n                currency=\"eur\",\n                curves=[\"eureur\", \"eureur\"],\n                fixed_rate=4.0,\n            ),\n            DataFrame(\n                [-3785.37376],\n                index=Index([dt(2022, 1, 15)], name=\"payment\"),\n                columns=MultiIndex.from_tuples(\n                    [(\"EUR\", \"eur\")],\n                    names=[\"local_ccy\", \"collateral_ccy\"],\n                ),\n            ),\n        ),\n        (\n            FXForward(\n                dt(2022, 1, 15),\n                pair=\"eurusd\",\n                curves=[\"eureur\", \"eureur\", \"usdusd\", \"usdeur\"],\n            ),\n            DataFrame(\n                [[1000000.0, -1101072.93429]],\n                index=Index([dt(2022, 1, 15)], name=\"payment\"),\n                columns=MultiIndex.from_tuples(\n                    [(\"EUR\", \"eur\"), (\"USD\", \"eur\")],\n                    names=[\"local_ccy\", \"collateral_ccy\"],\n                ),\n            ),\n        ),\n        (\n            XCS(\n                dt(2022, 1, 5),\n                \"3M\",\n                \"M\",\n                currency=\"eur\",\n                pair=\"eurusd\",\n                leg2_mtm=True,\n                curves=[\"eureur\", \"eurusd\", \"usdusd\", \"usdusd\"],\n            ),\n            DataFrame(\n                [\n                    [1000000.0, -1100306.44743],\n                    [0.0, -2377.86409],\n                    [-2128.20822, 4630.97804],\n                    [0.0, -2152.16480],\n                    [-1922.05479, 4191.00596],\n                    [-1000000, 1104836.47633],\n                    [-2128.20822, 4650.04405],\n                ],\n                index=Index(\n                    [\n                        dt(2022, 1, 5),\n                        dt(2022, 2, 5),\n                        dt(2022, 2, 7),\n                        dt(2022, 3, 5),\n                        dt(2022, 3, 7),\n                        dt(2022, 4, 5),\n                        dt(2022, 4, 7),\n                    ],\n                    name=\"payment\",\n                ),\n                columns=MultiIndex.from_tuples(\n                    [(\"EUR\", \"usd\"), (\"USD\", \"usd\")],\n                    names=[\"local_ccy\", \"collateral_ccy\"],\n                ),\n            ),\n        ),\n        (\n            FXSwap(\n                dt(2022, 1, 5),\n                \"3M\",\n                pair=\"eurusd\",\n                curves=[\"eureur\", \"eurusd\", \"usdusd\", \"usdusd\"],\n            ),\n            DataFrame(\n                [[-1000000.0, 1100306.44743], [1000000.0, -1107224.13024]],\n                index=Index([dt(2022, 1, 5), dt(2022, 4, 5)], name=\"payment\"),\n                columns=MultiIndex.from_tuples(\n                    [(\"EUR\", \"usd\"), (\"USD\", \"usd\")],\n                    names=[\"local_ccy\", \"collateral_ccy\"],\n                ),\n            ),\n        ),\n    ],\n)\ndef test_fx_settlements_table(inst, expected) -> None:\n    usdusd = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.95}, id=\"usdusd\")\n    eureur = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.975}, id=\"eureur\")\n    eurusd = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.976}, id=\"eurusd\")\n    fxr = FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 1))\n    fxf = FXForwards(\n        fx_rates=fxr,\n        fx_curves={\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"eurusd\": eurusd,\n        },\n    )\n    usdeur = fxf.curve(\"usd\", \"eur\", id=\"usdeur\")\n    eur_eurusd = fxf.curve(\"eur\", [\"usd\", \"eur\"], id=\"eur_eurusd\")\n\n    solver = Solver(\n        curves=[usdusd, eureur, eurusd, usdeur, eur_eurusd],\n        instruments=[\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=usdusd),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=eureur),\n            XCS(\n                dt(2022, 1, 1),\n                \"1y\",\n                \"Q\",\n                currency=\"eur\",\n                pair=\"eurusd\",\n                curves=[eureur, eurusd, usdusd, usdusd],\n            ),\n        ],\n        s=[5.0, 2.5, -10],\n        fx=fxf,\n    )\n    assert eureur.meta.collateral == \"eur\"  # collateral tags populated by FXForwards\n\n    pf = Portfolio([inst])\n    result = pf.cashflows_table(solver=solver)\n    assert_frame_equal(expected, result, atol=1e-4)\n\n    result = inst.cashflows_table(solver=solver)\n    assert_frame_equal(expected, result, atol=1e-4)\n\n\ndef test_fx_settlements_table_no_fxf() -> None:\n    solver = Solver(\n        curves=[Curve({dt(2023, 8, 1): 1.0, dt(2024, 8, 1): 1.0}, id=\"usd\")],\n        instruments=[IRS(dt(2023, 8, 1), \"1Y\", \"Q\", curves=\"usd\")],\n        s=[2.0],\n        instrument_labels=[\"1Y\"],\n        id=\"us_rates\",\n        algorithm=\"gauss_newton\",\n    )\n    irs_mkt = IRS(\n        dt(2023, 8, 1),\n        \"1Y\",\n        \"Q\",\n        curves=\"usd\",\n        fixed_rate=2.0,\n        notional=999556779.81,\n    )\n    result = irs_mkt.cashflows_table(solver=solver)\n    assert abs(result.iloc[0, 0] - 69.49810) < 1e-5\n    assert abs(result.iloc[3, 0] - 69.49810) < 1e-5\n\n\n@pytest.fixture\ndef fxfo():\n    # FXForwards for FX Options tests\n    eureur = Curve(\n        {dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.9851909811629752},\n        calendar=\"tgt\",\n        id=\"eureur\",\n    )\n    usdusd = Curve(\n        {dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.976009366603271},\n        calendar=\"nyc\",\n        id=\"usdusd\",\n    )\n    eurusd = Curve({dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.987092591908283}, id=\"eurusd\")\n    fxr = FXRates({\"eurusd\": 1.0615}, settlement=dt(2023, 3, 20))\n    fxf = FXForwards(fx_curves={\"eureur\": eureur, \"eurusd\": eurusd, \"usdusd\": usdusd}, fx_rates=fxr)\n    # fxf.swap(\"eurusd\", [dt(2023, 3, 20), dt(2023, 6, 20)]) = 60.10\n    return fxf\n\n\nclass TestFXOptions:\n    # replicate https://quant.stackexchange.com/a/77802/29443\n    @pytest.mark.parametrize(\n        (\"pay\", \"k\", \"exp_pts\", \"exp_prem\", \"dlty\", \"exp_dl\"),\n        [\n            (dt(2023, 3, 20), 1.101, 69.378, 138756.54, \"spot\", 0.250124),\n            (dt(2023, 3, 20), 1.101, 69.378, 138756.54, \"forward\", 0.251754),\n            (dt(2023, 6, 20), 1.101, 70.226, 140451.53, \"spot\", 0.250124),\n            (dt(2023, 6, 20), 1.101, 70.226, 140451.53, \"forward\", 0.251754),\n            (dt(2023, 6, 20), 1.10101922, 70.180, 140360.17, \"spot\", 0.250000),\n        ],\n    )\n    @pytest.mark.parametrize(\"smile\", [True, False])\n    def test_big_usd_pips(self, fxfo, pay, k, exp_pts, exp_prem, dlty, exp_dl, smile) -> None:\n        vol = FXDeltaVolSmile(\n            {\n                0.75: 8.9,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"spot\",\n            id=\"vol\",\n            ad=1,\n        )\n        vol = vol if smile else 8.90\n        fxc = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            strike=k,\n            payment_lag=pay,\n            delivery_lag=2,\n            calendar=\"tgt\",\n            modifier=\"mf\",\n            premium_ccy=\"usd\",\n            delta_type=dlty,\n        )\n        result = fxc.rate(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            fx=fxfo,\n            vol=vol,\n        )\n        assert abs(result - exp_pts) < 1e-3\n\n        result = fxc.rate(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            fx=fxfo,\n            vol=vol,\n            metric=\"premium\",\n        )\n        assert abs(result - exp_prem) < 1e-2\n\n    @pytest.mark.parametrize(\n        (\"pay\", \"k\", \"exp_pts\", \"exp_prem\", \"exp_dl\"),\n        [\n            (dt(2023, 3, 20), 1.101, 0.6536, 130717.44, 0.245175),\n            (dt(2023, 6, 20), 1.101, 0.6578, 131569.29, 0.245178),\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            8.9,\n            FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"forward\",\n            ),\n            FXSabrSmile(\n                nodes={\"alpha\": 0.089, \"beta\": 1.0, \"rho\": 0.0, \"nu\": 0.0},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n            ),\n        ],\n    )\n    def test_premium_big_eur_pc(self, fxfo, pay, k, exp_pts, exp_prem, exp_dl, vol) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=pay,\n            strike=k,\n            notional=20e6,\n            delta_type=\"forward\",\n            premium_ccy=\"eur\",\n        )\n        result = fxo.rate(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            fx=fxfo,\n            vol=vol,\n        )\n        expected = exp_pts\n        assert abs(result - expected) < 1e-3\n\n        result = 20e6 * result / 100\n        expected = exp_prem\n        assert abs(result - expected) < 1e-1\n\n    @pytest.mark.parametrize(\n        (\"pay\", \"k\", \"exp_pts\", \"exp_prem\", \"exp_dl\"),\n        [\n            (dt(2023, 3, 20), 1.101, 0.6536, 130717.44, 0.243588),\n            (dt(2023, 6, 20), 1.101, 0.6578, 131569.29, 0.243548),\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            8.9,\n            FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"spot\",\n            ),\n            FXSabrSmile(\n                nodes={\"alpha\": 0.089, \"beta\": 1.0, \"rho\": 0.0, \"nu\": 0.0},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n            ),\n        ],\n    )\n    def test_premium_big_eur_pc_spot(self, fxfo, pay, k, exp_pts, exp_prem, exp_dl, vol) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=pay,\n            strike=k,\n            notional=20e6,\n            delta_type=\"spot\",\n            premium_ccy=\"eur\",\n        )\n        result = fxo.rate(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            fx=fxfo,\n            vol=vol,\n        )\n        expected = exp_pts\n        assert abs(result - expected) < 1e-3\n\n        result = 20e6 * result / 100\n        expected = exp_prem\n        assert abs(result - expected) < 1e-1\n\n    def test_fx_call_npv_unpriced(self, fxfo) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=1.101,\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.npv(curves=curves, fx=fxfo, vol=8.9)\n        expected = 0.0\n        assert abs(result - expected) < 1e-6\n\n    def test_fx_call_cashflows(self, fxfo) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=1.101,\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.cashflows(curves=curves, fx=fxfo, vol=8.9)\n        assert isinstance(result, DataFrame)\n        assert result[\"Type\"].iloc[0] == \"FXCallPeriod\"\n        assert result[\"Type\"].iloc[1] == \"Cashflow\"\n\n    def test_fx_call_cashflows_table(self, fxfo) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=1.101,\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.cashflows_table(curves=curves, fx=fxfo, vol=8.9)\n        expected = DataFrame(\n            data=[[0.0]],\n            index=Index([dt(2023, 6, 20)], name=\"payment\"),\n            columns=MultiIndex.from_tuples([(\"USD\", \"usd\")], names=[\"local_ccy\", \"collateral_ccy\"]),\n        )\n        assert_frame_equal(result, expected)\n\n    @pytest.mark.parametrize(\n        (\"ccy\", \"exp_rate\", \"exp_strike\"),\n        [\n            (\"usd\", 70.180131, 1.10101920113408469),\n            (\"eur\", 0.680949, 1.099976),\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            8.90,\n            FXDeltaVolSmile(\n                {\n                    0.75: 8.9,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"spot\",\n                id=\"vol\",\n                ad=1,\n            ),\n            FXSabrSmile(\n                nodes={\"alpha\": 0.089, \"beta\": 1.0, \"rho\": 0.0, \"nu\": 0.0},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                id=\"vol\",\n                ad=1,\n            ),\n        ],\n    )\n    def test_fx_call_rate_delta_strike(self, fxfo, ccy, exp_rate, exp_strike, vol) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=\"25d\",\n            delta_type=\"spot\",\n            premium_ccy=ccy,\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n        expected = exp_rate\n        assert abs(result - expected) < 1e-6\n        assert abs(fxo._option.fx_option_params.strike - exp_strike) < 1e-4\n\n    def test_fx_call_rate_expiry_tenor(self, fxfo) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=\"3m\",\n            eval_date=dt(2023, 3, 16),\n            modifier=\"mf\",\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=dt(2023, 6, 20),\n            calendar=\"tgt\",\n            strike=\"25d\",\n            delta_type=\"spot\",\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=8.9)\n        expected = 70.180131\n        assert abs(result - expected) < 1e-6\n\n    def test_fx_call_plot_payoff(self, fxfo) -> None:\n        fxc = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            strike=1.101,\n            premium=0.0,\n        )\n        result = fxc.plot_payoff(\n            [1.03, 1.12],\n            fx=fxfo,\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n        )\n        x, y = result[2][0]._x, result[2][0]._y\n        assert x[0] == 1.03\n        assert x[1000] == 1.12\n        assert y[0] == 0.0\n        assert y[1000] == (1.12 - 1.101) * 20e6\n\n    def test_fx_put_rate(self, fxfo) -> None:\n        fxo = FXPut(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=\"-25d\",\n            delta_type=\"spot\",\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=10.15)\n        expected = 83.975596\n        assert abs(result - expected) < 1e-6\n\n    def test_str_tenor_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`expiry` as string tenor requires `eval_date`\"):\n            FXCall(pair=\"eurusd\", expiry=\"3m\", strike=1.0)\n\n    def test_premium_ccy_raises(self) -> None:\n        with pytest.raises(\n            ValueError,\n            match=\"`premium_ccy`: 'chf' must be one of option currency pair\",\n        ):\n            FXCall(\n                pair=\"eurusd\",\n                expiry=\"3m\",\n                eval_date=dt(2023, 3, 16),\n                premium_ccy=\"chf\",\n                strike=1.0,\n            )\n\n    @pytest.mark.parametrize(\"dlty\", [(\"forward\")])\n    def test_call_put_parity_50d(self, fxfo, dlty) -> None:\n        fxp = FXPut(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=\"-50d\",\n            premium_ccy=\"usd\",\n            delta_type=dlty,\n        )\n        fxc = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=\"50d\",\n            premium_ccy=\"usd\",\n            delta_type=dlty,\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        assert abs(fxc.analytic_greeks(curves, fx=fxfo, vol=10.0)[\"delta\"] - 0.5) < 1e-14\n        assert abs(fxc._option.fx_option_params.strike - 1.068856) < 1e-6\n        assert abs(fxp.analytic_greeks(curves, fx=fxfo, vol=10.0)[\"delta\"] + 0.5) < 1e-14\n        assert abs(fxp._option.fx_option_params.strike - 1.068856) < 1e-6\n\n    def test_analytic_vega(self, fxfo) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=\"3m\",\n            eval_date=dt(2023, 3, 16),\n            modifier=\"mf\",\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=dt(2023, 3, 16),\n            calendar=\"tgt\",\n            strike=1.101,\n            delta_type=\"spot\",\n        )\n        result = fxo.analytic_greeks(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            fx=fxfo,\n            vol=8.9,\n        )[\"vega\"]\n        # see test_periods/test_analytic_vega\n        assert abs(result * 20e6 / 100 - 33757.945) < 1e-2\n\n    @pytest.mark.skip(\n        reason=\"An Option could expire in the past but settle forward, should still price\"\n    )\n    def test_rate_vol_raises(self, fxfo) -> None:\n        args = {\n            \"expiry\": dt(2009, 6, 16),\n            \"pair\": \"eurusd\",\n            \"curves\": [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            \"delta_type\": \"spot\",\n        }\n        vol = FXDeltaVolSmile(\n            {0.75: 8.9},\n            eval_date=dt(2009, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"spot\",\n            id=\"vol\",\n            ad=1,\n        )\n        fxc = FXCall(strike=1.10, **args, notional=100e6, vol=vol)\n        # the expiry is before the eval date. This still needs to price\n        with pytest.raises(ValueError, match=\"The `eval_date` on the FXDeltaVolSmile and the\"):\n            fxc.rate(fx=fxfo)\n\n    @pytest.mark.parametrize(\"phi\", [-1.0, 1.0])\n    @pytest.mark.parametrize(\"prem_ccy\", [\"usd\", \"eur\"])\n    @pytest.mark.parametrize(\"dt_0\", [\"spot\", \"forward\"])\n    @pytest.mark.parametrize(\"dt_1\", [\"spot\", \"forward\", \"spot_pa\", \"forward_pa\"])\n    @pytest.mark.parametrize(\"smile\", [True, False])\n    def test_atm_rates(self, fxfo, phi, prem_ccy, smile, dt_0, dt_1) -> None:\n        FXOp = FXCall if phi > 0 else FXPut\n        fxvs = FXDeltaVolSmile(\n            {0.25: 10.15, 0.5: 7.8, 0.75: 8.9},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=dt_1,\n            id=\"vol\",\n        )\n        vol = fxvs if smile else 9.50\n        fxo = FXOp(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=dt_0,\n            vol=vol,\n            premium_ccy=prem_ccy,\n            strike=\"atm_delta\",\n        )\n        result = fxo.analytic_greeks(fx=fxfo)\n\n        f_d = fxfo.rate(\"eurusd\", dt(2023, 6, 20))\n        eta = 0.5 if prem_ccy == \"usd\" else -0.5\n        expected = f_d * dual_exp(result[\"__vol\"] ** 2 * fxvs.meta.t_expiry * eta)\n        assert abs(result[\"__strike\"] - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"phi\", [-1.0, 1.0])\n    @pytest.mark.parametrize(\"prem_ccy\", [\"usd\", \"eur\"])\n    @pytest.mark.parametrize(\"dt_0\", [\"spot\", \"forward\"])\n    def test_atm_rates_sabr(self, fxfo, phi, prem_ccy, dt_0) -> None:\n        FXOp = FXCall if phi > 0 else FXPut\n        vol = FXSabrSmile(\n            {\"alpha\": 0.072, \"beta\": 1.0, \"rho\": -0.1, \"nu\": 0.80},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n        )\n        fxo = FXOp(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=dt_0,\n            vol=vol,\n            premium_ccy=prem_ccy,\n            strike=\"atm_delta\",\n        )\n        result = fxo.analytic_greeks(fx=fxfo)\n\n        f_d = fxfo.rate(\"eurusd\", dt(2023, 6, 20))\n        eta = 0.5 if prem_ccy == \"usd\" else -0.5\n        expected = f_d * dual_exp(result[\"__vol\"] ** 2 * vol.meta.t_expiry * eta)\n        assert abs(result[\"__strike\"] - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"phi\", [1.0, -1.0])\n    @pytest.mark.parametrize(\n        (\"vol_\", \"expected\"),\n        [\n            (\n                FXDeltaVolSmile(\n                    {0.25: 10.15, 0.5: 7.8, 0.75: 8.9},\n                    eval_date=dt(2023, 3, 16),\n                    expiry=dt(2023, 6, 16),\n                    delta_type=\"spot\",\n                ),\n                8.899854,\n            ),\n            (\n                FXSabrSmile(\n                    nodes={\"alpha\": 0.078, \"beta\": 1.0, \"rho\": 0.03, \"nu\": 0.04},\n                    eval_date=dt(2023, 3, 16),\n                    expiry=dt(2023, 6, 16),\n                ),\n                7.799409,\n            ),\n            (\n                FXSabrSurface(\n                    expiries=[dt(2023, 5, 16), dt(2023, 7, 16)],\n                    node_values=[\n                        [0.078, 1.0, 0.03, 0.04],\n                        [0.08, 1.0, 0.04, 0.05],\n                    ],\n                    eval_date=dt(2023, 3, 16),\n                    pair=\"eurusd\",\n                    calendar=\"tgt|fed\",\n                ),\n                7.934473,\n            ),\n        ],\n    )\n    def test_traded_option_rate_vol(self, fxfo, phi, vol_, expected) -> None:\n        FXOp = FXCall if phi > 0 else FXPut\n        fxo = FXOp(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=\"spot\",\n            premium_ccy=\"usd\",\n            strike=1.05,\n            premium=100000.0,\n        )\n        result = fxo.rate(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            vol=vol_,\n            fx=fxfo,\n            metric=\"vol\",\n        )\n        assert abs(result - expected) < 1e-6\n\n    def test_option_strike_premium_validation(self) -> None:\n        with pytest.raises(TypeError, match=\"missing 1 required positional argument: 'strike'\"):\n            FXCall(\n                pair=\"eurusd\",\n                expiry=dt(2023, 6, 16),\n            )\n\n        with pytest.raises(ValueError, match=\"FXOption with string delta as `strike` cannot be\"):\n            FXCall(pair=\"eurusd\", expiry=dt(2023, 6, 16), strike=\"25d\", premium=0.0)\n\n    @pytest.mark.parametrize(\n        (\"notn\", \"expected\", \"phi\"),\n        [\n            (1e6, [0.5, 500000], 1.0),\n            (2e6, [0.5, 1000000], 1.0),\n            (-2e6, [0.5, 1000000], 1.0),\n            (1e6, [-0.5, -500000], -1.0),\n            (2e6, [-0.5, -1000000], -1.0),\n            (-2e6, [-0.5, -1000000], -1.0),\n        ],\n    )\n    def test_greeks_delta_direction(self, fxfo, notn, expected, phi) -> None:\n        # test the delta and delta_eur are not impacted by a Buy or Sell. Delta is expressed\n        # relative to a Buy.\n        FXOp = FXCall if phi > 0 else FXPut\n        delta = f\"{'-' if phi < 0 else ''}50d\"\n        fxo = fxo = FXOp(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=delta,\n            notional=notn,\n        )\n        fxvs = FXDeltaVolSmile(\n            {0.25: 10.15, 0.5: 7.8, 0.75: 8.9},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"forward\",\n        )\n        result = fxo.analytic_greeks(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            vol=fxvs,\n            fx=fxfo,\n        )\n        assert abs(result[\"delta\"] - expected[0]) < 1e-6\n        assert abs(result[\"delta_eur\"] - expected[1]) < 1e-6\n\n    def test_metric_and_period_metric_compatible(self) -> None:\n        # ensure that vol and pips_or_% can be interchanged\n\n        eur = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 30): 1.0}, calendar=\"tgt\")\n        usd = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 30): 1.0}, calendar=\"nyc\")\n        eurusd = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 30): 1.0})\n        fxr = FXRates({\"eurusd\": 1.0727}, settlement=dt(2024, 6, 24))\n        fxf = FXForwards(fx_rates=fxr, fx_curves={\"eureur\": eur, \"eurusd\": eurusd, \"usdusd\": usd})\n        pre_solver = Solver(\n            curves=[eur, usd, eurusd],\n            instruments=[\n                IRS(dt(2024, 6, 24), \"3m\", spec=\"eur_irs\", curves=eur),\n                IRS(dt(2024, 6, 24), \"3m\", spec=\"usd_irs\", curves=usd),\n                FXForward(\n                    pair=\"eurusd\",\n                    settlement=dt(2024, 9, 24),\n                    curves=[eurusd, usd],\n                ),\n            ],\n            s=[3.77, 5.51, 1.0775],\n            fx=fxf,\n        )\n\n        smile = FXDeltaVolSmile(\n            nodes={0.25: 5.0, 0.50: 5.0, 0.75: 5.0},\n            eval_date=dt(2024, 6, 20),\n            expiry=dt(2024, 9, 20),\n            delta_type=\"spot\",\n        )\n        fx_args = dict(\n            expiry=dt(2024, 9, 20),\n            pair=\"eurusd\",\n            delta_type=\"spot\",\n            metric=\"vol\",  # note how the option is pre-configured with a metric as \"vol\"\n            curves=[eurusd, usd],\n            vol=smile,\n            premium_ccy=\"eur\",\n            delivery_lag=2,\n            payment_lag=2,\n        )\n        solver = Solver(\n            pre_solvers=[pre_solver],\n            curves=[smile],\n            instruments=[\n                FXPut(strike=1.0504, **fx_args),\n                FXCall(strike=1.0728, **fx_args),\n                FXCall(strike=1.0998, **fx_args),\n            ],\n            s=[7.621, 6.60, 6.12],\n            fx=fxf,\n        )\n\n        result = FXCall(strike=1.0728, **fx_args).rate(metric=\"pips_or_%\", solver=solver)\n        expected = 1.543289  # % of EUR notional\n        assert abs(result - expected) < 1e-6\n\n        result = FXCall(strike=1.0728, **fx_args).rate(solver=solver)  # should default to \"vol\"\n        expected = 6.60  # vol points\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"evald\", \"eom\", \"expected\"),\n        [\n            (\n                dt(2024, 4, 26),\n                True,\n                dt(2024, 5, 29),\n            ),  # 2bd before 31st May (rolled from End of April)\n            (\n                dt(2024, 4, 26),\n                False,\n                dt(2024, 5, 28),\n            ),  # 2bd before 30th May (rolled from 30th April)\n        ],\n    )\n    def test_expiry_delivery_tenor_eom(self, evald, eom, expected) -> None:\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=\"1m\",\n            eval_date=evald,\n            eom=eom,\n            calendar=\"tgt|fed\",\n            modifier=\"mf\",\n            strike=1.0,\n        )\n        assert fxo.kwargs.leg1[\"expiry\"] == expected\n\n    def test_single_vol_not_no_input(self, fxfo):\n        fxo = FXCall(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"eur\"), fxfo.curve(\"usd\", \"eur\")],\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=1.1,\n            notional=1e6,\n        )\n        with pytest.raises(ValueError, match=\"`vol` must be supplied. Got\"):\n            fxo.rate(metric=\"vol\", fx=fxfo)\n\n    def test_hyper_parameter_setting_and_solver_interaction(self):\n        # Define the interest rate curves for EUR, USD and X-Ccy basis\n        usdusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"nyc\", id=\"usdusd\")\n        eureur = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"tgt\", id=\"eureur\")\n        eurusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, id=\"eurusd\")\n\n        # Create an FX Forward market with spot FX rate data\n        fxr = FXRates({\"eurusd\": 1.0760}, settlement=dt(2024, 5, 9))\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\"eureur\": eureur, \"usdusd\": usdusd, \"eurusd\": eurusd},\n        )\n\n        pre_solver = Solver(\n            curves=[eureur, eurusd, usdusd],\n            instruments=[\n                IRS(dt(2024, 5, 9), \"3W\", spec=\"eur_irs\", curves=\"eureur\"),\n                IRS(dt(2024, 5, 9), \"3W\", spec=\"usd_irs\", curves=\"usdusd\"),\n                FXSwap(dt(2024, 5, 9), \"3W\", pair=\"eurusd\", curves=[\"eurusd\", \"usdusd\"]),\n            ],\n            s=[3.90, 5.32, 8.85],\n            fx=fxf,\n            id=\"rates_sv\",\n        )\n        dv_smile = FXDeltaVolSmile(\n            nodes={\n                0.10: 10.0,\n                0.25: 10.0,\n                0.50: 10.0,\n                0.75: 10.0,\n                0.90: 10.0,\n            },\n            eval_date=dt(2024, 5, 7),\n            expiry=dt(2024, 5, 28),\n            delta_type=\"spot\",\n            id=\"eurusd_3w_smile\",\n        )\n        option_args = dict(\n            pair=\"eurusd\",\n            expiry=dt(2024, 5, 28),\n            calendar=\"tgt|fed\",\n            delta_type=\"spot\",\n            curves=[\"eurusd\", \"usdusd\"],\n            vol=\"eurusd_3w_smile\",\n        )\n\n        dv_solver = Solver(\n            pre_solvers=[pre_solver],\n            curves=[dv_smile],\n            instruments=[\n                FXStraddle(strike=\"atm_delta\", **option_args),\n                FXRiskReversal(strike=(\"-25d\", \"25d\"), **option_args),\n                FXRiskReversal(strike=(\"-10d\", \"10d\"), **option_args),\n                FXBrokerFly(strike=((\"-25d\", \"25d\"), \"atm_delta\"), **option_args),\n                FXBrokerFly(strike=((\"-10d\", \"10d\"), \"atm_delta\"), **option_args),\n            ],\n            s=[5.493, -0.157, -0.289, 0.071, 0.238],\n            fx=fxf,\n            id=\"dv_solver\",\n        )\n        fc = FXCall(\n            expiry=dt(2024, 5, 28),\n            pair=\"eurusd\",\n            strike=1.07,\n            notional=100e6,\n            curves=[\"eurusd\", \"usdusd\"],\n            vol=\"eurusd_3w_smile\",\n            premium=98.216647 * 1e8 / 1e4,\n            premium_ccy=\"usd\",\n            delta_type=\"spot\",\n        )\n        assert abs(fc.npv(solver=dv_solver, base=\"usd\")) < 1e-2\n        delta = fc.delta(solver=dv_solver, base=\"usd\").loc[(\"fx\", \"fx\", \"eurusd\"), (\"all\", \"usd\")]\n        gamma = fc.gamma(solver=dv_solver, base=\"usd\").loc[\n            (\"all\", \"usd\", \"fx\", \"fx\", \"eurusd\"), (\"fx\", \"fx\", \"eurusd\")\n        ]\n\n        fxr.update({\"eurusd\": 1.0761})\n        pre_solver.iterate()\n        dv_solver.iterate()\n\n        result = fc.npv(solver=dv_solver, base=\"usd\")\n        expected = delta + 0.5 * gamma\n        assert abs(result - expected) < 5e-2\n\n        fxr.update({\"eurusd\": 1.0759})\n        pre_solver.iterate()\n        dv_solver.iterate()\n\n        result = fc.npv(solver=dv_solver, base=\"usd\")\n        expected = -delta + 0.5 * gamma\n        assert abs(result - expected) < 5e-2\n\n    @pytest.mark.parametrize(\"k\", [1.07, \"25d\", \"atm_delta\"])\n    def test_pricing_with_interpolated_sabr_surface(self, k, fxfo):\n        surf = FXSabrSurface(\n            eval_date=dt(2023, 3, 16),\n            expiries=[dt(2023, 6, 16), dt(2023, 10, 17)],\n            node_values=[[0.05, 1.0, 0.03, 0.04], [0.055, 1.0, 0.04, 0.05]],\n            pair=\"eurusd\",\n            calendar=\"tgt|fed\",\n            ad=1,\n            id=\"v\",\n        )\n        fxc = FXCall(\n            expiry=dt(2023, 7, 21),\n            pair=\"eurusd\",\n            calendar=\"tgt|fed\",\n            delta_type=\"spot\",\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            vol=surf,\n            strike=k,\n        )\n        fxc.rate(fx=fxfo)\n        result = fxc._pricing\n        assert abs(result.vol - 5.25) < 1e-2\n        assert np.all(gradient(result.vol, vars=[\"v_0_0\", \"v_1_0\"]) > 49.2)\n        assert np.all(gradient(result.vol, vars=[\"v_0_0\", \"v_1_0\"]) < 50.6)\n\n    @pytest.mark.skip(reason=\"non-deliverability for FXOption instruments not yet implemented\")\n    @pytest.mark.parametrize(\"ndpair\", [FXIndex(\"usdbrl\", \"all\", 0), FXIndex(\"brlusd\", \"all\", 0)])\n    def test_non_deliverable_fx_option_npv_vol_from_delta(self, ndpair):\n        # see the equivalent test for an FXOptionPeriod with static vol\n        fxf = FXForwards(\n            fx_rates=FXRates({\"usdbrl\": 5.0}, settlement=dt(2000, 1, 1)),\n            fx_curves={\n                \"usdusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.98}),\n                \"brlusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.983}),\n                \"brlbrl\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.984}),\n            },\n        )\n        fxv = FXDeltaVolSmile(\n            nodes={0.4: 10.0, 0.6: 11.0},\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2000, 2, 28),\n            delta_type=\"forward\",\n        )\n        fxo = FXCall(\n            delivery_lag=dt(2000, 3, 1),\n            pair=\"USDBRL\",\n            strike=\"50d\",\n            delta_type=\"spot\",\n            expiry=dt(2000, 2, 28),\n        )\n        fxond = FXCall(\n            delivery_lag=dt(2000, 3, 1),\n            pair=\"USDBRL\",\n            nd_pair=ndpair,\n            delta_type=\"spot\",\n            strike=\"50d\",\n            expiry=dt(2000, 2, 28),\n        )\n\n        npv = fxo.local_npv(\n            fx=fxf,\n            vol=fxv,\n            curves=[fxf.curve(\"usd\", \"usd\"), fxf.curve(\"brl\", \"usd\")],\n        )\n        npv_nd = fxond.local_npv(\n            fx=fxf,\n            vol=fxv,\n            curves=[fxf.curve(\"usd\", \"usd\"), fxf.curve(\"usd\", \"usd\")],\n        )\n\n        # local NPV should be expressed in USD for ND type\n        result = npv / 5.0 - npv_nd\n        assert abs(result) < 1e-9\n\n\nclass TestRiskReversal:\n    @pytest.mark.parametrize(\n        (\"metric\", \"expected\"),\n        [\n            (\"pips_or_%\", -13.795465),\n            (\"vol\", -1.25),\n            (\"premium\", -27590.930533),\n        ],\n    )\n    def test_risk_reversal_rate_metrics(self, fxfo, metric, expected) -> None:\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=[\"-25d\", \"25d\"],\n            delta_type=\"spot\",\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=[10.15, 8.9], metric=metric)\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"prem\", \"prem_ccy\", \"local\", \"exp\"),\n        [\n            ((NoInput(0), NoInput(0)), NoInput(0), False, 0.0),\n            ((NoInput(0), NoInput(0)), \"eur\", False, 0.0),\n            ((-167500.0, 140500.0), \"usd\", False, -219.590678),\n            ((-167500 / 1.06751, 140500 / 1.06751), \"eur\", False, -219.590678),\n            (\n                (-167500 / 1.06751, 140500 / 1.06751),\n                \"eur\",\n                True,\n                {\"eur\": 25121.646, \"usd\": -26879.673},\n            ),\n        ],\n    )\n    def test_risk_reversal_npv(self, fxfo, prem, prem_ccy, local, exp) -> None:\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=[1.033, 1.101],\n            premium=prem,\n            premium_ccy=prem_ccy,\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.npv(curves=curves, fx=fxfo, vol=[10.15, 8.9], local=local)\n        expected = exp\n        if not local:\n            assert abs(result - expected) < 1e-6\n        else:\n            for k in expected:\n                assert abs(result[k] - expected[k]) < 1e-3\n\n    @pytest.mark.parametrize(\"prem_ccy\", [\"usd\", \"eur\"])\n    def test_risk_reversal_component_npv(self, fxfo, prem_ccy) -> None:\n        fxo = FXPut(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=1.033,\n            premium=NoInput(0),\n            premium_ccy=prem_ccy,\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.npv(curves=curves, fx=fxfo, vol=10.15, local=False)\n        expected = 0.0\n        assert abs(result - expected) < 1e-6\n\n    def test_risk_reversal_plot(self, fxfo) -> None:\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=[1.033, 1.101],\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.plot_payoff([1.03, 1.12], curves, fx=fxfo, vol=[10.15, 8.9])\n        x, y = result[2][0]._x, result[2][0]._y\n        assert x[0] == 1.03\n        assert x[1000] == 1.12\n        assert abs(y[0] + (1.033 - 1.03) * 20e6) < 1e-5\n        assert abs(y[1000] - (1.12 - 1.101) * 20e6) < 1e-5\n\n    def test_rr_strike_premium_validation(self) -> None:\n        with pytest.raises(TypeError, match=\"missing 1 required positional argument: 'strike'\"):\n            FXRiskReversal(\n                pair=\"eurusd\",\n                expiry=dt(2023, 6, 16),\n            )\n\n        with pytest.raises(ValueError, match=\"FXOption with string delta as `strike` cannot be in\"):\n            FXRiskReversal(\n                pair=\"eurusd\",\n                expiry=dt(2023, 6, 16),\n                strike=[\"25d\", \"35d\"],\n                premium=[NoInput(0), 1.0],\n            )\n\n    @pytest.mark.parametrize(\n        (\"notn\", \"expected_grks\", \"expected_ccy\"),\n        [\n            (1e6, [0.5, -1.329654, -0.035843], [500000, -14194.192533, -358.428628]),\n            (2e6, [0.5, -1.329654, -0.035843], [1000000, -28388.384, -716.8572]),\n            (-2e6, [0.5, -1.329654, -0.035843], [1000000, -28388.384, -716.8572]),\n        ],\n    )\n    def test_greeks_delta_direction(self, fxfo, notn, expected_grks, expected_ccy) -> None:\n        # test the delta and delta_eur are not impacted by a Buy or Sell. Delta is expressed\n        # relative to a Buy.\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=[\"-30d\", \"20d\"],\n            notional=notn,\n        )\n        fxvs = FXDeltaVolSmile(\n            {0.25: 10.15, 0.5: 7.8, 0.75: 8.9},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"forward\",\n        )\n        result = fxo.analytic_greeks(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            vol=fxvs,\n            fx=fxfo,\n        )\n        assert abs(result[\"delta\"] - expected_grks[0]) < 1e-6\n        assert abs(result[\"gamma\"] - expected_grks[1]) < 1e-6\n        assert abs(result[\"vega\"] - expected_grks[2]) < 1e-6\n\n        assert abs(result[\"delta_eur\"] - expected_ccy[0]) < 1e-2\n        assert abs(result[\"gamma_eur_1%\"] - expected_ccy[1]) < 1e-2\n        assert abs(result[\"vega_usd\"] - expected_ccy[2]) < 1e-2\n\n    def test_repr(self):\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=[1.033, 1.101],\n        )\n        expected = f\"<rl.FXRiskReversal at {hex(id(fxo))}>\"\n        assert fxo.__repr__() == expected\n\n    def test_cashflows(self, fxfo) -> None:\n        # test the delta and delta_eur are not impacted by a Buy or Sell. Delta is expressed\n        # relative to a Buy.\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=[\"-30d\", \"20d\"],\n        )\n        result = fxo.cashflows()\n        assert isinstance(result, DataFrame)\n\n    @pytest.mark.parametrize(\"ccy\", [\"usd\", \"eur\"])\n    def test_populate_curves_on_init(self, ccy):\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            curves=[\"A\", \"B\"],\n            premium_ccy=ccy,\n            strike=[\"-30d\", \"20d\"],\n        )\n        if ccy == \"usd\":\n            assert fxo.kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n        else:\n            assert fxo.kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n\n    def test_populate_all_vols_on_init(self):\n        # test also validates FXStraddle and FXStrangle\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            vol=[\"a\", \"b\"],\n            strike=[1.10, 1.12],\n        )\n        assert fxo.instruments[0].kwargs.meta[\"vol\"].fx_vol == \"a\"\n        assert fxo.instruments[1].kwargs.meta[\"vol\"].fx_vol == \"b\"\n\n    def test_populate_single_vols_on_init(self):\n        # test also validates FXStraddle and FXStrangle\n        fxo = FXRiskReversal(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            vol=\"myvol\",\n            strike=[1.10, 1.12],\n        )\n        assert fxo.instruments[0].kwargs.meta[\"vol\"].fx_vol == \"myvol\"\n        assert fxo.instruments[1].kwargs.meta[\"vol\"].fx_vol == \"myvol\"\n\n\nclass TestFXStraddle:\n    @pytest.mark.parametrize(\n        (\"dlty\", \"strike\", \"ccy\", \"exp\"),\n        [\n            # (\"forward\", [\"50d\", \"-50d\"], \"usd\", [1.068856203, 1.068856203]),\n            # (\"spot\", [\"50d\", \"-50d\"], \"usd\", [1.06841799, 1.069294591]),\n            (\"spot\", \"atm_forward\", \"usd\", [1.06750999, 1.06750999]),\n            (\"spot\", \"atm_spot\", \"usd\", [1.061500, 1.061500]),\n            (\"forward\", \"atm_delta\", \"usd\", [1.068856203, 1.068856203]),\n            (\"spot\", \"atm_delta\", \"usd\", [1.068856203, 1.068856203]),\n            (\"spot\", \"atm_forward\", \"eur\", [1.06750999, 1.06750999]),\n            (\"spot\", \"atm_spot\", \"eur\", [1.061500, 1.061500]),\n            (\"forward\", \"atm_delta\", \"eur\", [1.06616549, 1.06616549]),\n            (\"spot\", \"atm_delta\", \"eur\", [1.06616549, 1.06616549]),\n            # (\"forward\", [\"50d\", \"-50d\"], \"eur\", [1.0660752074, 1.06624508149]),  # pa strikes\n            # (\"spot\", [\"50d\", \"-50d\"], \"eur\", [1.0656079102, 1.066656812]),  # pa strikes\n        ],\n    )\n    @pytest.mark.parametrize(\"smile\", [True, False])\n    def test_straddle_strikes(self, fxfo, dlty, strike, ccy, exp, smile) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={0.5: 10.0},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"forward\",\n        )\n        vol_ = fxvs if smile else 10.0\n        fxo = FXStraddle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=strike,\n            premium_ccy=ccy,\n            delta_type=dlty,\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        fxo.npv(curves=curves, fx=fxfo, vol=vol_)\n        call_k = fxo.instruments[0]._option.fx_option_params.strike\n        put_k = fxo.instruments[1]._option.fx_option_params.strike\n        assert abs(call_k - exp[0]) < 1e-7\n        assert abs(put_k - exp[1]) < 1e-7\n\n    @pytest.mark.parametrize(\n        (\"metric\", \"expected\"),\n        [\n            (\"pips_or_%\", 337.998151),\n            (\"vol\", 7.9),\n            (\"premium\", 675996.301147),\n        ],\n    )\n    def test_straddle_rate_metrics(self, fxfo, metric, expected) -> None:\n        fxo = FXStraddle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=\"atm_delta\",\n            delta_type=\"spot\",\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=7.9, metric=metric)\n        assert abs(result - expected) < 1e-6\n\n    def test_strad_strike_premium_validation(self) -> None:\n        with pytest.raises(TypeError, match=\"missing 1 required positional argument: 'strike'\"):\n            FXStraddle(\n                pair=\"eurusd\",\n                expiry=dt(2023, 6, 16),\n            )\n\n        with pytest.raises(ValueError, match=\"FXOption with string delta as `strike` cannot be \"):\n            FXStraddle(\n                pair=\"eurusd\",\n                expiry=dt(2023, 6, 16),\n                strike=\"25d\",\n                premium=[NoInput(0), 1.0],\n            )\n\n    @pytest.mark.parametrize(\n        (\"notn\", \"expected_grks\", \"expected_ccy\"),\n        [\n            (1e6, [0.0, 19.086488, 0.422238], [0, 203750.1688, 4222.379]),\n            (2e6, [0.0, 19.086488, 0.422238], [0, 407500.336, 8444.758]),\n            (-2e6, [0.0, 19.086488, 0.422238], [0, 407500.336, 8444.758]),\n        ],\n    )\n    def test_greeks_delta_direction(self, fxfo, notn, expected_grks, expected_ccy) -> None:\n        # test the delta and delta_eur are not impacted by a Buy or Sell. Delta is expressed\n        # relative to a Buy.\n        fxo = FXStraddle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=\"atm_delta\",\n            notional=notn,\n        )\n        fxvs = FXDeltaVolSmile(\n            {0.25: 10.15, 0.5: 7.8, 0.75: 8.9},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"forward\",\n        )\n        result = fxo.analytic_greeks(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            vol=fxvs,\n            fx=fxfo,\n        )\n        assert abs(result[\"delta\"] - expected_grks[0]) < 1e-6\n        assert abs(result[\"gamma\"] - expected_grks[1]) < 1e-6\n        assert abs(result[\"vega\"] - expected_grks[2]) < 1e-6\n\n        assert abs(result[\"delta_eur\"] - expected_ccy[0]) < 1e-2\n        assert abs(result[\"gamma_eur_1%\"] - expected_ccy[1]) < 1e-2\n        assert abs(result[\"vega_usd\"] - expected_ccy[2]) < 1e-2\n\n    def test_repr(self):\n        fxo = FXStraddle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=1.0,\n        )\n        expected = f\"<rl.FXStraddle at {hex(id(fxo))}>\"\n        assert expected == fxo.__repr__()\n\n    @pytest.mark.parametrize(\"ccy\", [\"usd\", \"eur\"])\n    def test_populate_curves_on_init(self, ccy):\n        fxo = FXStraddle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            curves=[\"A\", \"B\"],\n            premium_ccy=ccy,\n            strike=1.10,\n        )\n        if ccy == \"usd\":\n            assert fxo.kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n        else:\n            assert fxo.kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n\n\nclass TestFXStrangle:\n    @pytest.mark.parametrize(\n        (\"strike\", \"ccy\"),\n        [\n            ([1.02, 1.10], \"usd\"),\n            ([\"-20d\", \"20d\"], \"usd\"),\n            ([1.02, 1.10], \"eur\"),\n            ([\"-20d\", \"20d\"], \"eur\"),\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            FXDeltaVolSmile(\n                nodes={\n                    0.25: 10.15,\n                    0.50: 7.9,\n                    0.75: 8.9,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"forward\",\n            ),\n            FXDeltaVolSmile(\n                nodes={\n                    0.25: 10.15,\n                    0.50: 7.9,\n                    0.75: 8.9,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"spot_pa\",\n            ),\n            10.0,\n            FXSabrSmile(\n                nodes={\n                    \"alpha\": 0.10,\n                    \"beta\": 1.0,\n                    \"rho\": 0.00,\n                    \"nu\": 0.50,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n            ),\n        ],\n    )\n    def test_strangle_rate_forward(self, fxfo, strike, ccy, vol) -> None:\n        # test pricing a straddle with vol 10.0 returns 10.0\n        fxo = FXStrangle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=strike,\n            premium_ccy=ccy,\n            delta_type=\"forward\",\n        )\n\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n\n        premium = fxo.rate(curves=curves, fx=fxfo, vol=result, metric=\"pips_or_%\")\n        metric = \"pips\" if ccy == \"usd\" else \"percent\"\n        premium_vol = fxo.instruments[0]._option.rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol,\n            metric=metric,\n        )\n        premium_vol += fxo.instruments[1]._option.rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol,\n            metric=metric,\n        )\n        assert abs(premium - premium_vol) < 5e-2\n\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            FXDeltaVolSmile(\n                nodes={\n                    0.25: 10.15,\n                    0.50: 7.9,\n                    0.75: 8.9,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"spot\",\n                ad=1,\n            ),\n            FXSabrSmile(\n                nodes={\n                    \"alpha\": 0.079,\n                    \"beta\": 1.0,\n                    \"rho\": 0.00,\n                    \"nu\": 0.50,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n            ),\n        ],\n    )\n    def test_strangle_rate_strike_str(self, fxfo, vol) -> None:\n        # test pricing a strangle with delta as string that is not a delta percent should fail?\n        fxo = FXStrangle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=[\"atm_spot\", \"atm_forward\"],\n            premium_ccy=\"eur\",\n            delta_type=\"forward\",\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n\n        premium = fxo.rate(curves=curves, fx=fxfo, vol=result, metric=\"pips_or_%\")\n        metric = \"percent\"\n        premium_vol = fxo.instruments[0]._option.rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol,\n            metric=metric,\n        )\n        premium_vol += fxo.instruments[1]._option.rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol,\n            metric=metric,\n        )\n        assert abs(premium - premium_vol) < 5e-2\n\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            FXDeltaVolSmile(\n                nodes={\n                    0.25: 10.15,\n                    0.50: 7.9,\n                    0.75: 8.9,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"spot\",\n                ad=1,\n                id=\"vol\",\n            ),\n            FXSabrSmile(\n                nodes={\n                    \"alpha\": 0.079,\n                    \"beta\": 1.0,\n                    \"rho\": 0.00,\n                    \"nu\": 0.50,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                ad=1,\n                id=\"vol\",\n            ),\n        ],\n    )\n    def test_strangle_rate_ad(self, fxfo, vol) -> None:\n        # test pricing a strangle with delta as string that is not a delta percent should fail?\n        fxo = FXStrangle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=[\"atm_spot\", \"atm_forward\"],\n            premium_ccy=\"eur\",\n            delta_type=\"forward\",\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n\n        # test fwd diff\n        v = vol._get_node_vector()\n        m_ = {\n            0: [0.001, 0.0, 0.0],\n            1: [0.0, 0.001, 0.0],\n            2: [0.0, 0.0, 0.001],\n        }\n        for i in range(3):\n            vol._set_node_vector(v + np.array(m_[i]), ad=1)\n            result2 = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n            fwd_diff = (result2 - result) * 1000.0\n            assert abs(fwd_diff - gradient(result, [f\"vol{i}\"])[0]) < 2e-4\n\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            FXDeltaVolSmile(\n                nodes={\n                    0.25: 10.15,\n                    0.50: 7.9,\n                    0.75: 8.9,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"spot\",\n                ad=2,\n                id=\"vol\",\n            ),\n            FXSabrSmile(\n                nodes={\n                    \"alpha\": 0.079,\n                    \"beta\": 1.0,\n                    \"rho\": 0.00,\n                    \"nu\": 0.50,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                ad=2,\n                id=\"vol\",\n            ),\n        ],\n    )\n    def test_strangle_rate_ad2(self, fxfo, vol) -> None:\n        # test pricing a strangle with delta as string that is not a delta percent should fail?\n        fxo = FXStrangle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=[\"atm_spot\", \"atm_forward\"],\n            premium_ccy=\"eur\",\n            delta_type=\"forward\",\n        )\n        fxfo._set_ad_order(2)\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n\n        # test fwd diff\n        m_ = {\n            0: [0.001, 0.0, 0.0],\n            1: [0.0, 0.001, 0.0],\n            2: [0.0, 0.0, 0.001],\n        }\n        n_ = {\n            0: [-0.001, 0.0, 0.0],\n            1: [0.0, -0.001, 0.0],\n            2: [0.0, 0.0, -0.001],\n        }\n        v = vol._get_node_vector()\n        for i in range(3):\n            vol._set_node_vector(v + np.array(m_[i]), ad=2)\n            result_plus = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n            vol._set_node_vector(v + np.array(n_[i]), ad=2)\n            result_min = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n\n            fwd_diff = (result_plus + result_min - 2 * result) * 1000000.0\n            assert abs(fwd_diff - gradient(result, [f\"vol{i}\"], order=2)[0][0]) < 1e-4\n\n    def test_strangle_rate_2vols(self, fxfo) -> None:\n        # test pricing a straddle with vol [8.0, 10.0] returns a valid value close to 9.0\n        fxo = FXStrangle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=20e6,\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=[\"-25d\", \"25d\"],\n            premium_ccy=\"usd\",\n            delta_type=\"forward\",\n        )\n        vol = [8.0, 10.0]\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n\n        premium = fxo.rate(curves=curves, fx=fxfo, vol=result, metric=\"pips_or_%\")\n        premium_vol = fxo.instruments[0]._option.rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol[0],\n        )\n        premium_vol += fxo.instruments[1]._option.rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol[1],\n        )\n\n        assert abs(premium - premium_vol) < 5e-2\n\n    @pytest.mark.parametrize(\n        (\"notn\", \"expected_grks\", \"expected_ccy\"),\n        [\n            (1e6, [-0.026421, 10.217368, 0.294605], [-26421.408, 109071.429, 2946.046]),\n            (2e6, [-0.026421, 10.217368, 0.294605], [-52842.816, 218142.858, 5892.092]),\n            (-2e6, [-0.026421, 10.217368, 0.294605], [-52842.816, 218142.858, 5892.092]),\n        ],\n    )\n    @pytest.mark.parametrize(\"strikes\", [(\"-20d\", \"20d\"), (1.0238746345527665, 1.1159199351325004)])\n    def test_greeks_delta_direction(self, fxfo, notn, expected_grks, expected_ccy, strikes) -> None:\n        # test the delta and delta_eur are not impacted by a Buy or Sell. Delta is expressed\n        # relative to a Buy.\n        fxo = FXStrangle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=strikes,\n            notional=notn,\n        )\n        fxvs = FXDeltaVolSmile(\n            {0.25: 10.15, 0.5: 7.8, 0.75: 8.9},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"forward\",\n        )\n        result = fxo.analytic_greeks(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            vol=fxvs,\n            fx=fxfo,\n        )\n        assert abs(result[\"delta\"] - expected_grks[0]) < 1e-6\n        assert abs(result[\"gamma\"] - expected_grks[1]) < 1e-6\n        assert abs(result[\"vega\"] - expected_grks[2]) < 1e-6\n\n        assert abs(result[\"delta_eur\"] - expected_ccy[0]) < 1e-1\n        assert abs(result[\"gamma_eur_1%\"] - expected_ccy[1]) < 1e-1\n        assert abs(result[\"vega_usd\"] - expected_ccy[2]) < 1e-1\n\n    def test_strang_strike_premium_validation(self) -> None:\n        # with pytest.raises(ValueError, match=\"`strike` for FXStrangle must be set\"):\n        #     FXStrangle(\n        #         pair=\"eurusd\",\n        #         expiry=dt(2023, 6, 16),\n        #         strike=[\"25d\", NoInput(0)],\n        #     )\n\n        with pytest.raises(\n            ValueError, match=\"FXOption with string delta as `strike` cannot be initialised\"\n        ):\n            FXStrangle(\n                pair=\"eurusd\",\n                expiry=dt(2023, 6, 16),\n                strike=[\"25d\", \"35d\"],\n                premium=[NoInput(0), 1.0],\n            )\n\n    def test_repr(self):\n        fxo = FXStrangle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=[1.0, 1.1],\n        )\n        expected = f\"<rl.FXStrangle at {hex(id(fxo))}>\"\n        assert expected == fxo.__repr__()\n\n    @pytest.mark.parametrize(\"ccy\", [\"usd\", \"eur\"])\n    def test_populate_curves_on_init(self, ccy):\n        fxo = FXStrangle(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            curves=[\"A\", \"B\"],\n            premium_ccy=ccy,\n            strike=[1.10, 1.12],\n        )\n        if ccy == \"usd\":\n            assert fxo.kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n        else:\n            assert fxo.kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n\n\nclass TestFXBrokerFly:\n    @pytest.mark.parametrize(\n        (\"strike\", \"ccy\"),\n        [\n            ([[1.024, 1.116], 1.0683], \"usd\"),\n            ([[\"-20d\", \"20d\"], \"atm_delta\"], \"usd\"),\n            ([[1.024, 1.116], 1.0683], \"eur\"),\n            ([[\"-20d\", \"20d\"], \"atm_delta\"], \"eur\"),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"vol\", \"expected\"),\n        [\n            (\n                FXDeltaVolSmile(\n                    nodes={\n                        0.25: 10.15,\n                        0.50: 7.9,\n                        0.75: 8.9,\n                    },\n                    eval_date=dt(2023, 3, 16),\n                    expiry=dt(2023, 6, 16),\n                    delta_type=\"forward\",\n                ),\n                2.225,\n            ),\n            (\n                FXDeltaVolSmile(\n                    nodes={\n                        0.25: 10.15,\n                        0.50: 7.9,\n                        0.75: 8.9,\n                    },\n                    eval_date=dt(2023, 3, 16),\n                    expiry=dt(2023, 6, 16),\n                    delta_type=\"spot_pa\",\n                ),\n                2.39,\n            ),\n            (9.5, 0.0),\n            (\n                FXSabrSmile(\n                    nodes={\n                        \"alpha\": 0.071,\n                        \"beta\": 1.0,\n                        \"rho\": 0.00,\n                        \"nu\": 2.5,\n                    },\n                    eval_date=dt(2023, 3, 16),\n                    expiry=dt(2023, 6, 16),\n                ),\n                2.065,\n            ),\n        ],\n    )\n    def test_fxbf_rate(self, fxfo, strike, ccy, vol, expected) -> None:\n        # test pricing a straddle with vol 10.0 returns 10.0\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=[20e6, NoInput(0)],\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=strike,\n            premium_ccy=ccy,\n            delta_type=\"forward\",\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n\n        assert abs(result - expected) < 3e-2\n\n    @pytest.mark.parametrize(\n        (\"strike\", \"ccy\"),\n        [\n            ([[1.024, 1.116], 1.0683], \"usd\"),\n            ([[\"-20d\", \"20d\"], \"atm_delta\"], \"usd\"),\n            ([[1.0228, 1.1147], 1.0683], \"eur\"),\n            ([[\"-20d\", \"20d\"], \"atm_delta\"], \"eur\"),\n        ],\n    )\n    @pytest.mark.parametrize(\"smile\", [True])\n    def test_fxbf_rate_pips(self, fxfo, strike, ccy, smile) -> None:\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=[20e6, NoInput(0)],\n            delivery_lag=2,\n            payment_lag=2,\n            calendar=\"tgt\",\n            strike=strike,\n            premium_ccy=ccy,\n            delta_type=\"forward\",\n            metric=\"pips_or_%\",\n        )\n        fxvs = FXDeltaVolSmile(\n            nodes={\n                0.25: 10.15,\n                0.50: 7.8,\n                0.75: 8.9,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"spot\",\n        )\n        vol = fxvs if smile else 9.5\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n        expected = (-111.2, 0.1) if ccy == \"usd\" else (-1.041, 0.02)\n        assert abs(result - expected[0]) < expected[1]\n\n    @pytest.mark.parametrize(\n        (\"strike\", \"ccy\"),\n        [\n            ([[1.024, 1.116], 1.0683], \"usd\"),\n            ([[\"-20d\", \"20d\"], \"atm_delta\"], \"usd\"),\n            ([[1.024, 1.116], 1.06668], \"eur\"),\n            ([[\"-20d\", \"20d\"], \"atm_delta\"], \"eur\"),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"vol\", \"expected\"),\n        [\n            (\n                FXDeltaVolSmile(\n                    nodes={\n                        0.25: 10.15,\n                        0.50: 7.8,\n                        0.75: 8.9,\n                    },\n                    eval_date=dt(2023, 3, 16),\n                    expiry=dt(2023, 6, 16),\n                    delta_type=\"forward\",\n                ),\n                (-221743, -210350),\n            ),\n            (\n                FXSabrSmile(\n                    nodes={\n                        \"alpha\": 0.071,\n                        \"beta\": 1.0,\n                        \"rho\": 0.00,\n                        \"nu\": 2.5,\n                    },\n                    eval_date=dt(2023, 3, 16),\n                    expiry=dt(2023, 6, 16),\n                ),\n                (-240740, -225500),\n            ),\n        ],\n    )\n    def test_fxbf_rate_premium(self, fxfo, strike, ccy, vol, expected) -> None:\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=[20e6, NoInput(0)],\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            strike=strike,\n            premium_ccy=ccy,\n            delta_type=\"forward\",\n            metric=\"premium\",\n        )\n        curves = [fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")]\n        result = fxo.rate(curves=curves, fx=fxfo, vol=vol)\n\n        tolerance = 300 if ccy == \"usd\" else 800\n        expected = expected[0] if ccy == \"usd\" else expected[1]\n        assert abs(result - expected) < tolerance\n\n    def test_bf_rate_vols_list(self, fxfo) -> None:\n        fxbf = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            notional=[20e6, -13.5e6],\n            strike=((\"-20d\", \"20d\"), \"atm_delta\"),\n            payment_lag=2,\n            delivery_lag=2,\n            calendar=\"tgt\",\n            premium_ccy=\"usd\",\n            delta_type=\"spot\",\n        )\n        result = fxbf.rate(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            fx=fxfo,\n            vol=[[10.15, 8.9], 1.0],\n        )\n        expected = 8.539499\n        assert abs(result - expected) < 1e-6\n\n        result = fxbf.rate(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            fx=fxfo,\n            vol=[[10.15, 8.9], 7.8],\n            metric=\"pips_or_%\",\n        )\n        expected = -110.098920\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"notn\", \"expected_grks\", \"expected_ccy\"),\n        [\n            ([1e6, NoInput(0)], [-0.026421, -3.099693, 0.000000], [-26421.408, -33089.534, 0.000]),\n            ([2e6, NoInput(0)], [-0.026421, -3.099693, 0.000000], [-52842.816, -66179.068, 0.000]),\n            ([-2e6, NoInput(0)], [-0.026421, -3.099693, 0.000000], [-52842.816, -66179.068, 0.000]),\n            ([1e6, -600e3], [-0.026421, -1.234524, 0.041262], [-26421.408, -13178.672, 412.619]),\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"strikes\",\n        [\n            ((\"-20d\", \"20d\"), \"atm_delta\"),\n            ((1.0238746345527665, 1.1159199351325004), 1.0683288279019205),\n        ],\n    )\n    def test_greeks_delta_direction(self, fxfo, notn, expected_grks, expected_ccy, strikes) -> None:\n        # test the delta and delta_eur are not impacted by a Buy or Sell. Delta is expressed\n        # relative to a Buy.\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=strikes,\n            notional=notn,\n        )\n        fxvs = FXDeltaVolSmile(\n            {0.25: 10.15, 0.5: 7.8, 0.75: 8.9},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"forward\",\n        )\n        result = fxo.analytic_greeks(\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            vol=fxvs,\n            fx=fxfo,\n        )\n        assert abs(result[\"delta\"] - expected_grks[0]) < 1e-6\n        assert abs(result[\"gamma\"] - expected_grks[1]) < 1e-4\n        assert abs(result[\"vega\"] - expected_grks[2]) < 1e-5\n\n        assert abs(result[\"delta_eur\"] - expected_ccy[0]) < 1e-1\n        assert abs(result[\"gamma_eur_1%\"] - expected_ccy[1]) < 1.5\n        assert abs(result[\"vega_usd\"] - expected_ccy[2]) < 1e-1\n\n    def test_single_vol_definition(self, fxfo) -> None:\n        # test the metric of the rate can be input as \"single_vol\" and a result returned.\n        fxvs = FXDeltaVolSmile(\n            nodes={\n                0.25: 10.15,\n                0.50: 7.9,\n                0.75: 8.9,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"forward\",\n        )\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            curves=[fxfo.curve(\"eur\", \"usd\"), fxfo.curve(\"usd\", \"usd\")],\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=[[\"-20d\", \"20d\"], \"atm_delta\"],\n            vol=fxvs,\n        )\n        result = fxo.rate(metric=\"single_vol\", fx=fxfo)\n        expected = 10.147423 - 7.90\n        assert (result - expected) < 1e-6\n\n    def test_repr(self):\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery_lag=dt(2023, 6, 20),\n            payment_lag=dt(2023, 6, 20),\n            delta_type=\"forward\",\n            premium_ccy=\"usd\",\n            strike=[[\"-20d\", \"20d\"], \"atm_delta\"],\n        )\n        expected = f\"<rl.FXBrokerFly at {hex(id(fxo))}>\"\n        assert expected == fxo.__repr__()\n\n    @pytest.mark.parametrize(\"ccy\", [\"usd\", \"eur\"])\n    def test_populate_curves_on_init(self, ccy):\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            curves=[\"A\", \"B\"],\n            premium_ccy=ccy,\n            strike=[[1.10, 1.12], 1.11],\n        )\n        if ccy == \"usd\":\n            assert fxo.kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n            assert fxo.instruments[0].kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n            assert fxo.instruments[1].kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n            assert fxo.instruments[0].instruments[0].kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n            assert fxo.instruments[0].instruments[1].kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n            assert fxo.instruments[1].instruments[0].kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n            assert fxo.instruments[1].instruments[1].kwargs.meta[\"curves\"].leg2_disc_curve == \"B\"\n        else:\n            assert fxo.kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n            assert fxo.instruments[0].kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n            assert fxo.instruments[1].kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n            assert fxo.instruments[0].instruments[0].kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n            assert fxo.instruments[0].instruments[1].kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n            assert fxo.instruments[1].instruments[0].kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n            assert fxo.instruments[1].instruments[1].kwargs.meta[\"curves\"].leg2_disc_curve == \"A\"\n\n    def test_populate_all_vols_on_init(self):\n        # test also validates FXStraddle and FXStrangle\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            vol=[[\"a\", \"b\"], [\"c\", \"d\"]],\n            strike=[[1.10, 1.12], 1.11],\n        )\n        assert fxo.instruments[0].instruments[0].kwargs.meta[\"vol\"] == _Vol(fx_vol=\"a\")\n        assert fxo.instruments[0].instruments[1].kwargs.meta[\"vol\"] == _Vol(fx_vol=\"b\")\n        assert fxo.instruments[1].instruments[0].kwargs.meta[\"vol\"] == _Vol(fx_vol=\"c\")\n        assert fxo.instruments[1].instruments[1].kwargs.meta[\"vol\"] == _Vol(fx_vol=\"d\")\n\n        assert fxo.instruments[0].kwargs.meta[\"vol\"] == (_Vol(fx_vol=\"a\"), _Vol(fx_vol=\"b\"))\n        assert fxo.instruments[1].kwargs.meta[\"vol\"] == (_Vol(fx_vol=\"c\"), _Vol(fx_vol=\"d\"))\n\n        assert fxo.kwargs.meta[\"vol\"] == (\n            (_Vol(fx_vol=\"a\"), _Vol(fx_vol=\"b\")),\n            (_Vol(fx_vol=\"c\"), _Vol(fx_vol=\"d\")),\n        )\n\n    def test_populate_single_vol_on_init(self):\n        # test also validates FXStraddle and FXStrangle\n        fxo = FXBrokerFly(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            vol=\"myvol\",\n            strike=[[1.10, 1.12], 1.11],\n        )\n        _ = _Vol(fx_vol=\"myvol\")\n        assert fxo.kwargs.meta[\"vol\"] == ((_, _), (_, _))\n        assert fxo.instruments[0].kwargs.meta[\"vol\"] == (_, _)\n        assert fxo.instruments[1].kwargs.meta[\"vol\"] == (_, _)\n        assert fxo.instruments[0].instruments[0].kwargs.meta[\"vol\"] == _\n        assert fxo.instruments[0].instruments[1].kwargs.meta[\"vol\"] == _\n        assert fxo.instruments[1].instruments[0].kwargs.meta[\"vol\"] == _\n        assert fxo.instruments[1].instruments[1].kwargs.meta[\"vol\"] == _\n\n    @pytest.mark.parametrize(\n        \"inst\",\n        [\n            FXCall(\n                spec=\"eurusd_call\",\n                expiry=dt(2023, 6, 16),\n                strike=1.10,\n                vol=\"smile\",\n                curves=[\"eurusd\", \"usdusd\"],\n            ),\n            FXStraddle(\n                spec=\"eurusd_call\",\n                expiry=dt(2023, 6, 16),\n                strike=1.10,\n                vol=\"smile\",\n                curves=[\"eurusd\", \"usdusd\"],\n            ),\n            FXStrangle(\n                spec=\"eurusd_call\",\n                expiry=dt(2023, 6, 16),\n                strike=[1.10, 1.11],\n                vol=\"smile\",\n                curves=[\"eurusd\", \"usdusd\"],\n            ),\n            FXRiskReversal(\n                spec=\"eurusd_call\",\n                expiry=dt(2023, 6, 16),\n                strike=[1.10, 1.11],\n                vol=\"smile\",\n                curves=[\"eurusd\", \"usdusd\"],\n            ),\n            FXBrokerFly(\n                spec=\"eurusd_call\",\n                expiry=dt(2023, 6, 16),\n                strike=[[1.10, 1.13], 1.11],\n                vol=\"smile\",\n                curves=[\"eurusd\", \"usdusd\"],\n            ),\n        ],\n    )\n    def test_str_vol_price_from_solver(self, inst, fxfo):\n        smile = FXDeltaVolSmile(\n            nodes={0.5: 10.0},\n            expiry=dt(2023, 6, 16),\n            eval_date=dt(2023, 3, 16),\n            delta_type=\"forward\",\n            id=\"smile\",\n        )\n        solver = Solver(\n            curves=[\n                smile,\n                fxfo.curve(\"eur\", \"eur\"),\n                fxfo.curve(\"eur\", \"usd\"),\n                fxfo.curve(\"usd\", \"usd\"),\n            ],\n            instruments=[\n                FXVolValue(index_value=0.5, vol=\"smile\"),\n                IRS(dt(2023, 3, 20), \"1b\", spec=\"eur_irs\", curves=\"eureur\"),\n                IRS(dt(2023, 3, 20), \"1b\", spec=\"eur_irs\", curves=\"eurusd\"),\n                IRS(dt(2023, 3, 20), \"1b\", spec=\"usd_irs\", curves=\"usdusd\"),\n            ],\n            s=[9.5, 2.4, 2.5, 4.5],\n            fx=fxfo,\n        )\n        result = inst.rate(solver=solver)\n        assert isinstance(result, Dual)\n\n    @pytest.mark.parametrize(\n        (\"inst\", \"strike\", \"exp\"),\n        [\n            (FXRiskReversal, [1.10, 1.12], 0.0),\n            (FXStraddle, \"atm_delta\", 11.0),\n            (FXStrangle, [\"-20d\", \"20d\"], 11.0),\n            (FXBrokerFly, [[\"-25d\", \"25d\"], \"atm_delta\"], 0.0),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"vol_meta\"), [NoInput(0), 11.0, \"smile\", [11.0, 11.0], [\"smile\", \"smile\"]]\n    )\n    @pytest.mark.parametrize((\"vol\"), [NoInput(0), 11.0, \"smile\", [11.0, 11.0], [\"smile\", \"smile\"]])\n    def test_vol_input_combinations(self, fxfo, inst, strike, exp, vol_meta, vol):\n        if isinstance(vol_meta, NoInput) and isinstance(vol, NoInput):\n            pytest.skip(\"Invalid parameter combinations\")\n        obj = inst(\n            spec=\"eurusd_call\",\n            expiry=dt(2023, 6, 16),\n            strike=strike,\n            vol=vol_meta,\n            curves=[\"eurusd\", \"usdusd\"],\n        )\n        smile = FXDeltaVolSmile(\n            nodes={0.5: 10.0},\n            expiry=dt(2023, 6, 16),\n            eval_date=dt(2023, 3, 16),\n            delta_type=\"forward\",\n            id=\"smile\",\n        )\n        solver = Solver(\n            curves=[\n                smile,\n                fxfo.curve(\"eur\", \"eur\"),\n                fxfo.curve(\"eur\", \"usd\"),\n                fxfo.curve(\"usd\", \"usd\"),\n            ],\n            instruments=[\n                FXVolValue(index_value=0.5, vol=\"smile\"),\n                IRS(dt(2023, 3, 20), \"1b\", spec=\"eur_irs\", curves=\"eureur\"),\n                IRS(dt(2023, 3, 20), \"1b\", spec=\"eur_irs\", curves=\"eurusd\"),\n                IRS(dt(2023, 3, 20), \"1b\", spec=\"usd_irs\", curves=\"usdusd\"),\n            ],\n            s=[11.0, 2.4, 2.5, 4.5],\n            fx=fxfo,\n        )\n        result = obj.rate(solver=solver, vol=vol, metric=\"single_vol\")\n        assert abs(result - exp) < 1e-6\n\n\nclass TestFXVolValue:\n    def test_solver_passthrough(self) -> None:\n        smile = FXDeltaVolSmile(\n            nodes={0.25: 10.0, 0.5: 10.0, 0.75: 10.0},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"forward\",\n            id=\"VolSmile\",\n        )\n        instruments = [\n            FXVolValue(0.25, vol=smile),\n            FXVolValue(0.5, vol=\"VolSmile\"),\n            FXVolValue(0.75, vol=\"VolSmile\"),\n        ]\n        Solver(curves=[smile], instruments=instruments, s=[8.9, 8.2, 9.1])\n        assert abs(smile[0.25] - 8.9) < 5e-7\n        assert abs(smile[0.5] - 8.2) < 5e-7\n        assert abs(smile[0.75] - 9.1) < 5e-7\n\n    def test_solver_passthrough_sabr(self) -> None:\n        smile = FXSabrSmile(\n            nodes={\"alpha\": 0.01, \"beta\": 1.0, \"rho\": 0.01, \"nu\": 0.01},\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delivery_lag=2,\n            calendar=\"tgt|fed\",\n            pair=\"eurusd\",\n            id=\"VolSmile\",\n        )\n        fxf = FXForwards(\n            fx_curves={\n                \"eureur\": Curve({dt(2023, 3, 16): 1.0, dt(2025, 6, 9): 0.95}),\n                \"eurusd\": Curve({dt(2023, 3, 16): 1.0, dt(2025, 6, 9): 0.95}),\n                \"usdusd\": Curve({dt(2023, 3, 16): 1.0, dt(2025, 6, 9): 0.93}),\n            },\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2023, 3, 18)),\n        )\n        instruments = [\n            FXVolValue(1.0, vol=smile),\n            FXVolValue(1.10, vol=\"VolSmile\"),\n            FXVolValue(1.20, vol=\"VolSmile\"),\n        ]\n        Solver(curves=[smile], instruments=instruments, s=[8.9, 8.2, 9.1], fx=fxf)\n        assert abs(smile.get_from_strike(1.0, fxf.rate(\"eurusd\", dt(2023, 6, 20)))[1] - 8.9) < 5e-7\n        assert abs(smile.get_from_strike(1.10, fxf.rate(\"eurusd\", dt(2023, 6, 20)))[1] - 8.2) < 5e-7\n        assert abs(smile.get_from_strike(1.20, fxf.rate(\"eurusd\", dt(2023, 6, 20)))[1] - 9.1) < 5e-7\n\n    def test_solver_surface_passthrough(self) -> None:\n        surface = FXDeltaVolSurface(\n            delta_indexes=[0.5],\n            expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n            node_values=[[1.0], [1.0]],\n            eval_date=dt(1999, 12, 1),\n            delta_type=\"forward\",\n            id=\"VolSurf\",\n        )\n        instruments = [\n            FXVolValue(0.25, dt(2000, 1, 1), vol=surface),\n            FXVolValue(0.5, dt(2001, 1, 1), vol=\"VolSurf\"),\n        ]\n        Solver(surfaces=[surface], instruments=instruments, s=[8.9, 8.2], func_tol=1e-14)\n        assert abs(surface._get_index(0.5, dt(2000, 1, 1)) - 8.9) < 5e-7\n        assert abs(surface._get_index(0.5, dt(2001, 1, 1)) - 8.2) < 5e-7\n\n    def test_no_solver_vol_value(self) -> None:\n        vv = FXVolValue(0.25, vol=\"string_id\")\n        with pytest.raises(ValueError, match=\"`fx_vol` must contain FXVol object, not str, if\"):\n            vv.rate()\n\n    def test_repr(self):\n        v = FXVolValue(0.25)\n        expected = f\"<rl.FXVolValue at {hex(id(v))}>\"\n        assert v.__repr__() == expected\n\n    def test_sabr_surface(self):\n        fxss = FXSabrSurface(\n            expiries=[dt(2000, 6, 1), dt(2000, 9, 1)],\n            node_values=[[0.1, 1.0, 0.01, 0.01], [0.11, 1.0, 0.01, 0.01]],\n            eval_date=dt(2000, 1, 1),\n            pair=\"eurusd\",\n        )\n        fxvv = FXVolValue(index_value=1.1, expiry=dt(2000, 8, 1))\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.15}, settlement=dt(2000, 1, 4)),\n            fx_curves={\n                \"eureur\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.95}),\n                \"eurusd\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.951}),\n                \"usdusd\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.94}),\n            },\n        )\n        result = fxvv.rate(vol=fxss, fx=fxf)\n        assert abs(result - 10.767884) < 1e-5\n\n\n@pytest.mark.parametrize(\n    \"inst\",\n    [\n        IRS(dt(2000, 1, 1), \"1y\", spec=\"usd_irs\", curves=\"sofr\"),\n        SBS(dt(2000, 1, 1), \"1y\", spec=\"eur_sbs36\", curves=[\"eur\", \"eur\", \"eur\", \"eur\"]),\n        STIRFuture(dt(2020, 1, 1), \"1m\", spec=\"usd_stir1\", curves=[\"sofr\"]),\n        XCS(dt(2000, 1, 1), \"1y\", spec=\"eurusd_xcs\", curves=[\"a\", \"b\", \"c\", \"d\"]),\n        CDS(dt(2000, 3, 20), \"2y\", spec=\"us_ig_cds\", curves=[\"a\", \"b\"]),\n        ZCS(dt(2000, 1, 1), \"5y\", spec=\"gbp_zcs\", curves=[\"sonia\"]),\n        ZCIS(dt(2000, 1, 1), \"2y\", spec=\"gbp_zcis\", curves=[\"index\", \"sonia\"]),\n        IIRS(dt(2000, 1, 1), \"1y\", spec=\"usd_irs\", curves=[\"index\", \"sonia\", \"rate\", \"sonia\"]),\n        FRA(dt(2000, 1, 1), \"3m\", spec=\"eur_fra3\", curves=[\"eur\"]),\n        NDF(dt(2000, 1, 1), pair=\"eurusd\", curves=[\"usd\"]),\n        FixedRateBond(\n            dt(2000, 1, 1), \"2y\", spec=\"uk_gb\", curves=[\"uk\"], fixed_rate=1.2, metric=\"ytm\"\n        ),\n    ],\n)\ndef test_unpriced_cashflows_string_id(inst):\n    result = inst.cashflows()\n    assert isinstance(result, DataFrame)\n\n\n@pytest.mark.parametrize(\n    (\"inst\", \"curves\"),\n    [\n        (IRS(dt(2022, 2, 1), \"1m\", spec=\"usd_irs\", fixed_rate=2.0), [\"c\"]),\n        (\n            SBS(dt(2022, 2, 1), \"2m\", frequency=\"2M\", leg2_frequency=\"1M\", float_spread=2.0),\n            [\"c\", \"c\", \"c2\", \"c\"],\n        ),\n        (STIRFuture(dt(2022, 2, 1), \"1m\", spec=\"usd_stir1\", price=99.0), [\"c\"]),\n        (CDS(dt(2022, 2, 1), \"1m\", frequency=\"1M\", fixed_rate=1.0), [\"c2\", \"c\"]),\n        (ZCS(dt(2022, 1, 1), \"2m\", frequency=\"3M\", fixed_rate=2.0), [\"c\"]),\n        (\n            ZCIS(\n                dt(2022, 1, 1),\n                \"2M\",\n                frequency=\"3M\",\n                fixed_rate=2.0,\n                leg2_index_base=99.0,\n                leg2_index_lag=0,\n            ),\n            [\"c2\", \"c\"],\n        ),\n        (\n            IIRS(\n                dt(2022, 1, 1), \"2M\", spec=\"usd_irs\", fixed_rate=2.0, index_base=99.0, index_lag=0\n            ),\n            [\"c2\", \"c\", \"c\", \"c\"],\n        ),\n        (FRA(dt(2022, 2, 1), \"1m\", fixed_rate=1.0, frequency=\"1M\"), [\"c\"]),\n    ],\n)\ndef test_forward_npv_argument(curve, curve2, inst, curves):\n    c_ = {\"c\": curve, \"c2\": curve2}\n    npv = inst.npv(curves=[c_[v] for v in curves])\n    forward_npv = inst.npv(\n        curves=[c_[v] for v in curves],\n        forward=dt(2022, 3, 15),\n    )\n    assert abs(forward_npv - npv / curve[dt(2022, 3, 15)]) < 1e-10\n\n\n@pytest.mark.parametrize(\n    (\"inst\", \"curves\"),\n    [\n        (\n            XCS(\n                dt(2022, 1, 1),\n                \"2m\",\n                mtm=True,\n                fixed=True,\n                leg2_fixed=True,\n                fixed_rate=2.0,\n                leg2_fixed_rate=3.0,\n                frequency=\"1M\",\n                fx_fixings=2.0,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                leg2_notional=10e6,\n            ),\n            [\"c\", \"c\", \"c2\", \"c2\"],\n        ),\n        (\n            XCS(\n                dt(2022, 1, 1),\n                \"2m\",\n                mtm=True,\n                fixed=True,\n                fixed_rate=2.0,\n                frequency=\"1M\",\n                fx_fixings=2.0,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                leg2_notional=10e6,\n            ),\n            [\"c\", \"c\", \"c2\", \"c2\"],\n        ),\n        (\n            XCS(\n                dt(2022, 1, 1),\n                \"2m\",\n                mtm=True,\n                leg2_fixed=True,\n                leg2_fixed_rate=2.0,\n                float_spread=0.0,\n                frequency=\"1M\",\n                fx_fixings=2.0,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                leg2_notional=10e6,\n            ),\n            [\"c\", \"c\", \"c2\", \"c2\"],\n        ),\n        (\n            XCS(\n                dt(2022, 1, 1),\n                \"2m\",\n                mtm=True,\n                float_spread=0.0,\n                leg2_float_spread=0.0,\n                frequency=\"1M\",\n                fx_fixings=2.0,\n                currency=\"eur\",\n                pair=\"eurusd\",\n                leg2_notional=10e6,\n            ),\n            [\"c\", \"c\", \"c2\", \"c2\"],\n        ),\n        (\n            XCS(\n                dt(2022, 1, 1),\n                \"2m\",\n                fixed=True,\n                leg2_fixed=True,\n                fixed_rate=2.0,\n                leg2_fixed_rate=2.5,\n                frequency=\"1M\",\n                leg2_fx_fixings=2.0,\n                currency=\"usd\",\n                pair=\"eurusd\",\n            ),\n            [\"c2\", \"c2\", \"c\", \"c\"],\n        ),\n        (\n            XCS(\n                dt(2022, 1, 1),\n                \"2m\",\n                fixed=True,\n                fixed_rate=2.0,\n                frequency=\"1M\",\n                leg2_fx_fixings=2.0,\n                currency=\"usd\",\n                pair=\"usdeur\",\n            ),\n            [\"c2\", \"c2\", \"c\", \"c\"],\n        ),\n        (\n            XCS(\n                dt(2022, 1, 1),\n                \"2m\",\n                leg2_fixed=True,\n                leg2_fixed_rate=2.0,\n                float_spread=0.0,\n                frequency=\"1M\",\n                leg2_fx_fixings=2.0,\n                currency=\"usd\",\n                pair=\"usdeur\",\n            ),\n            [\"c2\", \"c2\", \"c\", \"c\"],\n        ),\n        (\n            XCS(\n                dt(2022, 1, 1),\n                \"2m\",\n                float_spread=0.0,\n                leg2_float_spread=0.0,\n                frequency=\"1M\",\n                leg2_fx_fixings=2.0,\n                currency=\"usd\",\n                pair=\"usdeur\",\n            ),\n            [\"c2\", \"c2\", \"c\", \"c\"],\n        ),\n        (NDF(dt(2022, 2, 15), pair=\"eurusd\", fx_rate=1.15), [\"c\"]),\n        (\n            FXSwap(dt(2022, 2, 15), \"1m\", pair=\"eurusd\", fx_rate=1.15, points=56.5),\n            [\"c\", \"c2\"],\n        ),\n        (FXForward(dt(2022, 2, 16), \"eurusd\", fx_rate=1.15), [\"c\", \"c2\"]),\n    ],\n)\ndef test_forward_npv_argument_with_fx(curve, curve2, inst, curves):\n    fxr = FXRates({\"eurusd\": 1.12}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(fx_rates=fxr, fx_curves={\"eureur\": curve, \"eurusd\": curve, \"usdusd\": curve2})\n    c_ = {\"c\": curve, \"c2\": curve2}\n    npv = inst.npv(curves=[c_[v] for v in curves], fx=fxf, base=\"eur\")\n    forward_npv = inst.npv(\n        curves=[c_[v] for v in curves], forward=dt(2022, 3, 15), fx=fxf, base=\"eur\"\n    )\n\n    result = npv / curve[dt(2022, 3, 15)] - forward_npv\n    assert abs(result) < 1e-7\n\n\nclass TestFixings:\n    def test_local_fixings_rate_and_fx(self):\n        fixings.add(\"wmr_eurusd\", Series(index=[dt(1999, 1, 1)], data=[100.0]))\n        fixings.add(\"rpi\", Series(index=[dt(1999, 1, 1)], data=[100.0]))\n        fixings.add(\"ibor_1M\", Series(index=[dt(1999, 1, 1)], data=[100.0]))\n\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2000, 2, 15): 0.999, dt(2005, 1, 1): 0.9})\n        curve2 = Curve({dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.95})\n        fxf = FXForwards(\n            fx_curves={\"eurusd\": curve2, \"usdusd\": curve, \"eureur\": curve2},\n            fx_rates=FXRates({\"eurusd\": 1.10}, settlement=dt(2000, 1, 1)),\n        )\n        irs = IRS(\n            dt(2000, 1, 1),\n            dt(2000, 4, 1),\n            \"M\",\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fx_fixings=\"wmr\",\n            leg2_fixing_method=\"ibor(0)\",\n            leg2_rate_fixings=\"ibor\",\n            payment_lag=0,\n            curves=[curve],\n            fixed_rate=2.07,\n        )\n\n        # cf = irs.cashflows(fx=fxf)\n        cft = irs.cashflows_table(fx=fxf)\n\n        result = irs.local_fixings(\n            identifiers=[\n                (\n                    \"wmr_eurusd\",\n                    Series(\n                        index=[dt(2000, 1, 28), dt(2000, 2, 28), dt(2000, 3, 30)],\n                        data=[1.0998008124280523, 1.1002139078693074, 1.101254251708383],\n                    ),\n                ),\n                (\n                    \"ibor_1m\",\n                    Series(\n                        index=[dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)],\n                        data=[0.8006761616124619, 1.4777702977501797, 2.110198054725164],\n                    ),\n                ),\n            ],\n            scalars=(1.0, 0.01),\n            fx=fxf,\n        )\n\n        expected_rate_fixings = irs.local_analytic_rate_fixings(fx=fxf)\n        for i in range(3):\n            assert (\n                abs(result[(\"usd\", \"ibor_1m\")].iloc[i * 2] - expected_rate_fixings.iloc[i, 0])\n                < 1e-8\n            )\n        expected_fx_fixings = [\n            cft.iloc[0, 0] / 1.0998008124280523 * curve[dt(2000, 2, 1)],\n            cft.iloc[1, 0] / 1.1002139078693074 * curve[dt(2000, 3, 1)],\n            cft.iloc[2, 0] / 1.101254251708383 * curve[dt(2000, 4, 1)],\n        ]\n        for i in range(3):\n            assert (\n                abs(result[(\"usd\", \"wmr_eurusd\")].iloc[i * 2 + 1] - expected_fx_fixings[i]) < 1e-6\n            )\n\n\ndef test_wmr_crosses_not_allowed_standard_instruments():\n    sek = Curve({dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.8})\n    cad = Curve({dt(2000, 1, 1): 1.0, dt(2005, 1, 1): 0.85})\n    fxf = FXForwards(\n        fx_rates=FXRates({\"cadsek\": 8.0}, settlement=dt(2000, 1, 3)),\n        fx_curves={\"cadcad\": cad, \"sekcad\": sek, \"seksek\": sek},\n    )\n    fxvs = FXDeltaVolSmile(\n        eval_date=dt(2000, 1, 1),\n        expiry=dt(2000, 6, 2),\n        nodes={0.5: 10.0},\n        delta_type=\"forward\",\n    )\n\n    instruments = [\n        FXForward(dt(2000, 6, 1), FXIndex(\"cadsek\", \"tro,stk\", 2), curves=[cad, sek]),\n        FXForward(dt(2000, 6, 1), FXIndex(\"cadsek\", \"tro,stk\", 2), fx_rate=8.1, curves=[cad, sek]),\n        FXSwap(dt(2000, 6, 2), dt(2000, 7, 2), FXIndex(\"cadsek\", \"tro,stk\", 2), curves=[cad, sek]),\n        FXSwap(\n            dt(2000, 6, 2),\n            dt(2000, 7, 2),\n            FXIndex(\"cadsek\", \"tro,stk\", 2),\n            fx_rate=8.0,\n            points=100.0,\n            curves=[cad, sek],\n        ),\n        FXCall(\n            expiry=dt(2000, 6, 2),\n            strike=8.0,\n            pair=FXIndex(\"cadsek\", \"tro,stk\", 2),\n            curves=[cad, sek],\n        ),\n    ]\n    for inst in instruments:\n        inst.npv(vol=fxvs, fx=fxf)\n\n\nclass TestSwaptions:\n    def test_npv_no_set_premium(self):\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"tgt\"\n        )\n        irsw = IRSCall(\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            strike=3.020383,\n            irs_series=\"usd_irs\",\n        )\n        result = irsw.npv(curves=curve, vol=25.16)\n        expected = 0.0\n        assert abs(result - expected) < 1e-6\n\n    def test_npv_with_set_premium(self):\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"tgt\"\n        )\n        irsw = IRSCall(\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            strike=3.020383,\n            irs_series=\"usd_irs\",\n            premium=10000.0,\n        )\n        result = irsw.npv(curves=curve, vol=25.16)\n        expected = -8246.831212232395\n        assert abs(result - expected) < 1e-6\n\n    def test_npv_local(self):\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"tgt\"\n        )\n        irsw = IRSCall(\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            strike=3.020383,\n            irs_series=\"usd_irs\",\n            premium=10000.0,\n        )\n        result = irsw.npv(curves=curve, vol=25.16, local=True)\n        expected = -8246.831212232395\n        assert abs(result[\"usd\"] - expected) < 1e-6\n\n    def test_default_payment_date(self):\n        irsw = IRSCall(\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            strike=3.020383,\n            irs_series=\"usd_irs\",\n            premium=10000.0,\n        )\n        assert irsw.leg2.periods[0].settlement_params.payment == dt(2027, 2, 18)\n\n    @pytest.mark.parametrize(\n        (\"metric\", \"expected\"),\n        [\n            (\"BlackVolShift_0\", 25.16),\n            (\"Premium\", 149725.796514),\n            (\"NormalVol\", 75.792872),\n            (\"Black_vol_shift_100\", 18.880156),\n            (\"Black_vol_shift_200\", 15.111396),\n            (\"Black_vol_shift_300\", 12.597702),\n            (\"PercentNotional\", 0.149725),\n        ],\n    )\n    def test_rate(self, metric, expected):\n        # if we know that the exercise will occur (from the fixing_value) value the cashflow\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        irsw = IRSCall(\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n            irs_series=\"usd_irs\",\n            premium=10000.0,\n        )\n        result = irsw.rate(\n            curves=[curve],\n            vol=25.16,\n            metric=metric,\n        )\n        assert abs(result - expected) < 1e-5\n\n    @pytest.mark.parametrize(\n        (\"metric\", \"expected\"),\n        [(\"Premium\", 149725.796514), (\"PercentNotional\", 0.149725)],\n    )\n    @pytest.mark.parametrize(\"date\", [dt(2027, 1, 3), dt(2027, 3, 19)])\n    def test_rate_unconventional_payment_date(self, metric, expected, date):\n        # if we know that the exercise will occur (from the fixing_value) value the cashflow\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        alt_curve = Curve(nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.91}, calendar=\"nyc\")\n        irsw = IRSCall(\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n            irs_series=\"usd_irs\",\n            premium=10000.0,\n            payment_lag=date,\n        )\n        result = irsw.rate(\n            curves=[curve, alt_curve, curve],\n            vol=25.16,\n            metric=metric,\n        )\n        expected = expected * alt_curve[date] / alt_curve[dt(2027, 2, 18)]\n        assert abs(result - expected) < 1e-5\n\n    def test_cashflows(self):\n        # if we know that the exercise will occur (from the fixing_value) value the cashflow\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        irsw = IRSCall(\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n            irs_series=\"usd_irs\",\n            premium=10000.0,\n        )\n        result = irsw.cashflows(\n            curves=[curve],\n            vol=25.16,\n        )\n        assert len(result.index) == 2\n        assert abs(result.loc[\"leg1\", \"DF\"].iloc[0] - 0.969902553602701) < 1e-8\n        assert abs(result.loc[\"leg1\", \"Cashflow\"].iloc[0] - 149725.7965143448) < 1e-8\n        assert abs(result.loc[\"leg1\", \"NPV\"].iloc[0] - 145219.43237946142) < 1e-8\n        assert result.loc[\"leg1\", \"Ccy\"].iloc[0] == \"USD\"\n        assert result.loc[\"leg1\", \"Type\"].iloc[0] == \"IRSCallPeriod\"\n\n    @pytest.mark.parametrize(\n        (\"metric\", \"weights\"),\n        [\n            (\"PercentNotional\", [1.0, 1.0]),\n            (\"Premium\", [1.0, 1.0]),\n            (\"NormalVol\", [0.5, 0.5]),\n            (\"BlackVolShift_0\", [0.5, 0.5]),\n            (\"BlackVolShift_100\", [0.5, 0.5]),\n            (\"BlackVolShift_200\", [0.5, 0.5]),\n            (\"BlackVolShift_300\", [0.5, 0.5]),\n        ],\n    )\n    def test_straddle_rate(self, metric, weights):\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"tgt\"\n        )\n        irss = IRSabrSmile(\n            eval_date=dt(2026, 2, 16),\n            expiry=dt(2026, 8, 16),\n            tenor=\"6m\",\n            nodes={\n                \"alpha\": 0.2,\n                \"rho\": -0.05,\n                \"nu\": 0.6,\n            },\n            beta=0.5,\n            irs_series=\"usd_irs\",\n        )\n        irsc = IRSCall(\n            irs_series=\"usd_irs\",\n            expiry=dt(2026, 8, 16),\n            tenor=\"6m\",\n            strike=2.90,\n            metric=metric,\n        )\n        irsp = IRSPut(\n            irs_series=\"usd_irs\",\n            expiry=dt(2026, 8, 16),\n            tenor=\"6m\",\n            strike=2.90,\n            metric=metric,\n        )\n        irstr = IRSStraddle(\n            irs_series=\"usd_irs\",\n            expiry=dt(2026, 8, 16),\n            tenor=\"6m\",\n            strike=2.90,\n            metric=metric,\n        )\n        r1 = irsc.rate(vol=irss, curves=curve)\n        r2 = irsp.rate(vol=irss, curves=curve)\n        r3 = irstr.rate(vol=irss, curves=curve)\n        assert abs(r3 - r1 * weights[0] - r2 * weights[1]) < 1e-5\n\n    def test_straddle_npv(self):\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"tgt\"\n        )\n        irss = IRSabrSmile(\n            eval_date=dt(2026, 2, 16),\n            expiry=dt(2026, 8, 16),\n            tenor=\"6m\",\n            nodes={\n                \"alpha\": 0.2,\n                \"rho\": -0.05,\n                \"nu\": 0.6,\n            },\n            beta=0.5,\n            irs_series=\"usd_irs\",\n        )\n        irsc = IRSCall(\n            irs_series=\"usd_irs\",\n            expiry=dt(2026, 8, 16),\n            tenor=\"6m\",\n            strike=2.90,\n        )\n        irsp = IRSPut(\n            irs_series=\"usd_irs\",\n            expiry=dt(2026, 8, 16),\n            tenor=\"6m\",\n            strike=2.90,\n        )\n        irstr = IRSStraddle(\n            irs_series=\"usd_irs\",\n            expiry=dt(2026, 8, 16),\n            tenor=\"6m\",\n            strike=2.90,\n        )\n        r1 = irsc.npv(vol=irss, curves=curve)\n        r2 = irsp.npv(vol=irss, curves=curve)\n        r3 = irstr.npv(vol=irss, curves=curve)\n        assert abs(r3 - r1 - r2) < 1e-5\n\n    def test_delta_rate_scalar(self):\n        smile = IRSabrSmile(\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2000, 7, 1),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.05,\n                \"nu\": 0.65,\n            },\n            beta=0.5,\n            id=\"sofr_vol\",\n        )\n        curve = Curve(nodes={dt(2000, 1, 1): 1.0, dt(2003, 1, 1): 0.90}, id=\"sofr\")\n        option_args = dict(\n            expiry=dt(2000, 7, 1),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            metric=\"NormalVol\",\n            curves=\"sofr\",\n            vol=\"sofr_vol\",\n        )\n\n        solver = Solver(\n            curves=[curve, smile],\n            instruments=[\n                IRS(dt(2000, 1, 1), \"1y\", spec=\"usd_irs\", curves=\"sofr\"),\n                IRSCall(strike=\"-20bps\", **option_args),\n                IRSCall(strike=\"atm\", **option_args),\n                IRSCall(strike=\"+20bps\", **option_args),\n            ],\n            s=[3.0, 50.0, 45.0, 49.0],\n            instrument_labels=[\"1Y IRS\", \"-20bps Vol\", \"ATM Vol\", \"+20bps Vol\"],\n        )\n\n        irc = IRSCall(strike=3.05, premium=0.0, **option_args)\n        delta = irc.delta(solver=solver)\n\n        before = irc.npv(solver=solver)\n        solver.s = [3.0, 50.0, 46.0, 49.0]\n        solver.iterate()\n        after = irc.npv(solver=solver)\n        finite_diff = after - before\n        assert abs(delta.iloc[2, 0] - finite_diff) < 1e-1\n\n    @pytest.mark.parametrize((\"strike\", \"expected\"), [(3.99, 5558.52), (\"+0bps\", -48193.65)])\n    def test_npv_from_normal_vol_object(self, strike, expected, curve):\n        smile = IRSplineSmile(\n            nodes={-100: 100.0, 0: 95.0, 100: 100.0},\n            eval_date=dt(2022, 1, 1),\n            expiry=dt(2023, 1, 3),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n        )\n        iro = IRSCall(\n            eval_date=dt(2022, 1, 1),\n            expiry=\"1y\",\n            tenor=\"1y\",\n            strike=strike,\n            irs_series=\"usd_irs\",\n            curves=curve,\n            vol=smile,\n            notional=100e6,\n            premium=420000.0,\n        )\n        result = iro.npv()\n        assert abs(result - expected) < 1e-2\n\n\nclass TestIRVolValue:\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            IRSabrSmile(\n                nodes={\n                    \"alpha\": 0.17431060,\n                    \"rho\": -0.11268306,\n                    \"nu\": 0.81694072,\n                },\n                beta=0.75,\n                eval_date=dt(2001, 1, 1),\n                expiry=\"1y\",\n                irs_series=\"eur_irs6\",\n                tenor=\"1y\",\n                id=\"VolSmile\",\n            ),\n            IRSabrCube(\n                eval_date=dt(2001, 1, 1),\n                expiries=[\"1y\"],\n                irs_series=\"eur_irs6\",\n                tenors=[\"1y\"],\n                alpha=0.17,\n                beta=0.75,\n                rho=-0.11,\n                nu=0.817,\n                id=\"VolSmile\",\n            ),\n        ],\n    )\n    def test_solver_passthrough(self, vol) -> None:\n        instruments = [\n            IRVolValue(\n                strike=1.0,\n                expiry=\"1y\",\n                tenor=\"1y\",\n                irs_series=\"eur_irs6\",\n                eval_date=dt(2001, 1, 1),\n                vol=vol,\n                metric=\"alpha\",\n            ),\n            IRVolValue(\n                strike=1.0,\n                expiry=\"1y\",\n                tenor=\"1y\",\n                irs_series=\"eur_irs6\",\n                eval_date=dt(2001, 1, 1),\n                vol=\"VolSmile\",\n                metric=\"rho\",\n            ),\n            IRVolValue(\n                strike=1.0,\n                expiry=\"1y\",\n                tenor=\"1y\",\n                irs_series=\"eur_irs6\",\n                eval_date=dt(2001, 1, 1),\n                vol=\"VolSmile\",\n                metric=\"nu\",\n            ),\n        ]\n        Solver(curves=[vol], instruments=instruments, s=[0.25, -0.04, 0.75])\n        for param, expected in zip([\"alpha\", \"rho\", \"nu\"], [0.25, -0.04, 0.75]):\n            if isinstance(vol, IRSabrCube):\n                result = getattr(\n                    vol.get_smile(expiry=dt(2002, 1, 2), tenor=dt(2003, 1, 4)).nodes, param\n                )\n            else:\n                result = getattr(vol.nodes, param)\n            assert abs(result - expected) < 1e-6\n\n        v = IRVolValue(\n            strike=9.0,\n            expiry=\"1y\",\n            tenor=\"1y\",\n            irs_series=\"eur_irs6\",\n            eval_date=dt(2001, 1, 1),\n            vol=vol,\n            metric=\"black_vol_shift_0\",\n        )\n        result = v.rate(vol=vol, curves=Curve({dt(2001, 1, 1): 1.0, dt(2005, 1, 1): 0.7}))\n        expected = 15.170743310759043\n        assert abs(result - expected) < 1e-6\n\n    def test_no_solver_vol_value(self) -> None:\n        vv = IRVolValue(\n            strike=1.0,\n            irs_series=\"eur_irs6\",\n            expiry=\"1y\",\n            tenor=\"1y\",\n            eval_date=dt(2000, 1, 1),\n            vol=\"string_id\",\n        )\n        with pytest.raises(ValueError, match=\"`vol` must contain IRVol object, not str,\"):\n            vv.rate()\n\n    def test_repr(self):\n        v = IRVolValue(\n            strike=0.25,\n            expiry=\"1y\",\n            tenor=\"1y\",\n            eval_date=dt(2000, 1, 1),\n            irs_series=\"usd_irs\",\n        )\n        expected = f\"<rl.IRVolValue at {hex(id(v))}>\"\n        assert v.__repr__() == expected\n\n    @pytest.mark.parametrize(\n        \"vol\",\n        [\n            # IRSabrSmile(\n            #     nodes={\n            #         \"alpha\": 0.17431060,\n            #         \"rho\": -0.11268306,\n            #         \"nu\": 0.81694072,\n            #     },\n            #     beta=1.0,\n            #     eval_date=dt(2001, 1, 1),\n            #     expiry=\"1y\",\n            #     irs_series=\"eur_irs6\",\n            #     tenor=\"1y\",\n            #     id=\"vol\",\n            # ),\n            IRSabrCube(\n                eval_date=dt(2001, 1, 1),\n                expiries=[\"1y\"],\n                tenors=[\"1Y\", \"2y\"],\n                irs_series=\"usd_irs\",\n                beta=1.0,\n                alpha=np.array([[0.17431060, 0.2]]),\n                rho=np.array([[-0.11268306, 0.2]]),\n                nu=np.array([[0.81694072, 0.2]]),\n            ),\n        ],\n    )\n    @pytest.mark.parametrize(\"metric\", [\"alpha\", \"beta\", \"rho\", \"nu\"])\n    def test_sabr_param(self, vol, metric):\n        v = IRVolValue(\n            strike=0.25,\n            expiry=\"1y\",\n            tenor=\"1y\",\n            eval_date=dt(2001, 1, 1),\n            irs_series=\"usd_irs\",\n            metric=metric,\n        )\n        expected = {\n            \"alpha\": 0.17431060,\n            \"beta\": 1.0,\n            \"rho\": -0.11268306,\n            \"nu\": 0.81694072,\n        }\n        assert v.rate(vol=vol) == expected[metric]\n\n\nclass TestFee:\n    # init\n\n    def test_date_and_attributes(self):\n        fee = Fee(dt(2022, 1, 1), 2e6, calendar=\"tgt\", payment_lag=0, ex_div=2, currency=\"EUR\")\n        assert fee.settlement_params.payment == dt(2022, 1, 3)\n        assert fee.settlement_params.notional == 2e6\n        assert fee.settlement_params.ex_dividend == dt(2021, 12, 30)\n        assert fee.settlement_params.currency == \"eur\"\n\n    # protocols\n\n    def test_npv(self, curve):\n        fee = Fee(dt(2022, 3, 1), 2e6)\n        result = fee.npv(curves=curve)\n        assert abs(result + 1986866.2068519176) < 1e-7\n\n    @pytest.mark.parametrize((\"metric\", \"exp\"), [(\"npv\", -1986866.20), (\"payment\", -2e6)])\n    def test_rate(self, curve, metric, exp):\n        fee = Fee(dt(2022, 3, 1), 2e6)\n        result = fee.rate(curves=curve, metric=metric)\n        assert abs(result - exp) < 1e-2\n\n    def test_analytic_delta(self, curve):\n        fee = Fee(dt(2022, 3, 1), 2e6)\n        result = fee.analytic_delta(curves=curve)\n        assert abs(result - 0.0) < 1e-2\n\n    def test_cashflows(self, curve):\n        fee = Fee(dt(2022, 3, 1), 2e6)\n        result = fee.cashflows(curves=curve)\n        assert isinstance(result, DataFrame)\n\n    def test_fixings(self, curve):\n        fee = Fee(dt(2022, 3, 1), 2e6)\n        result = fee.local_analytic_rate_fixings(curves=curve)\n        assert isinstance(result, DataFrame)\n\n    def test_non_deliverable(self, curve):\n        name = str(hash(os.urandom(2)))\n        fixings.add(name + \"_eurusd\", Series(index=[dt(2022, 2, 25)], data=[1.50]))\n        fee = Fee(\n            effective=dt(2022, 3, 1), notional=2e6, currency=\"usd\", pair=\"eurusd\", fx_fixings=name\n        )\n        result = fee.npv(curves=curve)\n        fixings.pop(name + \"_eurusd\")\n        assert abs(result + curve[dt(2022, 3, 1)] * 2e6 * 1.5) < 1e-7\n\n    def test_indexation(self, curve):\n        name = str(hash(os.urandom(2)))\n        fixings.add(name, Series(index=[dt(2022, 2, 1), dt(2022, 3, 1)], data=[1.10, 1.50]))\n        fee = Fee(\n            effective=dt(2022, 3, 1),\n            notional=2e6,\n            currency=\"usd\",\n            index_fixings=name,\n            index_lag=0,\n            index_base_date=dt(2022, 2, 1),\n        )\n        result = fee.npv(curves=curve)\n        fixings.pop(name)\n        assert abs(result + curve[dt(2022, 3, 1)] * 2e6 * 1.5 / 1.1) < 1e-7\n\n\nclass TestLoan:\n    # init\n\n    def test_date_and_attributes(self):\n        loan = Loan(\n            dt(2022, 1, 1),\n            \"1y\",\n            \"Q\",\n            notional=2e6,\n            calendar=\"tgt\",\n            payment_lag=0,\n            ex_div=2,\n            currency=\"EUR\",\n        )\n        assert loan.settlement_params.notional == 2e6\n        assert loan.settlement_params.ex_dividend == dt(2022, 3, 30)\n        assert loan.settlement_params.currency == \"eur\"\n        assert isinstance(loan.leg1.periods[0], Cashflow)\n        assert isinstance(loan.leg1.periods[-1], Cashflow)\n\n    # protocols\n\n    def test_npv(self, curve):\n        loan = Loan(\n            dt(2022, 1, 1),\n            \"1y\",\n            \"Q\",\n            notional=2e6,\n            calendar=\"tgt\",\n            payment_lag=0,\n            ex_div=2,\n            currency=\"EUR\",\n            fixed_rate=10.0,\n        )\n        result = loan.npv(curves=curve)\n        assert abs(result + 117558.44166647314) < 1e-7\n\n    @pytest.mark.parametrize((\"metric\", \"exp\"), [(\"npv\", 0.0)])\n    def test_rate(self, curve, metric, exp):\n        loan = Loan(\n            dt(2022, 1, 1),\n            \"1y\",\n            \"Q\",\n            notional=2e6,\n            calendar=\"tgt\",\n            payment_lag=0,\n            ex_div=2,\n            currency=\"EUR\",\n            fixed=False,\n        )\n        result = loan.rate(curves=curve, metric=metric)\n        assert abs(result - exp) < 1e-2\n\n    def test_analytic_delta(self, curve):\n        loan = Loan(\n            dt(2022, 1, 1),\n            \"1y\",\n            \"Q\",\n            notional=10e6,\n            calendar=\"tgt\",\n            payment_lag=0,\n            ex_div=2,\n            currency=\"EUR\",\n        )\n        result = loan.analytic_delta(curves=curve)\n        assert abs(result - 985.608939) < 1e-2\n\n    def test_cashflows(self, curve):\n        loan = Loan(\n            dt(2022, 1, 1),\n            \"1y\",\n            \"Q\",\n            notional=10e6,\n            calendar=\"tgt\",\n            payment_lag=0,\n            ex_div=2,\n            currency=\"EUR\",\n            fixed_rate=10.0,\n        )\n        result = loan.cashflows(curves=curve)\n        assert isinstance(result, DataFrame)\n\n    def test_fixings(self, curve):\n        loan = Loan(\n            dt(2022, 1, 1),\n            \"1y\",\n            \"Q\",\n            notional=10e6,\n            calendar=\"tgt\",\n            payment_lag=0,\n            ex_div=2,\n            currency=\"EUR\",\n            fixed=False,\n        )\n        result = loan.local_analytic_rate_fixings(curves=curve)\n        assert isinstance(result, DataFrame)\n\n    def test_non_deliverable(self, curve):\n        name = str(hash(os.urandom(2)))\n        fixings.add(name + \"_eurusd\", Series(index=[dt(2021, 12, 30)], data=[1.50]))\n        loan = Loan(\n            dt(2022, 1, 1),\n            \"3m\",\n            \"Q\",\n            notional=1e6,\n            calendar=\"all\",\n            payment_lag=0,\n            ex_div=2,\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fx_fixings=name,\n            fixed_rate=0.0,\n        )\n        result = loan.npv(curves=curve)\n        fixings.pop(name + \"_eurusd\")\n        assert abs(result + curve[dt(2022, 4, 1)] * 1e6 * 1.5 - 1.5e6) < 1e-7\n        assert loan.settlement_params.currency == \"usd\"\n        assert loan.settlement_params.notional_currency == \"eur\"\n\n    def test_indexation(self, curve):\n        name = str(hash(os.urandom(2)))\n        fixings.add(name, Series(index=[dt(2022, 2, 1), dt(2022, 3, 1)], data=[1.10, 1.50]))\n        loan = Loan(\n            dt(2022, 2, 1),\n            \"1m\",\n            \"Q\",\n            notional=2e6,\n            calendar=\"tgt\",\n            payment_lag=0,\n            ex_div=0,\n            currency=\"EUR\",\n            index_lag=0,\n            index_method=\"monthly\",\n            index_fixings=name,\n            fixed_rate=0.0,\n        )\n        result = loan.npv(curves=curve)\n        expected = 2e6 * (curve[dt(2022, 2, 1)] - curve[dt(2022, 3, 1)] * 1.5 / 1.1)\n        fixings.pop(name)\n        assert abs(result - expected) < 1e-7\n\n    @pytest.mark.skip(reason=\"metric not implemented\")\n    @pytest.mark.parametrize(\n        (\"settlement\", \"exp\"),\n        [(NoInput(0), 4.058910928323769), (dt(2022, 4, 5), 4.058910928323769)],\n    )\n    def test_metric_fixed_rate(self, settlement, exp, curve):\n        loan = Loan(\n            dt(2022, 1, 1),\n            \"1y\",\n            \"Q\",\n            notional=2e6,\n            calendar=\"tgt\",\n            payment_lag=0,\n            ex_div=0,\n            currency=\"EUR\",\n        )\n        result = loan.rate(curves=curve, metric=\"fixed_rate\")\n        assert abs(result - exp) < 1e-7\n\n    @pytest.mark.skip(reason=\"metric not implemented\")\n    @pytest.mark.parametrize(\n        (\"settlement\", \"exp\"),\n        [\n            (NoInput(0), 4.058910928323769),\n            # (dt(2022, 4, 5), 4.058910928323769)\n        ],\n    )\n    def test_metric_float_spread(self, settlement, exp, curve):\n        disc_curve = curve.shift(0.0)\n        loan = Loan(\n            dt(2022, 1, 3),\n            \"1y\",\n            \"Q\",\n            notional=2e6,\n            calendar=\"tgt\",\n            convention=\"act360\",\n            payment_lag=0,\n            ex_div=0,\n            currency=\"EUR\",\n            fixed=False,\n            spread_compound_method=\"isda_compounding\",\n        )\n        _pv = loan.npv(curves=curve)\n        result = loan.rate(curves=[curve, disc_curve], metric=\"float_spread\")\n        assert abs(result - 0.0) < 1e-7\n"
  },
  {
    "path": "python/tests/legs/test_analytic_delta.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.curves import Curve\nfrom rateslib.legs import FixedLeg\nfrom rateslib.scheduling import Schedule\n\n\n@pytest.fixture\ndef curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.99,\n        dt(2022, 7, 1): 0.98,\n        dt(2022, 10, 1): 0.97,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\ndef test_analytic_delta_protocol_local(curve):\n    leg = FixedLeg(\n        schedule=Schedule(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 4, 1),\n            frequency=\"M\",\n        ),\n        fixed_rate=1.0,\n    )\n    result = leg.analytic_delta(disc_curve=curve, local=True)\n    expected = {\"usd\": 24.827510962072353}\n    assert result == expected\n\n\ndef test_forward_settlement(curve):\n    # tset that the analytic delta reacts to the settlement/ex-div constraint\n    leg = FixedLeg(\n        schedule=Schedule(\n            effective=dt(2021, 12, 2),\n            termination=dt(2022, 4, 2),\n            frequency=\"M\",\n            payment_lag=0,\n        ),\n        fixed_rate=1.0,\n        notional=1e9,\n    )\n    result = leg.analytic_delta(disc_curve=curve, local=False)\n    result2 = leg.analytic_delta(disc_curve=curve, local=False, settlement=dt(2022, 1, 3))\n    assert result2 < (result - 5000)\n\n\ndef test_forward(curve):\n    # tset that the analytic delta reacts to the forward argument\n    leg = FixedLeg(\n        schedule=Schedule(\n            effective=dt(2021, 12, 2),\n            termination=dt(2022, 4, 2),\n            frequency=\"M\",\n            payment_lag=0,\n        ),\n        fixed_rate=1.0,\n        notional=1e9,\n    )\n    result = leg.analytic_delta(disc_curve=curve, local=False)\n    result2 = leg.analytic_delta(disc_curve=curve, local=False, forward=dt(2022, 3, 15))\n\n    expected = result / curve[dt(2022, 3, 15)]\n    assert abs(result2 - expected) < 1e-6\n"
  },
  {
    "path": "python/tests/legs/test_init.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.legs.fixed import FixedLeg\nfrom rateslib.scheduling import Schedule\n\n\nclass TestFixedLeg:\n    def test_init(self):\n        FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2001, 1, 1),\n                frequency=\"3M\",\n                payment_lag=2,\n                payment_lag_exchange=0,\n                extra_lag=-1,\n            ),\n            notional=1000000.0,\n            amortization=1000.0,\n            currency=\"USD\",\n            final_exchange=True,\n            initial_exchange=True,\n        )\n"
  },
  {
    "path": "python/tests/legs/test_leg_fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom pandas import Series\nfrom rateslib import fixings\nfrom rateslib.curves import Curve\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.enums.parameters import FloatFixingMethod\nfrom rateslib.legs import FixedLeg, FloatLeg\nfrom rateslib.scheduling import Schedule\n\n\nclass TestFixedLeg:\n    def test_populated_resets(self):\n        fixings.add(\n            name=\"index\",\n            series=Series(\n                index=[dt(2000, 1, 1), dt(2000, 7, 1), dt(2001, 1, 1)], data=[1.0, 1.1, 1.2]\n            ),\n            state=100,\n        )\n        fixings.add(\n            name=\"fx_eurusd\", series=Series(index=[dt(1999, 12, 30)], data=[2.0]), state=100\n        )\n\n        fl = FixedLeg(\n            schedule=Schedule(dt(2000, 1, 1), \"1y\", \"S\"),\n            index_fixings=\"index\",\n            index_lag=0,\n            index_method=\"monthly\",\n            pair=\"eurusd\",\n            fx_fixings=\"fx\",\n        )\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[0].index_params.index_fixing.value == 1.1\n        assert fl.periods[0].index_params.index_base.value == 1.0\n        assert fl.periods[1].index_params.index_fixing.value == 1.2\n        assert fl.periods[1].index_params.index_base.value == 1.0\n\n        fixings.pop(\"index\")\n        fixings.pop(\"fx_eurusd\")\n        fl.reset_fixings(100)\n        assert fl.periods[0].non_deliverable_params.fx_fixing._value == NoInput(0)\n        assert fl.periods[1].non_deliverable_params.fx_fixing._value == NoInput(0)\n        assert fl.periods[0].index_params.index_fixing._value == NoInput(0)\n        assert fl.periods[0].index_params.index_base._value == NoInput(0)\n        assert fl.periods[1].index_params.index_fixing._value == NoInput(0)\n        assert fl.periods[1].index_params.index_base._value == NoInput(0)\n\n    def test_populated_at_init_no_reset(self):\n        fixings.add(\n            name=\"index\",\n            series=Series(\n                index=[dt(2000, 1, 1), dt(2000, 7, 1), dt(2001, 1, 1)], data=[1.0, 1.1, 1.2]\n            ),\n            state=100,\n        )\n        fixings.add(\n            name=\"fx_eurusd\", series=Series(index=[dt(1999, 12, 30)], data=[2.0]), state=100\n        )\n\n        fl = FixedLeg(\n            schedule=Schedule(dt(2000, 1, 1), \"1y\", \"S\"),\n            index_fixings=\"index\",\n            index_lag=0,\n            index_method=\"monthly\",\n            pair=\"eurusd\",\n            fx_fixings=\"fx\",\n        )\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[0].index_params.index_fixing.value == 1.1\n        assert fl.periods[0].index_params.index_base.value == 1.0\n        assert fl.periods[1].index_params.index_fixing.value == 1.2\n        assert fl.periods[1].index_params.index_base.value == 1.0\n\n        fixings.pop(\"index\")\n        fixings.pop(\"fx_eurusd\")\n        fl.reset_fixings(666)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[0].index_params.index_fixing.value == 1.1\n        assert fl.periods[0].index_params.index_base.value == 1.0\n        assert fl.periods[1].index_params.index_fixing.value == 1.2\n        assert fl.periods[1].index_params.index_base.value == 1.0\n\n    def test_populated_resets_notional_exchanges(self):\n        fixings.add(\n            name=\"index\",\n            series=Series(\n                index=[dt(2000, 1, 1), dt(2000, 7, 1), dt(2001, 1, 1)], data=[1.0, 1.1, 1.2]\n            ),\n            state=100,\n        )\n        fixings.add(\n            name=\"fx_eurusd\", series=Series(index=[dt(1999, 12, 30)], data=[2.0]), state=100\n        )\n\n        fl = FixedLeg(\n            schedule=Schedule(dt(2000, 1, 1), \"1y\", \"S\"),\n            index_fixings=\"index\",\n            index_lag=0,\n            index_method=\"monthly\",\n            pair=\"eurusd\",\n            fx_fixings=\"fx\",\n            initial_exchange=True,\n        )\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[-1].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[0].index_params.index_fixing.value == 1.0\n        assert fl.periods[0].index_params.index_base.value == 1.0\n        assert fl.periods[-1].index_params.index_fixing.value == 1.2\n        assert fl.periods[-1].index_params.index_base.value == 1.0\n\n        fixings.pop(\"index\")\n        fixings.pop(\"fx_eurusd\")\n        fl.reset_fixings(100)\n        assert fl.periods[0].non_deliverable_params.fx_fixing._value == NoInput(0)\n        assert fl.periods[-1].non_deliverable_params.fx_fixing._value == NoInput(0)\n        assert fl.periods[0].index_params.index_fixing._value == NoInput(0)\n        assert fl.periods[0].index_params.index_base._value == NoInput(0)\n        assert fl.periods[-1].index_params.index_fixing._value == NoInput(0)\n        assert fl.periods[-1].index_params.index_base._value == NoInput(0)\n\n\nclass TestFloatLeg:\n    def test_populated_resets_ibor(self):\n        fixings.add(\n            name=\"index\",\n            series=Series(\n                index=[dt(2000, 1, 1), dt(2000, 3, 1), dt(2000, 6, 1)], data=[1.0, 1.1, 1.2]\n            ),\n            state=100,\n        )\n        fixings.add(\n            name=\"fx_eurusd\", series=Series(index=[dt(1999, 12, 30)], data=[2.0]), state=100\n        )\n        fixings.add(\n            name=\"ibor_1M\",\n            series=Series(index=[dt(2000, 1, 1), dt(2000, 3, 1)], data=[1.0, 2.0]),\n            state=100,\n        )\n        fixings.add(\n            name=\"ibor_3M\",\n            series=Series(index=[dt(2000, 1, 1), dt(2000, 3, 1)], data=[1.1, 2.1]),\n            state=100,\n        )\n\n        fl = FloatLeg(\n            schedule=Schedule(dt(2000, 1, 1), \"5m\", \"Q\"),\n            index_fixings=\"index\",\n            index_lag=0,\n            index_method=\"monthly\",\n            pair=\"eurusd\",\n            fx_fixings=\"fx\",\n            fixing_method=\"ibor(0)\",\n            rate_fixings=\"ibor\",\n        )\n        assert fl.periods[0].rate_params.rate_fixing.value == 1.0483333333333333\n        assert fl.periods[1].rate_params.rate_fixing.value == 2.1\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[0].index_params.index_fixing.value == 1.1\n        assert fl.periods[0].index_params.index_base.value == 1.0\n        assert fl.periods[1].index_params.index_fixing.value == 1.2\n        assert fl.periods[1].index_params.index_base.value == 1.0\n        assert fl.periods[1].index_params.index_fixing.value == 1.2\n        assert fl.periods[1].index_params.index_base.value == 1.0\n\n        fixings.pop(\"index\")\n        fixings.pop(\"fx_eurusd\")\n        fixings.pop(\"ibor_1M\")\n        fixings.pop(\"ibor_3M\")\n        fixings.add(name=\"ibor_1M\", series=Series(index=[dt(1999, 1, 1)], data=[99.0]), state=100)\n        fixings.add(\n            name=\"ibor_3M\",\n            series=Series(\n                index=[\n                    dt(1999, 1, 1),\n                ],\n                data=[99.0],\n            ),\n            state=100,\n        )\n\n        fl.reset_fixings(100)\n        assert fl.periods[0].rate_params.rate_fixing.value == NoInput(0)\n        assert fl.periods[1].rate_params.rate_fixing.value == NoInput(0)\n        assert fl.periods[0].non_deliverable_params.fx_fixing._value == NoInput(0)\n        assert fl.periods[1].non_deliverable_params.fx_fixing._value == NoInput(0)\n        assert fl.periods[0].index_params.index_fixing._value == NoInput(0)\n        assert fl.periods[0].index_params.index_base._value == NoInput(0)\n        assert fl.periods[1].index_params.index_fixing._value == NoInput(0)\n        assert fl.periods[1].index_params.index_base._value == NoInput(0)\n\n    def test_populated_at_init_no_reset(self):\n        fixings.add(\n            name=\"index\",\n            series=Series(\n                index=[dt(2000, 1, 1), dt(2000, 3, 1), dt(2000, 6, 1)], data=[1.0, 1.1, 1.2]\n            ),\n            state=100,\n        )\n        fixings.add(\n            name=\"fx_eurusd\", series=Series(index=[dt(1999, 12, 30)], data=[2.0]), state=100\n        )\n        fixings.add(\n            name=\"ibor_1M\",\n            series=Series(index=[dt(2000, 1, 1), dt(2000, 3, 1)], data=[1.0, 2.0]),\n            state=100,\n        )\n        fixings.add(\n            name=\"ibor_3M\",\n            series=Series(index=[dt(2000, 1, 1), dt(2000, 3, 1)], data=[1.1, 2.1]),\n            state=100,\n        )\n\n        fl = FloatLeg(\n            schedule=Schedule(dt(2000, 1, 1), \"5m\", \"Q\"),\n            index_fixings=\"index\",\n            index_lag=0,\n            index_method=\"monthly\",\n            pair=\"eurusd\",\n            fx_fixings=\"fx\",\n            fixing_method=FloatFixingMethod.IBOR(0),\n            rate_fixings=\"ibor\",\n        )\n        assert fl.periods[0].rate_params.rate_fixing.value == 1.0483333333333333\n        assert fl.periods[1].rate_params.rate_fixing.value == 2.1\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[0].index_params.index_fixing.value == 1.1\n        assert fl.periods[0].index_params.index_base.value == 1.0\n        assert fl.periods[1].index_params.index_fixing.value == 1.2\n        assert fl.periods[1].index_params.index_base.value == 1.0\n\n        fixings.pop(\"index\")\n        fixings.pop(\"fx_eurusd\")\n        fixings.pop(\"ibor_1M\")\n        fixings.pop(\"ibor_3M\")\n        fixings.add(name=\"ibor_1M\", series=Series(index=[dt(1999, 1, 1)], data=[99.0]), state=100)\n        fixings.add(\n            name=\"ibor_3M\",\n            series=Series(\n                index=[\n                    dt(1999, 1, 1),\n                ],\n                data=[99.0],\n            ),\n            state=100,\n        )\n        fl.reset_fixings(666)\n        assert fl.periods[0].rate_params.rate_fixing.value == 1.0483333333333333\n        assert fl.periods[1].rate_params.rate_fixing.value == 2.1\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == 2.0\n        assert fl.periods[0].index_params.index_fixing.value == 1.1\n        assert fl.periods[0].index_params.index_base.value == 1.0\n        assert fl.periods[1].index_params.index_fixing.value == 1.2\n        assert fl.periods[1].index_params.index_base.value == 1.0\n"
  },
  {
    "path": "python/tests/legs/test_legs_legacy.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\nfrom datetime import datetime as dt\n\nimport numpy as np\nimport pytest\nimport rateslib.errors as err\nfrom pandas import DataFrame, Index, MultiIndex, Series, date_range, isna\nfrom pandas.testing import assert_frame_equal, assert_series_equal\nfrom rateslib import default_context, defaults, fixings\nfrom rateslib.curves import Curve\nfrom rateslib.data.fixings import FloatRateSeries, FXIndex\nfrom rateslib.default import NoInput\nfrom rateslib.dual import Dual\nfrom rateslib.enums import SpreadCompoundMethod\nfrom rateslib.enums.generics import _drb\nfrom rateslib.enums.parameters import LegMtm\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.legs import (\n    Amortization,\n    CreditPremiumLeg,\n    CreditProtectionLeg,\n    CustomLeg,\n    FixedLeg,\n    FloatLeg,\n    ZeroFixedLeg,\n    ZeroFloatLeg,\n)\nfrom rateslib.legs.amortization import _AmortizationType\nfrom rateslib.periods import (\n    Cashflow,\n    CreditPremiumPeriod,\n    CreditProtectionPeriod,\n    FixedPeriod,\n    FloatPeriod,\n    ZeroFloatPeriod,\n)\nfrom rateslib.rs import LegIndexBase\nfrom rateslib.scheduling import Frequency, Schedule, get_calendar\n\n\n@pytest.fixture\ndef curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.99,\n        dt(2022, 7, 1): 0.98,\n        dt(2022, 10, 1): 0.97,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef hazard_curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.999,\n        dt(2022, 7, 1): 0.997,\n        dt(2022, 10, 1): 0.991,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\", id=\"hazard_fixture\")\n\n\n@pytest.mark.parametrize(\n    \"Leg\",\n    [\n        FloatLeg,\n        FixedLeg,\n        ZeroFixedLeg,\n        ZeroFloatLeg,\n    ],\n)\ndef test_repr(Leg):\n    leg = Leg(schedule=Schedule(dt(2022, 1, 1), \"1y\", \"Q\"))\n    result = leg.__repr__()\n    expected = f\"<rl.{type(leg).__name__} at {hex(id(leg))}>\"\n    assert result == expected\n\n\n@pytest.mark.parametrize(\"Leg\", [FixedLeg, FloatLeg])\ndef test_repr_mtm(Leg):\n    leg = Leg(\n        schedule=Schedule(dt(2022, 1, 1), \"1y\", \"Q\"),\n        currency=\"usd\",\n        pair=\"eurusd\",\n        mtm=\"xcs\",\n        initial_exchange=True,\n    )\n    result = leg.__repr__()\n    expected = f\"<rl.{type(leg).__name__} at {hex(id(leg))}>\"\n    assert result == expected\n\n\ndef test_repr_custom():\n    period = FixedPeriod(\n        start=dt(2000, 1, 1),\n        end=dt(2000, 2, 1),\n        payment=dt(2000, 2, 1),\n        frequency=Frequency.Months(1, None),\n    )\n    leg = CustomLeg([period])\n    assert leg.__repr__() == f\"<rl.CustomLeg at {hex(id(leg))}>\"\n\n\nclass TestFloatLeg:\n    @pytest.mark.parametrize(\n        \"obj\",\n        [\n            (\n                FloatLeg(\n                    schedule=Schedule(\n                        effective=dt(2022, 1, 1),\n                        termination=dt(2022, 6, 1),\n                        payment_lag=0,\n                        frequency=\"Q\",\n                    ),\n                    notional=1e9,\n                    convention=\"Act360\",\n                    fixing_method=\"rfr_payment_delay\",\n                    spread_compound_method=\"none_simple\",\n                    currency=\"nok\",\n                )\n            ),\n            (\n                FloatLeg(\n                    schedule=Schedule(\n                        effective=dt(2022, 1, 1),\n                        termination=dt(2022, 6, 1),\n                        payment_lag=0,\n                        payment_lag_exchange=0,\n                        frequency=\"Q\",\n                    ),\n                    notional=1e9,\n                    convention=\"Act360\",\n                    fixing_method=\"rfr_payment_delay\",\n                    spread_compound_method=\"none_simple\",\n                    currency=\"nok\",\n                    initial_exchange=True,\n                    final_exchange=True,\n                )\n            ),\n        ],\n    )\n    def test_float_leg_analytic_delta_with_npv(self, curve, obj) -> None:\n        result = 5 * obj.analytic_delta(rate_curve=curve, disc_curve=curve)\n        before_npv = -obj.npv(rate_curve=curve, disc_curve=curve)\n        obj.float_spread = 5\n        after_npv = -obj.npv(rate_curve=curve, disc_curve=curve)\n        expected = after_npv - before_npv\n        assert abs(result - expected) < 1e-7\n\n    def test_float_leg_analytic_delta_with_npv_mtm_exchange(self, curve) -> None:\n        obj = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=0,\n                payment_lag_exchange=0,\n                frequency=\"Q\",\n            ),\n            convention=\"Act360\",\n            fixing_method=\"rfr_payment_delay\",\n            spread_compound_method=\"none_simple\",\n            currency=\"nok\",\n            pair=FXIndex(\"usdnok\", \"osl|fed\", 2, \"osl\", -2),\n            notional=1e8,\n            mtm=\"xcs\",\n            initial_exchange=True,\n        )\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0})\n        fxf = FXForwards(\n            fx_curves={\"usdusd\": curve, \"usdnok\": curve, \"noknok\": curve},\n            fx_rates=FXRates({\"usdnok\": 1.0}, settlement=dt(2022, 1, 1)),\n        )\n        result = 5 * obj.analytic_delta(rate_curve=curve, disc_curve=curve, fx=fxf)\n        before_npv = -obj.npv(rate_curve=curve, disc_curve=curve, fx=fxf)\n        obj.float_spread = 5\n        after_npv = -obj.npv(rate_curve=curve, disc_curve=curve, fx=fxf)\n        expected = after_npv - before_npv\n        assert abs(result - expected) < 1e-7\n\n    def test_float_leg_analytic_delta(self, curve) -> None:\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=1e9,\n            convention=\"Act360\",\n        )\n        result = float_leg.analytic_delta(rate_curve=curve)\n        assert abs(result - 41400.42965267) < 1e-7\n\n    def test_float_leg_cashflows(self, curve) -> None:\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            float_spread=NoInput(0),\n            notional=1e9,\n            convention=\"Act360\",\n        )\n        result = float_leg.cashflows(rate_curve=curve)\n        # test a couple of return elements\n        assert abs(result.loc[0, defaults.headers[\"cashflow\"]] + 6610305.76834) < 1e-4\n        assert abs(result.loc[1, defaults.headers[\"df\"]] - 0.98307) < 1e-4\n        assert abs(result.loc[1, defaults.headers[\"notional\"]] - 1e9) < 1e-7\n\n    def test_float_leg_npv(self, curve) -> None:\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            float_spread=NoInput(0),\n            notional=1e9,\n            convention=\"Act360\",\n        )\n        result = float_leg.npv(rate_curve=curve)\n        assert abs(result + 16710777.50089434) < 1e-7\n\n    def test_float_leg_fixings(self, curve) -> None:\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 2, 1),\n                termination=\"9M\",\n                frequency=\"Q\",\n                payment_lag=0,\n            ),\n            rate_fixings=[10.0, 20.0],\n        )\n        assert float_leg.periods[0].rate_params.rate_fixing.value == 10\n        assert float_leg.periods[1].rate_params.rate_fixing.value == 20\n        assert float_leg.periods[2].rate_params.rate_fixing.value is NoInput(0)\n\n    def test_float_leg_fixings2(self, curve) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_3M\", Series(index=[dt(2022, 2, 1), dt(2022, 5, 1)], data=[10.0, 20.0]))\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 2, 1),\n                termination=\"9M\",\n                frequency=\"Q\",\n                payment_lag=0,\n            ),\n            rate_fixings=name,\n            fixing_method=\"IBOR(0)\",\n        )\n        assert float_leg.periods[0].rate_params.rate_fixing.value == 10\n        assert float_leg.periods[1].rate_params.rate_fixing.value == 20\n        assert float_leg.periods[2].rate_params.rate_fixing.value is NoInput(0)\n\n    def test_float_leg_fixings_series(self, curve) -> None:\n        fixings = Series(0.5, index=date_range(dt(2021, 11, 1), dt(2022, 2, 15)))\n        float_leg = FloatLeg(\n            schedule=Schedule(dt(2021, 12, 1), \"9M\", \"M\", payment_lag=0), rate_fixings=fixings\n        )\n        assert float_leg.periods[0].rate_params.rate_fixing.value != NoInput(0)  # december fixings\n        assert float_leg.periods[1].rate_params.rate_fixing.value != NoInput(0)  # january fixings\n        assert float_leg.periods[2].rate_params.rate_fixing.value == NoInput(0)  # february fixings\n        assert float_leg.periods[4].rate_params.rate_fixing.value == NoInput(0)  # no march fixings\n\n    def test_float_leg_fixings_scalar(self, curve) -> None:\n        float_leg = FloatLeg(\n            schedule=Schedule(dt(2022, 2, 1), \"9M\", \"Q\", payment_lag=0), rate_fixings=5.0\n        )\n        assert float_leg.periods[0].rate_params.rate_fixing.value == 5.0\n        assert float_leg.periods[1].rate_params.rate_fixing.value is NoInput(0)\n        assert float_leg.periods[2].rate_params.rate_fixing.value is NoInput(0)\n\n    @pytest.mark.parametrize(\n        (\"method\"),\n        [\n            \"rfr_payment_delay\",\n            \"rfr_lockout(1)\",\n            \"rfr_observation_shift(0)\",\n        ],\n    )\n    def test_float_leg_rfr_fixings_table(self, method, curve) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            f\"{name}_1B\",\n            Series(\n                [1.19, 1.19, -8.81],\n                index=[dt(2022, 12, 28), dt(2022, 12, 29), dt(2022, 12, 30)],\n            ),\n        )\n\n        curve._set_ad_order(order=1)\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 12, 28),\n                termination=\"2M\",\n                frequency=\"M\",\n                payment_lag=0,\n            ),\n            rate_fixings=name,\n            currency=\"SEK\",\n            fixing_method=method,\n        )\n        result = float_leg.local_analytic_rate_fixings(rate_curve=curve)\n        result = result[dt(2022, 12, 28) : dt(2023, 1, 1)]\n        assert isinstance(result.iloc[0, 0], Dual)\n        data = [_.real for _ in result.iloc[0:5, 0]]\n        expected = [0, 0, 0, -0.266647, -0.266647]\n        for x, y in zip(data, expected):\n            assert abs(x - y) < 1e-6\n        fixings.pop(f\"{name}_1B\")\n\n    @pytest.mark.skip(reason=\"Unclear what this does: maybe tests an IRS fixing table?\")\n    def test_rfr_with_fixings_fixings_table_issue(self) -> None:\n        from rateslib import IRS\n\n        instruments = [\n            IRS(dt(2024, 1, 15), dt(2024, 3, 20), spec=\"eur_irs\", curves=\"estr\"),\n            IRS(dt(2024, 3, 20), dt(2024, 6, 19), spec=\"eur_irs\", curves=\"estr\"),\n            IRS(dt(2024, 6, 19), dt(2024, 9, 18), spec=\"eur_irs\", curves=\"estr\"),\n        ]\n        curve = Curve(\n            nodes={\n                dt(2024, 1, 11): 1.0,\n                dt(2024, 3, 20): 1.0,\n                dt(2024, 6, 19): 1.0,\n                dt(2024, 9, 18): 1.0,\n            },\n            calendar=\"tgt\",\n            convention=\"act360\",\n            id=\"estr\",\n        )\n        from rateslib import Solver\n\n        Solver(\n            curves=[curve],\n            instruments=instruments,\n            s=[\n                3.89800324,\n                3.63414284,\n                3.16864932,\n            ],\n            id=\"eur\",\n        )\n        fixings = Series(\n            data=[\n                3.904,\n                3.904,\n                3.904,\n                3.905,\n                3.902,\n                3.904,\n                3.906,\n                3.882,\n                3.9,\n                3.9,\n                3.899,\n                3.899,\n                3.901,\n                3.901,\n            ],\n            index=[\n                dt(2024, 1, 10),\n                dt(2024, 1, 9),\n                dt(2024, 1, 8),\n                dt(2024, 1, 5),\n                dt(2024, 1, 4),\n                dt(2024, 1, 3),\n                dt(2024, 1, 2),\n                dt(2023, 12, 29),\n                dt(2023, 12, 28),\n                dt(2023, 12, 27),\n                dt(2023, 12, 22),\n                dt(2023, 12, 21),\n                dt(2023, 12, 20),\n                dt(2023, 12, 19),\n            ],\n        )\n\n        swap = IRS(\n            dt(2023, 12, 20),\n            dt(2024, 1, 31),\n            spec=\"eur_irs\",\n            curves=\"estr\",\n            leg2_fixings=fixings,\n            notional=3e9,\n            fixed_rate=3.922,\n        )\n        result = swap.leg2.local_rate_fixings(rate_curve=curve)\n        assert result.loc[dt(2024, 1, 10), (curve.id, \"notional\")] == 0.0\n        assert abs(result.loc[dt(2024, 1, 11), (curve.id, \"notional\")] - 3006829846) < 1.0\n        assert abs(result.loc[dt(2023, 12, 20), (curve.id, \"rates\")] - 3.901) < 0.001\n\n    def test_float_leg_set_float_spread(self, curve) -> None:\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        assert float_leg.float_spread == 0.0\n        assert float_leg.periods[0].rate_params.float_spread == 0\n\n        float_leg.float_spread = 2.0\n        assert float_leg.float_spread == 2.0\n        assert float_leg.periods[0].rate_params.float_spread == 2.0\n\n    @pytest.mark.parametrize(\n        (\"method\", \"spread_method\", \"expected\"),\n        [\n            (\"ibor(2)\", NoInput(0), True),\n            (\"rfr_payment_delay\", \"none_simple\", True),\n            (\"rfr_payment_delay\", \"isda_compounding\", False),\n            (\"rfr_payment_delay\", \"isda_flat_compounding\", False),\n        ],\n    )\n    def test_is_linear(self, method, spread_method, expected) -> None:\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            fixing_method=method,\n            spread_compound_method=spread_method,\n        )\n        assert float_leg._is_linear is expected\n\n    @pytest.mark.parametrize(\n        (\"method\", \"settlement\", \"forward\", \"expected\"),\n        [\n            (\"ISDA_compounding\", NoInput(0), NoInput(0), 357.7019143401966),\n            (\"ISDA_compounding\", dt(2022, 4, 6), dt(2022, 4, 6), 580.3895480501503),\n            (\"ISDA_flat_compounding\", NoInput(0), NoInput(0), 360.65913016465225),\n            (\"ISDA_flat_compounding\", dt(2022, 4, 6), dt(2022, 4, 6), 587.64160672647),\n            (\"NONE_Simple\", NoInput(0), NoInput(0), 362.2342162),\n            (\"NONE_Simple\", NoInput(0), dt(2022, 2, 1), 360.98240826375957),\n            (\"NONE_Simple\", dt(2022, 4, 6), dt(2022, 4, 6), 590.6350781908598),\n        ],\n    )\n    def test_float_leg_spread_calculation(\n        self, method, settlement, forward, expected, curve\n    ) -> None:\n        leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=0,\n                frequency=\"Q\",\n            ),\n            notional=1e9,\n            convention=\"Act360\",\n            fixing_method=\"rfr_payment_delay\",\n            spread_compound_method=method,\n            currency=\"nok\",\n            float_spread=0,\n        )\n        base_npv = leg.npv(\n            rate_curve=curve, disc_curve=curve, forward=forward, settlement=settlement\n        )\n        result = leg.spread(\n            target_npv=-15000000 + base_npv,\n            rate_curve=curve,\n            disc_curve=curve,\n            settlement=settlement,\n            forward=forward,\n        )\n        assert abs(result - expected) < 1e-3\n        leg.float_spread = result\n        assert (\n            abs(\n                leg.npv(rate_curve=curve, disc_curve=curve, forward=forward, settlement=settlement)\n                - base_npv\n                + 15000000\n            )\n            < 2e2\n        )\n\n    def test_fixing_method_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`fixing_method`\"):\n            FloatLeg(schedule=Schedule(dt(2022, 2, 1), \"9M\", \"Q\"), fixing_method=\"bad\")\n\n    @pytest.mark.parametrize(\n        (\"eff\", \"term\", \"freq\", \"stub\", \"expected\"),\n        [\n            (\n                dt(2022, 1, 1),\n                dt(2022, 6, 15),\n                \"Q\",\n                \"ShortFront\",\n                [dt(2022, 1, 1), dt(2022, 3, 15), dt(2022, 6, 15)],\n            ),\n            (\n                dt(2022, 1, 1),\n                dt(2022, 6, 15),\n                \"Q\",\n                \"ShortBack\",\n                [dt(2022, 1, 1), dt(2022, 4, 1), dt(2022, 6, 15)],\n            ),\n            (\n                dt(2022, 1, 1),\n                dt(2022, 9, 15),\n                \"Q\",\n                \"LongFront\",\n                [dt(2022, 1, 1), dt(2022, 6, 15), dt(2022, 9, 15)],\n            ),\n            (\n                dt(2022, 1, 1),\n                dt(2022, 9, 15),\n                \"Q\",\n                \"LongBack\",\n                [dt(2022, 1, 1), dt(2022, 4, 1), dt(2022, 9, 15)],\n            ),\n        ],\n    )\n    def test_leg_periods_unadj_dates(self, eff, term, freq, stub, expected) -> None:\n        leg = FloatLeg(\n            schedule=Schedule(effective=eff, termination=term, frequency=freq, stub=stub)\n        )\n        assert leg.schedule.uschedule == expected\n\n    @pytest.mark.parametrize(\n        (\"eff\", \"term\", \"freq\", \"stub\", \"expected\"),\n        [\n            (\n                dt(2022, 1, 1),\n                dt(2022, 6, 15),\n                \"Q\",\n                \"ShortFront\",\n                [dt(2022, 1, 3), dt(2022, 3, 15), dt(2022, 6, 15)],\n            ),\n            (\n                dt(2022, 1, 1),\n                dt(2022, 6, 15),\n                \"Q\",\n                \"ShortBack\",\n                [dt(2022, 1, 3), dt(2022, 4, 1), dt(2022, 6, 15)],\n            ),\n            (\n                dt(2022, 1, 1),\n                dt(2022, 9, 15),\n                \"Q\",\n                \"LongFront\",\n                [dt(2022, 1, 3), dt(2022, 6, 15), dt(2022, 9, 15)],\n            ),\n            (\n                dt(2022, 1, 1),\n                dt(2022, 9, 15),\n                \"Q\",\n                \"LongBack\",\n                [dt(2022, 1, 3), dt(2022, 4, 1), dt(2022, 9, 15)],\n            ),\n        ],\n    )\n    def test_leg_periods_adj_dates(self, eff, term, freq, stub, expected) -> None:\n        leg = FloatLeg(\n            schedule=Schedule(\n                effective=eff, termination=term, frequency=freq, stub=stub, calendar=\"bus\"\n            )\n        )\n        assert leg.schedule.aschedule == expected\n\n    @pytest.mark.parametrize(\n        (\"eff\", \"term\", \"freq\", \"stub\", \"expected\"),\n        [\n            (\n                dt(2022, 1, 1),\n                dt(2022, 6, 15),\n                \"Q\",\n                \"ShortFront\",\n                [\n                    FloatPeriod(\n                        start=dt(2022, 1, 3),\n                        end=dt(2022, 3, 15),\n                        payment=dt(2022, 3, 17),\n                        frequency=Frequency.Months(3, None),\n                        notional=defaults.notional,\n                        convention=defaults.convention,\n                        termination=dt(2022, 6, 15),\n                    ),\n                    FloatPeriod(\n                        start=dt(2022, 3, 15),\n                        end=dt(2022, 6, 15),\n                        payment=dt(2022, 6, 17),\n                        frequency=Frequency.Months(3, None),\n                        notional=defaults.notional,\n                        convention=defaults.convention,\n                        termination=dt(2022, 6, 15),\n                    ),\n                ],\n            ),\n        ],\n    )\n    def test_leg_periods_adj_dates2(self, eff, term, freq, stub, expected) -> None:\n        # as of v2.5 rateslib no longer puts details of the period into the str REPR.\n        leg = FloatLeg(\n            schedule=Schedule(\n                effective=eff,\n                termination=term,\n                frequency=freq,\n                stub=stub,\n                payment_lag=2,\n                calendar=\"bus\",\n            )\n        )\n        for i in range(2):\n            assert leg.periods[i].__str__()[:19] == expected[i].__str__()[:19]\n\n    def test_spread_compound_method_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`spread_compound_method`\"):\n            FloatLeg(\n                schedule=Schedule(\n                    dt(2022, 2, 1),\n                    \"9M\",\n                    \"Q\",\n                ),\n                spread_compound_method=\"bad\",\n            )\n\n    def test_leg_fixings_as_2_tuple(self) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_1M\", Series([2.0, 3.0], index=[dt(2022, 6, 2), dt(2022, 7, 4)]))\n        float_leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 5, 2),\n                termination=\"4M\",\n                frequency=\"M\",\n                calendar=\"stk\",\n            ),\n            rate_fixings=(1.5, name),\n            currency=\"SEK\",\n            fixing_method=\"ibor(0)\",\n        )\n        assert float_leg.periods[0].rate_params.rate_fixing.value == 1.5\n        assert float_leg.periods[1].rate_params.rate_fixing.value == 2.0\n        assert float_leg.periods[2].rate_params.rate_fixing.value == 3.0\n        assert float_leg.periods[3].rate_params.rate_fixing.value == NoInput.blank\n        assert float_leg.periods[3].rate_params.rate_fixing.identifier == f\"{name}_1M\"\n\n    def test_ex_div(self):\n        leg = FloatLeg(schedule=Schedule(dt(2000, 1, 1), dt(2001, 1, 1), \"Q\", extra_lag=-3))\n        assert not leg.ex_div(dt(2000, 3, 29))\n        assert leg.ex_div(dt(2000, 3, 30))\n        assert leg.ex_div(dt(2000, 4, 1))\n\n    def test_mtm_xcs_type_type_sets_fx_fixing_start_initially(self):\n        fixings.add(\n            \"EURUSD_1600\",\n            Series(\n                index=[dt(2000, 4, 1), dt(2000, 4, 2), dt(2000, 7, 2)], data=[1.268, 1.27, 1.29]\n            ),\n        )\n        leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 7, 1),\n                frequency=\"Q\",\n                payment_lag=1,\n                payment_lag_exchange=0,\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            initial_exchange=True,\n            mtm=\"xcs\",\n            notional=5e6,\n            fx_fixings=(1.25, \"EURUSD_1600\"),\n        )\n        assert leg.periods[2].mtm_params.fx_fixing_start.value == 1.25\n        fixings.pop(\"EURUSD_1600\")\n\n    ## 4 types of non-deliverability\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"expected\"),\n        [\n            (\"ABCD\", 1.10),\n            (1.5, 1.5),\n            ((1.2, \"ABCD\"), 1.2),\n        ],\n    )\n    def test_non_mtm_xcs_type(self, fx_fixings, expected):\n        fixings.add(\"ABCD_EURUSD\", Series(index=[dt(1999, 12, 30)], data=[1.10]))\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 3, 1),\n                frequency=\"M\",\n                payment_lag=2,\n                payment_lag_exchange=1,\n                calendar=\"all\",\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            mtm=\"initial\",\n            initial_exchange=True,\n            final_exchange=True,\n            fx_fixings=fx_fixings,\n        )\n        # this leg has 4 periods with only one initial fixing date\n        assert fl.periods[0].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[1].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[2].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[3].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == expected\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == expected\n        assert fl.periods[2].non_deliverable_params.fx_fixing.value == expected\n        assert fl.periods[3].non_deliverable_params.fx_fixing.value == expected\n        fixings.pop(\"ABCD_EURUSD\")\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"expected\"),\n        [\n            (\"ABCDE\", [1.21, 1.31]),\n            (1.5, [1.5, NoInput(0)]),  # this is bad practice: should just supply str ID\n            ((1.5, \"ABCDE\"), [1.5, 1.31]),  # this is bad practice: should just supply str ID\n        ],\n    )\n    def test_irs_nd_type(self, fx_fixings, expected):\n        fixings.add(\n            \"ABCDE_EURUSD\",\n            Series(\n                index=[\n                    dt(1999, 12, 30),\n                    dt(2000, 1, 31),\n                    dt(2000, 2, 1),\n                    dt(2000, 2, 29),\n                    dt(2000, 3, 1),\n                ],\n                data=[1.10, 1.20, 1.21, 1.30, 1.31],\n            ),\n        )\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 3, 1),\n                frequency=\"M\",\n                payment_lag=2,\n                payment_lag_exchange=1,\n                calendar=\"all\",\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            mtm=\"payment\",\n            initial_exchange=False,\n            final_exchange=False,\n            fx_fixings=fx_fixings,\n        )\n        # this leg has 2 periods and only 2 relevant fixings dates\n        assert fl.periods[0].non_deliverable_params.fx_fixing.date == dt(2000, 2, 1)\n        assert fl.periods[1].non_deliverable_params.fx_fixing.date == dt(2000, 3, 1)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == expected[0]\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == expected[1]\n        fixings.pop(\"ABCDE_EURUSD\")\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"expected\"),\n        [\n            (\"ADE\", [1.10, 1.10, 1.20, 1.20, 1.20]),\n            (\n                1.5,\n                [1.5, 1.5, NoInput(0), NoInput(0), NoInput(0)],\n            ),  # this is bad practice: should just supply str ID\n            (\n                (1.5, \"ADE\"),\n                [1.5, 1.5, 1.20, 1.20, 1.20],\n            ),  # this is bad practice: should just supply str ID\n        ],\n    )\n    def test_mtm_xcs_nd_type(self, fx_fixings, expected):\n        fixings.add(\n            \"ADE_EURUSD\",\n            Series(\n                index=[\n                    dt(1999, 12, 30),\n                    dt(2000, 1, 31),\n                    dt(2000, 2, 1),\n                    dt(2000, 2, 29),\n                    dt(2000, 3, 1),\n                ],\n                data=[1.10, 1.20, 1.21, 1.30, 1.31],\n            ),\n        )\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 3, 1),\n                frequency=\"M\",\n                payment_lag=2,\n                payment_lag_exchange=1,\n                calendar=\"all\",\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            mtm=LegMtm.XCS,\n            initial_exchange=True,\n            final_exchange=True,\n            fx_fixings=fx_fixings,\n        )\n        # this leg has 5 periods with only two relevant fixing dates\n        assert fl.periods[0].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[1].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[2].mtm_params.fx_fixing_end.date == dt(2000, 1, 31)\n        assert fl.periods[3].non_deliverable_params.fx_fixing.date == dt(2000, 1, 31)\n        assert fl.periods[4].non_deliverable_params.fx_fixing.date == dt(2000, 1, 31)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == expected[0]\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == expected[1]\n        assert fl.periods[2].mtm_params.fx_fixing_end.value == expected[2]\n        assert fl.periods[3].non_deliverable_params.fx_fixing.value == expected[3]\n        assert fl.periods[4].non_deliverable_params.fx_fixing.value == expected[4]\n        fixings.pop(\"ADE_EURUSD\")\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"expected\"),\n        [\n            (\"AXDE\", [1.10, 1.21, 1.31, 1.30]),\n            (\n                1.5,\n                [1.5, NoInput(0), NoInput(0), NoInput(0)],\n            ),  # this is bad practice: should just supply str ID\n            (\n                (1.5, \"AXDE\"),\n                [1.5, 1.21, 1.31, 1.30],\n            ),  # this is bad practice: should just supply str ID\n        ],\n    )\n    def test_non_mtm_xcs_nd_type(self, fx_fixings, expected):\n        fixings.add(\n            \"AXDE_EURUSD\",\n            Series(\n                index=[\n                    dt(1999, 12, 30),\n                    dt(2000, 1, 31),\n                    dt(2000, 2, 1),\n                    dt(2000, 2, 29),\n                    dt(2000, 3, 1),\n                ],\n                data=[1.10, 1.20, 1.21, 1.30, 1.31],\n            ),\n        )\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 3, 1),\n                frequency=\"M\",\n                payment_lag=2,\n                payment_lag_exchange=1,\n                calendar=\"all\",\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            mtm=\"payment\",\n            initial_exchange=True,\n            final_exchange=True,\n            fx_fixings=fx_fixings,\n        )\n        # this leg has 4 periods with 3 or 4 (if lag exchange is different) relevant fixing dates.\n        assert fl.periods[0].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[1].non_deliverable_params.fx_fixing.date == dt(2000, 2, 1)\n        assert fl.periods[2].non_deliverable_params.fx_fixing.date == dt(2000, 3, 1)\n        assert fl.periods[3].non_deliverable_params.fx_fixing.date == dt(2000, 2, 29)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == expected[0]\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == expected[1]\n        assert fl.periods[2].non_deliverable_params.fx_fixing.value == expected[2]\n        assert fl.periods[3].non_deliverable_params.fx_fixing.value == expected[3]\n        fixings.pop(\"AXDE_EURUSD\")\n\n    def test_sub_zero(self):\n        # test that a Leg with a zero flag can be composed of multiple ZeroFloatPeriods\n        # e.g. quarterly payments on 7d\n        # this tests specifically a 1Y CNY IRS with Quarterly payments to CNRR007 7d rate.\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2026, 1, 21),\n                termination=dt(2027, 1, 21),\n                frequency=\"Q\",\n                calendar=\"all\",\n            ),\n            fixing_frequency=\"7d\",\n            fixing_method=\"ibor(1)\",\n            zero_periods=True,\n        )\n        curve = Curve({dt(2026, 1, 20): 1.0, dt(2027, 10, 1): 0.95})\n\n        # ensure all periods have rates\n        for zero_period in fl._regular_periods:\n            for float_period in zero_period.float_periods:\n                _ = float_period.rate(rate_curve=curve)\n\n        result = fl.local_analytic_rate_fixings(rate_curve=curve)\n        # first 4 fixings are regular: back stubs.\n        assert [\n            dt(2026, 1, 20),\n            dt(2026, 1, 27),\n            dt(2026, 2, 3),\n            dt(2026, 2, 10),\n        ] == result.index.to_list()[:4]\n        # around the July Payment date\n        assert dt(2026, 7, 13) in result.index\n        assert dt(2026, 7, 20) in result.index\n        assert dt(2026, 7, 27) in result.index\n\n        # around the October Payment date with stubs\n        assert dt(2026, 10, 12) in result.index\n        assert dt(2026, 10, 19) in result.index\n        assert dt(2026, 10, 20) in result.index\n        assert dt(2026, 10, 27) in result.index\n\n        # final fixings\n        assert dt(2027, 1, 12) in result.index\n        assert dt(2027, 1, 19) in result.index\n        assert isinstance(fl._regular_periods[0], ZeroFloatPeriod)\n\n    def test_sub_zero_bjs_calendar(self):\n        # test that a Leg with a zero flag can be composed of multiple ZeroFloatPeriods\n        # e.g. quarterly payments on 7d\n        # this tests specifically a 1Y CNY IRS with Quarterly payments to CNRR007 7d rate.\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2026, 1, 21),\n                termination=dt(2027, 1, 21),\n                frequency=\"Q\",\n                calendar=\"bjs\",\n            ),\n            fixing_frequency=\"7d\",\n            fixing_method=\"ibor(1)\",\n            fixing_series=FloatRateSeries(\n                lag=1,\n                convention=\"Act365F\",\n                calendar=\"bjs\",\n                tenors=[\"7D\"],\n                zero_period_stub=\"shortback\",\n                modifier=\"F\",\n                eom=False,\n            ),\n            zero_periods=True,\n        )\n        curve = Curve(\n            nodes={dt(2026, 1, 20): 1.0, dt(2027, 10, 1): 0.95},\n            convention=\"act365f\",\n        )\n\n        # ensure all periods have rates\n        for zero_period in fl._regular_periods:\n            for float_period in zero_period.float_periods:\n                _ = float_period.rate(rate_curve=curve)\n\n        result = fl.local_analytic_rate_fixings(rate_curve=curve)\n        # first 4 fixings are regular: back stubs.\n        assert [\n            dt(2026, 1, 20),\n            dt(2026, 1, 27),\n            dt(2026, 2, 3),\n            dt(2026, 2, 10),\n        ] == result.index.to_list()[:4]\n        # around the July Payment date\n        assert dt(2026, 7, 13) in result.index\n        assert dt(2026, 7, 20) in result.index\n        assert dt(2026, 7, 27) in result.index\n\n        # around the October Payment date with stubs\n        assert dt(2026, 10, 12) in result.index\n        assert dt(2026, 10, 19) in result.index\n        assert dt(2026, 10, 20) in result.index\n        assert dt(2026, 10, 27) in result.index\n\n        # final fixings\n        assert dt(2027, 1, 12) in result.index\n        assert dt(2027, 1, 19) in result.index\n        assert isinstance(fl._regular_periods[0], ZeroFloatPeriod)\n\n    def test_sub_zero_equivalence_with_rfr_type_rate(self):\n        # test the two representations of an object yield the same data.\n        curve = Curve(\n            nodes={dt(2026, 1, 20): 1.0, dt(2026, 3, 20): 0.99, dt(2026, 5, 20): 0.984},\n            calendar=\"nyc\",\n            convention=\"act360\",\n        )\n        regular = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2026, 1, 20),\n                termination=dt(2026, 2, 3),\n                frequency=\"7d\",\n                calendar=\"nyc\",\n                modifier=\"F\",\n            ),\n            fixing_series=\"usd_rfr\",\n            fixing_frequency=\"1b\",\n            fixing_method=\"rfr_payment_delay\",\n        )\n        zero_type = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2026, 1, 20),\n                termination=dt(2026, 2, 3),\n                frequency=\"7d\",\n                calendar=\"nyc\",\n                modifier=\"F\",\n            ),\n            fixing_series=\"usd_rfr\",\n            fixing_frequency=\"1b\",\n            fixing_method=\"rfr_payment_delay\",\n            zero_periods=True,\n        )\n\n        rates = [\n            curve.rate(dt(2026, 1, 20), dt(2026, 1, 21)),\n            curve.rate(dt(2026, 1, 21), dt(2026, 1, 22)),\n            curve.rate(dt(2026, 1, 22), dt(2026, 1, 23)),\n            curve.rate(dt(2026, 1, 23), dt(2026, 1, 26)),\n            curve.rate(dt(2026, 1, 26), dt(2026, 1, 27)),\n        ]\n        from math import prod\n\n        rate = prod(\n            [\n                1 + r / 100 * d\n                for (r, d) in zip(rates, [1 / 360, 1 / 360, 1 / 360, 3 / 360, 1 / 360])\n            ]\n        )\n        rate = (rate - 1) * 36000 / 7\n\n        rate1 = regular.periods[0].rate(rate_curve=curve)\n        rate2 = zero_type.periods[0].rate(rate_curve=curve)\n        assert abs(rate1 - rate) < 1e-8\n        assert abs(rate2 - rate) < 1e-8\n\n        rates2 = [_.rate(rate_curve=curve) for _ in zero_type.periods[0].float_periods]\n        assert all(abs(x - y) < 1e-10 for (x, y) in zip(rates, rates2))\n\n    def test_sub_zero_equivalence_with_rfr_type_rate_with_fixings(self):\n        # test the two representations of an object yield the same data.\n        name = str(hash(os.urandom(3)))\n        fixings.add(\n            name + \"_1B\", Series(index=[dt(2026, 1, 20), dt(2026, 1, 21)], data=[10.0, 12.0])\n        )\n        curve = Curve(\n            nodes={dt(2026, 1, 20): 1.0, dt(2026, 3, 20): 0.99, dt(2026, 5, 20): 0.984},\n            calendar=\"nyc\",\n            convention=\"act360\",\n        )\n        regular = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2026, 1, 20),\n                termination=dt(2026, 2, 3),\n                frequency=\"7d\",\n                calendar=\"nyc\",\n                modifier=\"F\",\n            ),\n            fixing_series=\"usd_rfr\",\n            fixing_frequency=\"1b\",\n            fixing_method=\"rfr_payment_delay\",\n            rate_fixings=name,\n        )\n        zero_type = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2026, 1, 20),\n                termination=dt(2026, 2, 3),\n                frequency=\"7d\",\n                calendar=\"nyc\",\n                modifier=\"F\",\n            ),\n            fixing_series=\"usd_rfr\",\n            fixing_frequency=\"1b\",\n            fixing_method=\"rfr_payment_delay\",\n            zero_periods=True,\n            rate_fixings=name,\n        )\n\n        rates = [\n            # curve.rate(dt(2026, 1, 20), dt(2026, 1, 21)),\n            # curve.rate(dt(2026, 1, 21), dt(2026, 1, 22)),\n            10.0,\n            12.0,\n            curve.rate(dt(2026, 1, 22), dt(2026, 1, 23)),\n            curve.rate(dt(2026, 1, 23), dt(2026, 1, 26)),\n            curve.rate(dt(2026, 1, 26), dt(2026, 1, 27)),\n        ]\n        from math import prod\n\n        rate = prod(\n            [\n                1 + r / 100 * d\n                for (r, d) in zip(rates, [1 / 360, 1 / 360, 1 / 360, 3 / 360, 1 / 360])\n            ]\n        )\n        rate = (rate - 1) * 36000 / 7\n\n        rate1 = regular.periods[0].rate(rate_curve=curve)\n        rate2 = zero_type.periods[0].rate(rate_curve=curve)\n        assert abs(rate1 - rate) < 1e-8\n        assert abs(rate2 - rate) < 1e-8\n\n        rates2 = [_.rate(rate_curve=curve) for _ in zero_type.periods[0].float_periods]\n        assert all(abs(x - y) < 1e-10 for (x, y) in zip(rates, rates2))\n        fixings.pop(name + \"_1B\")\n\n    def test_sub_zero_index_dates(self):\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2026, 1, 20),\n                termination=dt(2026, 2, 3),\n                frequency=\"7d\",\n                calendar=\"nyc\",\n                modifier=\"F\",\n            ),\n            fixing_series=\"usd_rfr\",\n            fixing_frequency=\"1b\",\n            fixing_method=\"rfr_payment_delay\",\n            zero_periods=True,\n            index_base=300.0,\n        )\n        assert len(fl.periods) == 2\n        assert fl.periods[0].index_params.index_fixing.date == dt(2026, 1, 27)\n        assert fl.periods[1].index_params.index_fixing.date == dt(2026, 2, 3)\n        assert fl.periods[0].index_params.index_base.date == dt(2026, 1, 20)\n        assert fl.periods[1].index_params.index_base.date == dt(2026, 1, 20)\n\n    def test_sub_zero_spread_compounding(self):\n        # test that a spread under `zero_periods` is added to eahc rate individually prior to\n        # compounding. The spread compound method only operates at the Period level which\n        # is specific for a ZeroFloatPeriod.\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2026, 1, 20),\n                termination=dt(2027, 1, 20),\n                frequency=\"A\",\n                calendar=\"all\",\n                modifier=\"F\",\n            ),\n            fixing_frequency=\"S\",\n            fixing_method=\"ibor(0)\",\n            rate_fixings=[[5.0, 5.5]],\n            float_spread=50.0,\n            zero_periods=True,\n            spread_compound_method=SpreadCompoundMethod.NoneSimple,\n        )\n        result = fl.periods[0].rate()\n        expected = (\n            ((1 + 181 / 36000 * (5.0 + 0.5)) * (1 + 184 / 36000 * (5.5 + 0.5)) - 1) * 36000 / 365\n        )\n        assert abs(result - expected) < 1e-10\n\n    def test_leg_index_base_period_on_period(self):\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 7),\n                termination=dt(2000, 3, 7),\n                frequency=\"M\",\n                calendar=\"all\",\n            ),\n            index_fixings=\"some\",\n            index_lag=0,\n            index_base_type=LegIndexBase.PeriodOnPeriod,\n        )\n        assert fl.periods[0].index_params.index_base.date == dt(2000, 1, 7)\n        assert fl.periods[1].index_params.index_base.date == dt(2000, 2, 7)\n\n    def test_index_only_all_periods(self, curve):\n        name = str(hash(os.urandom(2)))\n        fixings.add(\n            name,\n            Series(index=[dt(2022, 1, 1), dt(2022, 2, 1), dt(2022, 3, 1)], data=[1.0, 1.1, 1.3]),\n        )\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 7),\n                termination=dt(2022, 3, 7),\n                frequency=\"M\",\n                calendar=\"all\",\n            ),\n            index_fixings=name,\n            index_lag=0,\n            index_method=\"monthly\",\n            index_base_type=LegIndexBase.PeriodOnPeriod,\n            initial_exchange=True,\n            index_only=True,\n        )\n        result = fl.cashflows(rate_curve=curve, disc_curve=curve)\n\n        # the rates are approximately 4% and in each period inflation increases around 10% and 20%.\n        # this means the `index only` amount of each cashflows are approximately below:\n        expected = [0.0, -346.7781, -569.3935, -181818.1818]\n        for i in range(4):\n            assert abs(result.loc[i, \"Cashflow\"] - expected[i]) < 1e-2\n\n    def test_period_on_period_zero_periods(self):\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 7),\n                termination=dt(2000, 3, 7),\n                frequency=\"M\",\n                calendar=\"all\",\n            ),\n            zero_periods=True,\n            fixing_frequency=\"7d\",\n            index_base_type=LegIndexBase.PeriodOnPeriod,\n            index_lag=2,\n        )\n        assert fl.periods[0].index_params.index_base.date == dt(2000, 1, 7)\n        assert fl.periods[1].index_params.index_base.date == dt(2000, 2, 7)\n\n\nclass TestZeroFloatLeg:\n    def test_zero_float_leg_set_float_spread(self, curve) -> None:\n        float_leg = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        assert float_leg.float_spread == 0.0\n        assert float_leg.periods[0].float_spread == 0.0\n\n        float_leg.float_spread = 2.0\n        assert float_leg.float_spread == 2.0\n        assert float_leg.periods[0].float_spread == 2.0\n\n    def test_with_fixings(self):\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            f\"{name}_3m\",\n            Series(\n                index=[dt(2022, 1, 1), dt(2022, 2, 1), dt(2022, 5, 1)],\n                data=[1.0, 2.0, 3.0],\n            ),\n        )\n        fixings.add(\n            f\"{name}_1m\",\n            Series(\n                index=[dt(2022, 1, 1), dt(2022, 2, 1), dt(2022, 5, 1)],\n                data=[5.0, 0.0, 0.0],\n            ),\n        )\n        leg = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 8, 1),\n                front_stub=dt(2022, 2, 1),\n                frequency=\"Q\",\n                calendar=\"all\",\n            ),\n            fixing_method=\"ibor(0)\",\n            rate_fixings=name,\n        )\n        expected = [5.0, 2.0, 3.0]\n        for i, period in enumerate(leg.periods[0]._float_periods):\n            assert period.rate_params.rate_fixing.value == expected[i]\n\n        result = leg.periods[0].rate()\n        assert abs(result - 2.8743158337825925) < 1e-8\n\n    def test_zero_float_leg_dcf(self) -> None:\n        ftl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        p = ftl.periods[0]\n        result = p.dcf\n        expected = p._float_periods[0].period_params.dcf + p._float_periods[1].period_params.dcf\n        assert result == expected\n\n    def test_zero_float_leg_cashflow(self, curve) -> None:\n        ftl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            float_spread=500,\n        )\n        p = ftl.periods[0]\n        result = p.try_unindexed_reference_cashflow(rate_curve=curve).unwrap()\n        expected = (\n            1\n            + p._float_periods[0].period_params.dcf\n            * p._float_periods[0].rate(rate_curve=curve)\n            / 100\n        )\n        expected *= (\n            1\n            + p._float_periods[1].period_params.dcf\n            * p._float_periods[1].rate(rate_curve=curve)\n            / 100\n        )\n        expected = (expected - 1) * 1e9\n        assert abs(result - expected) < 1e-9\n\n    def test_zero_float_leg_cashflows(self, curve) -> None:\n        ftl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            float_spread=500,\n        )\n        result = ftl.cashflows(rate_curve=curve)\n        expected = DataFrame(\n            {\n                \"Type\": [\"ZeroFloatPeriod\"],\n                \"Acc Start\": [dt(2022, 1, 1)],\n                \"Acc End\": [dt(2022, 6, 1)],\n                \"DCF\": [0.419444444444444],\n                \"Spread\": [500.0],\n            },\n        )\n        assert_frame_equal(result[[\"Type\", \"Acc Start\", \"Acc End\", \"DCF\", \"Spread\"]], expected)\n\n    def test_zero_float_leg_npv(self, curve) -> None:\n        ftl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        result = ftl.npv(rate_curve=curve)\n        expected = 16710778.891147703\n        assert abs(result - expected) < 1e-2\n        result2 = ftl.npv(rate_curve=curve, local=True)\n        assert abs(result2[\"usd\"] - expected) < 1e-2\n\n    def test_cashflows_none(self) -> None:\n        ftl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        result = ftl.cashflows()\n        assert result.iloc[0].to_dict()[defaults.headers[\"npv\"]] is None\n        assert result.iloc[0].to_dict()[defaults.headers[\"npv_fx\"]] is None\n\n    def test_amortization_raises(self) -> None:\n        with pytest.raises(TypeError, match=\"unexpected keyword argument\"):\n            ZeroFloatLeg(\n                schedule=Schedule(\n                    effective=dt(2022, 1, 1),\n                    termination=dt(2022, 6, 1),\n                    payment_lag=2,\n                    frequency=\"Q\",\n                ),\n                notional=-1e9,\n                convention=\"Act360\",\n                amortization=1.0,\n            )\n\n    def test_rfr_fixings_table(self, curve) -> None:\n        zfl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 10, 1),\n                payment_lag=0,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        # fl = FloatLeg(\n        #     effective=dt(2022, 1, 1),\n        #     termination=dt(2022, 10, 1),\n        #     payment_lag=0,\n        #     notional=-1e9,\n        #     convention=\"Act360\",\n        #     frequency=\"Q\",\n        # )\n        result = zfl.local_analytic_rate_fixings(rate_curve=curve)\n        # compare = fl.fixings_table(curve)\n        for i in range(len(result.index)):\n            # consistent risk throught the compounded leg\n            assert abs(result.iloc[i, 0] - 277.75) < 1e-1\n\n    def test_ibor_fixings_table(self, curve) -> None:\n        zfl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 10, 1),\n                payment_lag=0,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            fixing_method=\"ibor(0)\",\n        )\n        result = zfl.local_analytic_rate_fixings(rate_curve=curve)\n        assert abs(result.iloc[0, 0] - 24750) < 1e-3\n        assert abs(result.iloc[1, 0] - 25022.4466) < 1e-2\n        assert abs(result.iloc[2, 0] - 25294.7845) < 1e-2\n\n    def test_ibor_stub_fixings_table(self, curve) -> None:\n        curve2 = curve.copy()\n        curve2._id = \"3mIBOR\"\n        curve._id = \"1mIBOR\"\n        zfl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 9, 1),\n                payment_lag=0,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            fixing_method=\"ibor(0)\",\n        )\n        result = zfl.local_analytic_rate_fixings(\n            rate_curve={\"1m\": curve, \"3m\": curve2}, disc_curve=curve\n        )\n        assert abs(result.iloc[0, 0] - 8554.562) < 1e-2\n        assert abs(result.iloc[0, 1] - 7726.701) < 1e-2\n        assert isna(result.iloc[1, 0])\n        assert abs(result.iloc[2, 1] - 25294.7235) < 1e-3\n\n    @pytest.mark.parametrize(\n        \"fixings\", [[2.0, 2.5], Series([2.0, 2.5], index=[dt(2021, 7, 1), dt(2021, 10, 1)])]\n    )\n    def test_ibor_fixings_table_after_known_fixings(self, curve, fixings) -> None:\n        curve2 = curve.copy()\n        curve2._id = \"3mIBOR\"\n        curve._id = \"1mIBOR\"\n        zfl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2021, 7, 1),\n                termination=dt(2022, 9, 1),\n                payment_lag=0,\n                frequency=\"Q\",\n                stub=\"shortBack\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            fixing_method=\"ibor(0)\",\n            rate_fixings=fixings,\n        )\n        result = zfl.local_analytic_rate_fixings(\n            rate_curve={\"1m\": curve, \"3m\": curve2}, disc_curve=curve\n        )\n        assert abs(result.iloc[0, 0] - 0) < 1e-2\n        assert abs(result.iloc[1, 0] - 0) < 1e-2\n        assert isna(result.iloc[0, 1])\n        assert abs(result.iloc[4, 0] - 8792.231) < 1e-2\n        assert abs(result.iloc[4, 1] - 8508.6111) < 1e-3\n\n    def test_frequency_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`frequency` for a ZeroFloatLeg should not be 'Z'\"):\n            ZeroFloatLeg(\n                schedule=Schedule(\n                    effective=dt(2022, 1, 1),\n                    termination=\"5y\",\n                    payment_lag=0,\n                    frequency=\"Z\",\n                ),\n                notional=-1e8,\n                convention=\"ActActISDA\",\n            )\n\n    def test_zero_float_leg_analytic_delta(self, curve) -> None:\n        zfl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            float_spread=1.0,\n            fixing_series=FloatRateSeries(\n                lag=0,\n                calendar=\"all\",\n                modifier=\"f\",\n                convention=\"act360\",\n                eom=False,\n            ),\n        )\n        result = zfl.analytic_delta(rate_curve=curve)\n        expected = -47914.3660\n\n        assert abs(result - expected) < 1e-3\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"forward\", \"exp\"),\n        [\n            (NoInput(0), NoInput(0), 0.71008),\n            (NoInput(0), dt(2023, 1, 1), -0.11739),\n            (dt(2026, 1, 1), dt(2026, 1, 1), -2.40765),\n        ],\n    )\n    def test_zero_float_spread_calc(self, settlement, forward, exp, curve) -> None:\n        rate_curve = curve.shift(25)\n        zfl = ZeroFloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"Act360\",\n            fixing_method=\"ibor(2)\",\n        )\n        tgt_npv = 25000000 * curve[dt(2027, 1, 1)]\n        result = zfl.spread(\n            target_npv=tgt_npv,\n            rate_curve=rate_curve,\n            disc_curve=curve,\n            settlement=settlement,\n            forward=forward,\n        )\n\n        zfl.float_spread = result\n        tested = zfl.local_npv(\n            rate_curve=rate_curve,\n            disc_curve=curve,\n            settlement=settlement,\n            forward=forward,\n        )\n        assert abs(result / 100 - exp) < 1e-3\n        assert abs(tgt_npv - tested) < 1e-3\n\n\nclass TestZeroFixedLeg:\n    @pytest.mark.parametrize(\n        (\"freq\", \"cash\", \"rate\"),\n        [\n            (\"A\", 13140821.29, 2.50),\n            (\"S\", 13227083.80, 2.50),\n            (\"A\", None, NoInput(0)),\n        ],\n    )\n    def test_zero_fixed_leg_cashflows(self, freq, cash, rate, curve) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=freq,\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=rate,\n        )\n        result = zfl.cashflows(disc_curve=curve)\n        expected = DataFrame(\n            {\n                \"Type\": [\"ZeroFixedPeriod\"],\n                \"Acc Start\": [dt(2022, 1, 1)],\n                \"Acc End\": [dt(2027, 1, 1)],\n                \"DCF\": [5.0],\n                \"Rate\": [_drb(None, rate)],\n                \"Cashflow\": [cash],\n            },\n        )\n        assert_frame_equal(\n            result[[\"Type\", \"Acc Start\", \"Acc End\", \"DCF\", \"Rate\", \"Cashflow\"]],\n            expected,\n            rtol=1e-3,\n        )\n\n    def test_zero_fixed_leg_cashflows_cal(self, curve) -> None:\n        # assert stated cashflows accrual dates are adjusted according to calendar\n        # GH561/562\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2024, 12, 15),\n                termination=\"5y\",\n                payment_lag=0,\n                calendar=\"tgt\",\n                modifier=\"mf\",\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=2.0,\n        )\n        result = zfl.cashflows(disc_curve=curve)\n        expected = DataFrame(\n            {\n                \"Type\": [\"ZeroFixedPeriod\"],\n                \"Acc Start\": [dt(2024, 12, 16)],\n                \"Acc End\": [dt(2029, 12, 17)],\n                \"DCF\": [5.0],\n                \"Rate\": [2.0],\n            },\n        )\n        assert_frame_equal(\n            result[[\"Type\", \"Acc Start\", \"Acc End\", \"DCF\", \"Rate\"]],\n            expected,\n            rtol=1e-3,\n        )\n\n    def test_zero_fixed_leg_npv(self, curve) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=2.5,\n        )\n        result = zfl.npv(disc_curve=curve)\n        expected = 13140821.29 * curve[dt(2027, 1, 1)]\n        assert abs(result - expected) < 1e-2\n        result2 = zfl.npv(disc_curve=curve, local=True)\n        assert abs(result2[\"usd\"] - expected) < 1e-2\n\n    def test_zero_fixed_leg_analytic_delta(self, curve) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=2.5,\n        )\n        result2 = zfl.analytic_delta(disc_curve=curve)\n        assert abs(result2 + 45024.1974) < 1e-3\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"forward\", \"exp\"),\n        [\n            (NoInput(0), NoInput(0), 2.50),\n            (NoInput(0), dt(2023, 1, 1), 2.404826),\n            (dt(2026, 1, 1), NoInput(0), 2.139550),\n        ],\n    )\n    def test_zero_fixed_spread(self, settlement, forward, exp, curve) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=NoInput(0),\n        )\n        result = zfl.spread(\n            target_npv=13140821.29 * curve[dt(2027, 1, 1)],\n            rate_curve=NoInput(0),\n            disc_curve=curve,\n            settlement=settlement,\n            forward=forward,\n        )\n        assert abs(result / 100 - exp) < 1e-3\n\n    @pytest.mark.parametrize(\"final_exchange\", [False, True])\n    def test_zero_fixed_spread_exchanges(self, curve, final_exchange) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 5),\n                termination=\"8m\",\n                payment_lag=0,\n                frequency=\"M\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            final_exchange=final_exchange,\n            fixed_rate=NoInput(0),\n        )\n        result = zfl.spread(\n            target_npv=50000.0 + 1e8 * curve[dt(2022, 9, 5)] * final_exchange, rate_curve=curve\n        )\n        expected = 7.718420018560934  # bps\n        assert abs(result - expected) < 1e-8\n\n        zfl.fixed_rate = expected / 100.0\n        result = zfl.npv(rate_curve=curve)\n        assert abs(result - (50000.0 + 1e8 * curve[dt(2022, 9, 5)] * final_exchange)) < 1e-7\n\n    def test_zero_fixed_spread_raises_settlement(self, curve) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=NoInput(0),\n        )\n        with pytest.raises(ZeroDivisionError):\n            zfl.spread(\n                target_npv=13140821.29 * curve[dt(2027, 1, 1)],\n                rate_curve=NoInput(0),\n                disc_curve=curve,\n                settlement=dt(2029, 1, 1),\n                forward=NoInput(0),\n            )\n\n    @pytest.mark.parametrize(\"final_exchange\", [False, True])\n    def test_zero_fixed_spread_indexed(self, curve, final_exchange) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=NoInput(0),\n            final_exchange=final_exchange,\n            index_base=100.0,\n            index_fixings=110.0,\n        )\n        target_npv = (13140821.29 + 1e8 * 1.1 * final_exchange) * curve[dt(2027, 1, 1)]\n        result = zfl.spread(\n            target_npv=target_npv,\n            rate_curve=NoInput(0),\n            disc_curve=curve,\n        )\n        assert abs(result / 100 - 2.2826266057484057) < 1e-3\n\n        zfl.fixed_rate = result / 100.0\n        result = zfl.npv(rate_curve=curve)\n        assert abs(result - target_npv) < 1e-7\n\n    @pytest.mark.parametrize(\"final_exchange\", [False, True])\n    def test_zero_fixed_spread_non_deliverable(self, curve, final_exchange) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=NoInput(0),\n            currency=\"usd\",\n            final_exchange=final_exchange,\n            pair=\"eurusd\",\n            fx_fixings=2.0,\n        )\n        target_npv = (13140821.29 + 1e8 * 2.0 * final_exchange) * curve[dt(2027, 1, 1)]\n        result = zfl.spread(\n            target_npv=target_npv,\n            rate_curve=NoInput(0),\n            disc_curve=curve,\n        )\n        assert abs(result / 100 - 1.2808477472765924) < 1e-3\n\n        zfl.fixed_rate = result / 100.0\n        result = zfl.npv(rate_curve=curve)\n        assert abs(result - target_npv) < 1e-7\n\n    def test_amortization_raises(self) -> None:\n        with pytest.raises(TypeError, match=\"unexpected keyword argument 'amortization'\"):\n            ZeroFixedLeg(\n                schedule=Schedule(\n                    effective=dt(2022, 1, 1),\n                    termination=\"5y\",\n                    payment_lag=0,\n                    frequency=\"A\",\n                ),\n                notional=-1e8,\n                convention=\"ActActISDA\",\n                fixed_rate=NoInput(0),\n                amortization=1.0,\n            )\n\n    def test_frequency_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`frequency` for a ZeroFixedLeg should not be 'Z'\"):\n            ZeroFixedLeg(\n                schedule=Schedule(\n                    effective=dt(2022, 1, 1),\n                    termination=\"5y\",\n                    payment_lag=0,\n                    frequency=\"Z\",\n                ),\n                notional=-1e8,\n                convention=\"ActActISDA\",\n                fixed_rate=NoInput(0),\n            )\n\n    def test_analytic_delta_no_fixed_rate(self, curve) -> None:\n        zfl = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"5y\",\n                payment_lag=0,\n                frequency=\"A\",\n            ),\n            notional=-1e8,\n            convention=\"ActActISDA\",\n            fixed_rate=NoInput(0),\n        )\n        with pytest.raises(ValueError, match=\"A `fixed_rate` must be set for a \"):\n            zfl.analytic_delta(disc_curve=curve)\n\n\nclass TestZeroIndexLeg:\n    @pytest.mark.parametrize(\n        (\"index_base\", \"index_fixings\", \"meth\", \"exp\"),\n        [\n            (NoInput(0), NoInput(0), \"monthly\", -61855.670),\n            (NoInput(0), NoInput(0), \"daily\", -61782.379),\n            (100.0, NoInput(0), \"monthly\", -61855.670),\n            (NoInput(0), 110.0, \"monthly\", -100000.0),\n            (NoInput(0), 110.0, \"daily\", -98696.645),\n            (100.0, 110.0, \"monthly\", -100000.0),\n            (100.0, 110.0, \"daily\", -100000.0),\n        ],\n    )\n    def test_zero_index_cashflow(self, index_base, index_fixings, meth, exp) -> None:\n        index_curve = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.97,\n            },\n            index_base=100.0,\n            index_lag=3,\n            interpolation=\"linear_index\",\n        )\n        zil = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 15),\n                termination=\"2Y\",\n                frequency=\"A\",\n            ),\n            fixed_rate=0.0,\n            convention=\"1+\",\n            index_base=index_base,\n            index_fixings=index_fixings,\n            index_method=meth,\n            final_exchange=True,\n            index_only=True,\n        )\n        result = zil.cashflows(index_curve=index_curve).loc[1, \"Cashflow\"]\n        assert abs(result - exp) < 1e-3\n\n    @pytest.mark.skip(reason=\"v2.2 no longer permits fixing setting\")\n    def test_set_index_leg_after_init(self) -> None:\n        leg = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 3, 15),\n                termination=\"9M\",\n                frequency=\"Q\",\n                payment_lag=0,\n            ),\n            convention=\"1+\",\n            notional=40e6,\n            index_base=None,\n        )\n        for period in leg.periods[:1]:\n            assert period.index_base is None\n        leg.index_base = 205.0\n        for period in leg.periods[:1]:\n            assert period.index_base == 205.0\n\n    # The following test no longer passes after ZeroIndexLeg removed from use.\n    # def test_zero_analytic_delta(self, curve) -> None:\n    #     zil = ZeroFixedLeg(\n    #         schedule=Schedule(\n    #             effective=dt(2022, 1, 15),\n    #             termination=\"2Y\",\n    #             frequency=\"A\",\n    #         ),\n    #         convention=\"1+\",\n    #         index_lag=0,\n    #         index_base=100.0,\n    #         index_fixings=110.0,\n    #         index_only=True,\n    #         final_exchange=True,\n    #         fixed_rate=0.0,\n    #     )\n    #     assert zil.analytic_delta(disc_curve=curve) == 0.0\n\n    def test_cashflows(self) -> None:\n        index_curve = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2023, 1, 1): 0.97,\n            },\n            index_base=100.0,\n            index_lag=3,\n            interpolation=\"linear_index\",\n        )\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.97})\n        zil = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 15),\n                termination=\"2Y\",\n                frequency=\"A\",\n                payment_lag=0,\n                payment_lag_exchange=0,\n            ),\n            convention=\"1+\",\n            index_lag=3,\n            index_method=\"curve\",\n            index_only=True,\n            fixed_rate=0.0,\n            final_exchange=True,\n        )\n        result = zil.cashflows(index_curve=index_curve, disc_curve=curve)\n        expected = DataFrame(\n            {\n                \"Type\": [\"ZeroFixedPeriod\", \"Cashflow\"],  #  [\"ZeroIndexLeg\"],\n                \"Notional\": [1000000.0, 1000000.0],\n                \"Unindexed Cashflow\": [-0.0, -1000000.0],\n                \"Index Base\": [100.11863, 100.11863],\n                \"Index Ratio\": [1.06178, 1.06178],\n                \"Cashflow\": [0.0, -61782.379],\n                \"NPV\": [0.0, -58063.1659],  # [-58053.47605],\n            },\n        )\n        assert_frame_equal(\n            result[\n                [\n                    \"Type\",\n                    \"Notional\",\n                    \"Unindexed Cashflow\",\n                    \"Index Base\",\n                    \"Index Ratio\",\n                    \"Cashflow\",\n                    \"NPV\",\n                ]\n            ],\n            expected,\n            rtol=1e-3,\n        )\n\n    @pytest.mark.parametrize(\"only\", [True, False])\n    def test_three_ways(self, only):\n        # A Zero Index Legs can also be created in three ways.\n        one = ZeroFixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"2Y\",\n                frequency=\"A\",\n                payment_lag=0,\n                payment_lag_exchange=0,\n            ),\n            fixed_rate=0.0,\n            index_base=100.0,\n            index_fixings=110.0,\n            index_only=only,\n            final_exchange=True,\n        )\n        result1 = one.cashflows().loc[1, \"Cashflow\"]\n\n        two = Cashflow(\n            payment=dt(2024, 1, 1),\n            notional=1e6,\n            index_base=100.0,\n            index_fixings=110.0,\n            index_only=only,\n        )\n        result2 = two.cashflows()[\"Cashflow\"]\n\n        three = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=\"2Y\",\n                frequency=\"Z\",\n                payment_lag=0,\n                payment_lag_exchange=0,\n            ),\n            fixed_rate=0.0,\n            index_base=100.0,\n            index_fixings=110.0,\n            index_only=only,\n            final_exchange=True,\n        )\n        result3 = three.cashflows().loc[1, \"Cashflow\"]\n\n        # four = ZeroIndexLeg(\n        #     schedule=Schedule(\n        #         effective=dt(2022, 1, 1),\n        #         termination=\"2Y\",\n        #         frequency=\"Z\",\n        #         payment_lag=0,\n        #         payment_lag_exchange=0,\n        #     ),\n        #     index_base=100.0,\n        #     index_fixings=110.0,\n        #     final_exchange=not only,\n        # )\n        # result4 = four.cashflows().loc[0, \"Cashflow\"]\n\n        assert abs(result1 - result2) < 1e-8\n        assert abs(result1 - result3) < 1e-8\n        # assert abs(result1 - result4) < 1e-8\n\n    @pytest.mark.parametrize(\n        (\"ini\", \"final\", \"mtm\", \"lenn\", \"nd_dt\", \"cf\"),\n        [\n            (False, False, \"initial\", 2, dt(2000, 1, 1), 500e3 * 2.0),\n            (False, False, \"payment\", 2, dt(2001, 1, 1), 500e3 * 3.0),\n            (False, True, \"initial\", 2, dt(2000, 1, 1), 1.5e6 * 2.0),\n            (False, True, \"payment\", 2, dt(2001, 1, 1), 1.5e6 * 3.0),\n            # (True, False, False, 2, dt(2000, 1, 1)), # final exch True by default\n            # (True, False, True, 2, dt(2000, 1, 1)),  # final exch True by default\n            (True, True, \"initial\", 3, dt(2000, 1, 1), 1.5e6 * 2.0),\n            (True, True, \"payment\", 3, dt(2001, 1, 1), 1.5e6 * 3.0),\n        ],\n    )\n    def test_attributes(self, ini, final, mtm, lenn, nd_dt, cf) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name, Series(index=[dt(2000, 1, 1), dt(2001, 1, 1)], data=[10.0, 15.0]))\n        fixings.add(\n            name + \"fx_EURUSD\", Series(index=[dt(1999, 12, 30), dt(2000, 12, 28)], data=[2.0, 3.0])\n        )\n        leg = ZeroFixedLeg(\n            schedule=Schedule(effective=dt(2000, 1, 1), termination=dt(2001, 1, 1), frequency=\"A\"),\n            currency=\"usd\",\n            initial_exchange=ini,\n            final_exchange=True,\n            pair=\"eurusd\",\n            mtm=mtm,\n            fx_fixings=name + \"fx\",\n            index_lag=0,\n            index_fixings=name,\n            notional=-1e6,\n            index_only=not final,\n            fixed_rate=0.0,\n        )\n        assert len(leg.periods) == lenn\n        assert leg.periods[-1].non_deliverable_params.delivery == nd_dt\n        assert leg.periods[-1].non_deliverable_params.publication == get_calendar(\n            \"ldn\"\n        ).lag_bus_days(nd_dt, -2, True)\n        assert leg.periods[-1].cashflow() == cf\n        fixings.pop(name)\n        fixings.pop(name + \"fx_EURUSD\")\n\n\nclass TestFloatLegExchange:\n    @pytest.mark.skip(reason=\"v 2.2 removed ability to mutate notional\")\n    def test_float_leg_exchange_notional_setter(self) -> None:\n        float_leg_exc = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            initial_exchange=True,\n            final_exchange=True,\n        )\n        float_leg_exc.notional = 200\n        assert float_leg_exc.notional == 200\n\n    @pytest.mark.skip(reason=\"v 2.2 removed ability to mutate amortisation.\")\n    def test_float_leg_exchange_amortization_setter(self) -> None:\n        float_leg_exc = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 10, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1000,\n            convention=\"Act360\",\n            initial_exchange=True,\n            final_exchange=True,\n        )\n        float_leg_exc.amortization = -200\n\n        cashflows = [2, 4, 6]\n        cash_notionals = [None, -200, None, -200, None, -600]\n        fixed_notionals = [None, -1000, None, -800, None, -600]\n        for i in cashflows:\n            assert isinstance(float_leg_exc.periods[i], Cashflow)\n            assert float_leg_exc.periods[i].notional == cash_notionals[i - 1]\n\n            assert isinstance(float_leg_exc.periods[i - 1], FloatPeriod)\n            assert float_leg_exc.periods[i - 1].notional == fixed_notionals[i - 1]\n\n    def test_float_leg_exchange_set_float_spread(self) -> None:\n        float_leg_exc = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 10, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1000,\n            convention=\"Act360\",\n            initial_exchange=True,\n            final_exchange=True,\n        )\n        assert float_leg_exc.float_spread == 0.0\n        float_leg_exc.float_spread = 2.0\n        assert float_leg_exc.float_spread == 2.0\n        for period in float_leg_exc.periods:\n            if isinstance(period, FloatPeriod):\n                period.rate_params.float_spread == 2.0\n\n    def test_float_leg_exchange_amortization(self, curve) -> None:\n        leg = FloatLeg(\n            schedule=Schedule(\n                dt(2022, 1, 1),\n                dt(2023, 1, 1),\n                \"Q\",\n                payment_lag=0,\n            ),\n            notional=5e6,\n            amortization=1e6,\n            initial_exchange=True,\n            final_exchange=True,\n        )\n        assert len(leg.periods) == 9\n        for i in [0, 2, 4, 6, 8]:\n            assert type(leg.periods[i]) is Cashflow\n        for i in [1, 3, 5, 7]:\n            assert type(leg.periods[i]) is FloatPeriod\n        assert leg.periods[1].settlement_params.notional == 5e6\n        assert leg.periods[7].settlement_params.notional == 2e6\n        assert leg.periods[8].settlement_params.notional == 2e6\n        assert abs(leg.npv(rate_curve=curve).real) < 1e-9\n\n    def test_float_leg_exchange_npv(self, curve) -> None:\n        fle = FloatLeg(\n            schedule=Schedule(\n                dt(2022, 2, 1),\n                \"6M\",\n                \"Q\",\n                payment_lag=0,\n            ),\n            initial_exchange=True,\n            final_exchange=True,\n        )\n        result = fle.npv(rate_curve=curve)\n        assert abs(result) < 1e-9\n\n    def test_float_leg_exchange_fixings_table(self, curve) -> None:\n        fle = FloatLeg(\n            schedule=Schedule(\n                dt(2022, 2, 1),\n                \"6M\",\n                \"Q\",\n                payment_lag=0,\n            ),\n            initial_exchange=True,\n            final_exchange=True,\n        )\n        result = fle.local_analytic_rate_fixings(rate_curve=curve)\n        expected = DataFrame(\n            data=[-0.2767869527597316, -0.27405055522733884],\n            index=Index([dt(2022, 4, 30), dt(2022, 5, 1)], name=\"obs_dates\"),\n            columns=MultiIndex.from_tuples(\n                [(curve.id, \"usd\", \"usd\", \"1B\")],\n                names=[\"identifier\", \"local_ccy\", \"display_ccy\", \"frequency\"],\n            ),\n        )\n        assert_frame_equal(result[dt(2022, 4, 30) : dt(2022, 5, 1)], expected)\n\n\nclass TestFixedLeg:\n    def test_fixed_leg_analytic_delta(self, curve) -> None:\n        fixed_leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=1e9,\n            convention=\"Act360\",\n        )\n        result = fixed_leg.analytic_delta(rate_curve=curve)\n        assert abs(result - 41400.42965267) < 1e-7\n\n    def test_fixed_leg_npv(self, curve) -> None:\n        fixed_leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=1e9,\n            convention=\"Act360\",\n            fixed_rate=4.00,\n        )\n        result = fixed_leg.npv(disc_curve=curve)\n        assert abs(result + 400 * fixed_leg.analytic_delta(disc_curve=curve)) < 1e-7\n\n    def test_fixed_leg_cashflows(self, curve) -> None:\n        fixed_leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            fixed_rate=4.00,\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        result = fixed_leg.cashflows(disc_curve=curve)\n        # test a couple of return elements\n        assert abs(result.loc[0, defaults.headers[\"cashflow\"]] - 6555555.55555) < 1e-4\n        assert abs(result.loc[1, defaults.headers[\"df\"]] - 0.98307) < 1e-4\n        assert abs(result.loc[1, defaults.headers[\"notional\"]] + 1e9) < 1e-7\n\n    def test_fixed_leg_set_fixed(self, curve) -> None:\n        fixed_leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        assert fixed_leg.fixed_rate is NoInput(0)\n        assert fixed_leg.periods[0].rate_params.fixed_rate is NoInput(0)\n\n        fixed_leg.fixed_rate = 2.0\n        assert fixed_leg.fixed_rate == 2.0\n        assert fixed_leg.periods[0].rate_params.fixed_rate == 2.0\n\n    def test_fixed_leg_final_exchange_custom_amort(self):\n        leg = FixedLeg(\n            schedule=Schedule(dt(2000, 1, 1), dt(2000, 5, 1), \"M\"),\n            notional=100,\n            amortization=Amortization(4, 100, [0, 50.0, 0]),\n            final_exchange=True,\n        )\n        result = leg.cashflows()\n        assert result[\"Notional\"].tolist() == [100.0, 0.0, 100.0, 50.0, 50.0, 0.0, 50.0, 50.0]\n\n    def test_non_deliverable(self, curve):\n        fxf = FXForwards(\n            fx_curves={\"usdusd\": curve, \"brlusd\": curve, \"brlbrl\": curve},\n            fx_rates=FXRates({\"usdbrl\": 25.0}, settlement=dt(2022, 1, 3)),\n        )\n        fixed_leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                payment_lag_exchange=1,\n                frequency=\"Q\",\n            ),\n            notional=1e9,\n            convention=\"Act360\",\n            fixed_rate=4.00,\n            currency=\"usd\",\n            pair=FXIndex(\"usdbrl\", \"all\", 0),\n        )\n        cf = fixed_leg.cashflows(disc_curve=curve, fx=fxf)\n\n        assert fixed_leg.periods[0].non_deliverable_params.fx_fixing.date == dt(2022, 1, 2)\n        assert fixed_leg.periods[1].non_deliverable_params.fx_fixing.date == dt(2022, 1, 2)\n\n        assert abs(cf.loc[1, \"Cashflow\"] + 408888.8888) < 1e-4\n        assert cf.loc[0, \"Reference Ccy\"] == \"BRL\"\n\n    # v2.5\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"forward\", \"exp\"),\n        [\n            (NoInput(0), NoInput(0), 403.9491881327746),\n            (dt(2022, 3, 30), dt(2022, 3, 30), 399.9990223763462),\n            (dt(2022, 4, 6), dt(2022, 4, 6), 799.0147512470912),\n        ],\n    )\n    def test_fixed_leg_spread(self, settlement, forward, exp, curve) -> None:\n        fixed_leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 7, 1),\n                payment_lag=2,\n                payment_lag_exchange=1,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            fixed_rate=4.00,\n            currency=\"usd\",\n        )\n        result = fixed_leg.spread(\n            target_npv=20000000,\n            disc_curve=curve,\n            rate_curve=curve,\n            index_curve=curve,\n            settlement=settlement,\n            forward=forward,\n        )\n        assert abs(result - exp) < 1e-6\n\n    @pytest.mark.parametrize(\"initial\", [True, False])\n    @pytest.mark.parametrize(\"final\", [True, False])\n    @pytest.mark.parametrize(\"amortization\", [True, False])\n    def test_construction_of_relevant_periods(self, initial, final, amortization):\n        # test construction cases:\n        #\n        #  - Regular periods only; no amortization, no exchanges\n        #  - Regular with different exchanges: final and initial\n        #  - Regular with Amortization, but no exchanges.\n        #  - Regular with Amortization and with exchanges.\n        #\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 7, 1),\n                frequency=\"Q\",\n            ),\n            initial_exchange=initial,\n            final_exchange=final,\n            amortization=250000.0 if amortization else NoInput(0),\n        )\n        assert len(fl._regular_periods) == 2\n        assert (fl._exchange_periods[0] is None) is not initial\n        assert (fl._exchange_periods[1] is None) is not (final or initial)\n        if not amortization:\n            assert fl.amortization._type == _AmortizationType.NoAmortization\n            assert fl._amortization_exchange_periods is None\n        else:\n            assert fl.amortization._type == _AmortizationType.ConstantPeriod\n            if not (final or initial):  # initial sets final to True\n                assert fl._amortization_exchange_periods is None\n            else:\n                assert len(fl._amortization_exchange_periods) == 1\n\n    @pytest.mark.parametrize(\"initial\", [True, False])\n    @pytest.mark.parametrize(\"final\", [True, False])\n    @pytest.mark.parametrize(\"amortization\", [True, False])\n    def test_construction_of_relevant_periods_non_deliverable(self, initial, final, amortization):\n        # when the leg is ND but not MTM the same construction as in the regular deliverable\n        # case should be permitted. All FXFixings should beb determined by a single rate of\n        # exchange. This test builds on the above test for non-deliverability.\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 7, 1),\n                frequency=\"Q\",\n                payment_lag_exchange=1,\n            ),\n            fixed_rate=10.0,\n            currency=\"usd\",\n            pair=\"eurusd\",  # the notional of this leg is expressed in BRL but payments made in USD\n            initial_exchange=initial,\n            final_exchange=final,\n            amortization=250000.0 if amortization else NoInput(0),\n            fx_fixings=2.0,  # this should not impact the reference currency notional and amortiz\n        )\n        for rp in fl._regular_periods:\n            assert rp.non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n            assert rp.non_deliverable_params.fx_fixing.value == 2.0\n\n        if initial:\n            assert fl._exchange_periods[0].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n            assert fl._exchange_periods[0].non_deliverable_params.fx_fixing.value == 2.0\n\n        if final:\n            assert fl._exchange_periods[1].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n            assert fl._exchange_periods[1].non_deliverable_params.fx_fixing.value == 2.0\n\n        if amortization and final:\n            assert fl._amortization_exchange_periods[0].non_deliverable_params.fx_fixing.date == dt(\n                1999, 12, 30\n            )\n            assert (\n                fl._amortization_exchange_periods[0].non_deliverable_params.fx_fixing.value == 2.0\n            )\n            assert fl.amortization.amortization == (250000.0,)\n\n            cf = fl.cashflows()\n\n            if initial:\n                assert abs(cf.loc[0, \"Cashflow\"] - 2000000.0) < 1e-4  # ini exchange\n                assert abs(cf.loc[1, \"Cashflow\"] + 50555.55555) < 1e-4  # fixed rate\n                assert abs(cf.loc[2, \"Cashflow\"] + 500000.0) < 1e-4  # amort exchange\n                assert abs(cf.loc[3, \"Cashflow\"] + 37916.66666) < 1e-4  # fixed rate\n                assert abs(cf.loc[4, \"Cashflow\"] + 1500000.0) < 1e-4  # final exchange\n\n    def test_construction_index_fixings(self):\n        # test that amortization index_value date is correctly applied to each period.\n\n        name = str(hash(os.urandom(8)))\n        fixings.add(name, Series(index=[dt(2000, 1, 1)], data=[101.0]))\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 7, 1),\n                frequency=\"Q\",\n                payment_lag_exchange=1,\n                payment_lag=2,\n            ),\n            fixed_rate=2.0,\n            convention=\"Act360\",\n            notional=5000000,\n            amortization=1000000,\n            final_exchange=True,\n            index_fixings=name,\n            index_lag=0,\n            index_method=\"monthly\",\n        )\n        assert leg._regular_periods[0].index_params.index_fixing.date == leg.schedule.aschedule[1]\n        assert leg._regular_periods[1].index_params.index_fixing.date == leg.schedule.aschedule[2]\n        assert (\n            leg._amortization_exchange_periods[0].index_params.index_fixing.date\n            == leg.schedule.aschedule[1]\n        )\n        assert leg._exchange_periods[1].index_params.index_fixing.date == leg.schedule.aschedule[2]\n\n        assert leg._regular_periods[0].index_params.index_base.value == 101.0\n        assert leg._regular_periods[1].index_params.index_base.value == 101.0\n        assert leg._amortization_exchange_periods[0].index_params.index_base.value == 101.0\n        assert leg._exchange_periods[1].index_params.index_base.value == 101.0\n\n        fixings.pop(name)\n\n    @pytest.mark.parametrize(\"amortization\", [True, False])\n    def test_construction_of_relevant_periods_non_deliverable_mtm(self, amortization):\n        # when the leg is ND and MTM the FXFixings should be determined by their appropriate\n        # payment dates deriving fixing date. This test excludes notional exchanges,\n        # designed for ND-IRS\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name + \"_EURUSD\",\n            Series(\n                index=[\n                    dt(1999, 12, 24),\n                    dt(1999, 12, 29),\n                    dt(2000, 3, 29),\n                    dt(2000, 3, 30),\n                    dt(2000, 6, 28),\n                    dt(2000, 6, 29),\n                ],\n                data=[1.1, 2.2, 3.3, 4.4, 5.5, 6.6],\n            ),\n        )\n\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 5),\n                termination=dt(2000, 7, 5),\n                frequency=\"Q\",\n                payment_lag_exchange=1,\n                payment_lag=0,\n            ),\n            fixed_rate=10.0,\n            currency=\"usd\",\n            pair=FXIndex(\"eurusd\", \"tgt|fed\", 2, \"ldn\", -5),\n            mtm=\"payment\",\n            initial_exchange=False,\n            final_exchange=False,\n            amortization=250000.0 if amortization else NoInput(0),\n            fx_fixings=name,  # this should not impact the reference currency notional and amortiz\n        )\n        expected = [3.3, 5.5]\n        for i, rp in enumerate(fl._regular_periods):\n            # every regular period in a typical leg has an FX fixing date equal to coupon payment dt\n            assert rp.non_deliverable_params.fx_fixing.date == (\n                get_calendar(\"ldn\").lag_bus_days(fl.schedule.pschedule[i + 1], -5, True)\n            )\n            assert rp.non_deliverable_params.fx_fixing.value == expected[i]\n\n        fixings.pop(name + \"_EURUSD\")\n\n    def test_construction_of_relevant_periods_non_deliverable_mtm_exchange(self):\n        # when the leg is ND and MTM the FXFixings should be determined at the start of a period.\n        # MTM cashflows are generated with notional exchanges between FX fixings at start and end.\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            name + \"_EURUSD\",\n            Series(\n                index=[\n                    dt(1999, 12, 24),\n                    dt(1999, 12, 29),\n                    dt(2000, 3, 29),\n                    dt(2000, 3, 30),\n                    dt(2000, 6, 28),\n                    dt(2000, 6, 29),\n                ],\n                data=[1.1, 2.2, 3.3, 4.4, 5.5, 6.6],\n            ),\n        )\n\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 5),\n                termination=dt(2000, 7, 5),\n                frequency=\"Q\",\n                payment_lag_exchange=1,\n                payment_lag=0,\n            ),\n            fixed_rate=10.0,\n            currency=\"usd\",\n            pair=FXIndex(\"eurusd\", \"tgt|fed\", 2, \"ldn\", -5),\n            mtm=LegMtm.XCS,\n            initial_exchange=True,\n            final_exchange=True,\n            amortization=NoInput(0),\n            fx_fixings=name,  # this should not impact the reference currency notional and amortiz\n        )\n        expected = [2.2, 4.4]\n        for i, rp in enumerate(fl._regular_periods):\n            assert rp.non_deliverable_params.fx_fixing.date == (\n                get_calendar(\"ldn\").lag_bus_days(fl.schedule.pschedule2[i], -5, True)\n            )\n            assert rp.non_deliverable_params.fx_fixing.value == expected[i]\n\n        # there should be 1 MTM cashflow exchanges:\n        assert len(fl._mtm_exchange_periods) == 1\n        assert fl._mtm_exchange_periods[0].mtm_params.fx_fixing_start.date == (\n            get_calendar(\"ldn\").lag_bus_days(dt(2000, 1, 6), -5, True)\n        )\n        assert fl._mtm_exchange_periods[0].mtm_params.fx_fixing_end.date == (\n            get_calendar(\"ldn\").lag_bus_days(dt(2000, 4, 6), -5, True)\n        )\n\n        fixings.pop(name + \"_EURUSD\")\n\n    def test_construction_of_relevant_periods_non_deliverable_mtm_exchange_amortization(self):\n        # when the leg is ND and MTM the FXFixings should be determined at the start of a period.\n        # MTM cashflows are generated with notional exchanges between FX fixings at start and end.\n        # Amortization has interim cashflows.\n        usd = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.95})\n        eur = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.075})\n        fxf = FXForwards(\n            fx_curves={\"eureur\": eur, \"usdusd\": usd, \"eurusd\": eur},\n            fx_rates=FXRates({\"eurusd\": 1.1}, settlement=dt(2000, 1, 1)),\n        )\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 5),\n                termination=dt(2000, 10, 5),\n                frequency=\"Q\",\n                payment_lag=1,\n                payment_lag_exchange=0,\n            ),\n            convention=\"actacticma\",\n            fixed_rate=1.0,\n            currency=\"usd\",\n            pair=FXIndex(\"eurusd\", \"tgt|fed\", 2, \"ldn\", -5),\n            initial_exchange=True,\n            mtm=LegMtm.XCS,\n            notional=-1e6,\n            amortization=-2e5,\n            fx_fixings=Series(\n                index=[\n                    dt(1999, 12, 24),\n                    dt(1999, 12, 29),\n                    dt(2000, 3, 29),\n                    dt(2000, 3, 30),\n                    dt(2000, 6, 28),\n                    dt(2000, 6, 29),\n                    dt(2000, 9, 28),\n                ],\n                data=[1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7],\n            ),\n        )\n        d1, d2, d3 = dt(1999, 12, 24), dt(2000, 3, 29), dt(2000, 6, 28)\n        expected = DataFrame(\n            {\n                \"Type\": [\n                    \"Cashflow\",\n                    \"FixedPeriod\",\n                    \"MtmCashflow\",\n                    \"Cashflow\",\n                    \"FixedPeriod\",\n                    \"MtmCashflow\",\n                    \"Cashflow\",\n                    \"FixedPeriod\",\n                    \"Cashflow\",\n                ],\n                \"Notional\": [1e6, -1e6, 1e6, -2e5, -8e5, 8e5, -2e5, -6e5, -6e5],\n                \"Cashflow\": [-1.1e6, 2750, -2e5, 2.6e5, 2600, -1.6e5, 3e5, 2250, 9e5],\n                \"FX Fix Date\": [d1, d1, d2, d2, d2, d3, d3, d3, d3],\n            }\n        )\n        result = fl.cashflows(fx=fxf)[[\"Type\", \"Notional\", \"Cashflow\", \"FX Fix Date\"]]\n        assert_frame_equal(result, expected)\n\n    def test_ex_div(self):\n        leg = FixedLeg(schedule=Schedule(dt(2000, 1, 1), dt(2001, 1, 1), \"Q\", extra_lag=-3))\n        assert not leg.ex_div(dt(2000, 3, 29))\n        assert leg.ex_div(dt(2000, 3, 30))\n        assert leg.ex_div(dt(2000, 4, 1))\n\n    def test_mtm_xcs_type_type_sets_fx_fixing_start_initially(self):\n        fixings.add(\n            \"EURUSD_1600\",\n            Series(\n                index=[dt(2000, 4, 1), dt(2000, 4, 2), dt(2000, 7, 2)], data=[1.268, 1.27, 1.29]\n            ),\n        )\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 7, 1),\n                frequency=\"Q\",\n                payment_lag=1,\n                payment_lag_exchange=0,\n            ),\n            fixed_rate=1.0,\n            currency=\"usd\",\n            pair=\"eurusd\",\n            initial_exchange=True,\n            mtm=\"xcs\",\n            notional=5e6,\n            fx_fixings=(1.25, \"EURUSD_1600\"),\n        )\n        assert leg.periods[2].mtm_params.fx_fixing_start.value == 1.25\n        fixings.pop(\"EURUSD_1600\")\n\n    ## 4 types of non-deliverability\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"expected\"),\n        [\n            (\"ABCD\", 1.10),\n            (1.5, 1.5),\n            ((1.2, \"ABCD\"), 1.2),\n        ],\n    )\n    def test_non_mtm_xcs_type(self, fx_fixings, expected):\n        fixings.add(\"ABCD_EURUSD\", Series(index=[dt(1999, 12, 30)], data=[1.10]))\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 3, 1),\n                frequency=\"M\",\n                payment_lag=2,\n                payment_lag_exchange=1,\n                calendar=\"all\",\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            mtm=\"initial\",\n            initial_exchange=True,\n            final_exchange=True,\n            fx_fixings=fx_fixings,\n        )\n        # this leg has 4 periods with only one initial fixing date\n        assert fl.periods[0].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[1].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[2].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[3].non_deliverable_params.fx_fixing.date == dt(1999, 12, 30)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == expected\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == expected\n        assert fl.periods[2].non_deliverable_params.fx_fixing.value == expected\n        assert fl.periods[3].non_deliverable_params.fx_fixing.value == expected\n        fixings.pop(\"ABCD_EURUSD\")\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"expected\"),\n        [\n            (\"ABCDE\", [1.20, 1.30]),\n            (1.5, [1.5, NoInput(0)]),  # this is bad practice: should just supply str ID\n            ((1.5, \"ABCDE\"), [1.5, 1.30]),  # this is bad practice: should just supply str ID\n        ],\n    )\n    def test_irs_nd_type(self, fx_fixings, expected):\n        fixings.add(\n            \"ABCDE_EURUSD\",\n            Series(\n                index=[\n                    dt(2000, 1, 5),\n                    dt(2000, 2, 3),\n                    dt(2000, 2, 4),\n                    dt(2000, 3, 3),\n                    dt(2000, 3, 6),\n                ],\n                data=[1.10, 1.20, 1.21, 1.30, 1.31],\n            ),\n        )\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 7),\n                termination=dt(2000, 3, 7),\n                frequency=\"M\",\n                payment_lag=0,\n                payment_lag_exchange=1,\n                calendar=\"all\",\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            mtm=\"payment\",\n            initial_exchange=False,\n            final_exchange=False,\n            fx_fixings=fx_fixings,\n        )\n        # this leg has 2 periods and only 2 relevant fixings dates\n        assert fl.periods[0].non_deliverable_params.fx_fixing.date == dt(2000, 2, 3)\n        assert fl.periods[1].non_deliverable_params.fx_fixing.date == dt(2000, 3, 3)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == expected[0]\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == expected[1]\n        fixings.pop(\"ABCDE_EURUSD\")\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"expected\"),\n        [\n            (\"ADE\", [1.10, 1.10, 1.20, 1.20, 1.20]),\n            (\n                1.5,\n                [1.5, 1.5, NoInput(0), NoInput(0), NoInput(0)],\n            ),  # this is bad practice: should just supply str ID\n            (\n                (1.5, \"ADE\"),\n                [1.5, 1.5, 1.20, 1.20, 1.20],\n            ),  # this is bad practice: should just supply str ID\n        ],\n    )\n    def test_mtm_xcs_nd_type(self, fx_fixings, expected):\n        fixings.add(\n            \"ADE_EURUSD\",\n            Series(\n                index=[\n                    dt(2000, 1, 6),\n                    dt(2000, 2, 4),\n                    dt(2000, 2, 8),\n                    dt(2000, 3, 7),\n                    dt(2000, 3, 8),\n                ],\n                data=[1.10, 1.20, 1.21, 1.30, 1.31],\n            ),\n        )\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 7),\n                termination=dt(2000, 3, 7),\n                frequency=\"M\",\n                payment_lag=2,\n                payment_lag_exchange=1,\n                calendar=\"all\",\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            mtm=LegMtm.XCS,\n            initial_exchange=True,\n            final_exchange=True,\n            fx_fixings=fx_fixings,\n        )\n        # this leg has 5 periods with only two relevant fixing dates\n        assert fl.periods[0].non_deliverable_params.fx_fixing.date == dt(2000, 1, 6)\n        assert fl.periods[1].non_deliverable_params.fx_fixing.date == dt(2000, 1, 6)\n        assert fl.periods[2].mtm_params.fx_fixing_end.date == dt(2000, 2, 4)\n        assert fl.periods[3].non_deliverable_params.fx_fixing.date == dt(2000, 2, 4)\n        assert fl.periods[4].non_deliverable_params.fx_fixing.date == dt(2000, 2, 4)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == expected[0]\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == expected[1]\n        assert fl.periods[2].mtm_params.fx_fixing_end.value == expected[2]\n        assert fl.periods[3].non_deliverable_params.fx_fixing.value == expected[3]\n        assert fl.periods[4].non_deliverable_params.fx_fixing.value == expected[4]\n        fixings.pop(\"ADE_EURUSD\")\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"expected\"),\n        [\n            (\"AXDE\", [1.10, 1.21, 1.31, 1.30]),\n            (\n                1.5,\n                [1.5, NoInput(0), NoInput(0), NoInput(0)],\n            ),  # this is bad practice: should just supply str ID\n            (\n                (1.5, \"AXDE\"),\n                [1.5, 1.21, 1.31, 1.30],\n            ),  # this is bad practice: should just supply str ID\n        ],\n    )\n    def test_non_mtm_xcs_nd_type(self, fx_fixings, expected):\n        fixings.add(\n            \"AXDE_EURUSD\",\n            Series(\n                index=[\n                    dt(2000, 1, 5),\n                    dt(2000, 2, 3),\n                    dt(2000, 2, 4),\n                    dt(2000, 3, 3),\n                    dt(2000, 3, 6),\n                ],\n                data=[1.10, 1.20, 1.21, 1.30, 1.31],\n            ),\n        )\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 7),\n                termination=dt(2000, 3, 7),\n                frequency=\"M\",\n                payment_lag=1,\n                payment_lag_exchange=0,\n                calendar=\"all\",\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            mtm=\"payment\",\n            initial_exchange=True,\n            final_exchange=True,\n            fx_fixings=fx_fixings,\n        )\n        # this leg has 4 periods with 3 or 4 (if lag exchange is different) relevant fixing dates.\n        assert fl.periods[0].non_deliverable_params.fx_fixing.date == dt(2000, 1, 5)\n        assert fl.periods[1].non_deliverable_params.fx_fixing.date == dt(2000, 2, 4)\n        assert fl.periods[2].non_deliverable_params.fx_fixing.date == dt(2000, 3, 6)\n        assert fl.periods[3].non_deliverable_params.fx_fixing.date == dt(2000, 3, 3)\n        assert fl.periods[0].non_deliverable_params.fx_fixing.value == expected[0]\n        assert fl.periods[1].non_deliverable_params.fx_fixing.value == expected[1]\n        assert fl.periods[2].non_deliverable_params.fx_fixing.value == expected[2]\n        assert fl.periods[3].non_deliverable_params.fx_fixing.value == expected[3]\n        fixings.pop(\"AXDE_EURUSD\")\n\n    def test_leg_index_base(self):\n        fl = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2000, 1, 7),\n                termination=dt(2000, 3, 7),\n                frequency=\"M\",\n                calendar=\"all\",\n            ),\n            index_fixings=\"some\",\n            index_lag=0,\n            index_base_type=LegIndexBase.PeriodOnPeriod,\n        )\n        assert fl.periods[0].index_params.index_base.date == dt(2000, 1, 7)\n        assert fl.periods[1].index_params.index_base.date == dt(2000, 2, 7)\n\n\nclass TestCreditPremiumLeg:\n    @pytest.mark.parametrize(\n        (\"premium_accrued\", \"exp\"), [(True, 41357.455568685626), (False, 41330.94188109829)]\n    )\n    def test_premium_leg_analytic_delta(self, hazard_curve, curve, premium_accrued, exp) -> None:\n        leg = CreditPremiumLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=1e9,\n            convention=\"Act360\",\n            premium_accrued=premium_accrued,\n        )\n        result = leg.analytic_delta(rate_curve=hazard_curve, disc_curve=curve)\n        assert abs(result - exp) < 1e-7\n\n    @pytest.mark.parametrize((\"premium_accrued\"), [True, False])\n    def test_premium_leg_npv(self, hazard_curve, curve, premium_accrued) -> None:\n        leg = CreditPremiumLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=1e9,\n            convention=\"Act360\",\n            premium_accrued=premium_accrued,\n            fixed_rate=4.00,\n        )\n        result = leg.npv(rate_curve=hazard_curve, disc_curve=curve)\n        assert (\n            abs(result + 400 * leg.analytic_delta(rate_curve=hazard_curve, disc_curve=curve)) < 1e-7\n        )\n\n    def test_premium_leg_cashflows(self, hazard_curve, curve) -> None:\n        leg = CreditPremiumLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            fixed_rate=4.00,\n        )\n        result = leg.cashflows(rate_curve=hazard_curve, disc_curve=curve)\n        # test a couple of return elements\n        assert abs(result.loc[0, defaults.headers[\"cashflow\"]] - 6555555.55555) < 1e-4\n        assert abs(result.loc[1, defaults.headers[\"df\"]] - 0.98307) < 1e-4\n        assert abs(result.loc[1, defaults.headers[\"notional\"]] + 1e9) < 1e-7\n\n    def test_premium_leg_set_fixed_rate(self, curve) -> None:\n        leg = CreditPremiumLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n        )\n        assert leg.fixed_rate is NoInput(0)\n        assert leg.periods[0].rate_params.fixed_rate is NoInput(0)\n\n        leg.fixed_rate = 2.0\n        assert leg.fixed_rate == 2.0\n        assert leg.periods[0].rate_params.fixed_rate == 2.0\n\n    @pytest.mark.parametrize(\n        (\"date\", \"exp\"),\n        [\n            (dt(2022, 2, 1), 1e9 * 0.02 * 0.25 * 31 / 90),\n            (dt(2022, 3, 1), 0.0),\n            (dt(2022, 6, 1), 0.0),\n        ],\n    )\n    def test_premium_leg_accrued(self, date, exp):\n        leg = CreditPremiumLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"ActActICMA\",\n            fixed_rate=2.0,\n        )\n        result = leg.accrued(date)\n        assert abs(result - exp) < 1e-6\n\n    @pytest.mark.parametrize(\"final\", [True, False])\n    def test_exchanges_raises(self, final):\n        with pytest.raises(TypeError, match=\"unexpected keyword argument\"):\n            CreditPremiumLeg(\n                schedule=Schedule(\n                    effective=dt(2022, 1, 1),\n                    termination=dt(2022, 6, 1),\n                    payment_lag=2,\n                    frequency=\"Q\",\n                ),\n                notional=-1e9,\n                convention=\"ActActICMA\",\n                fixed_rate=2.0,\n                initial_exchange=final,\n                final_exchange=not final,\n            )\n\n    @pytest.mark.parametrize(\n        (\"settlement\", \"forward\", \"exp\"),\n        [\n            (NoInput(0), NoInput(0), 408.02994815795125),\n            (dt(2022, 3, 30), dt(2022, 3, 30), 404.03987718823055),\n            (dt(2022, 4, 6), dt(2022, 4, 6), 811.1815703665554),\n        ],\n    )\n    def test_fixed_leg_spread(self, settlement, forward, exp, curve) -> None:\n        fixed_leg = CreditPremiumLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 7, 1),\n                payment_lag=2,\n                payment_lag_exchange=1,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            fixed_rate=4.00,\n            currency=\"usd\",\n        )\n        result = fixed_leg.spread(\n            target_npv=20000000,\n            disc_curve=curve,\n            rate_curve=curve,\n            index_curve=curve,\n            settlement=settlement,\n            forward=forward,\n        )\n        assert abs(result - exp) < 1e-6\n\n    def test_ex_div(self):\n        leg = CreditPremiumLeg(schedule=Schedule(dt(2000, 1, 1), dt(2001, 1, 1), \"Q\", extra_lag=-3))\n        assert not leg.ex_div(dt(2000, 3, 29))\n        assert leg.ex_div(dt(2000, 3, 30))\n        assert leg.ex_div(dt(2000, 4, 1))\n\n\nclass TestCreditProtectionLeg:\n    def test_leg_analytic_delta(self, hazard_curve, curve) -> None:\n        leg = CreditProtectionLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=1e9,\n        )\n        result = leg.analytic_delta(rate_curve=hazard_curve, disc_curve=curve)\n        assert abs(result) < 1e-7\n\n    def test_leg_analytic_rec_risk(self, hazard_curve, curve) -> None:\n        leg = CreditProtectionLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2027, 1, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=1e7,\n        )\n        result = leg.analytic_rec_risk(rate_curve=hazard_curve, disc_curve=curve)\n\n        pv0 = leg.npv(rate_curve=hazard_curve, disc_curve=curve)\n        hazard_curve.update_meta(\"credit_recovery_rate\", 0.41)\n        pv1 = leg.npv(rate_curve=hazard_curve, disc_curve=curve)\n        expected = pv1 - pv0\n        assert abs(result - expected) < 1e-7\n\n    @pytest.mark.parametrize((\"premium_accrued\"), [True, False])\n    def test_leg_npv(self, hazard_curve, curve, premium_accrued) -> None:\n        leg = CreditProtectionLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Z\",\n            ),\n            notional=1e9,\n        )\n        result = leg.npv(rate_curve=hazard_curve, disc_curve=curve)\n        expected = -1390922.0390295777  # with 1 cds_discretization this is -1390906.242843\n        assert abs(result - expected) < 1e-7\n\n    def test_leg_cashflows(self, hazard_curve, curve) -> None:\n        leg = CreditProtectionLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            # convention=\"Act360\",\n        )\n        result = leg.cashflows(rate_curve=hazard_curve, disc_curve=curve)\n        # test a couple of return elements\n        assert abs(result.loc[0, defaults.headers[\"cashflow\"]] - 600e6) < 1e-4\n        assert abs(result.loc[1, defaults.headers[\"df\"]] - 0.98307) < 1e-4\n        assert abs(result.loc[1, defaults.headers[\"notional\"]] + 1e9) < 1e-7\n\n    def test_leg_zero_sched(self):\n        leg = CreditProtectionLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2024, 6, 1),\n                frequency=\"Z\",\n            ),\n            notional=-1e9,\n            # convention=\"Act360\",\n        )\n        assert len(leg.periods) == 1\n        assert leg.periods[0].period_params.end == dt(2024, 6, 1)\n\n\nclass TestIndexFixedLegExchange:\n    @pytest.mark.parametrize(\n        \"i_fixings\",\n        [\n            NoInput(0),\n            # [210, 220, 230], # list not supported in v2.0\n            # 210, # dualtypes is not supported as of v2.2\n            Series(\n                [210.0, 220.0, 230.0],\n                index=[dt(2022, 6, 15), dt(2022, 9, 15), dt(2022, 12, 15)],\n            ),\n        ],\n    )\n    def test_idx_leg_cashflows(self, i_fixings) -> None:\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 3, 15),\n                termination=\"9M\",\n                frequency=\"Q\",\n                payment_lag=0,\n            ),\n            convention=\"ActActICMA\",\n            notional=40e6,\n            fixed_rate=5.0,\n            index_base=200.0,\n            index_lag=0,\n            index_fixings=i_fixings,\n            initial_exchange=False,\n            final_exchange=True,\n            index_method=\"curve\",\n        )\n        index_curve = Curve(\n            nodes={\n                dt(2022, 3, 15): 1.0,\n                dt(2022, 6, 15): 1.0 / 1.05,\n                dt(2022, 9, 15): 1.0 / 1.10,\n                dt(2022, 12, 15): 1.0 / 1.15,\n            },\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=0,\n        )\n        disc_curve = Curve({dt(2022, 3, 15): 1.0, dt(2022, 12, 15): 1.0})\n        flows = leg.cashflows(index_curve=index_curve, disc_curve=disc_curve)\n\n        def equals_with_tol(a, b):\n            if isinstance(a, str):\n                return a == b\n            else:\n                return abs(a - b) < 1e-7\n\n        expected = {\n            \"Type\": \"FixedPeriod\",\n            \"DCF\": 0.250,\n            \"Notional\": 40e6,\n            \"Rate\": 5.0,\n            \"Unindexed Cashflow\": -500e3,\n            \"Index Val\": 210.0,\n            \"Index Ratio\": 1.05,\n            \"Cashflow\": -525000,\n        }\n        flow = flows.iloc[0].to_dict()\n        for key in set(expected.keys()) & set(flow.keys()):\n            assert equals_with_tol(expected[key], flow[key])\n\n        final_flow = flows.iloc[3].to_dict()\n        expected = {\n            \"Type\": \"Cashflow\",\n            \"Notional\": 40e6,\n            \"Unindexed Cashflow\": -40e6,\n            \"Index Val\": 230.0,\n            \"Index Ratio\": 1.15,\n            \"Cashflow\": -46e6,\n        }\n        for key in set(expected.keys()) & set(final_flow.keys()):\n            assert equals_with_tol(expected[key], final_flow[key])\n\n    def test_args_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`index_method` as string: 'BAD' is not \"):\n            FixedLeg(\n                schedule=Schedule(\n                    effective=dt(2022, 3, 15),\n                    termination=\"9M\",\n                    frequency=\"Q\",\n                ),\n                index_base=200.0,\n                index_method=\"BAD\",\n                initial_exchange=True,\n                final_exchange=True,\n            )\n\n    @pytest.mark.skip(reason=\"v2.2 removed the ability to mutate `index_base` at period level.\")\n    def test_set_index_leg_after_init(self) -> None:\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 3, 15),\n                termination=\"9M\",\n                frequency=\"Q\",\n                payment_lag=0,\n            ),\n            convention=\"ActActICMA\",\n            notional=40e6,\n            fixed_rate=5.0,\n            index_base=None,\n            initial_exchange=False,\n            final_exchange=True,\n        )\n        for period in leg.periods:\n            assert period.index_base is None\n        leg.index_base = 205.0\n        for period in leg.periods:\n            assert period.index_base == 205.0\n\n    def test_npv(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98})\n        index_curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99},\n            index_base=100.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        index_leg_exch = FixedLeg(\n            schedule=Schedule(\n                dt(2022, 1, 1),\n                \"9M\",\n                \"Q\",\n                payment_lag=2,\n                payment_lag_exchange=0,\n            ),\n            notional=1000000,\n            amortization=200000,\n            index_base=100.0,\n            initial_exchange=False,\n            fixed_rate=1.0,\n            final_exchange=True,\n            index_lag=3,\n        )\n        result = index_leg_exch.npv(index_curve=index_curve, disc_curve=curve)\n        expected = -999993.7970219046\n        assert abs(result - expected) < 1e-4\n\n    def test_index_lag_on_periods(self):\n        index_leg_exch = FixedLeg(\n            schedule=Schedule(\n                dt(2022, 1, 1),\n                \"6M\",\n                \"Q\",\n            ),\n            notional=1000000,\n            amortization=200000,\n            index_base=100.0,\n            fixed_rate=1.0,\n            final_exchange=True,\n            index_lag=4,\n        )\n        for period in index_leg_exch.periods:\n            assert period.index_params.index_lag == 4\n\n\nclass TestIndexFixedLeg:\n    @pytest.mark.parametrize(\n        (\"i_fixings\", \"meth\"),\n        [\n            (NoInput(0), \"daily\"),\n            # ([210, 220, 230], \"daily\"), # list unsupported in v2.0\n            # (210, \"daily\"),  # dualtypes unsupported as of v2.2\n            (\n                Series(\n                    [210.0, 210, 220, 220, 230, 230],\n                    index=[\n                        dt(2022, 6, 1),\n                        dt(2022, 7, 1),\n                        dt(2022, 9, 1),\n                        dt(2022, 10, 1),\n                        dt(2022, 12, 1),\n                        dt(2023, 1, 1),\n                    ],\n                ),\n                \"daily\",\n            ),\n            (\n                Series(\n                    [210.0, 220, 230],\n                    index=[dt(2022, 6, 1), dt(2022, 9, 1), dt(2022, 12, 1)],\n                ),\n                \"monthly\",\n            ),\n        ],\n    )\n    def test_idx_leg_cashflows(self, i_fixings, meth) -> None:\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 3, 15),\n                termination=\"9M\",\n                frequency=\"Q\",\n                payment_lag=0,\n            ),\n            convention=\"ActActICMA\",\n            notional=40e6,\n            fixed_rate=5.0,\n            index_base=200.0,\n            index_fixings=i_fixings,\n            index_method=meth,\n            index_lag=0,\n        )\n        index_curve = Curve(\n            nodes={\n                dt(2022, 3, 15): 1.0,\n                dt(2022, 6, 15): 1.0 / 1.05,\n                dt(2022, 9, 15): 1.0 / 1.10,\n                dt(2022, 12, 15): 1.0 / 1.15,\n            },\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=0,\n        )\n        disc_curve = Curve({dt(2022, 3, 15): 1.0, dt(2022, 12, 15): 1.0})\n        flows = leg.cashflows(index_curve=index_curve, disc_curve=disc_curve)\n\n        def equals_with_tol(a, b):\n            if isinstance(a, str):\n                return a == b\n            else:\n                return abs(a - b) < 1e-7\n\n        expected = {\n            \"Type\": \"FixedPeriod\",\n            \"DCF\": 0.250,\n            \"Notional\": 40e6,\n            \"Rate\": 5.0,\n            \"Unindexed Cashflow\": -500e3,\n            \"Index Val\": 210.0,\n            \"Index Ratio\": 1.05,\n            \"Cashflow\": -525000,\n        }\n        flow = flows.iloc[0].to_dict()\n        for key in set(expected.keys()) & set(flow.keys()):\n            assert equals_with_tol(expected[key], flow[key])\n\n    @pytest.mark.parametrize((\"meth\", \"exp\"), [(\"daily\", 230.0), (\"monthly\", 227.91208)])\n    def test_missing_fixings(self, meth, exp) -> None:\n        i_fixings = Series(\n            [210.0, 210, 220, 220],\n            index=[dt(2022, 6, 1), dt(2022, 7, 1), dt(2022, 9, 1), dt(2022, 10, 1)],\n        )\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 3, 20),\n                termination=\"9M\",\n                frequency=\"Q\",\n                payment_lag=0,\n            ),\n            convention=\"ActActICMA\",\n            notional=40e6,\n            fixed_rate=5.0,\n            index_base=200.0,\n            index_fixings=i_fixings,\n            index_method=meth,\n            index_lag=0,\n        )\n        index_curve = Curve(\n            nodes={\n                dt(2022, 3, 20): 1.0,\n                dt(2022, 6, 20): 1.0 / 1.05,\n                dt(2022, 9, 20): 1.0 / 1.10,\n                dt(2022, 12, 20): 1.0 / 1.15,\n            },\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=0,\n        )\n        cashflows = leg.cashflows(index_curve=index_curve)\n        result = cashflows.iloc[2][\"Index Val\"]\n        assert abs(result - exp) < 1e-3\n\n    @pytest.mark.skip(reason=\"v2.2 removed the ability to mutate `index_base` at period level.\")\n    def test_set_index_leg_after_init(self) -> None:\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 3, 15),\n                termination=\"9M\",\n                frequency=\"Q\",\n                payment_lag=0,\n            ),\n            convention=\"ActActICMA\",\n            notional=40e6,\n            fixed_rate=5.0,\n            index_base=None,\n        )\n        for period in leg.periods:\n            assert period.index_params.index_base is None\n        leg.index_base = 205.0\n        for period in leg.periods:\n            assert period.index_params.index_base == 205.0\n\n    @pytest.mark.skip(reason=\"v2.2 removed the ability to mutate `index_base` at period level.\")\n    @pytest.mark.parametrize(\n        \"i_base\",\n        [\n            200.0,\n            Series([199.0, 201.0], index=[dt(2022, 4, 1), dt(2022, 5, 1)]),\n        ],\n    )\n    def test_set_index_base(self, curve, i_base) -> None:\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 4, 16),\n                termination=dt(2022, 5, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            index_method=\"daily\",\n            index_lag=0,\n        )\n        assert leg.periods[0].index_params.index_base == NoInput(0)\n\n        leg.index_base = i_base\n        assert leg.periods[0].index_base == 200.0\n\n    @pytest.mark.parametrize(\n        (\"i_base\", \"exp\"),\n        [\n            (NoInput(0), NoInput(0)),\n            (110.0, 110.0),\n        ],\n    )\n    def test_initialise_index_base(self, i_base, exp) -> None:\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            index_base=i_base,\n            index_lag=0,\n        )\n        assert leg.periods[-1].index_params.index_base.value == exp\n\n    @pytest.mark.parametrize(\n        (\"i_base\", \"exp\"),\n        [\n            (Series([199.0, 200.0], index=[dt(2021, 12, 31), dt(2022, 1, 1)]), 200.0),\n            (Series([1.0, 2.0], index=[dt(2000, 1, 1), dt(2000, 12, 1)]), NoInput(0)),\n        ],\n    )\n    def test_initialise_index_base2(self, i_base, exp) -> None:\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 6, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            notional=-1e9,\n            convention=\"Act360\",\n            index_fixings=i_base,\n            index_lag=0,\n        )\n        assert leg.periods[-1].index_params.index_base.value == exp\n\n    @pytest.mark.skip(reason=\"fixings as list removed in v2.0\")\n    def test_index_fixings_as_list(self) -> None:\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 10, 1),\n                payment_lag=2,\n                frequency=\"Q\",\n            ),\n            convention=\"Act360\",\n            notional=1e6,\n            amortization=250e3,\n            index_base=NoInput(0),\n            index_fixings=[100.0, 200.0],\n        )\n        assert leg.periods[0].index_fixings == 100.0\n        assert leg.periods[1].index_fixings == 200.0\n        assert leg.periods[2].index_fixings == NoInput(0)\n\n    @pytest.mark.skip(reason=\"fixings as list removed in v2.0\")\n    def test_index_fixings_as_list_final_exchange(self) -> None:\n        leg = FixedLeg(\n            effective=dt(2022, 1, 1),\n            termination=dt(2022, 10, 1),\n            payment_lag=2,\n            convention=\"Act360\",\n            frequency=\"Q\",\n            notional=1e6,\n            amortization=250e3,\n            index_base=NoInput(0),\n            index_fixings=[100.0, 100.0, 200.0, 199.0],\n            final_exchange=True,\n        )\n        assert leg.periods[0].index_fixings == 100.0\n        assert leg.periods[1].index_fixings == 100.0\n        assert leg.periods[2].index_fixings == 200.0\n        assert leg.periods[3].index_fixings == 199.0\n        assert leg.periods[4].index_fixings == NoInput(0)\n        assert leg.periods[5].index_fixings == NoInput(0)\n\n    @pytest.mark.skip(reason=\"v2.2 refactor fixings, + input as Series was stated as bad practice\")\n    @pytest.mark.parametrize(\n        \"index_fixings\",\n        [\n            Series([1, 2, 3], index=[dt(2000, 1, 1), dt(1999, 1, 1), dt(2001, 1, 1)]),\n            Series([1, 2, 3], index=[dt(2000, 1, 1), dt(2000, 1, 1), dt(2001, 1, 1)]),\n        ],\n    )\n    def test_index_as_series_invalid(self, index_fixings):\n        with pytest.raises(ValueError, match=\"`index_fixings` as Series must be\"):\n            FixedLeg(\n                schedule=Schedule(\n                    effective=dt(2022, 1, 1),\n                    termination=dt(2022, 10, 1),\n                    frequency=\"Q\",\n                ),\n                index_base=NoInput(0),\n                index_fixings=index_fixings,\n            )\n\n    @pytest.mark.skip(reason=\"v2.2 refactor fixings, + input as Series was stated as bad practice\")\n    def test_index_reverse_monotonic_decreasing_series(self):\n        s = Series([1, 2, 3], index=[dt(2000, 1, 1), dt(1999, 1, 1), dt(1998, 1, 1)])\n        assert s.index.is_monotonic_decreasing\n        leg = FixedLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 1),\n                termination=dt(2022, 10, 1),\n                frequency=\"Q\",\n            ),\n            index_base=NoInput(0),\n            index_fixings=s,\n        )\n        assert leg.index_fixings.index.is_monotonic_increasing\n\n\nclass TestFloatLegExchangeMtm:\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"exp\"),\n        [\n            (NoInput(0), [NoInput(0), NoInput(0), NoInput(0)]),\n            ([1.5], [1.5, NoInput(0), NoInput(0)]),\n            (1.25, [1.25, NoInput(0), NoInput(0)]),\n            ([1.25, 1.35], [1.25, 1.35, NoInput(0)]),\n            (Series([1.25, 1.3], index=[dt(2022, 1, 4), dt(2022, 4, 4)]), [1.25, 1.3, NoInput(0)]),\n            (Series([1.25], index=[dt(2022, 1, 4)]), [1.25, NoInput(0), NoInput(0)]),\n        ],\n    )\n    def test_float_leg_exchange_mtm(self, fx_fixings, exp) -> None:\n        float_leg_exch = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 3),\n                termination=dt(2022, 7, 3),\n                frequency=\"Q\",\n                payment_lag_exchange=3,\n            ),\n            float_spread=5.0,\n            currency=\"usd\",\n            pair=\"eurusd\",\n            notional=10e6,\n            fx_fixings=fx_fixings,\n            mtm=\"xcs\",\n            initial_exchange=True,\n        )\n        fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fxr,\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.965}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n                \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.987}),\n            },\n        )\n\n        d = [\n            dt(2022, 1, 6),\n            dt(2022, 4, 6),\n            dt(2022, 7, 6),\n        ]  # payment_lag_exchange is 3 days.\n        rate = [_ if _ is not NoInput(0) else fxf.rate(\"eurusd\", d[i]) for i, _ in enumerate(exp)]\n\n        float_leg_exch.cashflows(\n            rate_curve=fxf.curve(\"usd\", \"usd\"), disc_curve=fxf.curve(\"usd\", \"usd\"), fx=fxf\n        )\n        assert (\n            float(float_leg_exch.periods[0].try_cashflow(fx=fxf).unwrap() - 10e6 * rate[0]) < 1e-6\n        )\n        assert (\n            float(\n                float_leg_exch.periods[2].try_cashflow(fx=fxf).unwrap() - 10e6 * (rate[1] - rate[0])\n            )\n            < 1e-6\n        )\n        assert (\n            float(\n                float_leg_exch.periods[4].try_cashflow(fx=fxf).unwrap() - 10e6 * (rate[2] - rate[1])\n            )\n            < 1e-6\n        )\n        assert float_leg_exch.periods[4].settlement_params.payment == d[-1]\n\n        assert float_leg_exch.periods[1].settlement_params.notional == 10e6\n        assert float_leg_exch.periods[1].non_deliverable_params.fx_fixing.value == exp[0]\n        assert float_leg_exch.periods[1].non_deliverable_params.fx_fixing.date == dt(2022, 1, 4)\n\n        assert type(float_leg_exch.periods[1]) is FloatPeriod\n        assert float_leg_exch.periods[3].settlement_params.notional == 10e6\n        assert float_leg_exch.periods[3].non_deliverable_params.fx_fixing.value == exp[1]\n        assert float_leg_exch.periods[3].non_deliverable_params.fx_fixing.date == dt(2022, 4, 4)\n        assert type(float_leg_exch.periods[3]) is FloatPeriod\n\n        assert float_leg_exch.periods[-1].settlement_params.notional == 10e6\n        assert float_leg_exch.periods[-1].non_deliverable_params.fx_fixing.value == exp[1]\n        assert float_leg_exch.periods[-1].non_deliverable_params.fx_fixing.date == dt(2022, 4, 4)\n\n    def test_float_leg_exchange_fixings_table(self) -> None:\n        float_leg_exch = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 3),\n                termination=dt(2022, 7, 3),\n                frequency=\"Q\",\n                payment_lag_exchange=3,\n            ),\n            float_spread=5.0,\n            currency=\"usd\",\n            pair=\"eurusd\",\n            notional=10e6,\n            fixing_method=\"ibor(0)\",\n            mtm=\"xcs\",\n            initial_exchange=True,\n        )\n        fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fxr,\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.965}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n                \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.987}),\n            },\n        )\n\n        result = float_leg_exch.local_analytic_rate_fixings(\n            rate_curve=fxf.curve(\"usd\", \"usd\"), fx=fxf\n        )\n        assert isinstance(result, DataFrame)\n        assert isinstance(result.iloc[0, 0], Dual)\n        assert abs(result.iloc[0, 0] + 260.1507) < 1e-3\n        assert abs(result.iloc[1, 0] + 262.1683) < 1\n\n    def test_float_leg_exchange_fixings_table_rfr(self) -> None:\n        float_leg_exch = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 3),\n                termination=dt(2022, 7, 3),\n                frequency=\"Q\",\n                payment_lag_exchange=0,\n            ),\n            float_spread=5.0,\n            currency=\"usd\",\n            pair=\"eurusd\",\n            notional=10e6,\n            mtm=\"xcs\",\n            initial_exchange=True,\n        )\n        fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fxr,\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.965}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n                \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.987}),\n            },\n        )\n\n        result = float_leg_exch.local_analytic_rate_fixings(\n            rate_curve=fxf.curve(\"usd\", \"usd\"), disc_curve=fxf.curve(\"usd\", \"usd\"), fx=fxf\n        )\n        assert isinstance(result, DataFrame)\n        assert isinstance(result.iloc[0, 0], Dual)  # Dual is converted to float for fixings table\n        assert result.columns.values[0] == (fxf.curve(\"usd\", \"usd\").id, \"usd\", \"usd\", \"1B\")\n\n    def test_mtm_leg_exchange_spread(self) -> None:\n        leg = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 3),\n                termination=dt(2022, 7, 3),\n                frequency=\"Q\",\n                payment_lag=0,\n                payment_lag_exchange=0,\n            ),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            notional=1e9,\n            fixing_method=\"rfr_payment_delay\",\n            spread_compound_method=\"isda_compounding\",\n            float_spread=0.0,\n            mtm=\"xcs\",\n            initial_exchange=True,\n        )\n        fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fxr,\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.965}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n                \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.987}),\n            },\n        )\n\n        npv = leg.npv(\n            rate_curve=fxf.curve(\"usd\", \"usd\"), disc_curve=fxf.curve(\"usd\", \"usd\"), fx=fxf\n        )\n        # a_delta = leg.analytic_delta(fxf.curve(\"usd\", \"usd\"), fxf.curve(\"usd\", \"usd\"), fxf)\n        result = leg.spread(\n            target_npv=100,\n            rate_curve=fxf.curve(\"usd\", \"usd\"),\n            disc_curve=fxf.curve(\"usd\", \"usd\"),\n            fx=fxf,\n        )\n        leg.float_spread = result\n        npv2 = leg.npv(\n            rate_curve=fxf.curve(\"usd\", \"usd\"), disc_curve=fxf.curve(\"usd\", \"usd\"), fx=fxf\n        )\n        assert abs(npv2 - npv - 100) < 0.01\n\n    @pytest.mark.parametrize(\n        (\"fx_fixings\", \"exp\"),\n        [\n            (NoInput(0), [NoInput(0), NoInput(0), NoInput(0)]),\n            ([1.5], [1.5, NoInput(0), NoInput(0)]),\n            (1.25, [1.25, NoInput(0), NoInput(0)]),\n        ],\n    )\n    def test_mtm_leg_fx_fixings_warn_raise(self, curve, fx_fixings, exp) -> None:\n        float_leg_exch = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 3),\n                termination=dt(2022, 7, 3),\n                frequency=\"Q\",\n                payment_lag_exchange=3,\n            ),\n            float_spread=5.0,\n            currency=\"usd\",\n            pair=\"eurusd\",\n            notional=10e6,\n            fx_fixings=fx_fixings,\n            mtm=\"xcs\",\n            initial_exchange=True,\n        )\n        with pytest.raises(ValueError, match=\"Must provide `fx` argument to forecast FXFixing.\"):\n            float_leg_exch.npv(rate_curve=curve)\n\n    def test_mtm_leg_fx_fixings_series_raises(self, curve) -> None:\n        fl = FloatLeg(\n            schedule=Schedule(\n                effective=dt(2022, 1, 3),\n                termination=dt(2022, 7, 3),\n                frequency=\"Q\",\n                payment_lag_exchange=3,\n            ),\n            float_spread=5.0,\n            currency=\"usd\",\n            pair=\"eurusd\",\n            notional=10e6,\n            fx_fixings=Series([1.25], index=[dt(2022, 2, 6)]),\n            mtm=\"xcs\",\n            initial_exchange=True,\n        )\n        with pytest.raises(ValueError, match=\"Must provide `fx` argument to forecast FXFixing.\"):\n            fl.npv(rate_curve=curve)\n        # assert False  # TODO: this test should possibly fail if the FX is before the series range.\n        # although a FixingsRangeError is detected and the ixing value accepted is NoInput\n\n    def test_mtm_raises_alt(self) -> None:\n        with pytest.raises(ValueError, match=\"A non-deliverable pair must contain the settlement \"):\n            FloatLeg(\n                schedule=Schedule(\n                    effective=dt(2022, 1, 3),\n                    termination=dt(2022, 7, 3),\n                    frequency=\"Q\",\n                    payment_lag_exchange=3,\n                ),\n                float_spread=5.0,\n                currency=\"usd\",\n                pair=FXIndex(\"eursek\", \"tgt,stk|fed\", 2),\n                notional=10e6,\n            )\n\n\nclass TestCustomLeg:\n    @pytest.mark.parametrize(\n        \"period\",\n        [\n            FixedPeriod(\n                start=dt(2022, 1, 1),\n                end=dt(2023, 1, 1),\n                payment=dt(2023, 1, 9),\n                frequency=Frequency.Months(12, None),\n                fixed_rate=1.0,\n            ),\n            FloatPeriod(\n                start=dt(2022, 1, 1),\n                end=dt(2022, 4, 1),\n                payment=dt(2022, 4, 3),\n                notional=1e9,\n                convention=\"Act360\",\n                termination=dt(2022, 4, 1),\n                frequency=Frequency.Months(3, None),\n                float_spread=10.0,\n            ),\n            CreditPremiumPeriod(\n                start=dt(2022, 1, 1),\n                end=dt(2022, 4, 1),\n                payment=dt(2022, 4, 3),\n                notional=1e9,\n                convention=\"Act360\",\n                termination=dt(2022, 4, 1),\n                frequency=Frequency.Months(3, None),\n                fixed_rate=4.0,\n                currency=\"usd\",\n            ),\n            CreditProtectionPeriod(\n                start=dt(2022, 1, 1),\n                end=dt(2022, 4, 1),\n                payment=dt(2022, 4, 3),\n                notional=1e9,\n                # convention=\"Act360\",\n                termination=dt(2022, 4, 1),\n                frequency=Frequency.Months(3, None),\n                currency=\"usd\",\n            ),\n            Cashflow(notional=1e9, payment=dt(2022, 4, 3)),\n        ],\n    )\n    def test_init(self, curve, period) -> None:\n        CustomLeg(periods=[period, period])\n\n    def test_npv(self, curve) -> None:\n        cl = CustomLeg(\n            periods=[\n                FixedPeriod(\n                    start=dt(2022, 1, 1),\n                    end=dt(2023, 1, 1),\n                    payment=dt(2023, 1, 9),\n                    frequency=Frequency.Months(12, None),\n                    fixed_rate=1.0,\n                ),\n                FixedPeriod(\n                    start=dt(2022, 2, 1),\n                    end=dt(2023, 2, 1),\n                    payment=dt(2023, 2, 9),\n                    frequency=Frequency.Months(12, None),\n                    fixed_rate=2.0,\n                ),\n            ],\n        )\n        result = cl.npv(rate_curve=curve)\n        expected = -29109.962157023772\n        assert abs(result - expected) < 1e-6\n\n    def test_cashflows(self, curve) -> None:\n        cl = CustomLeg(\n            periods=[\n                FixedPeriod(\n                    start=dt(2022, 1, 1),\n                    end=dt(2023, 1, 1),\n                    payment=dt(2023, 1, 9),\n                    frequency=Frequency.Months(12, None),\n                    fixed_rate=1.0,\n                ),\n                FixedPeriod(\n                    start=dt(2022, 2, 1),\n                    end=dt(2023, 2, 1),\n                    payment=dt(2023, 2, 9),\n                    frequency=Frequency.Months(12, None),\n                    fixed_rate=2.0,\n                ),\n            ],\n        )\n        result = cl.cashflows(rate_curve=curve)\n        assert isinstance(result, DataFrame)\n        assert len(result.index) == 2\n\n    def test_analytic_delta(self, curve) -> None:\n        cl = CustomLeg(\n            periods=[\n                FixedPeriod(\n                    start=dt(2022, 1, 1),\n                    end=dt(2023, 1, 1),\n                    payment=dt(2023, 1, 9),\n                    frequency=Frequency.Months(12, None),\n                    fixed_rate=1.0,\n                ),\n                FixedPeriod(\n                    start=dt(2022, 2, 1),\n                    end=dt(2023, 2, 1),\n                    payment=dt(2023, 2, 9),\n                    frequency=Frequency.Months(12, None),\n                    fixed_rate=2.0,\n                ),\n            ],\n        )\n        result = cl.analytic_delta(rate_curve=curve)\n        expected = 194.1782607729773\n        assert abs(result - expected) < 1e-6\n\n\nclass TestNonDeliverableFixedLeg:\n    def test_set_periods(self):\n        leg = FixedLeg(\n            schedule=Schedule(dt(2000, 1, 1), dt(2000, 3, 1), \"M\"),\n            fixed_rate=2.0,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n        )\n        assert len(leg.periods) == 2\n\n    def test_npv(self):\n        fxr = FXRates({\"usdbrl\": 9.50}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fxr,\n            {\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.965}),\n                \"brlbrl\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n                \"brlusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.987}),\n            },\n        )\n        leg = FixedLeg(\n            schedule=Schedule(dt(2022, 1, 1), dt(2022, 3, 1), \"M\"),\n            fixed_rate=2.0,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            notional=1e6,  # 1mm BRL\n            mtm=\"payment\",\n        )\n        result = leg.npv(disc_curve=fxf.curve(\"usd\", \"brl\"), fx=fxf)\n        expected = -344.326093  #  2.0% * 1mm * (2 / 12) / 9.5\n        assert abs(result - expected) < 1e-6\n\n        result = leg.npv(disc_curve=fxf.curve(\"usd\", \"brl\"), fx=fxf, base=\"brl\")\n        expected = -344.326093 * fxf.rate(\"usdbrl\")  # 2.0% * 1mm * (2 / 12) / 9.5\n        assert abs(result - expected) < 1e-5\n\n    @pytest.mark.parametrize(\"fixings\", [[1.66], 1.66, Series(data=[1.66], index=[dt(2022, 2, 3)])])\n    def test_set_fixings(self, fixings):\n        leg = FixedLeg(\n            schedule=Schedule(dt(2022, 1, 1), dt(2022, 3, 1), \"M\"),\n            fixed_rate=2.0,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            notional=1e6,  # 1mm BRL\n            fx_fixings=fixings,\n            mtm=\"payment\",\n        )\n        assert leg.periods[0].non_deliverable_params.fx_fixing.value == 1.66\n        assert leg.periods[1].non_deliverable_params.fx_fixing.value == NoInput(0)\n\n\nclass TestAmortization:\n    def test_percent(self):\n        a = Amortization(4, 100.0, \"20%\")\n        assert a.outstanding == (100.0, 80.0, 64.0, 51.2)\n        assert a.amortization == (20.0, 16.0, 12.8)\n        assert a._type == _AmortizationType.CustomSchedule\n\n    def test_to_zero(self):\n        a = Amortization(4, 100.0, \"to_zero\")\n        assert a.outstanding == (100.0, 75.0, 50.0, 25.0)\n        assert a.amortization == (25.0, 25.0, 25.0)\n        assert a._type == _AmortizationType.ConstantPeriod\n\n    def test_custom(self):\n        a = Amortization(4, 100.0, [10.0, 20.0, 30.0])\n        assert a.outstanding == (100.0, 90.0, 70.0, 40.0)\n        assert a.amortization == (10.0, 20.0, 30.0)\n        assert a._type == _AmortizationType.CustomSchedule\n\n\ndef test_leg_amortization() -> None:\n    fixed_leg = FixedLeg(\n        schedule=Schedule(\n            dt(2022, 1, 1),\n            dt(2022, 10, 1),\n            frequency=\"Q\",\n        ),\n        notional=1e6,\n        amortization=250e3,\n        fixed_rate=2.0,\n    )\n    for i, period in enumerate(fixed_leg.periods):\n        assert period.settlement_params.notional == 1e6 - 250e3 * i\n\n    float_leg = FloatLeg(\n        schedule=Schedule(\n            dt(2022, 1, 1),\n            dt(2022, 10, 1),\n            frequency=\"Q\",\n        ),\n        notional=1e6,\n        amortization=250e3,\n        float_spread=2.0,\n    )\n    for i, period in enumerate(float_leg.periods):\n        assert period.settlement_params.notional == 1e6 - 250e3 * i\n\n    index_leg = FixedLeg(\n        schedule=Schedule(\n            dt(2022, 1, 1),\n            dt(2022, 10, 1),\n            frequency=\"Q\",\n        ),\n        notional=1e6,\n        amortization=250e3,\n        fixed_rate=2.0,\n        index_base=100.0,\n    )\n    for i, period in enumerate(index_leg.periods):\n        assert period.settlement_params.notional == 1e6 - 250e3 * i\n\n    index_leg_exchange = FixedLeg(\n        schedule=Schedule(\n            dt(2022, 1, 1),\n            dt(2022, 10, 1),\n            frequency=\"Q\",\n        ),\n        notional=1e6,\n        amortization=250e3,\n        fixed_rate=2.0,\n        index_base=100.0,\n        initial_exchange=False,\n        final_exchange=True,\n    )\n    for i, period in enumerate(index_leg_exchange.periods[0::2]):\n        assert period.settlement_params.notional == 1e6 - 250e3 * i\n    for i, period in enumerate(index_leg_exchange.periods[1:4:2]):\n        assert period.settlement_params.notional == 250e3\n\n\ndef test_custom_leg_raises() -> None:\n    with pytest.raises(ValueError):\n        _ = CustomLeg(periods=[\"bad_period\"])\n\n\ndef test_custom_leg() -> None:\n    float_leg = FloatLeg(\n        schedule=Schedule(effective=dt(2022, 1, 1), termination=dt(2023, 1, 1), frequency=\"S\"),\n    )\n    custom_leg = CustomLeg(periods=float_leg.periods)\n    for i, period in enumerate(custom_leg.periods):\n        assert period == float_leg.periods[i]\n\n\n@pytest.mark.parametrize(\n    (\"fx_fixings\", \"exp\"),\n    [\n        (NoInput(0), [NoInput(0), NoInput(0), NoInput(0)]),\n        ([1.5], [1.5, NoInput(0), NoInput(0)]),\n        (1.25, [1.25, NoInput(0), NoInput(0)]),\n        ((1.25, Series([1.5], index=[dt(2022, 4, 4)])), [1.25, 1.5, NoInput(0)]),\n    ],\n)\ndef test_fixed_leg_exchange_mtm(fx_fixings, exp) -> None:\n    fixed_leg_exch = FixedLeg(\n        schedule=Schedule(\n            effective=dt(2022, 1, 3),\n            termination=dt(2022, 7, 3),\n            frequency=\"Q\",\n            payment_lag_exchange=3,\n        ),\n        fixed_rate=5.0,\n        currency=\"usd\",\n        pair=\"eurusd\",\n        notional=10e6,\n        fx_fixings=fx_fixings,\n        mtm=\"xcs\",\n        initial_exchange=True,\n    )\n    fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(\n        fxr,\n        {\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.965}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n            \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.987}),\n        },\n    )\n\n    d = [\n        dt(2022, 1, 6),\n        dt(2022, 4, 6),\n        dt(2022, 7, 6),\n    ]  # payment_lag_exchange is 3 days.\n    rate = [_ if _ is not NoInput(0) else fxf.rate(\"eurusd\", d[i]) for i, _ in enumerate(exp)]\n\n    fixed_leg_exch.cashflows(\n        rate_curve=fxf.curve(\"usd\", \"usd\"), disc_curve=fxf.curve(\"usd\", \"usd\"), fx=fxf\n    )\n    assert float(fixed_leg_exch.periods[0].try_cashflow(fx=fxf).unwrap() - 10e6 * rate[0]) < 1e-6\n    assert (\n        float(fixed_leg_exch.periods[2].try_cashflow(fx=fxf).unwrap() - 10e6 * (rate[1] - rate[0]))\n        < 1e-6\n    )\n    assert (\n        float(fixed_leg_exch.periods[4].try_cashflow(fx=fxf).unwrap() - 10e6 * (rate[2] - rate[1]))\n        < 1e-6\n    )\n    assert fixed_leg_exch.periods[4].settlement_params.payment == dt(2022, 7, 6)\n\n    assert fixed_leg_exch.periods[1].settlement_params.notional == 10e6\n    assert fixed_leg_exch.periods[1].non_deliverable_params.fx_fixing.value == exp[0]\n    assert fixed_leg_exch.periods[1].non_deliverable_params.fx_fixing.date == dt(2022, 1, 4)\n\n    assert type(fixed_leg_exch.periods[1]) is FixedPeriod\n    assert fixed_leg_exch.periods[3].settlement_params.notional == 10e6\n    assert fixed_leg_exch.periods[3].non_deliverable_params.fx_fixing.value == exp[1]\n    assert fixed_leg_exch.periods[3].non_deliverable_params.fx_fixing.date == dt(2022, 4, 4)\n    assert type(fixed_leg_exch.periods[3]) is FixedPeriod\n\n    assert fixed_leg_exch.periods[-1].settlement_params.notional == 10e6\n    assert fixed_leg_exch.periods[-1].non_deliverable_params.fx_fixing.value == exp[1]\n    assert fixed_leg_exch.periods[-1].non_deliverable_params.fx_fixing.date == dt(2022, 4, 4)\n\n\n@pytest.mark.parametrize(\n    (\"type_\", \"expected\", \"kw\"),\n    [\n        (FloatLeg, [522.324262, 522.324262], {\"float_spread\": 1.0}),\n        (FixedLeg, [522.324262, 53772.226595], {\"fixed_rate\": 2.5}),\n    ],\n)\ndef test_mtm_leg_exchange_metrics(type_, expected, kw) -> None:\n    leg = type_(\n        schedule=Schedule(\n            effective=dt(2022, 1, 3),\n            termination=dt(2022, 7, 3),\n            frequency=\"Q\",\n            payment_lag=0,\n            payment_lag_exchange=0,\n        ),\n        currency=\"usd\",\n        pair=\"eurusd\",\n        notional=10e6,\n        initial_exchange=True,\n        mtm=\"xcs\",\n        **kw,\n    )\n    fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(\n        fxr,\n        {\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.965}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.985}),\n            \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.987}),\n        },\n    )\n\n    # d = [\n    #     dt(2022, 1, 6),\n    #     dt(2022, 4, 6),\n    #     dt(2022, 7, 6),\n    # ]  # payment_lag_exchange is 3 days.\n    # rate = [fxf.rate(\"eurusd\", d[i]) for i in range(3)]\n\n    result = leg.analytic_delta(\n        rate_curve=fxf.curve(\"usd\", \"usd\"), disc_curve=fxf.curve(\"usd\", \"usd\"), fx=fxf\n    )\n    assert float(result - expected[0]) < 1e-6\n\n    result = leg.npv(rate_curve=fxf.curve(\"usd\", \"usd\"), disc_curve=fxf.curve(\"usd\", \"usd\"), fx=fxf)\n    assert float(result - expected[1]) < 1e-6\n\n\n@pytest.mark.parametrize(\n    (\"klass\", \"kwargs\", \"expected\"),\n    [\n        (FixedLeg, {}, [200.0, 300.0, 400.0]),\n        (\n            FixedLeg,\n            {\"initial_exchange\": False, \"final_exchange\": True},\n            [200.0, 300.0, 400.0, 400.0],\n        ),\n        (ZeroFixedLeg, {}, [400.0]),\n    ],\n)\ndef test_set_index_fixings_series_leg_types(klass, kwargs, expected) -> None:\n    index_fixings = Series(\n        [100.0, 200.0, 300, 400.0, 500.0],\n        index=[dt(2022, 1, 1), dt(2022, 2, 1), dt(2022, 5, 1), dt(2022, 8, 1), dt(2022, 11, 1)],\n    )\n    obj = klass(\n        schedule=Schedule(\n            effective=dt(2022, 2, 5),\n            termination=\"9M\",\n            frequency=\"Q\",\n        ),\n        index_fixings=index_fixings,\n        index_base=100.0,\n        index_lag=3,\n        index_method=\"monthly\",\n        **kwargs,\n    )\n    for i, period in enumerate(obj.periods):\n        if type(period) is Cashflow:\n            continue\n        assert period.index_params.index_fixing.value == expected[i]\n\n\n@pytest.mark.skip(reason=\"fixings as a list removed in v2.0\")\n@pytest.mark.parametrize(\n    (\"klass\", \"kwargs\", \"expected\"),\n    [\n        (FixedLeg, {\"index_fixings\": [200.0, 300.0, 400.0]}, [200.0, 300.0, 400.0]),\n        (\n            FixedLeg,\n            {\n                \"initial_exchange\": False,\n                \"final_exchange\": True,\n                \"index_fixings\": [200.0, 300.0, 400.0, 400.0],\n            },\n            [200.0, 300.0, 400.0, 400.0],\n        ),\n        (ZeroFixedLeg, {\"index_fixings\": [400.0]}, [400.0]),\n    ],\n)\ndef test_set_index_fixings_list_leg_types(klass, kwargs, expected) -> None:\n    obj = klass(\n        schedule=Schedule(\n            effective=dt(2022, 2, 5),\n            termination=\"9M\",\n            frequency=\"Q\",\n        ),\n        index_base=100.0,\n        index_lag=3,\n        index_method=\"monthly\",\n        **kwargs,\n    )\n    for i, period in enumerate(obj.periods):\n        if type(period) is Cashflow:\n            continue\n        assert period.index_fixings == expected[i]\n\n\n@pytest.mark.skip(reason=\"v2.2 refactored fixings. Fixing as dualtype is not allowed.\")\n@pytest.mark.parametrize(\n    (\"klass\", \"kwargs\", \"expected\"),\n    [\n        (FixedLeg, {\"index_fixings\": 200.0}, [200.0, NoInput(0), NoInput(0)]),\n        (\n            FixedLeg,\n            {\"initial_exchange\": False, \"final_exchange\": True, \"index_fixings\": 200.0},\n            [200.0, NoInput(0), NoInput(0), NoInput(0)],\n        ),\n        (ZeroFixedLeg, {\"index_fixings\": 400.0}, [400.0]),\n    ],\n)\ndef test_set_index_fixings_float_leg_types(klass, kwargs, expected) -> None:\n    obj = klass(\n        schedule=Schedule(\n            effective=dt(2022, 2, 5),\n            termination=\"9M\",\n            frequency=\"Q\",\n        ),\n        index_base=100.0,\n        index_lag=3,\n        index_method=\"monthly\",\n        **kwargs,\n    )\n    for i, period in enumerate(obj.periods):\n        if type(period) is Cashflow:\n            continue\n        assert period.index_fixings == expected[i]\n"
  },
  {
    "path": "python/tests/periods/test_fixings_exposure.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\nfrom datetime import datetime as dt\n\nimport numpy as np\nimport pandas as pd\nimport pytest\nfrom rateslib import fixings\nfrom rateslib.curves import Curve\nfrom rateslib.data.fixings import FXIndex\nfrom rateslib.enums import FloatFixingMethod, SpreadCompoundMethod\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.instruments import IRS\nfrom rateslib.periods import FixedPeriod, FloatPeriod, FXCallPeriod, MtmCashflow, ZeroFloatPeriod\nfrom rateslib.scheduling import Schedule\nfrom rateslib.solver import Solver\n\n\n@pytest.fixture\ndef curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.99,\n        dt(2022, 7, 1): 0.98,\n        dt(2022, 10, 1): 0.97,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\", id=\"curve_fixture\")\n\n\nclass TestFloatPeriod:\n    @pytest.mark.parametrize(\n        (\"method\"),\n        [\n            FloatFixingMethod.RFRPaymentDelay(),\n            FloatFixingMethod.RFRObservationShift(3),\n            FloatFixingMethod.RFRLockout(2),\n            FloatFixingMethod.RFRLookback(3),\n            FloatFixingMethod.RFRLockoutAverage(2),\n            FloatFixingMethod.RFRPaymentDelayAverage(),\n            FloatFixingMethod.RFRObservationShiftAverage(3),\n            FloatFixingMethod.RFRLookbackAverage(3),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"scm\", \"spread\"),\n        [\n            (SpreadCompoundMethod.NoneSimple, 0.0),\n            (SpreadCompoundMethod.NoneSimple, 500.0),\n            (SpreadCompoundMethod.ISDACompounding, 0.0),\n            (SpreadCompoundMethod.ISDACompounding, 500.0),\n            (SpreadCompoundMethod.ISDAFlatCompounding, 0.0),\n            (SpreadCompoundMethod.ISDAFlatCompounding, 500.0),\n        ],\n    )\n    def test_baseline_versus_solver_fixings_sensitivity(self, method, scm, spread, curve):\n        # the Solver can make fixings exposure calculations independently from analytical\n        # calculations and approximations. This tests validates the analytical calculations\n        # against the Solver\n        if type(method) in [\n            FloatFixingMethod.RFRLockoutAverage,\n            FloatFixingMethod.RFRPaymentDelayAverage,\n            FloatFixingMethod.RFRObservationShiftAverage,\n            FloatFixingMethod.RFRLookbackAverage,\n        ] and scm in [\n            SpreadCompoundMethod.ISDAFlatCompounding,\n            SpreadCompoundMethod.ISDACompounding,\n        ]:\n            pytest.skip(reason=\"Impossible combination raises ValueError on initialisation.\")\n\n        # let us construct baseline instruments\n        rate_curve = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.00,\n                dt(2022, 1, 31): 0.99,\n                dt(2022, 2, 1): 0.99,\n                dt(2022, 2, 2): 0.99,\n                dt(2022, 2, 3): 0.99,\n                dt(2022, 2, 4): 0.99,\n                dt(2022, 2, 7): 0.99,\n                dt(2022, 2, 8): 0.99,\n                dt(2022, 2, 9): 0.99,\n                dt(2022, 2, 10): 0.98,\n                dt(2029, 2, 1): 0.97,\n            },\n            interpolation=\"log_linear\",\n            calendar=\"nyc\",\n            id=\"curve\",\n        )\n        solver = Solver(\n            curves=[rate_curve],\n            instruments=[\n                IRS(\n                    dt(2022, 1, 4), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 1, 31), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 2, 1), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 2, 2), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 2, 3), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 2, 4), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 2, 7), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 2, 8), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 2, 9), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n                IRS(\n                    dt(2022, 2, 10), \"1b\", spec=\"usd_irs\", payment_lag=0, curves=[rate_curve, curve]\n                ),\n            ],\n            s=[4.03] * 10,\n        )\n        p = FloatPeriod(\n            notional=-10e6,\n            fixing_series=\"usd_rfr\",\n            fixing_method=method,\n            frequency=\"A\",\n            start=dt(2022, 2, 3),\n            end=dt(2022, 2, 10),\n            float_spread=spread,\n            payment=dt(2022, 2, 10),\n            convention=\"act360\",\n            spread_compound_method=scm,\n        )\n        risk = solver.delta(npv=p.npv(rate_curve=rate_curve, disc_curve=curve, local=True))\n        fixings_ = p.local_analytic_rate_fixings(rate_curve=rate_curve, disc_curve=curve)\n        fixings_ = fixings_.reindex(\n            [\n                dt(2022, 1, 30),\n                dt(2022, 1, 31),\n                dt(2022, 2, 1),\n                dt(2022, 2, 2),\n                dt(2022, 2, 3),\n                dt(2022, 2, 4),\n                dt(2022, 2, 7),\n                dt(2022, 2, 8),\n                dt(2022, 2, 9),\n                dt(2022, 2, 10),\n            ],\n            fill_value=np.nan,\n        )\n\n        risk_compare = fixings_[(\"curve\", \"usd\", \"usd\", \"1B\")].astype(float).fillna(0.0).to_numpy()\n        risk_array = risk.to_numpy()[:, 0].copy()\n\n        _diff = np.max(np.abs(risk_compare - risk_array))\n        if scm == SpreadCompoundMethod.ISDAFlatCompounding and spread > 100.0:\n            atol = 1e-2\n        else:\n            atol = 1e-12\n        assert np.all(np.isclose(risk_array, risk_compare, atol=atol))\n\n        # now add some fixings\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            f\"{name}_1B\",\n            pd.Series(\n                index=[dt(2022, 1, 31), dt(2022, 2, 1), dt(2022, 2, 2), dt(2022, 2, 3)],\n                data=[4.03, 4.03, 4.03, 4.03],\n            ),\n        )\n        p = FloatPeriod(\n            notional=-10e6,\n            fixing_series=\"usd_rfr\",\n            fixing_method=method,\n            frequency=\"A\",\n            start=dt(2022, 2, 3),\n            end=dt(2022, 2, 10),\n            float_spread=spread,\n            payment=dt(2022, 2, 10),\n            convention=\"act360\",\n            spread_compound_method=scm,\n            rate_fixings=name,\n        )\n\n        fixings_ = p.local_analytic_rate_fixings(rate_curve=rate_curve, disc_curve=curve)\n        fixings_ = fixings_.reindex(\n            [\n                dt(2022, 1, 30),\n                dt(2022, 1, 31),\n                dt(2022, 2, 1),\n                dt(2022, 2, 2),\n                dt(2022, 2, 3),\n                dt(2022, 2, 4),\n                dt(2022, 2, 7),\n                dt(2022, 2, 8),\n                dt(2022, 2, 9),\n                dt(2022, 2, 10),\n            ],\n            fill_value=np.nan,\n        )\n\n        risk_array[:5] = 0.0\n        risk_compare = fixings_[(\"curve\", \"usd\", \"usd\", \"1B\")].astype(float).fillna(0.0).to_numpy()\n\n        assert np.all(np.isclose(risk_array, risk_compare, atol=atol))\n\n    def test_ibor_curve_example_book(self, curve):\n        p = FloatPeriod(\n            notional=-10e6,\n            fixing_series=\"eur_ibor\",\n            fixing_method=\"ibor(2)\",\n            frequency=\"Q\",\n            start=dt(2025, 10, 8),\n            end=dt(2026, 1, 8),\n            float_spread=100.0,\n            payment=dt(2026, 1, 8),\n            convention=\"act360\",\n            calendar=\"tgt\",\n        )\n        result = p.try_unindexed_reference_cashflow_analytic_rate_fixings(rate_curve=curve).unwrap()\n        assert abs(result.iloc[0, 0] - 10e2 * 92 / 360) < 1e-12\n        assert result.index[0] == dt(2025, 10, 6)\n\n    def test_ibor_stub_curve_example_book(self, curve):\n        p = FloatPeriod(\n            notional=-10e6,\n            fixing_method=FloatFixingMethod.IBOR(2),\n            frequency=\"Q\",\n            start=dt(2025, 10, 8),\n            end=dt(2025, 12, 16),\n            float_spread=100.0,\n            payment=dt(2025, 12, 16),\n            convention=\"act360\",\n            calendar=\"tgt\",\n            stub=True,\n        )\n        result = p.try_unindexed_reference_cashflow_analytic_rate_fixings(\n            rate_curve={\"2m\": curve, \"3m\": curve, \"6m\": curve}\n        ).unwrap()\n        alpha = 23 / 31.0\n        assert abs(result.iloc[0, 0] - 10e2 * 69 / 360 * alpha) < 1e-12\n        assert abs(result.iloc[0, 1] - 10e2 * 69 / 360 * (1 - alpha)) < 1e-12\n        assert result.index[0] == dt(2025, 10, 6)\n\n    def test_ibor_fixing_set(self, curve):\n        p = FloatPeriod(\n            notional=-10e6,\n            fixing_series=\"eur_ibor\",\n            fixing_method=\"ibor(2)\",\n            rate_fixings=2.0,\n            frequency=\"Q\",\n            start=dt(2025, 10, 8),\n            end=dt(2026, 1, 8),\n            float_spread=100.0,\n            payment=dt(2026, 1, 8),\n            convention=\"act360\",\n            calendar=\"tgt\",\n        )\n        result = p.try_unindexed_reference_cashflow_analytic_rate_fixings(rate_curve=curve).unwrap()\n        assert abs(result.iloc[0, 0]) < 1e-12\n        assert result.index[0] == dt(2025, 10, 6)\n\n    def test_ibor_stub_curve_fixings_set(self, curve):\n        p = FloatPeriod(\n            notional=-10e6,\n            fixing_method=\"ibor(2)\",\n            frequency=\"Q\",\n            start=dt(2025, 10, 8),\n            end=dt(2025, 12, 16),\n            float_spread=100.0,\n            payment=dt(2025, 12, 16),\n            convention=\"act360\",\n            calendar=\"tgt\",\n            stub=True,\n            rate_fixings=2.0,\n        )\n        result = p.try_unindexed_reference_cashflow_analytic_rate_fixings(\n            rate_curve={\"2m\": curve, \"3m\": curve, \"6m\": curve}\n        ).unwrap()\n        assert abs(result.iloc[0, 0]) < 1e-12\n        assert abs(result.iloc[0, 1]) < 1e-12\n        assert result.index[0] == dt(2025, 10, 6)\n\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\"),\n        [\n            (FloatFixingMethod.RFRPaymentDelay(), [0, 0, 0, 0, 277, 830, 277, 277, 277, 0]),\n            (FloatFixingMethod.RFRLockout(2), [0, 0, 0, 0, 277, 830, 830, 0, 0, 0]),\n            (FloatFixingMethod.RFRLookback(3), [0, 277, 830, 277, 277, 277, 0, 0, 0, 0]),\n            (FloatFixingMethod.RFRObservationShift(3), [0, 277, 277, 277, 277, 830, 0, 0, 0, 0]),\n        ],\n    )\n    def test_rfr_curve_book(self, method, expected, curve):\n        p = FloatPeriod(\n            notional=-1e6,\n            fixing_series=\"usd_rfr\",\n            fixing_method=method,\n            frequency=\"Q\",\n            start=dt(2022, 2, 3),\n            end=dt(2022, 2, 10),\n            float_spread=0.0,\n            payment=dt(2022, 2, 10),\n        )\n        result = p.local_analytic_rate_fixings(rate_curve=curve)\n        result = result.reindex(\n            pd.Index(\n                data=[\n                    dt(2022, 1, 30),\n                    dt(2022, 1, 31),\n                    dt(2022, 2, 1),\n                    dt(2022, 2, 2),\n                    dt(2022, 2, 3),\n                    dt(2022, 2, 4),\n                    dt(2022, 2, 7),\n                    dt(2022, 2, 8),\n                    dt(2022, 2, 9),\n                    dt(2022, 2, 10),\n                ]\n            ),\n            fill_value=0.0,\n        )\n        for i in range(10):\n            assert abs(expected[i] - result.iloc[i, 0] * 1000) < 5e-1\n\n    def test_doc_reset(self):\n        fp = FloatPeriod(\n            start=dt(2026, 1, 12),\n            end=dt(2026, 1, 16),\n            payment=dt(2026, 1, 16),\n            frequency=\"M\",\n            fixing_method=\"rfr_payment_delay\",\n            rate_fixings=\"sofr\",\n        )\n        fixings.add(\n            name=\"sofr_1B\",\n            series=pd.Series(\n                index=[dt(2026, 1, 12), dt(2026, 1, 13), dt(2026, 1, 14), dt(2026, 1, 15)],\n                data=[3.1, 3.2, 3.3, 3.4],\n            ),\n        )\n        # value is populated from given data\n        assert 3.245 < fp.rate_params.rate_fixing.value < 3.255\n        fp.reset_fixings()\n        # private data related to fixing is removed and requires new data lookup\n        assert fp.rate_params.rate_fixing._value == NoInput(0)\n        assert fp.rate_params.rate_fixing._populated.empty\n        fixings.pop(\"sofr_1B\")\n\n\nclass TestFixedPeriod:\n    def test_immediate_fixing_sensitivity(self, curve):\n        p = FixedPeriod(\n            fixed_rate=2.0,\n            start=dt(2022, 1, 1),\n            end=dt(2022, 2, 1),\n            payment=dt(2022, 2, 1),\n            frequency=\"M\",\n            notional=2e6,\n            currency=\"usd\",\n            convention=\"act360\",\n        )\n        result = p.try_immediate_analytic_rate_fixings(disc_curve=curve).unwrap()\n        assert isinstance(result, pd.DataFrame)\n        assert result.empty\n\n\nclass TestMtmCashflow:\n    def test_local_fixings(self):\n        curve1 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        curve2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        fxf = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.10}, dt(2000, 1, 1)),\n            fx_curves={\"eureur\": curve2, \"eurusd\": curve2, \"usdusd\": curve1},\n        )\n        fixings.add(\"wmr12_eurusd\", pd.Series(index=[dt(1999, 1, 1)], data=[1.15]))\n        mc = MtmCashflow(\n            currency=\"usd\",\n            notional=2e6,\n            pair=\"eurusd\",\n            payment=dt(2000, 2, 15),\n            start=dt(2000, 1, 10),\n            end=dt(2000, 2, 15),\n            fx_fixings_start=\"wmr12\",\n            fx_fixings_end=\"wmr12\",\n        )\n        result = mc.local_fixings(\n            disc_curve=curve1,\n            fx=fxf,\n            identifiers=[\n                (\n                    \"wmr12_eurusd\",\n                    pd.Series(\n                        index=[dt(2000, 1, 6), dt(2000, 2, 11)],\n                        data=[\n                            fxf.rate(\"eurusd\", dt(2000, 1, 10)),\n                            fxf.rate(\"eurusd\", dt(2000, 2, 15)),\n                        ],\n                    ),\n                )\n            ],\n        )\n        assert abs(result.iloc[0, 0] - 2e6 * 1.0 * curve1[dt(2000, 2, 15)]) < 1e-6\n        assert abs(result.iloc[1, 0] + 2e6 * 1.0 * curve1[dt(2000, 2, 15)]) < 1e-6\n        fixings.pop(\"wmr12_eurusd\")\n\n\nclass TestFXCallPeriod:\n    @pytest.mark.parametrize((\"fixing\", \"itm\"), [(1.15, True), (1.05, False)])\n    def test_itm_otm_fixing(self, fixing, itm):\n        curve1 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        # curve2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        # fxf = FXForwards(\n        #     fx_rates=FXRates({\"eurusd\": 1.10}, dt(2000, 1, 1)),\n        #     fx_curves={\"eureur\": curve2, \"eurusd\": curve2, \"usdusd\": curve1},\n        # )\n        fixings.add(\"wmr13_eurusd\", pd.Series(index=[dt(1999, 1, 1)], data=[1.15]))\n        fxo = FXCallPeriod(\n            delivery=dt(2000, 3, 1),\n            pair=\"eurusd\",\n            expiry=dt(2000, 2, 28),\n            strike=1.10,\n            delta_type=\"forward\",\n            notional=1e6,\n            option_fixings=\"wmr13\",\n        )\n        result = fxo.local_fixings(\n            identifiers=[\n                (\"wmr13_eurusd\", pd.Series(index=[dt(2000, 2, 28)], data=[fixing])),\n            ],\n            disc_curve=curve1,\n        )\n        assert abs(result.iloc[0, 0] - itm * 1e6 * 1.0 * curve1[dt(2000, 3, 1)]) < 1e-6\n        fixings.pop(\"wmr13_eurusd\")\n\n\nclass TestZeroFloatPeriod:\n    def test_multiple_sub_periods(self):\n        fixings.add(\"MY_RATE_INDEX_6M\", pd.Series(index=[dt(1999, 1, 1)], data=[1.15]))\n        period = ZeroFloatPeriod(\n            schedule=Schedule(dt(2000, 1, 1), \"2Y\", \"S\"),\n            fixing_method=FloatFixingMethod.IBOR(0),\n            rate_fixings=\"MY_RATE_INDEX\",\n            convention=\"Act360\",\n            notional=1e6,\n        )\n        rc = Curve({dt(2000, 1, 1): 1.0, dt(2003, 1, 1): 0.95})\n        from rateslib.legs import CustomLeg\n\n        # cf = CustomLeg(periods=period.float_periods).cashflows(rate_curve=rc)\n        result = period.local_fixings(\n            identifiers=[\n                (\n                    \"MY_RATE_INDEX_6M\",\n                    pd.Series(index=[dt(2000, 1, 1), dt(2000, 7, 1)], data=[1.692, 1.692]),\n                )\n            ],\n            scalars=[0.01],\n            rate_curve=rc,\n        )\n        expected = period.local_analytic_rate_fixings(rate_curve=rc)\n\n        assert abs(result.iloc[0, 0] - expected.iloc[0, 0]) < 1e-4\n        assert abs(result.iloc[1, 0] - expected.iloc[1, 0]) < 1e-4\n\n        assert period.float_periods[0].rate_params.rate_fixing.value == NoInput(0)\n        fixings.pop(\"MY_RATE_INDEX_6M\")\n\n\ndef test_local_fixings_raises_scalars():\n    curve1 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n    fixings.add(\"wmr12_eurusd\", pd.Series(index=[dt(1999, 1, 1)], data=[1.15]))\n    mc = MtmCashflow(\n        currency=\"usd\",\n        notional=2e6,\n        pair=FXIndex(\"eurusd\", \"tgt|fed\", 2, \"all\", 0),\n        payment=dt(2000, 2, 15),\n        start=dt(2000, 1, 10),\n        end=dt(2000, 2, 15),\n        fx_fixings_start=\"wmr12\",\n        fx_fixings_end=\"wmr12\",\n    )\n    with pytest.raises(ValueError, match=\"If given, ``scalars`` must be same length as\"):\n        mc.local_fixings(\n            identifiers=[\n                (\n                    \"wmr12_eurusd\",\n                    pd.Series(index=[dt(2000, 1, 10), dt(2000, 2, 15)], data=[1.1, 1.1]),\n                )\n            ],\n            scalars=[1.0, 2.0],\n            disc_curve=curve1,\n        )\n    fixings.pop(\"wmr12_eurusd\")\n"
  },
  {
    "path": "python/tests/periods/test_fixings_load.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\nfrom datetime import datetime as dt\n\nimport pytest\nimport rateslib.errors as err\nfrom pandas import Series\nfrom rateslib import fixings\nfrom rateslib.data.fixings import (\n    FloatRateIndex,\n    FloatRateSeries,\n    FXFixing,\n    FXIndex,\n    IBORFixing,\n    IBORStubFixing,\n    RFRFixing,\n)\nfrom rateslib.data.loader import FixingMissingDataError\nfrom rateslib.enums import FloatFixingMethod, SpreadCompoundMethod\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.enums.parameters import IndexMethod\nfrom rateslib.errors import VE_INDEX_BASE_NO_STR\nfrom rateslib.periods import Cashflow, FloatPeriod\nfrom rateslib.scheduling.frequency import Frequency\n\n\nclass TestIndexParams:\n    def test_index_lookup_and_populate_from_str_fixings(self):\n        rpi = Series(index=[dt(2000, 1, 1), dt(2000, 1, 2)], data=[101.0, 103.0])\n        name = str(hash(os.urandom(8)))\n        fixings.add(name, rpi)\n        c = Cashflow(\n            payment=dt(2000, 1, 2),\n            notional=1e6,\n            index_fixings=name,\n            index_method=IndexMethod.Curve,\n            index_base_date=dt(2000, 1, 1),\n            index_lag=0,\n        )\n        assert c.index_params.index_fixing.value == 103.0\n        assert c.index_params.index_base.value == 101.0\n        fixings.pop(name)\n\n    def test_lookup_and_populate_from_series_fixings(self):\n        rpi = Series(index=[dt(2000, 1, 1), dt(2000, 1, 2)], data=[101.0, 103.0])\n        with pytest.warns(FutureWarning, match=err.FW_FIXINGS_AS_SERIES[:25]):\n            c = Cashflow(\n                payment=dt(2000, 1, 2),\n                notional=1e6,\n                index_fixings=rpi,\n                index_method=IndexMethod.Curve,\n                index_base_date=dt(2000, 1, 1),\n                index_lag=0,\n            )\n        assert c.index_params.index_fixing.value == 103.0\n        assert c.index_params.index_base.value == 101.0\n\n    def test_immutable_index_fixings(self):\n        c = Cashflow(\n            payment=dt(2000, 1, 2),\n            notional=1e6,\n            index_fixings=0.0,\n            index_method=IndexMethod.Curve,\n            index_base_date=dt(2000, 1, 1),\n            index_lag=0,\n        )\n        with pytest.raises(ValueError, match=err.VE_ATTRIBUTE_IS_IMMUTABLE.format(\"index_fixing\")):\n            c.index_params.index_fixing = 2.0\n\n    def test_index_fixings_determined_once(self):\n        # a change in the datastore will not affect an already loaded fixing for the period\n        c = Cashflow(\n            payment=dt(2000, 1, 2),\n            notional=1e6,\n            index_fixings=\"rpi\",\n            index_method=IndexMethod.Curve,\n            index_base_date=dt(2000, 1, 1),\n            index_lag=0,\n        )\n        rpi = Series(index=[dt(2000, 1, 1), dt(2000, 1, 2)], data=[101.0, 103.0])\n        fixings.add(\"rpi\", rpi)\n        before1 = c.index_params.index_fixing.value\n        before2 = c.index_params.index_base.value\n\n        fixings.pop(\"rpi\")\n        rpi2 = Series(index=[dt(2000, 1, 1), dt(2000, 1, 2)], data=[201.0, 203.0])\n        fixings.add(\"rpi\", rpi2)\n        assert c.index_params.index_fixing.value == before1\n        assert c.index_params.index_base.value == before2\n\n    @pytest.mark.parametrize(\"int_or_float\", [3, 3.0])\n    def test_index_fixings_as_scalar(self, int_or_float):\n        # a scalar value for `index_fixings` will only impact `index_fixing` and not `index_base`\n        c = Cashflow(\n            payment=dt(2000, 1, 2),\n            notional=1e6,\n            index_fixings=int_or_float,\n            index_method=IndexMethod.Curve,\n            index_base_date=dt(2000, 1, 1),\n            index_lag=0,\n        )\n        assert c.index_params.index_fixing.value == int_or_float\n        assert c.index_params.index_base.value == NoInput(0)\n\n    def test_index_base_as_str_raises(self):\n        # index base as string series identifier will not work\n        with pytest.raises(ValueError, match=VE_INDEX_BASE_NO_STR):\n            Cashflow(\n                payment=dt(2000, 1, 2),\n                notional=1e6,\n                index_fixings=0.0,\n                index_method=IndexMethod.Curve,\n                index_base_date=dt(2000, 1, 1),\n                index_base=\"str\",\n                index_lag=0,\n            )\n\n    def test_index_realtime_updates(self):\n        # test that the first series contains no data and an update adds new data\n        rpi = Series(index=[dt(2000, 1, 1), dt(2000, 1, 2)], data=[101.0, 103.0])\n        name = str(hash(os.urandom(8)))\n        fixings.add(name, rpi)\n        c = Cashflow(\n            payment=dt(2000, 1, 3),\n            notional=1e6,\n            index_fixings=name,\n            index_method=IndexMethod.Curve,\n            index_base_date=dt(2000, 1, 3),\n            index_lag=0,\n        )\n        assert c.index_params.index_fixing.value == NoInput(0)\n        assert c.index_params.index_base.value == NoInput(0)\n        fixings.pop(name)\n        rpi = Series(index=[dt(2000, 1, 1), dt(2000, 1, 3)], data=[101.0, 105.0])\n        fixings.add(name, rpi)\n        assert c.index_params.index_fixing.value == 105.0\n        assert c.index_params.index_base.value == 105.0\n\n\nclass TestSettlementParams:\n    def test_fx_fixings_no_input(\n        self,\n    ):\n        c = Cashflow(currency=\"usd\", pair=\"eurusd\", payment=dt(2000, 1, 2), notional=2.0)\n        assert isinstance(c.non_deliverable_params.fx_fixing, FXFixing)\n        assert c.non_deliverable_params.fx_fixing.value is NoInput(0)\n\n    def test_fx_fixings_scalar_input(self):\n        c = Cashflow(\n            currency=\"usd\", pair=\"eurusd\", payment=dt(2000, 1, 2), notional=2.0, fx_fixings=2.0\n        )\n        assert c.non_deliverable_params.fx_fixing.value == 2.0\n        assert c.non_deliverable_params.fx_fixing._state == 0\n\n    def test_fx_fixings_series_input(self):\n        s = Series(index=[dt(1999, 12, 29), dt(1999, 12, 30)], data=[1.1, 2.1])\n        c = Cashflow(\n            currency=\"usd\", pair=\"eurusd\", payment=dt(2000, 1, 2), notional=2.0, fx_fixings=s\n        )\n        assert c.non_deliverable_params.fx_fixing._state == 0\n        assert c.non_deliverable_params.fx_fixing.value == 2.1\n\n    def test_fx_fixings_str_input(self):\n        s = Series(index=[dt(1999, 12, 29), dt(1999, 12, 30)], data=[1.1, 2.1])\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_eurusd\", s)\n        c = Cashflow(\n            currency=\"usd\", pair=\"eurusd\", payment=dt(2000, 1, 2), notional=2.0, fx_fixings=name\n        )\n        assert c.non_deliverable_params.fx_fixing.value == 2.1\n        assert isinstance(c.non_deliverable_params.fx_fixing.identifier, str)\n        assert c.non_deliverable_params.fx_fixing._state == hash(fixings[name + \"_eurusd\"][0])\n        fixings.pop(name + \"_eurusd\")\n\n    def test_fx_fixings_str_state_cache(self):\n        s = Series(index=[dt(2000, 1, 1), dt(2000, 1, 2)], data=[1.1, 2.1])\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_eurusd\", s)\n        c = Cashflow(\n            currency=\"usd\",\n            pair=\"eurusd\",\n            payment=dt(2000, 1, 3),  # <- not in Series\n            notional=2.0,\n            fx_fixings=name,\n        )\n        assert c.non_deliverable_params.fx_fixing.value is NoInput(0)\n        assert isinstance(c.non_deliverable_params.fx_fixing.identifier, str)\n        # states match the hash because the FXFixing uses composite FXFixingMajors\n        assert c.non_deliverable_params.fx_fixing._state == hash(fixings[name + \"_eurusd\"][0])\n\n        assert c.non_deliverable_params.fx_fixing.value is NoInput(0)\n        assert c.non_deliverable_params.fx_fixing._state == hash(fixings[name + \"_eurusd\"][0])\n        fixings.pop(name + \"_eurusd\")\n\n    def test_fx_fixing_cashflow(self):\n        s = Series(index=[dt(1999, 12, 29), dt(1999, 12, 30)], data=[1.1, 2.1])\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_eurusd\", s)\n        c = Cashflow(\n            notional=100,\n            payment=dt(2000, 1, 2),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fx_fixings=name,\n        )\n        cf = c.cashflows()\n        assert cf[\"FX Fixing\"] == 2.1\n        fix = c.non_deliverable_params.fx_fixing.value\n        assert fix == 2.1\n        fixings.pop(name + \"_eurusd\")\n\n    def test_immutable_fx_fixing(self):\n        c = Cashflow(\n            payment=dt(2000, 1, 2),\n            notional=1e6,\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fx_fixings=0.0,\n        )\n        with pytest.raises(ValueError, match=err.VE_ATTRIBUTE_IS_IMMUTABLE.format(\"fx_fixing\")):\n            c.non_deliverable_params.fx_fixing = 2.0\n\n    def test_fx_missing_data_raises(self):\n        s = Series(index=[dt(1999, 12, 29), dt(2000, 1, 1)], data=[1.1, 2.1])\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_eurusd\", s)\n        c = Cashflow(\n            notional=100,\n            payment=dt(2000, 1, 2),\n            currency=\"usd\",\n            pair=\"eurusd\",\n            fx_fixings=name,\n        )\n        with pytest.raises(FixingMissingDataError, match=\"Fixing lookup for date \"):\n            c.non_deliverable_params.fx_fixing.value\n        fixings.pop(name + \"_eurusd\")\n\n    def test_fx_missing_data_raises_cross(self):\n        s = Series(index=[dt(1999, 12, 29), dt(1999, 12, 30)], data=[1.1, 2.1])\n        s2 = Series(index=[dt(1999, 12, 29), dt(2000, 1, 1)], data=[1.1, 2.1])\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_usdinr\", s)\n        fixings.add(name + \"_usdrub\", s2)\n        c = Cashflow(\n            notional=100,\n            payment=dt(2000, 1, 2),\n            currency=\"inr\",\n            pair=FXIndex(\"inrrub\", \"mum|fed\", 2, \"mum\", -2),\n            fx_fixings=name,\n        )\n        with pytest.raises(FixingMissingDataError, match=\"Fixing lookup for date \"):\n            c.non_deliverable_params.fx_fixing.value\n        fixings.pop(name + \"_usdinr\")\n        fixings.pop(name + \"_usdrub\")\n\n\nclass TestRateParams:\n    def test_rate_fixings_input_as_str_out_of_range(\n        self,\n    ):\n        s = Series(index=[dt(1999, 1, 1), dt(1999, 1, 2)], data=[1.1, 2.1])\n        fixings.add(\"IBOR123dfgs_1M\", s)\n        c = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            notional=2.0,\n            frequency=\"M\",\n            fixing_series=\"usd_ibor\",\n            fixing_method=\"IBOR(2)\",\n            rate_fixings=\"IBOR123dfgs\",\n        )\n        assert c.rate_params.rate_fixing.value == NoInput(0)\n        assert c.rate_params.rate_fixing.value == NoInput(0)\n        assert c.rate_params.rate_fixing.identifier == \"IBOR123dfgs_1M\".upper()\n        assert c.rate_params.rate_fixing._state == fixings[\"IBOR123dfgs_1M\"][0]\n        fixings.pop(\"IBOR123dfgs_1M\")\n\n    def test_rate_fixings_no_input(\n        self,\n    ):\n        c = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            notional=2.0,\n            frequency=\"M\",\n            fixing_method=FloatFixingMethod.IBOR(2),\n            fixing_series=\"usd_ibor\",\n            rate_fixings=NoInput(0),\n        )\n        assert c.rate_params.rate_fixing.value == NoInput(0)\n        assert c.rate_params.rate_fixing.value == NoInput(0)\n        assert c.rate_params.rate_fixing._state == 0\n\n    def test_rate_fixings_scalar(\n        self,\n    ):\n        c = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            notional=2.0,\n            frequency=\"M\",\n            fixing_method=\"IBOR(2)\",\n            fixing_series=\"usd_ibor\",\n            rate_fixings=2.5,\n        )\n        assert c.rate_params.rate_fixing.value == 2.5\n        assert c.rate_params.rate_fixing.value == 2.5\n        assert c.rate_params.rate_fixing._state == 0\n\n    def test_ibor_fixing_load(self):\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_3M\", Series(index=[dt(2022, 1, 3)], data=[55.0]))\n        f = IBORFixing(\n            accrual_start=dt(2022, 1, 5),\n            rate_index=FloatRateIndex(\n                frequency=Frequency.Months(3, None),\n                series=\"eur_ibor\",\n            ),\n            identifier=f\"{name}_3M\",\n        )\n        assert f.value == 55.0\n        assert f._state == fixings[f\"{name}_3M\"][0]\n\n    def test_stub_ibor_fixing_load(self):\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_3M\", Series(index=[dt(2022, 1, 3)], data=[55.0]))\n        fixings.add(f\"{name}_6M\", Series(index=[dt(2022, 1, 3)], data=[65.0]))\n        index_series = FloatRateIndex(\n            frequency=Frequency.Months(3, None),\n            series=\"eur_ibor\",\n        ).series\n        f = IBORStubFixing(\n            accrual_start=dt(2022, 1, 5),\n            accrual_end=dt(2022, 5, 21),\n            rate_series=index_series,\n            identifier=name,\n        )\n        assert f.value == 55 * 45 / 91 + 65 * 46 / 91\n        fixings.pop(f\"{name}_3M\")\n        fixings.pop(f\"{name}_6M\")\n\n    def test_rfr_fixings_load(self):\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            f\"{name}_1B\",\n            Series(\n                index=[dt(2023, 2, 8), dt(2023, 2, 9), dt(2023, 2, 10), dt(2023, 2, 13)],\n                data=[1.0, 2.0, 3.0, 4.0],\n            ),\n        )\n        rate_index = FloatRateIndex(\n            frequency=\"1B\",\n            series=\"usd_rfr\",\n        )\n        f = RFRFixing(\n            accrual_start=dt(2023, 2, 8),\n            accrual_end=dt(2023, 2, 13),\n            rate_index=rate_index,\n            fixing_method=FloatFixingMethod.RFRPaymentDelay(),\n            spread_compound_method=SpreadCompoundMethod.NoneSimple,\n            identifier=f\"{name}_1B\",\n            float_spread=0.0,\n        )\n        result = f.value\n        expected = ((1 + 1 / 36000) * (1 + 2 / 36000) * (1 + 3 * 3 / 36000) - 1) * 36000 / 5\n        assert abs(result - expected) < 1e-10\n\n        f = RFRFixing(\n            accrual_start=dt(2023, 2, 8),\n            accrual_end=dt(2023, 2, 17),\n            rate_index=rate_index,\n            fixing_method=FloatFixingMethod.RFRPaymentDelay(),\n            spread_compound_method=SpreadCompoundMethod.NoneSimple,\n            identifier=f\"{name}_1B\",\n            float_spread=0.0,\n        )\n        result = f.value\n        assert result == NoInput(0)\n\n    # # test is removed because a `fixing_series` with no tenors now\n    # # defaults to [1w, 1M, 3M, 6M, 12M]\n    # def test_stub_ibor_warns_no_series(self):\n    #     with pytest.warns(UserWarning, match=err.UW_NO_TENORS[:15]):\n    #         fix = IBORStubFixing(\n    #             accrual_start=dt(2022, 1, 5),\n    #             accrual_end=dt(2022, 5, 21),\n    #             rate_series=FloatRateSeries(\n    #                 lag=0,\n    #                 calendar=\"tgt\",\n    #                 convention=\"act360\",\n    #                 modifier=\"mf\",\n    #                 eom=False,\n    #             ),\n    #             identifier=\"NOT_AVAILABLE\",\n    #         )\n    #     assert isinstance(fix.value, NoInput)\n\n    def test_rfr_fixing_identifier(self):\n        p = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 4, 1),\n            frequency=Frequency.Months(3, None),\n            payment=dt(2000, 1, 4),\n            fixing_method=FloatFixingMethod.RFRPaymentDelay(),\n            rate_fixings=\"TEST\",\n        )\n        assert p.rate_params.fixing_identifier == \"TEST\"\n        assert p.rate_params.rate_fixing.identifier == \"TEST_1B\"\n\n    def test_ibor_fixing_identifier(self):\n        p = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 4, 1),\n            frequency=Frequency.Months(3, None),\n            payment=dt(2000, 1, 4),\n            fixing_method=FloatFixingMethod.IBOR(2),\n            rate_fixings=\"TEST\",\n        )\n        assert p.rate_params.fixing_identifier == \"TEST\"\n        assert p.rate_params.rate_fixing.identifier == \"TEST_3M\"\n\n    def test_ibor12M_fixing_identifier(self):\n        p = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2001, 1, 1),\n            frequency=Frequency.Months(12, None),\n            payment=dt(2000, 1, 4),\n            fixing_method=FloatFixingMethod.IBOR(2),\n            rate_fixings=\"TEST\",\n        )\n        assert p.rate_params.fixing_identifier == \"TEST\"\n        assert p.rate_params.rate_fixing.identifier == \"TEST_12M\"\n\n    def test_ibor_stub_fixing_identifier(self):\n        # these tenors are derived from the default tenors [1W, 1M, 3M, 6M, 12M]\n        p = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 3, 1),\n            frequency=Frequency.Months(3, None),\n            payment=dt(2000, 1, 4),\n            fixing_method=FloatFixingMethod.IBOR(2),\n            stub=True,\n            rate_fixings=\"TEST\",\n        )\n        assert p.rate_params.rate_fixing.fixing1.identifier == \"TEST_1M\"\n        assert p.rate_params.rate_fixing.fixing2.identifier == \"TEST_3M\"\n"
  },
  {
    "path": "python/tests/periods/test_float_rate.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\nfrom datetime import datetime as dt\n\nimport pytest\nimport rateslib.errors as err\nfrom pandas import NA, Series\nfrom pandas.testing import assert_series_equal\nfrom rateslib import fixings\nfrom rateslib.curves import Curve, LineCurve\nfrom rateslib.data.fixings import FloatRateIndex, FloatRateSeries, _RFRRate\nfrom rateslib.data.loader import FixingMissingForecasterError\nfrom rateslib.default import NoInput\nfrom rateslib.enums.parameters import FloatFixingMethod, SpreadCompoundMethod\nfrom rateslib.periods.float_rate import rate_value\nfrom rateslib.scheduling import Adjuster, Convention, Frequency, NamedCal\n\n\n@pytest.fixture\ndef curve():\n    return Curve(\n        nodes={\n            dt(2000, 1, 3): 1.00,\n            dt(2000, 4, 3): 1.00 / (1.0 + 0.02 * 91 / 360),\n        },\n        convention=\"Act360\",\n        calendar=\"bus\",\n    )\n\n\n@pytest.fixture\ndef curve2():\n    return Curve(\n        nodes={\n            dt(2000, 1, 3): 1.00,\n            dt(2000, 7, 3): 1.00 / (1.0 + 0.03 * 182 / 360),\n        },\n        convention=\"Act360\",\n        calendar=\"bus\",\n    )\n\n\n@pytest.fixture\ndef line_curve():\n    return LineCurve(\n        nodes={\n            dt(1999, 12, 30): 2.00,\n            dt(2000, 3, 31): 10.0,\n        },\n        convention=\"Act360\",\n        calendar=\"bus\",\n    )\n\n\n@pytest.fixture\ndef line_curve2():\n    return LineCurve(\n        nodes={\n            dt(1999, 12, 30): 3.00,\n            dt(2000, 3, 31): 10.0,\n        },\n        convention=\"Act360\",\n        calendar=\"bus\",\n    )\n\n\nD = 1 / 360.0\n\n\nclass TestFloatRateIndex:\n    def test_init_attributes(self):\n        s = FloatRateSeries(\n            lag=1,\n            calendar=\"bus\",\n            convention=\"Act360\",\n            modifier=\"mf\",\n            eom=False,\n        )\n        assert s.calendar == NamedCal(\"bus\")\n        assert isinstance(s.calendar, NamedCal)\n        assert s.convention == Convention.Act360\n        assert s.modifier == Adjuster.ModifiedFollowing()\n        assert not s.eom\n        assert s.lag == 1\n\n    def test_init_index_attrbutes(self):\n        s = FloatRateIndex(\n            frequency=\"Q\",\n            series=\"usd_ibor\",\n        )\n        assert s.calendar == NamedCal(\"nyc\")\n        assert isinstance(s.calendar, NamedCal)\n        assert s.convention == Convention.Act360\n        assert s.modifier == Adjuster.ModifiedFollowing()\n        assert not s.eom\n        assert s.lag == 2\n        assert s.frequency == Frequency.Months(3, None)\n\n\nclass TestIBORRate:\n    def test_tenor_rate_from_curve(self, curve, line_curve):\n        # test an IBOR rate is calculated correctly from a forecast curve\n        for rate_curve in [curve, line_curve]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=NoInput(0),\n                start=dt(2000, 1, 3),\n                end=dt(2000, 4, 3),\n                stub=False,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            assert abs(result - 2.18) < 1e-12\n\n    def test_tenor_rate_from_curve_fail_from_history(self, curve, line_curve):\n        # test an IBOR rate cannot be forecast in the past\n        for rate_curve in [curve, line_curve]:\n            with pytest.raises(ValueError, match=\"`effective` date for rate period is before the\"):\n                rate_value(\n                    rate_curve=rate_curve,\n                    rate_fixings=NoInput(0),\n                    start=dt(1980, 1, 3),\n                    end=dt(1980, 4, 3),\n                    stub=False,\n                    frequency=\"3M\",\n                    fixing_method=\"IBOR(2)\",\n                    float_spread=18.0,\n                )\n\n    def test_tenor_rate_from_dict_curve(self, curve, curve2, line_curve, line_curve2):\n        # test an IBOR rate is calculated correctly from a dict of forecast curves\n        for rate_curve in [{\"3m\": curve, \"6m\": curve2}, {\"3m\": line_curve, \"6m\": line_curve2}]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=NoInput(0),\n                start=dt(2000, 1, 3),\n                end=dt(2000, 4, 3),\n                stub=False,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            assert abs(result - 2.18) < 1e-12\n\n    def test_tenor_rate_from_scalar_fixing(self, curve, curve2, line_curve, line_curve2):\n        # test an IBOR rate is calculated correctly from a direct scalar fixing\n        for rate_curve in [\n            curve,\n            line_curve,\n            {\"3m\": curve, \"6m\": curve2},\n            {\"3m\": line_curve, \"6m\": line_curve2},\n        ]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=1.5,\n                start=dt(2000, 1, 3),\n                end=dt(2000, 4, 3),\n                stub=False,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            assert abs(result - 1.68) < 1e-12\n\n    def test_tenor_rate_from_fixing_str(self, curve, line_curve, curve2, line_curve2):\n        # test an IBOR rate is calculated correctly from a fixing series\n        fixings.add(\"TEST_VALUES_3M\", Series(index=[dt(1999, 12, 30)], data=[1.2]))\n        for rate_curve in [\n            curve,\n            line_curve,\n            {\"3m\": curve, \"6m\": curve2},\n            {\"3m\": line_curve, \"6m\": line_curve2},\n        ]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=\"TEST_VALUES_3M\",\n                start=dt(2000, 1, 3),\n                end=dt(2000, 4, 3),\n                stub=False,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            assert abs(result - 1.38) < 1e-12\n        fixings.pop(\"TEST_VALUES_3M\")\n\n    def test_tenor_rate_from_fixing_str_fallback(self, curve, line_curve, curve2, line_curve2):\n        # test an IBOR rate is calculated correctly from a curve when no fixing date exists\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_3M\", Series(index=[dt(2001, 1, 1)], data=[1.2]))\n        for rate_curve in [\n            curve,\n            line_curve,\n            {\"3m\": curve, \"6m\": curve2},\n            {\"3m\": line_curve, \"6m\": line_curve2},\n        ]:\n            with pytest.warns(UserWarning, match=f\"Fixings are provided in series: '{name}_3M',\"):\n                result = rate_value(\n                    rate_curve=rate_curve,\n                    rate_fixings=f\"{name}_3M\",\n                    start=dt(2000, 1, 3),\n                    end=dt(2000, 4, 3),\n                    stub=False,\n                    frequency=\"3M\",\n                    fixing_method=\"IBOR(2)\",\n                    float_spread=18.0,\n                )\n            assert abs(result - 2.18) < 1e-12\n        fixings.pop(f\"{name}_3M\")\n\n    def test_stub_rate_from_fixing_dict(self, curve, line_curve, curve2, line_curve2):\n        # test an IBOR rate is calculated correctly from a fixing series\n        fixings.add(\"TEST_VALUES_3M\", Series(index=[dt(1999, 12, 30)], data=[1.2]))\n        fixings.add(\"TEST_VALUES_6M\", Series(index=[dt(1999, 12, 30)], data=[2.2]))\n        for rate_curve in [\n            curve,\n            line_curve,\n            {\"3m\": curve, \"6m\": curve2},\n            {\"3m\": line_curve, \"6m\": line_curve2},\n        ]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=\"TEST_VALUES\",\n                start=dt(2000, 1, 3),\n                end=dt(2000, 5, 18),\n                stub=True,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            expected = 1.2 + 1.0 * 45 / 91 + 0.18\n            assert abs(result - expected) < 1e-12\n        fixings.pop(\"TEST_VALUES_3M\")\n        fixings.pop(\"TEST_VALUES_6M\")\n\n    def test_stub_rate_from_fixing_dict_missing_data(self, curve, line_curve, curve2, line_curve2):\n        # test an IBOR rate is calculated correctly from a fixing series\n        fixings.add(\"TEST_VALUES_3M\", Series(index=[dt(1999, 12, 1)], data=[1.2]))\n        fixings.add(\"TEST_VALUES_6M\", Series(index=[dt(1999, 12, 1)], data=[2.2]))\n        for rate_curve, expected in [\n            (curve, 2.18249787441),\n            (line_curve, 2.180),\n            ({\"3m\": curve, \"6m\": curve2}, 2.674505494505512),\n            ({\"3m\": line_curve, \"6m\": line_curve2}, 2.6745054945054947),\n        ]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=\"TEST_VALUES\",\n                start=dt(2000, 1, 3),\n                end=dt(2000, 5, 18),\n                stub=True,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            # expected = 1.2 + 1.0 * 45 / 91 + 0.18\n            assert abs(result - expected) < 1e-11\n        fixings.pop(\"TEST_VALUES_3M\")\n        fixings.pop(\"TEST_VALUES_6M\")\n\n    def test_stub_rate_from_fixing_dict_1tenor(self, curve, line_curve, curve2, line_curve2):\n        # test an IBOR rate is calculated correctly from a fixing series\n        fixings.add(\"TEST_VALUES_6M\", Series(index=[dt(1999, 12, 30)], data=[4.1]))\n        for rate_curve in [\n            curve,\n            line_curve,\n            {\"3m\": curve, \"6m\": curve2},\n            {\"3m\": line_curve, \"6m\": line_curve2},\n        ]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=\"TEST_VALUES\",\n                start=dt(2000, 1, 3),\n                end=dt(2000, 5, 18),\n                stub=True,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            expected = 4.1 + 0.18\n            assert abs(result - expected) < 1e-12\n        fixings.pop(\"TEST_VALUES_6M\")\n\n    def test_stub_rate_from_scalar_fixing(self, curve, line_curve, curve2, line_curve2):\n        # test an IBOR stub rate is calculated correctly from a fixing scalar\n        for rate_curve in [\n            curve,\n            line_curve,\n            {\"3m\": curve, \"6m\": curve2},\n            {\"3m\": line_curve, \"6m\": line_curve2},\n        ]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=9.9,\n                start=dt(2000, 1, 3),\n                end=dt(2000, 5, 18),\n                stub=True,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            expected = 9.9 + 0.18\n            assert abs(result - expected) < 1e-12\n\n    def test_stub_rate_from_dict_curve(self, curve, curve2, line_curve, line_curve2):\n        # test an IBOR stub rate is calculated correctly from a dict of forecast curves\n        for rate_curve in [{\"3m\": curve, \"6m\": curve2}, {\"3m\": line_curve, \"6m\": line_curve2}]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=NoInput(0),\n                start=dt(2000, 1, 3),\n                end=dt(2000, 5, 18),\n                stub=True,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            expected = 2.0 * 46 / 91 + 3.0 * 45 / 91 + 0.18\n            assert abs(result - expected) < 1e-12\n\n    def test_stub_rate_from_dict_curve_long_curves(self, curve, curve2, line_curve, line_curve2):\n        # test an IBOR stub rate is calculated correctly from a dict of forecast curves\n        for rate_curve in [{\"9m\": curve, \"6m\": curve2}, {\"9m\": line_curve, \"6m\": line_curve2}]:\n            with pytest.warns(UserWarning, match=\"Interpolated stub period has a length shorter\"):\n                result = rate_value(\n                    rate_curve=rate_curve,\n                    rate_fixings=NoInput(0),\n                    start=dt(2000, 1, 3),\n                    end=dt(2000, 5, 18),\n                    stub=True,\n                    frequency=\"3M\",\n                    fixing_method=\"IBOR(2)\",\n                    float_spread=18.0,\n                )\n            expected = 3.0 + 0.18  # just the 6m curve\n            assert abs(result - expected) < 1e-12\n\n    def test_stub_rate_from_dict_curve_short_curves(self, curve, curve2, line_curve, line_curve2):\n        # test an IBOR stub rate is calculated correctly from a dict of forecast curves\n        for rate_curve in [{\"3m\": curve, \"1m\": curve2}, {\"3m\": line_curve, \"1m\": line_curve2}]:\n            with pytest.warns(UserWarning, match=\"Interpolated stub period has a length longer\"):\n                result = rate_value(\n                    rate_curve=rate_curve,\n                    rate_fixings=NoInput(0),\n                    start=dt(2000, 1, 3),\n                    end=dt(2000, 5, 18),\n                    stub=True,\n                    frequency=\"3M\",\n                    fixing_method=\"IBOR(2)\",\n                    float_spread=18.0,\n                )\n            expected = 2.0 + 0.18  # just the 3m curve\n            assert abs(result - expected) < 1e-12\n\n    def test_stub_rate_from_single_curve(self, curve, curve2, line_curve, line_curve2):\n        # test an IBOR stub rate is calculated from a single forecast curve\n        for rate_curve in [curve, line_curve]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=NoInput(0),\n                start=dt(2000, 1, 3),\n                end=dt(2000, 5, 18),\n                stub=True,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            expected = 2.0 + 0.18\n            assert abs(result - expected) < 3e-3\n\n    def test_stub_rate_from_dict_curve_on_fixing_fail(self, curve, curve2, line_curve, line_curve2):\n        # test an IBOR stub rate is calculated from curve when no fixing is found\n        for rate_curve in [{\"3m\": curve, \"6m\": curve2}, {\"3m\": line_curve, \"6m\": line_curve2}]:\n            result = rate_value(\n                rate_curve=rate_curve,\n                rate_fixings=\"NO_DATA\",\n                start=dt(2000, 1, 3),\n                end=dt(2000, 5, 18),\n                stub=True,\n                frequency=\"3M\",\n                fixing_method=\"IBOR(2)\",\n                float_spread=18.0,\n            )\n            expected = 2.0 * 46 / 91 + 3.0 * 45 / 91 + 0.18\n            assert abs(result - expected) < 1e-12\n\n\nclass TestRFRRate:\n    def test_pandas_series_update_mechanism(self):\n        # rateslib relies on the following mechanism. Test this for compatibility.\n        a = Series(index=[3, 4, 5, 6, 7], data=[NA, NA, NA, NA, NA])\n        b = Series(index=[1, 2, 3, 4, 5], data=[2, 4, 6, 8, 10])\n        a.update(b)\n        assert a.index.to_list() == [3, 4, 5, 6, 7]\n        assert a.to_list() == [6, 8, 10, NA, NA]\n\n    def test_populate_rates_from_rate_fixings(self):\n        fixing_rates = Series(\n            index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3), dt(2000, 1, 4)], data=NA\n        )\n        fixings.add(\n            \"USD_SOFR_1B\",\n            Series(index=[dt(1999, 1, 1), dt(2000, 1, 1), dt(2000, 1, 2)], data=[1.0, 2.0, 3.0]),\n        )\n        result, _, _ = _RFRRate._push_rate_fixings_as_series_to_fixing_rates(\n            fixing_rates, \"USD_SOFR_1B\", FloatFixingMethod.RFRPaymentDelay()\n        )\n        assert_series_equal(\n            result,\n            Series(\n                index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3), dt(2000, 1, 4)],\n                data=[2.0, 3.0, NA, NA],\n            ),\n        )\n        fixings.pop(\"USD_SOFR_1B\")\n\n    def test_populate_rates_from_rate_fixings_all_filled(self):\n        fixing_rates = Series(index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3)], data=NA)\n        fixings.add(\n            \"USD_SOFR_1B\",\n            Series(\n                index=[\n                    dt(1999, 1, 1),\n                    dt(2000, 1, 1),\n                    dt(2000, 1, 2),\n                    dt(2000, 1, 3),\n                    dt(2000, 1, 4),\n                ],\n                data=[1.0, 2.0, 3.0, 4.0, 5.0],\n            ),\n        )\n        result, _, _ = _RFRRate._push_rate_fixings_as_series_to_fixing_rates(\n            fixing_rates, \"USD_SOFR_1B\", FloatFixingMethod.RFRPaymentDelay()\n        )\n        assert_series_equal(\n            result,\n            Series(\n                index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3)],\n                data=[2.0, 3.0, 4.0],\n                dtype=object,\n            ),\n        )\n        fixings.pop(\"USD_SOFR_1B\")\n\n    def test_populate_rates_from_rate_fixings_none_filled(self):\n        fixing_rates = Series(index=[dt(2000, 1, 1), dt(2000, 1, 2)], data=NA)\n        fixings.add(\n            \"USD_SOFR_1B\",\n            Series(index=[dt(1999, 1, 1)], data=[1.0]),\n        )\n        result, _, _ = _RFRRate._push_rate_fixings_as_series_to_fixing_rates(\n            fixing_rates, \"USD_SOFR_1B\", FloatFixingMethod.RFRPaymentDelay()\n        )\n        assert_series_equal(\n            result,\n            Series(index=[dt(2000, 1, 1), dt(2000, 1, 2)], data=[NA, NA], dtype=object),\n        )\n        fixings.pop(\"USD_SOFR_1B\")\n\n    def test_populate_rates_from_rate_fixings_missing_fixing(self):\n        fixing_rates = Series(\n            index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3), dt(2000, 1, 4)], data=NA\n        )\n        fixings.add(\"USD_SOFR_1B\", Series(index=[dt(1999, 1, 1), dt(2000, 1, 2)], data=[1.0, 3.0]))\n        with pytest.raises(ValueError, match=\"The fixings series 'USD_SOFR_1B' for the RFR 1B rat\"):\n            _RFRRate._push_rate_fixings_as_series_to_fixing_rates(\n                fixing_rates, \"USD_SOFR_1B\", FloatFixingMethod.RFRPaymentDelay()\n            )\n        fixings.pop(\"USD_SOFR_1B\")\n\n    @pytest.mark.skip(reason=\"Not expecting the most recent fixing is an allowed oversight.\")\n    def test_populate_rates_from_rate_fixings_extra_fixing(self):\n        # this test will fail becuase of the validation that is applied. The missing fixing\n        # is right at the end of the series and is not detected at the populated/unpopulated\n        # crossover point.\n        fixing_rates = Series(\n            index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 4), dt(2000, 1, 5)], data=NA\n        )\n        fixings.add(\n            \"USD_SOFR_1B\",\n            Series(index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3)], data=[1.0, 2.0, 3.0]),\n        )\n        with pytest.warns(UserWarning, match=\"The fixings series 'USD_SOFR' for the RFR 1B rates\"):\n            _RFRRate._push_rate_fixings_as_series_to_fixing_rates(fixing_rates, \"USD_SOFR_1B\")\n        fixings.pop(\"USD_SOFR_1B\")\n\n    def test_populate_rates_from_rate_fixings_extra_fixing2(self):\n        # the lengths of the expected fixings in the return and fixing series is different and\n        # detected.\n        fixing_rates = Series(\n            index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 4), dt(2000, 1, 5)], data=NA\n        )\n        fixings.add(\n            \"USD_SOFR_1B\",\n            Series(\n                index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3), dt(2000, 1, 4)],\n                data=[1.0, 2.0, 3.0, 4.0],\n            ),\n        )\n        with pytest.warns(UserWarning, match=\"The fixings series 'USD_SOFR_1B' for the RFR 1B rat\"):\n            _RFRRate._push_rate_fixings_as_series_to_fixing_rates(\n                fixing_rates, \"USD_SOFR_1B\", FloatFixingMethod.RFRPaymentDelay()\n            )\n        fixings.pop(\"USD_SOFR_1B\")\n\n    @pytest.mark.parametrize(\n        (\"fixing_method\"),\n        [FloatFixingMethod.RFRPaymentDelay(), FloatFixingMethod.RFRObservationShift(1)],\n    )\n    @pytest.mark.parametrize(\n        (\"spread_compound_method\", \"float_spread\"),\n        [\n            (SpreadCompoundMethod.NoneSimple, 10.0),\n            (SpreadCompoundMethod.ISDACompounding, 0.0),\n            (SpreadCompoundMethod.ISDAFlatCompounding, 0.0),\n        ],\n    )\n    def test_efficient_calc(self, curve, fixing_method, spread_compound_method, float_spread):\n        # rates\n        r0 = curve._rate_with_raise(dt(2000, 1, 3), dt(2000, 1, 4))\n        r1 = curve._rate_with_raise(dt(2000, 1, 4), dt(2000, 1, 5))\n        r2 = curve._rate_with_raise(dt(2000, 1, 5), dt(2000, 1, 6))\n        r3 = curve._rate_with_raise(dt(2000, 1, 6), dt(2000, 1, 7))\n\n        result = rate_value(\n            start=dt(2000, 1, 4),\n            end=dt(2000, 1, 7),\n            rate_curve=curve,\n            spread_compound_method=spread_compound_method,\n            float_spread=float_spread,\n        )\n\n        if isinstance(fixing_method, FloatFixingMethod.RFRObservationShift):\n            expected = (\n                (1 + r0 / 36000) * (1 + r1 / 36000) * (1 + r2 / 36000) - 1\n            ) * 36000 / 3.0 + float_spread / 100.0\n        else:\n            expected = (\n                (1 + r1 / 36000) * (1 + r2 / 36000) * (1 + r3 / 36000) - 1\n            ) * 36000 / 3.0 + float_spread / 100.0\n\n        assert abs(result - expected) < 1e-10\n\n    def test_semi_inefficient_calc_with_populated_fixings(self, curve):\n        fixings.add(\"USD_SOFR_1B\", Series(index=[dt(2000, 1, 3), dt(2000, 1, 4)], data=[1.5, 1.7]))\n        r2 = curve._rate_with_raise(dt(2000, 1, 5), dt(2000, 1, 6))\n        r3 = curve._rate_with_raise(dt(2000, 1, 6), dt(2000, 1, 7))\n\n        result = rate_value(\n            start=dt(2000, 1, 3),\n            end=dt(2000, 1, 7),\n            rate_curve=curve,\n            spread_compound_method=SpreadCompoundMethod.NoneSimple,\n            float_spread=10.0,\n            rate_fixings=\"USD_SOFR_1B\",\n        )\n        expected = (\n            (1 + 0.015 / 360) * (1 + 0.017 / 360) * (1 + r2 / 36000) * (1 + r3 / 36000) - 1\n        ) * 36000 / 4 + 0.1\n        fixings.pop(\"USD_SOFR_1B\")\n        assert abs(result - expected) < 1e-10\n\n    def test_inefficient_calc_with_populated_fixings_no_curve_raises(self, curve):\n        fixings.add(\"USD_SOFR_1B\", Series(index=[dt(2000, 1, 3), dt(2000, 1, 4)], data=[1.5, 1.7]))\n        with pytest.raises(\n            FixingMissingForecasterError, match=err.VE_NEEDS_RATE_POPULATE_FIXINGS[:25]\n        ):\n            rate_value(\n                start=dt(2000, 1, 3),\n                end=dt(2000, 1, 7),\n                rate_curve=NoInput(0),\n                spread_compound_method=SpreadCompoundMethod.ISDACompounding,\n                float_spread=10.0,\n                rate_fixings=\"USD_SOFR_1B\",\n                rate_series=\"usd_rfr\",\n            )\n        fixings.pop(\"USD_SOFR_1B\")\n\n    def test_inefficient_calc_with_lockout_too_long_raises(self, curve):\n        # the lockout param is invalid\n        with pytest.raises(ValueError, match=err.VE_LOCKOUT_METHOD_PARAM[:25]):\n            rate_value(\n                start=dt(2000, 1, 3),\n                end=dt(2000, 1, 7),\n                rate_curve=curve,\n                spread_compound_method=SpreadCompoundMethod.ISDACompounding,\n                float_spread=10.0,\n                fixing_method=FloatFixingMethod.RFRLockout(9),\n            )\n\n    @pytest.mark.parametrize(\"curve_type\", [\"values\", \"dfs\"])\n    def test_inefficient_calc_with_populated_fixings(self, curve_type, curve, line_curve):\n        rate_curve = curve if curve_type == \"dfs\" else line_curve\n        fixings.add(\"USD_SOFR_1B\", Series(index=[dt(2000, 1, 3), dt(2000, 1, 4)], data=[1.5, 1.7]))\n        r2 = rate_curve._rate_with_raise(dt(2000, 1, 5), dt(2000, 1, 6))\n        r3 = rate_curve._rate_with_raise(dt(2000, 1, 6), dt(2000, 1, 7))\n\n        result = rate_value(\n            start=dt(2000, 1, 3),\n            end=dt(2000, 1, 7),\n            rate_curve=rate_curve,\n            spread_compound_method=SpreadCompoundMethod.NoneSimple,\n            float_spread=10.0,\n            fixing_method=FloatFixingMethod.RFRLookback(0),\n            rate_fixings=\"USD_SOFR_1B\",\n        )\n        expected = (\n            (1 + 0.015 / 360) * (1 + 0.017 / 360) * (1 + r2 / 36000) * (1 + r3 / 36000) - 1\n        ) * 36000 / 4 + 0.1\n        fixings.pop(\"USD_SOFR_1B\")\n        assert abs(result - expected) < 1e-10\n\n    def test_inefficient_calc_with_non_overlapping_fixings(self, curve):\n        fixings.add(\"USD_SOFR_1B\", Series(index=[dt(2001, 1, 1)], data=[100.0]))\n\n        rate_value(\n            start=dt(2000, 1, 4),\n            end=dt(2000, 1, 7),\n            rate_curve=curve,\n            spread_compound_method=SpreadCompoundMethod.NoneSimple,\n            float_spread=0.0,\n            rate_fixings=\"USD_SOFR_1B\",\n        )\n        fixings.pop(\"USD_SOFR_1B\")\n\n    @pytest.mark.parametrize(\n        (\"fixing_method\", \"expected\"),\n        [\n            (\n                FloatFixingMethod.RFRPaymentDelay(),\n                ((1 + 0.04 * D) * (1 + 0.05 * D) * (1 + 0.06 * D) * (1 + 0.07 * D) - 1)\n                * 100\n                / (4 * D),\n            ),\n            (\n                FloatFixingMethod.RFRObservationShift(2),\n                ((1 + 0.02 * D) * (1 + 0.03 * D) * (1 + 0.04 * D) * (1 + 0.05 * D) - 1)\n                * 100\n                / (4 * D),\n            ),\n            (\n                FloatFixingMethod.RFRLockout(2),\n                ((1 + 0.04 * D) * (1 + 0.05 * D) * (1 + 0.05 * D) * (1 + 0.05 * D) - 1)\n                * 100\n                / (4 * D),\n            ),\n            (\n                FloatFixingMethod.RFRLookback(2),\n                ((1 + 0.02 * D) * (1 + 0.03 * D) * (1 + 0.04 * D) * (1 + 0.05 * D) - 1)\n                * 100\n                / (4 * D),\n            ),\n            (\n                FloatFixingMethod.RFRPaymentDelayAverage(),\n                (4 + 5 + 6 + 7) / 4,\n            ),\n            (\n                FloatFixingMethod.RFRObservationShiftAverage(2),\n                (2 + 3 + 4 + 5) / 4,\n            ),\n            (\n                FloatFixingMethod.RFRLockoutAverage(2),\n                (4 + 5 + 5 + 5) / 4,\n            ),\n            (\n                FloatFixingMethod.RFRLookbackAverage(2),\n                (2 + 3 + 4 + 5) / 4,\n            ),\n        ],\n    )\n    def test_fixing_methods(self, fixing_method, expected):\n        rate_curve = LineCurve(\n            nodes={\n                dt(2000, 1, 1): 2.0,\n                dt(2000, 1, 2): 3.0,\n                dt(2000, 1, 3): 4.0,\n                dt(2000, 1, 4): 5.0,\n                dt(2000, 1, 5): 6.0,\n                dt(2000, 1, 6): 7.0,\n                dt(2000, 1, 7): 8.0,\n            },\n            convention=\"act360\",\n            calendar=\"all\",\n        )\n        result = rate_value(\n            start=dt(2000, 1, 3),\n            end=dt(2000, 1, 7),\n            rate_curve=rate_curve,\n            spread_compound_method=SpreadCompoundMethod.NoneSimple,\n            float_spread=0.0,\n            fixing_method=fixing_method,\n        )\n        assert abs(result - expected) < 1e-10\n\n    @pytest.mark.parametrize(\n        \"fixing_method\", [FloatFixingMethod.RFRPaymentDelay(), FloatFixingMethod.RFRLockout(0)]\n    )\n    def test_bus252_convention(self, fixing_method):\n        rate_curve = Curve(\n            nodes={\n                dt(2000, 1, 3): 1.0,\n                dt(2000, 1, 17): 0.999,\n            },\n            convention=\"bus252\",\n            calendar=\"bus\",\n        )\n        result = rate_value(\n            start=dt(2000, 1, 6),\n            end=dt(2000, 1, 11),\n            rate_curve=rate_curve,\n            spread_compound_method=SpreadCompoundMethod.NoneSimple,\n            float_spread=0.0,\n            fixing_method=fixing_method,\n        )\n\n        r1 = rate_curve._rate_with_raise(dt(2000, 1, 6), \"1b\")\n        r2 = rate_curve._rate_with_raise(dt(2000, 1, 7), \"1b\")\n        r3 = rate_curve._rate_with_raise(dt(2000, 1, 10), \"1b\")\n        expected = ((1 + r1 / 25200) * (1 + r2 / 25200) * (1 + r3 / 25200) - 1) * 25200 / 3\n        assert abs(result - expected) < 1e-10\n"
  },
  {
    "path": "python/tests/periods/test_periods_init.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nimport rateslib.errors as err\nfrom rateslib.periods import (\n    Cashflow,\n    CreditPremiumPeriod,\n    CreditProtectionPeriod,\n    FixedPeriod,\n    FloatPeriod,\n    # IndexCashflow,\n    # IndexFixedPeriod,\n    # IndexFloatPeriod,\n    # NonDeliverableCashflow,\n    # NonDeliverableFixedPeriod,\n    # NonDeliverableFloatPeriod,\n    # NonDeliverableIndexCashflow,\n    # NonDeliverableIndexFixedPeriod,\n    # NonDeliverableIndexFloatPeriod,\n    ZeroFixedPeriod,\n)\nfrom rateslib.periods.cashflow import MtmCashflow\nfrom rateslib.scheduling import Schedule\n\n\nclass TestCashflow:\n    def test_init(self):\n        Cashflow(currency=\"usd\", notional=2e6, payment=dt(2000, 1, 1))\n        pass\n\n\nclass TestFixedPeriod:\n    def test_init(self):\n        FixedPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            frequency=\"M\",\n            notional=2e6,\n            currency=\"usd\",\n            convention=\"act365f\",\n            calendar=\"tgt\",\n            adjuster=\"mf\",\n        )\n        pass\n\n\nclass TestFloatPeriod:\n    def test_init(self):\n        FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            frequency=\"M\",\n            notional=2e6,\n            currency=\"usd\",\n            convention=\"act365f\",\n            calendar=\"tgt\",\n            adjuster=\"mf\",\n        )\n        pass\n\n\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestIndexFixedPeriod:\n#     def test_init(self):\n#         IndexFixedPeriod(\n#             start=dt(2000, 1, 1),\n#             end=dt(2000, 2, 1),\n#             payment=dt(2000, 2, 1),\n#             frequency=\"M\",\n#             notional=2e6,\n#             currency=\"usd\",\n#             convention=\"act365f\",\n#             calendar=\"tgt\",\n#             adjuster=\"mf\",\n#             index_base=100.0,\n#         )\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_INDEX_PARAMS[:15]):\n#             IndexFixedPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#             )\n#\n#         with pytest.raises(ValueError, match=err.VE_HAS_ND_CURRENCY_PARAMS[:15]):\n#             IndexFixedPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#                 index_base=100.0,\n#                 pair=\"eurusd\",\n#             )\n#\n#\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestNonDeliverableIndexFixedPeriod:\n#     def test_init(self):\n#         NonDeliverableIndexFixedPeriod(\n#             pair=\"eurusd\",\n#             start=dt(2000, 1, 1),\n#             end=dt(2000, 2, 1),\n#             payment=dt(2000, 2, 1),\n#             frequency=\"M\",\n#             notional=2e6,\n#             currency=\"usd\",\n#             convention=\"act365f\",\n#             calendar=\"tgt\",\n#             adjuster=\"mf\",\n#             index_base=100.0,\n#         )\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_ND_CURRENCY_PARAMS[:15]):\n#             NonDeliverableIndexFixedPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#                 index_base=100.0,\n#             )\n#\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_INDEX_PARAMS[:15]):\n#             NonDeliverableIndexFixedPeriod(\n#                 pair=\"eurusd\",\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#             )\n#\n#\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestNonDeliverableCashflow:\n#     def test_init(self):\n#         NonDeliverableCashflow(\n#             currency=\"usd\", pair=\"brlusd\", notional=2e6, payment=dt(2000, 1, 1)\n#         )\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_ND_CURRENCY_PARAMS[:15]):\n#             NonDeliverableCashflow(currency=\"usd\", notional=2e6, payment=dt(2000, 1, 1))\n#\n#         with pytest.raises(ValueError, match=err.VE_HAS_INDEX_PARAMS[:15]):\n#             NonDeliverableCashflow(\n#                 currency=\"usd\",\n#                 pair=\"eurusd\",\n#                 notional=2e6,\n#                 payment=dt(2000, 1, 1),\n#                 index_base=100.0,\n#             )\n#\n#     def test_undefined_currencies(self):\n#         with pytest.raises(ValueError, match=err.VE_MISMATCHED_ND_PAIR[:15]):\n#             NonDeliverableCashflow(\n#                 pair=\"eurbrl\",\n#                 payment=dt(2000, 1, 1),\n#                 notional=2e6,\n#             )\n#\n#\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestIndexCashflow:\n#     def test_init(self):\n#         IndexCashflow(currency=\"usd\", notional=2e6, payment=dt(2000, 1, 1), index_base=100.0)\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_INDEX_PARAMS[:15]):\n#             IndexCashflow(currency=\"usd\", notional=2e6, payment=dt(2000, 1, 1))\n#\n#         with pytest.raises(ValueError, match=err.VE_HAS_ND_CURRENCY_PARAMS[:15]):\n#             IndexCashflow(\n#                 currency=\"usd\",\n#                 pair=\"eurusd\",\n#                 notional=2e6,\n#                 payment=dt(2000, 1, 1),\n#                 index_base=100.0,\n#             )\n#\n#\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestNonDeliverableIndexCashflow:\n#     def test_init(self):\n#         NonDeliverableIndexCashflow(\n#             currency=\"usd\", pair=\"eurusd\", notional=2e6, payment=dt(2000, 1, 1), index_base=100.0\n#         )\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_INDEX_PARAMS[:15]):\n#             NonDeliverableIndexCashflow(currency=\"usd\", notional=2e6, payment=dt(2000, 1, 1))\n#\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_ND_CURRENCY_PARAMS[:15]):\n#             NonDeliverableIndexCashflow(\n#                 currency=\"usd\",\n#                 notional=2e6,\n#                 payment=dt(2000, 1, 1),\n#                 index_base=100.0,\n#             )\n\n\nclass TestMtmCashflow:\n    def test_init(self):\n        MtmCashflow(\n            currency=\"usd\",\n            notional=2e6,\n            payment=dt(2000, 1, 10),\n            pair=\"eurusd\",\n            fx_fixings_start=2.0,\n            fx_fixings_end=3.0,\n            start=dt(2000, 1, 1),\n            end=dt(2000, 1, 10),\n        )\n\n\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestNonDeliverableFixedPeriod:\n#     def test_init(self):\n#         NonDeliverableFixedPeriod(\n#             start=dt(2000, 1, 1),\n#             end=dt(2000, 2, 1),\n#             payment=dt(2000, 2, 1),\n#             frequency=\"M\",\n#             notional=2e6,\n#             currency=\"usd\",\n#             convention=\"act365f\",\n#             calendar=\"tgt\",\n#             adjuster=\"mf\",\n#             pair=\"brlusd\",\n#         )\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_ND_CURRENCY_PARAMS[:15]):\n#             NonDeliverableFixedPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#             )\n#\n#         with pytest.raises(ValueError, match=err.VE_HAS_INDEX_PARAMS[:15]):\n#             NonDeliverableFixedPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#                 pair=\"brlusd\",\n#                 index_base=100.0,\n#             )\n#\n#\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestNonDeliverableFloatPeriod:\n#     def test_init(self):\n#         NonDeliverableFloatPeriod(\n#             start=dt(2000, 1, 1),\n#             end=dt(2000, 2, 1),\n#             payment=dt(2000, 2, 1),\n#             frequency=\"M\",\n#             notional=2e6,\n#             currency=\"usd\",\n#             convention=\"act365f\",\n#             calendar=\"tgt\",\n#             adjuster=\"mf\",\n#             pair=\"brlusd\",\n#         )\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_ND_CURRENCY_PARAMS[:15]):\n#             NonDeliverableFloatPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#             )\n#\n#         with pytest.raises(ValueError, match=err.VE_HAS_INDEX_PARAMS[:15]):\n#             NonDeliverableFloatPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#                 pair=\"brlusd\",\n#                 index_base=100.0,\n#             )\n#\n#\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestIndexFloatPeriod:\n#     def test_init(self):\n#         IndexFloatPeriod(\n#             start=dt(2000, 1, 1),\n#             end=dt(2000, 2, 1),\n#             payment=dt(2000, 2, 1),\n#             frequency=\"M\",\n#             notional=2e6,\n#             currency=\"usd\",\n#             convention=\"act365f\",\n#             calendar=\"tgt\",\n#             adjuster=\"mf\",\n#             index_base=100.0,\n#         )\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_INDEX_PARAMS[:15]):\n#             IndexFloatPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#             )\n#\n#         with pytest.raises(ValueError, match=err.VE_HAS_ND_CURRENCY_PARAMS[:15]):\n#             IndexFloatPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#                 index_base=100.0,\n#                 pair=\"eurusd\",\n#             )\n#\n#\n# @pytest.mark.skip(reason=\"Deprecated objects.\")\n# class TestNonDeliverableIndexFloatPeriod:\n#     def test_init(self):\n#         NonDeliverableIndexFloatPeriod(\n#             start=dt(2000, 1, 1),\n#             end=dt(2000, 2, 1),\n#             payment=dt(2000, 2, 1),\n#             frequency=\"M\",\n#             notional=2e6,\n#             currency=\"usd\",\n#             convention=\"act365f\",\n#             calendar=\"tgt\",\n#             adjuster=\"mf\",\n#             index_base=100.0,\n#             pair=\"eurusd\",\n#         )\n#         pass\n#\n#     def test_errors(self):\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_INDEX_PARAMS[:15]):\n#             NonDeliverableIndexFloatPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#                 pair=\"eurusd\",\n#             )\n#\n#         with pytest.raises(ValueError, match=err.VE_NEEDS_ND_CURRENCY_PARAMS[:15]):\n#             NonDeliverableIndexFloatPeriod(\n#                 start=dt(2000, 1, 1),\n#                 end=dt(2000, 2, 1),\n#                 payment=dt(2000, 2, 1),\n#                 frequency=\"M\",\n#                 notional=2e6,\n#                 currency=\"usd\",\n#                 convention=\"act365f\",\n#                 calendar=\"tgt\",\n#                 adjuster=\"mf\",\n#                 index_base=100.0,\n#             )\n\n\nclass TestCreditPremiumPeriod:\n    def test_init(self):\n        CreditPremiumPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            frequency=\"M\",\n            notional=2e6,\n            premium_accrued=False,\n        )\n\n\nclass TestCreditProtectionPeriod:\n    def test_init(self):\n        CreditProtectionPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            frequency=\"M\",\n            notional=2e6,\n        )\n\n\nclass TestZeroFixedPeriod:\n    def test_init(self):\n        ZeroFixedPeriod(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2000, 9, 1),\n                frequency=\"M\",\n            ),\n            convention=\"act365f\",\n        )\n"
  },
  {
    "path": "python/tests/periods/test_periods_legacy.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\nimport re\nfrom dataclasses import replace\nfrom datetime import datetime as dt\nfrom datetime import timedelta\n\nimport numpy as np\nimport pytest\nimport rateslib.errors as err\nfrom pandas import DataFrame, Index, MultiIndex, Series, date_range\nfrom pandas.testing import assert_frame_equal\nfrom rateslib import defaults, fixings\nfrom rateslib.curves import CompositeCurve, Curve, LineCurve\nfrom rateslib.curves.curves import _try_index_value\nfrom rateslib.data.fixings import FloatRateSeries, FXIndex\nfrom rateslib.data.loader import FixingMissingForecasterError\nfrom rateslib.default import NoInput, _drb\nfrom rateslib.dual import Dual, gradient\nfrom rateslib.enums import Err, FloatFixingMethod, Ok, OptionPricingModel\nfrom rateslib.enums.parameters import FXDeltaMethod, IndexMethod, SpreadCompoundMethod\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.periods import (\n    Cashflow,\n    CreditPremiumPeriod,\n    CreditProtectionPeriod,\n    FixedPeriod,\n    FloatPeriod,\n    FXCallPeriod,\n    FXPutPeriod,\n    IRSCallPeriod,\n    IRSPutPeriod,\n    MtmCashflow,\n    ZeroFixedPeriod,\n)\nfrom rateslib.periods.float_rate import rate_value\nfrom rateslib.scheduling import Cal, Frequency, RollDay, Schedule\nfrom rateslib.volatility import (\n    FXDeltaVolSmile,\n    FXSabrSmile,\n    FXSabrSurface,\n    IRSabrCube,\n    IRSabrSmile,\n    IRSplineCube,\n    IRSplineSmile,\n)\nfrom rateslib.volatility.utils import _OptionModelBlack76\n\n\n@pytest.fixture\ndef curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.99,\n        dt(2022, 7, 1): 0.98,\n        dt(2022, 10, 1): 0.97,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\", id=\"curve_fixture\")\n\n\n@pytest.fixture\ndef hazard_curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 4, 1): 0.999,\n        dt(2022, 7, 1): 0.997,\n        dt(2022, 10, 1): 0.991,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\", id=\"hazard_fixture\")\n\n\n@pytest.fixture\ndef fxr():\n    return FXRates({\"usdnok\": 10.0})\n\n\n@pytest.fixture\ndef rfr_curve():\n    v1 = 1 / (1 + 0.01 / 365)\n    v2 = v1 / (1 + 0.02 / 365)\n    v3 = v2 / (1 + 0.03 / 365)\n    v4 = v3 / (1 + 0.04 / 365)\n\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 1, 2): v1,\n        dt(2022, 1, 3): v2,\n        dt(2022, 1, 4): v3,\n        dt(2022, 1, 5): v4,\n    }\n    return Curve(nodes=nodes, interpolation=\"log_linear\", convention=\"act365f\")\n\n\n@pytest.fixture\ndef line_curve():\n    nodes = {\n        dt(2022, 1, 1): 1.00,\n        dt(2022, 1, 2): 2.00,\n        dt(2022, 1, 3): 3.00,\n        dt(2022, 1, 4): 4.00,\n        dt(2022, 1, 5): 5.00,\n    }\n    return LineCurve(nodes=nodes, interpolation=\"linear\", convention=\"act365f\")\n\n\n@pytest.mark.parametrize(\n    \"obj\",\n    [\n        FixedPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            frequency=Frequency.Months(1, None),\n            fixed_rate=2.0,\n        ),\n        Cashflow(notional=1e6, payment=dt(2022, 1, 1), currency=\"usd\"),\n        # IndexCashflow(notional=1e6, payment=dt(2022, 1, 1), currency=\"usd\", index_base=100.0),\n        # IndexFixedPeriod(\n        #     start=dt(2000, 1, 1),\n        #     end=dt(2000, 2, 1),\n        #     payment=dt(2000, 2, 1),\n        #     frequency=Frequency.Months(1, None),\n        #     fixed_rate=2.0,\n        #     index_base=1.0,\n        # ),\n        FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            payment=dt(2000, 2, 1),\n            frequency=Frequency.Months(1, None),\n        ),\n        FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2000, 1, 1),\n            delivery=dt(2000, 1, 1),\n        ),\n        FXPutPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2000, 1, 1),\n            delivery=dt(2000, 1, 1),\n        ),\n    ],\n)\ndef test_repr(obj):\n    result = obj.__repr__()\n    expected = f\"<rl.{type(obj).__name__} at {hex(id(obj))}>\"\n    assert result == expected\n\n\nclass TestFXandBase:\n    def test_fx_and_base_raise(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.96}, id=\"curve\")\n        per = FixedPeriod(\n            start=dt(2022, 2, 1),\n            end=dt(2022, 3, 1),\n            payment=dt(2022, 3, 1),\n            frequency=Frequency.Months(12, None),\n            fixed_rate=2,\n            currency=\"usd\",\n        )\n        with pytest.raises(ValueError, match=\"`base` \"):\n            per.npv(rate_curve=curve, base=\"eur\")\n\n    def test_fx_and_base_warn1(self) -> None:\n        # base and numeric fx given.\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.96}, id=\"curve\")\n        per = FixedPeriod(\n            start=dt(2022, 2, 1),\n            end=dt(2022, 3, 1),\n            payment=dt(2022, 3, 1),\n            frequency=Frequency.Months(12, None),\n            fixed_rate=2.0,\n            currency=\"usd\",\n        )\n        with pytest.warns(DeprecationWarning, match=\"`base` \"):\n            per.npv(rate_curve=curve, disc_curve=curve, fx=1.1, base=\"eur\")\n\n    def test_fx_and_base_warn2(self) -> None:\n        # base is none and numeric fx given.\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.96}, id=\"curve\")\n        per = FixedPeriod(\n            start=dt(2022, 2, 1),\n            end=dt(2022, 3, 1),\n            payment=dt(2022, 3, 1),\n            frequency=Frequency.Months(12, None),\n            fixed_rate=2.0,\n            currency=\"usd\",\n        )\n        with pytest.warns(UserWarning, match=\"It is not best practice to provide\"):\n            per.npv(rate_curve=curve, fx=1.1)\n\n\nclass TestFloatPeriod:\n    def test_none_cashflow(self) -> None:\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n        )\n        assert float_period.try_cashflow(rate_curve=None).is_err\n\n    @pytest.mark.parametrize(\n        (\"spread_method\", \"float_spread\", \"expected\"),\n        [\n            (\"none_simple\", 100.0, 24744.478172244584),\n            (\"isda_compounding\", 0.0, 24744.478172244584),\n            (\"isda_compounding\", 100.0, 25053.484941157145),\n            (\"isda_flat_compounding\", 100.0, 24867.852396116967),\n        ],\n    )\n    def test_float_period_analytic_delta(\n        self,\n        curve,\n        spread_method,\n        float_spread,\n        expected,\n    ) -> None:\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            float_spread=float_spread,\n            spread_compound_method=spread_method,\n        )\n        result = float_period.analytic_delta(rate_curve=curve)\n        assert abs(result - expected) < 1e-7\n\n    @pytest.mark.parametrize(\n        (\"spread\", \"crv\", \"fx\"),\n        [\n            (4.00, True, 2.0),\n            (NoInput(0), False, 2.0),\n            (4.00, True, 10.0),\n            (NoInput(0), False, 10.0),\n        ],\n    )\n    def test_float_period_cashflows(self, curve, fxr, spread, crv, fx) -> None:\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            float_spread=spread,\n        )\n        curve = curve if crv else None\n        rate = None if curve is None else float(float_period.rate(curve))\n        cashflow = None if rate is None else rate * -1e9 * float_period.period_params.dcf / 100\n        expected = {\n            defaults.headers[\"base\"]: \"UNSPECIFIED\" if fx == 2.0 else \"NOK\",\n            defaults.headers[\"type\"]: \"FloatPeriod\",\n            defaults.headers[\"stub_type\"]: \"Regular\",\n            defaults.headers[\"a_acc_start\"]: dt(2022, 1, 1),\n            defaults.headers[\"a_acc_end\"]: dt(2022, 4, 1),\n            defaults.headers[\"payment\"]: dt(2022, 4, 3),\n            defaults.headers[\"notional\"]: 1e9,\n            defaults.headers[\"currency\"]: \"USD\",\n            defaults.headers[\"convention\"]: \"Act360\",\n            defaults.headers[\"dcf\"]: float_period.period_params.dcf,\n            defaults.headers[\"df\"]: 0.9897791268897856 if crv else None,\n            defaults.headers[\"rate\"]: rate,\n            defaults.headers[\"spread\"]: 0.0 if spread is NoInput.blank else spread,\n            defaults.headers[\"npv\"]: -10096746.871171726 if crv else None,\n            defaults.headers[\"cashflow\"]: cashflow,\n            defaults.headers[\"fx\"]: fx,\n            defaults.headers[\"npv_fx\"]: -10096746.871171726 * fx if crv else None,\n            defaults.headers[\"collateral\"]: None,\n        }\n        if fx == 2.0:\n            with pytest.warns(UserWarning):\n                # It is not best practice to provide `fx` as numeric\n                result = float_period.cashflows(\n                    rate_curve=curve if crv else NoInput(0),\n                    fx=2.0,\n                    base=NoInput(0),\n                )\n        else:\n            result = float_period.cashflows(\n                rate_curve=curve if crv else NoInput(0),\n                fx=fxr,\n                base=\"nok\",\n            )\n        assert result == expected\n\n    def test_spread_compound_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`spread_compound_method`\"):\n            FloatPeriod(\n                start=dt(2022, 1, 1),\n                end=dt(2022, 4, 1),\n                payment=dt(2022, 4, 3),\n                frequency=Frequency.Months(3, None),\n                spread_compound_method=\"bad_vibes\",\n            )\n\n    def test_spread_compound_calc_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`spread_compound_method` as string: 'bad_input'\"):\n            rate_value(\n                start=dt(2022, 1, 1),\n                end=dt(2022, 4, 1),\n                spread_compound_method=\"bad_input\",\n                float_spread=1,\n            )\n\n    @pytest.mark.parametrize(\n        \"scm\",\n        [\n            SpreadCompoundMethod.ISDACompounding,\n            SpreadCompoundMethod.ISDAFlatCompounding,\n            SpreadCompoundMethod.NoneSimple,\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"meth\"),\n        [\n            FloatFixingMethod.RFRObservationShift(2),\n            FloatFixingMethod.RFRPaymentDelay(),\n            FloatFixingMethod.RFRLockout(2),\n            FloatFixingMethod.RFRLookback(2),\n        ],\n    )\n    def test_spread_compound_with_fixing_method_allowed(self, scm, meth):\n        FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 1),\n            frequency=\"Q\",\n            float_spread=1.0,\n            spread_compound_method=scm,\n            fixing_method=meth,\n        )\n\n    def test_rfr_lockout_too_few_dates(self, curve) -> None:\n        period = FloatPeriod(\n            start=dt(2022, 1, 10),\n            end=dt(2022, 1, 15),\n            payment=dt(2022, 1, 15),\n            frequency=Frequency.Months(1, None),\n            fixing_method=\"rfr_lockout(6)\",\n        )\n        with pytest.raises(ValueError, match=\"The `method_param` for an RFR Lockout type `fixing_\"):\n            period.rate(curve)\n\n    def test_fixing_method_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`fixing_method`\"):\n            FloatPeriod(\n                start=dt(2022, 1, 1),\n                end=dt(2022, 4, 1),\n                payment=dt(2022, 4, 3),\n                frequency=Frequency.Months(3, None),\n                fixing_method=\"bad_vibes\",\n            )\n\n    def test_float_period_npv(self, curve) -> None:\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n        )\n        result = float_period.npv(rate_curve=curve)\n        assert abs(result + 9997768.95848275) < 1e-7\n\n    @pytest.mark.parametrize(\n        \"scm\", [SpreadCompoundMethod.ISDACompounding, SpreadCompoundMethod.ISDAFlatCompounding]\n    )\n    @pytest.mark.parametrize(\n        \"fm\",\n        [\n            FloatFixingMethod.RFRObservationShiftAverage(2),\n            FloatFixingMethod.RFRPaymentDelayAverage(),\n            FloatFixingMethod.RFRLockoutAverage(2),\n            FloatFixingMethod.RFRLookbackAverage(2),\n            FloatFixingMethod.IBOR(2),\n        ],\n    )\n    def test_rfr_avg_method_raises(self, scm, fm, curve) -> None:\n        msg = \"is not compatible\"\n        with pytest.raises(ValueError, match=msg):\n            FloatPeriod(\n                start=dt(2022, 1, 1),\n                end=dt(2022, 1, 4),\n                payment=dt(2022, 1, 4),\n                frequency=Frequency.Months(3, None),\n                fixing_method=fm,\n                spread_compound_method=scm,\n            )\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_payment_delay_method(self, curve_type, rfr_curve, line_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = ((1 + 0.01 / 365) * (1 + 0.02 / 365) * (1 + 0.03 / 365) - 1) * 36500 / 3\n        assert abs(result - expected) < 1e-12\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_payment_delay_method_with_fixings(self, curve_type, rfr_curve, line_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_1B\", Series(index=[dt(2022, 1, 1), dt(2022, 1, 2)], data=[10.0, 8.0]))\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            rate_fixings=name,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = ((1 + 0.10 / 365) * (1 + 0.08 / 365) * (1 + 0.03 / 365) - 1) * 36500 / 3\n        assert abs(result - expected) < 1e-12\n        fixings.pop(f\"{name}_1B\")\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_payment_delay_avg_method(self, curve_type, rfr_curve, line_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay_avg\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = (1.0 + 2.0 + 3.0) / 3\n        assert abs(result - expected) < 1e-11\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_payment_delay_avg_method_with_fixings(\n        self,\n        curve_type,\n        rfr_curve,\n        line_curve,\n    ) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        fixings.add(\"887762_1B\", Series(index=[dt(2022, 1, 1), dt(2022, 1, 2)], data=[10.0, 8.0]))\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay_avg\",\n            rate_fixings=\"887762\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n\n        result = period.rate(curve)\n        expected = (10.0 + 8.0 + 3.0) / 3\n        assert abs(result - expected) < 1e-11\n        fixings.pop(\"887762_1B\")\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_lockout_avg_method(self, curve_type, rfr_curve, line_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_lockout_avg(2)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        # assert period.rate_params._is_inefficient is True  # lockout requires all fixings.\n        result = period.rate(curve)\n        expected = 1.0\n        assert abs(result - expected) < 1e-11\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 2),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_lockout_avg(1)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(rfr_curve)\n        expected = (2 + 3.0 + 3.0) / 3\n        assert abs(result - expected) < 1e-11\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_lockout_avg_method_with_fixings(self, curve_type, rfr_curve, line_curve) -> None:\n        name = str(hash(os.urandom(2)))\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        fixings.add(f\"{name}_1B\", Series(index=[dt(2022, 1, 1), dt(2022, 1, 2)], data=[10.0, 8.0]))\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_lockout_avg(2)\",\n            rate_fixings=name,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = 10.0\n        assert abs(result - expected) < 1e-12\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 1),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_lockout_avg(1)\",\n            rate_fixings=name,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(rfr_curve)\n        expected = (10.0 + 8.0 + 8.0) / 3\n        assert abs(result - expected) < 1e-12\n        fixings.pop(f\"{name}_1B\")\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_lockout_method(self, curve_type, rfr_curve, line_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_lockout(2)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        # assert period.rate_params._is_inefficient is True  # lockout requires all fixings.\n        result = period.rate(curve)\n        expected = ((1 + 0.01 / 365) * (1 + 0.01 / 365) * (1 + 0.01 / 365) - 1) * 36500 / 3\n        assert abs(result - expected) < 1e-12\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 2),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_lockout(1)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(rfr_curve)\n        expected = ((1 + 0.02 / 365) * (1 + 0.03 / 365) * (1 + 0.03 / 365) - 1) * 36500 / 3\n        assert abs(result - expected) < 1e-12\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_lockout_method_with_fixings(self, curve_type, rfr_curve, line_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        fixings.add(\"887762_1B\", Series(index=[dt(2022, 1, 1), dt(2022, 1, 2)], data=[10.0, 8.0]))\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_lockout(2)\",\n            rate_fixings=\"887762\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = ((1 + 0.10 / 365) * (1 + 0.10 / 365) * (1 + 0.10 / 365) - 1) * 36500 / 3\n        assert abs(result - expected) < 1e-12\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_lockout(1)\",\n            rate_fixings=\"887762\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(rfr_curve)\n        expected = ((1 + 0.10 / 365) * (1 + 0.08 / 365) * (1 + 0.08 / 365) - 1) * 36500 / 3\n        assert abs(result - expected) < 1e-12\n        fixings.pop(\"887762_1B\")\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_observation_shift_method(self, curve_type, rfr_curve, line_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        period = FloatPeriod(\n            start=dt(2022, 1, 2),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift(1)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = ((1 + 0.01 / 365) * (1 + 0.02 / 365) * (1 + 0.03 / 365) - 1) * 36500 / 3\n        assert abs(result - expected) < 1e-12\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift(2)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = ((1 + 0.01 / 365) * (1 + 0.02 / 365) - 1) * 36500 / 2\n        assert abs(result - expected) < 1e-12\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_observation_shift_method_with_fixings(\n        self,\n        curve_type,\n        rfr_curve,\n        line_curve,\n    ) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_1B\", Series(index=[dt(2022, 1, 1), dt(2022, 1, 2)], data=[10.0, 8.0]))\n        period = FloatPeriod(\n            start=dt(2022, 1, 2),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift(1)\",\n            rate_fixings=name,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = ((1 + 0.10 / 365) * (1 + 0.08 / 365) * (1 + 0.03 / 365) - 1) * 36500 / 3\n        assert abs(result - expected) < 1e-12\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift(2)\",\n            rate_fixings=name,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = ((1 + 0.10 / 365) * (1 + 0.08 / 365) - 1) * 36500 / 2\n        assert abs(result - expected) < 1e-12\n        fixings.pop(f\"{name}_1B\")\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_observation_shift_method_with_fixings_and_float_spread(\n        self,\n        curve_type,\n        rfr_curve,\n        line_curve,\n    ) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_1B\", Series(index=[dt(2022, 1, 1), dt(2022, 1, 2)], data=[10.0, 8.0]))\n        period = FloatPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift(2)\",\n            rate_fixings=name,\n            float_spread=1000.0,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        period.rate(curve)\n        result = period.rate(curve)  # double calc to test caching of fixing result\n        expected = ((1 + 0.10 / 365) * (1 + 0.08 / 365) - 1) * 36500 / 2 + 10.0\n        assert abs(result - expected) < 1e-12\n        fixings.pop(f\"{name}_1B\")\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_observation_shift_avg_method(self, curve_type, rfr_curve, line_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        period = FloatPeriod(\n            start=dt(2022, 1, 2),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift_avg(1)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = (1.0 + 2 + 3) / 3\n        assert abs(result - expected) < 1e-11\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift_avg(2)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = (1.0 + 2.0) / 2\n        assert abs(result - expected) < 1e-11\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"line_curve\"])\n    def test_rfr_observation_shift_avg_method_with_fixings(\n        self,\n        curve_type,\n        rfr_curve,\n        line_curve,\n    ) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        fixings.add(\"123454_1B\", Series(index=[dt(2022, 1, 1), dt(2022, 1, 2)], data=[10.0, 8.0]))\n        period = FloatPeriod(\n            start=dt(2022, 1, 2),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift_avg(1)\",\n            rate_fixings=\"123454\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = (10.0 + 8.0 + 3.0) / 3\n        assert abs(result - expected) < 1e-11\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 5),\n            payment=dt(2022, 1, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_observation_shift_avg(2)\",\n            rate_fixings=\"123454\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        expected = (10.0 + 8) / 2\n        assert abs(result - expected) < 1e-11\n        fixings.pop(\"123454_1B\")\n\n    def test_dcf_obs_period_raises(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, calendar=\"ldn\")\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 12, 31),\n            payment=dt(2022, 12, 31),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"rfr_lookback(5)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"ldn\",\n                lag=0,\n                convention=\"act360\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        # this may only raise when lookback is used ?\n        with pytest.raises(\n            ValueError, match=\"`start` and `end` for a calendar `bus_date_range` must both be vali\"\n        ):\n            float_period.rate(curve)\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping not yet implemented.\")\n    @pytest.mark.parametrize(\n        \"curve_type\",\n        [\"curve\", \"linecurve\"],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\", \"expected_date\"),\n        [\n            (\"rfr_payment_delay\", [1000000, 1000082, 1000191, 1000561], dt(2022, 1, 6)),\n            (\"rfr_observation_shift(2)\", [1499240, 1499281, 1499363, 1499486], dt(2022, 1, 4)),\n            (\"rfr_lockout(2)\", [999931, 4999411, 0, 0], dt(2022, 1, 6)),\n            (\"rfr_lookback(2)\", [999657, 999685, 2998726, 999821], dt(2022, 1, 4)),\n        ],\n    )\n    def test_rfr_fixings_array(self, curve_type, method, expected, expected_date) -> None:\n        # tests the fixings array and the compounding for different types of curve\n        # at different rates in the period.\n\n        v1 = 1 / (1 + 0.01 / 365)\n        v2 = v1 / (1 + 0.02 / 365)\n        v3 = v2 / (1 + 0.03 / 365)\n        v4 = v3 / (1 + 0.04 / 365)\n        v5 = v4 / (1 + 0.045 * 3 / 365)\n        v6 = v5 / (1 + 0.05 / 365)\n        v7 = v6 / (1 + 0.055 / 365)\n\n        nodes = {\n            dt(2022, 1, 3): 1.00,\n            dt(2022, 1, 4): v1,\n            dt(2022, 1, 5): v2,\n            dt(2022, 1, 6): v3,\n            dt(2022, 1, 7): v4,\n            dt(2022, 1, 10): v5,\n            dt(2022, 1, 11): v6,\n            dt(2022, 1, 12): v7,\n        }\n        curve = Curve(\n            nodes=nodes,\n            interpolation=\"log_linear\",\n            convention=\"act365f\",\n            calendar=\"bus\",\n        )\n\n        line_curve = LineCurve(\n            nodes={\n                dt(2022, 1, 2): -99,\n                dt(2022, 1, 3): 1.0,\n                dt(2022, 1, 4): 2.0,\n                dt(2022, 1, 5): 3.0,\n                dt(2022, 1, 6): 4.0,\n                dt(2022, 1, 7): 4.5,\n                dt(2022, 1, 10): 5.0,\n                dt(2022, 1, 11): 5.5,\n            },\n            interpolation=\"linear\",\n            convention=\"act365f\",\n            calendar=\"bus\",\n        )\n        rfr_curve = curve if curve_type == \"curve\" else line_curve\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 5),\n            end=dt(2022, 1, 11),\n            payment=dt(2022, 1, 11),\n            frequency=Frequency.Months(3, None),\n            fixing_method=method,\n            convention=\"act365f\",\n            notional=-1000000,\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"f\",\n                eom=True,\n            ),\n        )\n        table = period.try_unindexed_reference_fixings_exposure(\n            rate_curve=rfr_curve, disc_curve=curve\n        ).unwrap()\n\n        assert table.index.tolist()[1] == expected_date\n        assert np.all(np.isclose(np.array(expected), table[(rfr_curve.id, \"notional\")].to_numpy()))\n\n    @pytest.mark.parametrize(\n        \"curve_type\",\n        [\"curve\", \"linecurve\"],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\", \"expected_date\"),\n        [\n            (\"rfr_payment_delay\", [0.27393, 0.27392, 0.82155, 0.27391], dt(2022, 1, 6)),\n            (\"rfr_observation_shift(2)\", [0.41074, 0.41073, 0.41072, 0.41071], dt(2022, 1, 4)),\n            (\"rfr_lockout(2)\", [0.27391, 1.36933, 0, 0], dt(2022, 1, 6)),\n            (\"rfr_lookback(2)\", [0.27387, 0.27386, 0.82143, 0.27385], dt(2022, 1, 4)),\n        ],\n    )\n    def test_rfr_fixings_array_substitute(\n        self, curve_type, method, expected, expected_date\n    ) -> None:\n        # tests the fixings array and the compounding for different types of curve\n        # at different rates in the period.\n\n        v1 = 1 / (1 + 0.01 / 365)\n        v2 = v1 / (1 + 0.02 / 365)\n        v3 = v2 / (1 + 0.03 / 365)\n        v4 = v3 / (1 + 0.04 / 365)\n        v5 = v4 / (1 + 0.045 * 3 / 365)\n        v6 = v5 / (1 + 0.05 / 365)\n        v7 = v6 / (1 + 0.055 / 365)\n\n        nodes = {\n            dt(2022, 1, 3): 1.00,\n            dt(2022, 1, 4): v1,\n            dt(2022, 1, 5): v2,\n            dt(2022, 1, 6): v3,\n            dt(2022, 1, 7): v4,\n            dt(2022, 1, 10): v5,\n            dt(2022, 1, 11): v6,\n            dt(2022, 1, 12): v7,\n        }\n        curve = Curve(\n            nodes=nodes,\n            interpolation=\"log_linear\",\n            convention=\"act365f\",\n            calendar=\"bus\",\n        )\n\n        line_curve = LineCurve(\n            nodes={\n                dt(2022, 1, 2): -99,\n                dt(2022, 1, 3): 1.0,\n                dt(2022, 1, 4): 2.0,\n                dt(2022, 1, 5): 3.0,\n                dt(2022, 1, 6): 4.0,\n                dt(2022, 1, 7): 4.5,\n                dt(2022, 1, 10): 5.0,\n                dt(2022, 1, 11): 5.5,\n            },\n            interpolation=\"linear\",\n            convention=\"act365f\",\n            calendar=\"bus\",\n        )\n        rfr_curve = curve if curve_type == \"curve\" else line_curve\n\n        period = FloatPeriod(\n            start=dt(2022, 1, 5),\n            end=dt(2022, 1, 11),\n            payment=dt(2022, 1, 11),\n            frequency=Frequency.Months(3, None),\n            fixing_method=method,\n            convention=\"act365f\",\n            notional=-1000000,\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"f\",\n                eom=True,\n            ),\n        )\n        table = period.local_analytic_rate_fixings(rate_curve=rfr_curve, disc_curve=curve)\n\n        assert table.index.tolist()[1] == expected_date\n        result = table[(rfr_curve.id, \"usd\", \"usd\", \"1B\")].to_numpy()\n        assert np.all(np.isclose(np.array(expected), result, atol=1e-4))\n\n    def test_rfr_fixings_array_raises2(self, line_curve, curve) -> None:\n        period = FloatPeriod(\n            start=dt(2022, 1, 5),\n            end=dt(2022, 1, 11),\n            payment=dt(2022, 1, 11),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            convention=\"act365f\",\n            notional=-1000000,\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"f\",\n                eom=True,\n            ),\n        )\n        with pytest.raises(ValueError, match=\"`disc_curve` cannot be inferred from a non-DF\"):\n            period.local_analytic_rate_fixings(rate_curve=line_curve)\n\n        with pytest.raises(ValueError, match=\"A `rate_curve` supplied as dict to an RF\"):\n            period.local_analytic_rate_fixings(\n                rate_curve={\"1m\": line_curve, \"2m\": line_curve}, disc_curve=curve\n            )\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping not implemented\")\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\"),\n        [\n            (\"rfr_payment_delay\", 1000000),\n            (\"rfr_observation_shift(1)\", 333319),\n            (\"rfr_lookback(1)\", 333319),\n        ],\n    )\n    def test_rfr_fixings_array_single_period(self, method, expected) -> None:\n        rfr_curve = Curve(\n            nodes={dt(2022, 1, 3): 1.0, dt(2022, 1, 15): 0.9995},\n            interpolation=\"log_linear\",\n            convention=\"act365f\",\n            calendar=\"bus\",\n        )\n        period = FloatPeriod(\n            start=dt(2022, 1, 10),\n            end=dt(2022, 1, 11),\n            payment=dt(2022, 1, 11),\n            frequency=Frequency.Months(3, None),\n            fixing_method=method,\n            notional=-1000000,\n            convention=\"act365f\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"f\",\n                eom=True,\n            ),\n        )\n        result = period.try_unindexed_reference_fixings_exposure(rate_curve=rfr_curve).unwrap()\n        assert abs(result[(rfr_curve.id, \"notional\")].iloc[0] - expected) < 1\n\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\"),\n        [\n            (\"rfr_payment_delay\", 0.27388),\n            (\"rfr_observation_shift(1)\", 0.27388),\n            (\"rfr_lookback(1)\", 0.27388),\n        ],\n    )\n    def test_rfr_fixings_array_single_period_substitute(self, method, expected) -> None:\n        rfr_curve = Curve(\n            nodes={dt(2022, 1, 3): 1.0, dt(2022, 1, 15): 0.9995},\n            interpolation=\"log_linear\",\n            convention=\"act365f\",\n            calendar=\"bus\",\n        )\n        period = FloatPeriod(\n            start=dt(2022, 1, 10),\n            end=dt(2022, 1, 11),\n            payment=dt(2022, 1, 11),\n            frequency=Frequency.Months(3, None),\n            fixing_method=method,\n            notional=-1000000,\n            convention=\"act365f\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                lag=0,\n                convention=\"act365f\",\n                modifier=\"f\",\n                eom=True,\n            ),\n        )\n        result = period.local_analytic_rate_fixings(rate_curve=rfr_curve)\n        assert abs(result[(rfr_curve.id, \"usd\", \"usd\", \"1B\")].iloc[0] - expected) < 1\n\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\", \"index\"),\n        [\n            (\n                \"rfr_payment_delay\",\n                3.20040557,\n                [dt(2022, 1, 28), dt(2022, 1, 31), dt(2022, 2, 1)],\n            ),\n            (\"rfr_lockout(1)\", 3.80063892, [dt(2022, 1, 28), dt(2022, 1, 31), dt(2022, 2, 1)]),\n            (\"rfr_lookback(1)\", 3.20040557, [dt(2022, 1, 27), dt(2022, 1, 28), dt(2022, 1, 31)]),\n            (\n                \"rfr_observation_shift(1)\",\n                4.00045001,\n                [dt(2022, 1, 27), dt(2022, 1, 28), dt(2022, 1, 31)],\n            ),\n        ],\n    )\n    def test_rfr_period_all_types_with_defined_fixings(self, method, expected, index):\n        # This is probably a redundant test but it came later after some refactoring and\n        # was double checked with manual calculation in Excel. Easy to do.\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2022, 3, 1): 1.0}, calendar=\"nyc\")\n        name = str(hash(os.urandom(2)))\n        fixings.add(f\"{name}_1B\", Series(data=[3.0, 5.0, 2.0], index=index))\n        period = FloatPeriod(\n            start=dt(2022, 1, 28),\n            end=dt(2022, 2, 2),\n            frequency=Frequency.Months(12, None),\n            payment=dt(2022, 1, 1),\n            fixing_method=method,\n            convention=\"act360\",\n            calendar=\"nyc\",\n            rate_fixings=name,\n        )\n        result = period.rate(curve)\n        assert abs(result - expected) < 1e-8\n        fixings.pop(f\"{name}_1B\")\n\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\"),\n        [\n            (\n                \"none_simple\",\n                ((1 + 0.01 / 365) * (1 + 0.02 / 365) * (1 + 0.03 / 365) - 1) * 36500 / 3\n                + 100 / 100,\n            ),\n            (\n                \"isda_compounding\",\n                ((1 + 0.02 / 365) * (1 + 0.03 / 365) * (1 + 0.04 / 365) - 1) * 36500 / 3,\n            ),\n            (\"isda_flat_compounding\", 3.000173518986841),\n        ],\n    )\n    def test_rfr_compounding_float_spreads(self, method, expected, rfr_curve) -> None:\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(1, None),\n            float_spread=100,\n            spread_compound_method=method,\n            convention=\"act365f\",\n        )\n        result = period.rate(rfr_curve)\n        assert abs(result - expected) < 1e-8\n\n    def test_ibor_rate_line_curve(self, line_curve) -> None:\n        period = FloatPeriod(\n            start=dt(2022, 1, 5),\n            end=dt(2022, 4, 5),\n            payment=dt(2022, 4, 5),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            fixing_series=FloatRateSeries(\n                lag=2,\n                calendar=\"all\",\n                convention=\"act365f\",\n                modifier=\"mf\",\n                eom=True,\n            ),\n        )\n        # assert period.rate_params._is_inefficient is False\n        assert period.rate(line_curve) == 3.0\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping not implemented\")\n    def test_ibor_fixing_table(self, line_curve, curve) -> None:\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 4),\n            end=dt(2022, 4, 4),\n            payment=dt(2022, 4, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            convention=\"act365f\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=2,\n                convention=\"act365f\",\n                modifier=\"f\",\n                eom=True,\n            ),\n        )\n        result = float_period.try_unindexed_reference_fixings_exposure(\n            rate_curve=line_curve, disc_curve=curve\n        ).unwrap()\n        expected = DataFrame(\n            {\n                \"obs_dates\": [dt(2022, 1, 2)],\n                \"notional\": [-1e6],\n                \"risk\": [-24.402790080357686],\n                \"dcf\": [0.2465753424657534],\n                \"rates\": [2.0],\n            },\n        ).set_index(\"obs_dates\")\n        expected.columns = MultiIndex.from_tuples(\n            [\n                (line_curve.id, \"notional\"),\n                (line_curve.id, \"risk\"),\n                (line_curve.id, \"dcf\"),\n                (line_curve.id, \"rates\"),\n            ]\n        )\n        assert_frame_equal(expected, result)\n\n    def test_ibor_fixing_table_substitute(self, line_curve, curve) -> None:\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 4),\n            end=dt(2022, 4, 4),\n            payment=dt(2022, 4, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            convention=\"act365f\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=2,\n                convention=\"act365f\",\n                modifier=\"f\",\n                eom=True,\n            ),\n        )\n        result = float_period.local_analytic_rate_fixings(rate_curve=line_curve, disc_curve=curve)\n        assert abs(result.iloc[0, 0] + 24.402790080357686) < 1e-10\n\n    @pytest.mark.skip(reason=\"`right` removed by v2.5\")\n    def test_ibor_fixing_table_right(self, line_curve, curve) -> None:\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 4),\n            end=dt(2022, 4, 4),\n            payment=dt(2022, 4, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            convention=\"act365f\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                lag=2,\n                convention=\"act365f\",\n                modifier=\"f\",\n                eom=True,\n            ),\n        )\n        result = float_period.try_unindexed_reference_fixings_exposure(\n            rate_curve=line_curve, disc_curve=curve, right=dt(2022, 1, 1)\n        ).unwrap()\n        expected = DataFrame(\n            {\n                \"notional\": [],\n                \"risk\": [],\n                \"dcf\": [],\n                \"rates\": [],\n            },\n        )\n\n        expected.index = Index([], dtype=\"datetime64[ns]\", name=\"obs_dates\")\n        expected.columns = MultiIndex.from_tuples(\n            [\n                (line_curve.id, \"notional\"),\n                (line_curve.id, \"risk\"),\n                (line_curve.id, \"dcf\"),\n                (line_curve.id, \"rates\"),\n            ]\n        )\n        assert_frame_equal(expected, result)\n\n    # @pytest.mark.skip(reason=\"PERMANENT REMOVAL due to approximate method removed in v2.2. This \"\n    #                          \"test becomes identical to one above\"\n    # )\n    # def test_ibor_fixing_table_fast(self, line_curve, curve) -> None:\n    #     float_period = FloatPeriod(\n    #         start=dt(2022, 1, 4),\n    #         end=dt(2022, 4, 4),\n    #         payment=dt(2022, 4, 4),\n    #         frequency=Frequency.Months(3, None),\n    #         fixing_method=\"ibor\",\n    #         method_param=2,\n    #         convention=\"act365f\",\n    #     )\n    #     result = float_period.fixings_table(line_curve, disc_curve=curve, approximate=True)\n    #     expected = DataFrame(\n    #         {\n    #             \"obs_dates\": [dt(2022, 1, 2)],\n    #             \"notional\": [-1e6],\n    #             \"risk\": [-24.402790080357686],\n    #             \"dcf\": [0.2465753424657534],\n    #             \"rates\": [2.0],\n    #         },\n    #     ).set_index(\"obs_dates\")\n    #     expected.columns = MultiIndex.from_tuples(\n    #         [\n    #             (line_curve.id, \"notional\"),\n    #             (line_curve.id, \"risk\"),\n    #             (line_curve.id, \"dcf\"),\n    #             (line_curve.id, \"rates\"),\n    #         ]\n    #     )\n    #     assert_frame_equal(expected, result)\n\n    def test_ibor_fixings(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.90}, calendar=\"bus\")\n        fixings_ = Series(\n            [1.00, 2.801, 1.00, 1.00],\n            index=[dt(2023, 3, 1), dt(2023, 3, 2), dt(2023, 3, 3), dt(2023, 3, 6)],\n        )\n        fixings.add(\"TEST_VALUES_3M\", fixings_)\n        float_period = FloatPeriod(\n            start=dt(2023, 3, 6),\n            end=dt(2023, 6, 6),\n            payment=dt(2023, 6, 6),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            rate_fixings=\"TEST_VALUES\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                convention=\"act360\",\n                lag=2,\n                modifier=\"mf\",\n                eom=False,\n            ),\n        )\n        result = float_period.rate(curve)\n        assert result == 2.801\n        fixings.pop(\"TEST_VALUES_3M\")\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping not implemented\")\n    def test_ibor_fixings_table_historical_before_curve(self) -> None:\n        # fixing table should return a DataFrame with an unknown rate and zero exposure\n        # the fixing has occurred in the past but is unspecified.\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.90}, calendar=\"bus\")\n        float_period = FloatPeriod(\n            start=dt(2000, 2, 2),\n            end=dt(2000, 5, 2),\n            payment=dt(2000, 5, 2),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(0)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                convention=\"act360\",\n                lag=0,\n                modifier=\"mf\",\n                eom=False,\n            ),\n        )\n        result = float_period.try_unindexed_reference_fixings_exposure(rate_curve=curve).unwrap()\n        expected = DataFrame(\n            data=[[0.0, 0.0, 0.25, np.nan]],\n            index=Index([dt(2000, 2, 2)], name=\"obs_dates\"),\n            columns=MultiIndex.from_tuples(\n                [\n                    (curve.id, \"notional\"),\n                    (curve.id, \"risk\"),\n                    (curve.id, \"dcf\"),\n                    (curve.id, \"rates\"),\n                ],\n            ),\n        )\n        assert_frame_equal(expected, result)\n\n    def test_ibor_fixings_table_historical_before_curve_substitute(self) -> None:\n        # fixing table should return a DataFrame with an unknown rate and zero exposure\n        # the fixing has occurred in the past but is unspecified.\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.90}, calendar=\"bus\")\n        float_period = FloatPeriod(\n            start=dt(2000, 2, 2),\n            end=dt(2000, 5, 2),\n            payment=dt(2000, 5, 2),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(0)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                convention=\"act360\",\n                lag=0,\n                modifier=\"mf\",\n                eom=False,\n            ),\n        )\n        result = float_period.local_analytic_rate_fixings(rate_curve=curve)\n        expected = DataFrame(\n            data=[[0.0]],\n            index=Index([dt(2000, 2, 2)], name=\"obs_dates\"),\n            columns=MultiIndex.from_tuples(\n                [(curve.id, \"usd\", \"usd\", \"3M\")],\n                names=[\"identifier\", \"local_ccy\", \"display_ccy\", \"frequency\"],\n            ),\n        )\n        assert_frame_equal(expected, result)\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping not implemented.\")\n    def test_rfr_fixings_table_historical_before_curve(self) -> None:\n        # fixing table should return a DataFrame with an unknown rate and zero exposure\n        # the fixing has occurred in the past but is unspecified.\n        curve = Curve({dt(2022, 1, 4): 1.0, dt(2025, 1, 1): 0.90}, calendar=\"bus\")\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                convention=\"act360\",\n                eom=False,\n                modifier=\"F\",\n                lag=0,\n            ),\n        )\n        with pytest.raises(ValueError, match=\"`effective` date for rate period is before the init\"):\n            float_period.try_unindexed_reference_fixings_exposure(rate_curve=curve).unwrap()\n\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_1B\", Series(index=[dt(2022, 1, 3)], data=[4.0]))\n        float_period = FloatPeriod(\n            rate_fixings=name,\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                convention=\"act360\",\n                eom=False,\n                modifier=\"F\",\n                lag=0,\n            ),\n        )\n        result = float_period.try_unindexed_reference_fixings_exposure(rate_curve=curve).unwrap()\n\n        assert isinstance(result, DataFrame)\n        assert result.iloc[0, 0] == 0.0\n        assert result[f\"{curve.id}\", \"notional\"][dt(2022, 1, 3)] == 0.0\n\n    def test_rfr_fixings_table_historical_before_curve_substitute(self) -> None:\n        # fixing table should return a DataFrame with an unknown rate and zero exposure\n        # the fixing has occurred in the past but is unspecified.\n        curve = Curve({dt(2022, 1, 4): 1.0, dt(2025, 1, 1): 0.90}, calendar=\"bus\")\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                convention=\"act360\",\n                eom=False,\n                modifier=\"F\",\n                lag=0,\n            ),\n        )\n        with pytest.raises(ValueError, match=\"The Curve initial node date is after the required\"):\n            float_period.local_analytic_rate_fixings(rate_curve=curve)\n\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_1B\", Series(index=[dt(2022, 1, 3)], data=[4.0]))\n        float_period = FloatPeriod(\n            rate_fixings=name,\n            start=dt(2022, 1, 3),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                convention=\"act360\",\n                eom=False,\n                modifier=\"F\",\n                lag=0,\n            ),\n        )\n        result = float_period.local_analytic_rate_fixings(rate_curve=curve)\n\n        assert isinstance(result, DataFrame)\n        assert result.iloc[0, 0] == 0.0\n        assert result.index[0] == dt(2022, 1, 3)\n\n    def test_ibor_fixing_unavailable(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.90}, calendar=\"bus\")\n        lcurve = LineCurve({dt(2022, 1, 1): 2.0, dt(2025, 1, 1): 4.0}, calendar=\"bus\")\n        fixings_ = Series([2.801], index=[dt(2023, 3, 1)])\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_3M\", fixings_)\n        float_period = FloatPeriod(\n            start=dt(2023, 3, 20),\n            end=dt(2023, 6, 20),\n            payment=dt(2023, 6, 20),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            calendar=\"bus\",\n            rate_fixings=name,\n        )\n        result = float_period.rate(curve)  # fixing occurs 18th Mar, not in `fixings`\n        assert abs(result - 3.476095729528156) < 1e-5\n        result = float_period.rate(lcurve)  # fixing occurs 18th Mar, not in `fixings`\n        assert abs(result - 2.801094890510949) < 1e-5\n        fixings.pop(f\"{name}_3M\")\n\n    def test_ibor_fixings_exposure_with_fixing(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.90}, calendar=\"bus\")\n        float_period = FloatPeriod(\n            start=dt(2023, 3, 20),\n            end=dt(2023, 6, 20),\n            payment=dt(2023, 6, 20),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            calendar=\"bus\",\n            rate_fixings=2.0,\n        )\n        result = float_period.local_analytic_rate_fixings(rate_curve=curve)\n        assert result.iloc[0, 0] == 0.0\n\n    @pytest.mark.parametrize(\"float_spread\", [0, 100])\n    def test_ibor_rate_df_curve(self, float_spread, curve) -> None:\n        period = FloatPeriod(\n            start=dt(2022, 4, 1),\n            end=dt(2022, 7, 1),\n            payment=dt(2022, 7, 1),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            float_spread=float_spread,\n        )\n        expected = (0.99 / 0.98 - 1) * 36000 / 91 + float_spread / 100\n        result = period.rate(curve)\n        assert result == expected\n\n    @pytest.mark.parametrize(\"float_spread\", [0, 100])\n    def test_ibor_rate_stub_df_curve(self, float_spread, curve) -> None:\n        period = FloatPeriod(\n            start=dt(2022, 4, 1),\n            end=dt(2022, 5, 1),\n            payment=dt(2022, 5, 1),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            stub=True,\n            float_spread=float_spread,\n        )\n        expected = (0.99 / curve[dt(2022, 5, 1)] - 1) * 36000 / 30 + float_spread / 100\n        assert period.rate(curve) == expected\n\n    def test_single_fixing_override(self, curve) -> None:\n        period = FloatPeriod(\n            start=dt(2022, 4, 1),\n            end=dt(2022, 5, 1),\n            payment=dt(2022, 5, 1),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            stub=True,\n            float_spread=100,\n            rate_fixings=7.5,\n        )\n        expected = 7.5 + 1\n        assert period.rate(curve) == expected\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"linecurve\"])\n    def test_period_historic_fixings(self, curve_type, line_curve, rfr_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        fixings.add(\"123_1B\", Series(index=[dt(2021, 12, 30), dt(2021, 12, 31)], data=[1.50, 2.50]))\n        period = FloatPeriod(\n            start=dt(2021, 12, 30),\n            end=dt(2022, 1, 3),\n            payment=dt(2022, 1, 3),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            float_spread=100,\n            rate_fixings=\"123\",\n            convention=\"act365F\",\n        )\n        expected = (\n            (1 + 0.015 / 365) * (1 + 0.025 / 365) * (1 + 0.01 / 365) * (1 + 0.02 / 365) - 1\n        ) * 36500 / 4 + 1\n        assert period.rate(curve) == expected\n        fixings.pop(\"123_1B\")\n\n    @pytest.mark.parametrize(\"curve_type\", [\"curve\", \"linecurve\"])\n    def test_period_historic_fixings_series(self, curve_type, line_curve, rfr_curve) -> None:\n        curve = rfr_curve if curve_type == \"curve\" else line_curve\n        fixings_ = Series(\n            [99, 99, 1.5, 2.5],\n            index=[dt(1995, 1, 1), dt(2021, 12, 29), dt(2021, 12, 30), dt(2021, 12, 31)],\n        )\n        fixings.add(\"123_1B\", fixings_)\n        period = FloatPeriod(\n            start=dt(2021, 12, 30),\n            end=dt(2022, 1, 3),\n            payment=dt(2022, 1, 3),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            float_spread=100,\n            rate_fixings=\"123\",\n            convention=\"act365F\",\n        )\n        expected = (\n            (1 + 0.015 / 365) * (1 + 0.025 / 365) * (1 + 0.01 / 365) * (1 + 0.02 / 365) - 1\n        ) * 36500 / 4 + 1\n        result = period.rate(curve)\n        assert result == expected\n        fixings.pop(\"123_1B\")\n\n    @pytest.mark.parametrize(\"curve_type\", [\"linecurve\", \"curve\"])\n    def test_period_historic_fixings_series_missing_warns(\n        self,\n        curve_type,\n        line_curve,\n        rfr_curve,\n    ) -> None:\n        #\n        # This test modified by PR 357. The warning is still produced but the code also now\n        # later errors due to the missing fixing and no forecasting method.\n        #\n\n        # this test was modified for v2.2. Now a missing fixing raises an error directly\n        fixings_ = Series(\n            [4.0, 3.0, 2.5], index=[dt(1995, 12, 1), dt(2021, 12, 30), dt(2022, 1, 1)]\n        )\n        with pytest.raises(ValueError, match=\"The fixings series '199\"):\n            FloatPeriod(\n                start=dt(2021, 12, 30),\n                end=dt(2022, 1, 3),\n                payment=dt(2022, 1, 3),\n                frequency=Frequency.Months(3, None),\n                fixing_method=\"rfr_payment_delay\",\n                float_spread=100,\n                rate_fixings=fixings_,\n                convention=\"act365F\",\n            )\n\n    def test_more_fixings_than_expected_by_calendar_raises(self):\n        # Create historical fixings spanning 5 days for a FloatPeriod.\n        # But set a Cal that does not expect all of these - one holdiay midweek.\n        # Observe the rate calculation.\n        fixings_ = Series(\n            data=[1.0, 2.0, 3.0, 4.0, 5.0],\n            index=[\n                dt(2023, 1, 23),\n                dt(2023, 1, 24),\n                dt(2023, 1, 25),\n                dt(2023, 1, 26),\n                dt(2023, 1, 27),\n            ],\n        )\n        cal = Cal(holidays=[dt(2023, 1, 25)], week_mask=[5, 6])\n        fixings.add(\"x45_1B\", fixings_)\n        period = FloatPeriod(\n            start=dt(2023, 1, 23),\n            end=dt(2023, 1, 30),\n            payment=dt(2023, 1, 30),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            rate_fixings=\"x45\",\n            convention=\"act360\",\n            calendar=cal,\n        )\n        curve = Curve({dt(2023, 1, 26): 1.0, dt(2025, 1, 26): 1.0}, calendar=cal)\n        with pytest.warns(UserWarning, match=err.W02_0[:20]):\n            period.rate(curve)\n        fixings.pop(\"x45_1B\")\n\n    def test_fewer_fixings_than_expected_raises(self):\n        # Create historical fixings spanning 4 days for a FloatPeriod, with mid-week holiday\n        # But set a Cal that expects 5 (the cal does not have the holiday)\n        # Observe the rate calculation.\n\n        # this tests performs a minimal version of test_period_historic_fixings_series_missing_warns\n        fixings_ = Series(\n            data=[1.0, 2.0, 4.0, 5.0],\n            index=[dt(2023, 1, 23), dt(2023, 1, 24), dt(2023, 1, 26), dt(2023, 1, 27)],\n        )\n        with pytest.raises(ValueError, match=\"The fixings series '2023\"):\n            FloatPeriod(\n                start=dt(2023, 1, 23),\n                end=dt(2023, 1, 30),\n                payment=dt(2023, 1, 30),\n                frequency=Frequency.Months(3, None),\n                fixing_method=\"rfr_payment_delay\",\n                rate_fixings=fixings_,\n                convention=\"act365F\",\n                calendar=\"bus\",\n            )\n\n    @pytest.mark.skip(reason=\"new fixings processes in v2.2 require cached fixing. See next test\")\n    def test_fixing_with_float_spread_warning(self, curve) -> None:\n        float_period = FloatPeriod(\n            start=dt(2022, 1, 4),\n            end=dt(2022, 4, 4),\n            payment=dt(2022, 4, 4),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            spread_compound_method=\"isda_compounding\",\n            float_spread=100,\n            rate_fixings=1.0,\n        )\n        with pytest.warns(UserWarning):\n            result = float_period.rate(curve)\n        assert result == 2.0\n\n    def test_fixing_with_float_spread_complicated_compounding(self, curve) -> None:\n        # this test ensures float spread is calculated correctly and populate to the fixings\n        # value as a scalar and repeated calculations are avoided.\n        fixings.add(\n            \"x45_1B\", Series(index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3)], data=1.0)\n        )\n        float_period = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 1, 4),\n            payment=dt(2000, 1, 4),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"rfr_payment_delay\",\n            spread_compound_method=\"isda_compounding\",\n            float_spread=100,\n            rate_fixings=\"x45\",\n            fixing_series=FloatRateSeries(\n                calendar=\"all\",\n                convention=\"act360\",\n                lag=0,\n                modifier=\"F\",\n                eom=False,\n            ),\n        )\n        result = float_period.rate(curve)\n        assert abs(result - 2.000111113166) < 1e-10\n        assert abs(float_period.rate_params.rate_fixing.value - 2.000111113166) < 1e-10\n\n    # @pytest.mark.skip(reason=\"PERMANENTLY REMOVED due to reformed allowed inputs.\n    # This is input error.\")\n    # def test_float_period_fixings_list_raises_on_ibor(self, curve, line_curve) -> None:\n    #     with pytest.raises(ValueError, match=err.VE_FIXINGS_BAD_TYPE[:25]):\n    #         FloatPeriod(\n    #             start=dt(2022, 1, 4),\n    #             end=dt(2022, 4, 4),\n    #             payment=dt(2022, 4, 4),\n    #             frequency=Frequency.Months(3, None),\n    #             fixing_method=\"ibor\",\n    #             method_param=2,\n    #             rate_fixings=[1.00],\n    #         )\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping not implemented.\")\n    @pytest.mark.parametrize(\n        (\"meth\", \"exp\"),\n        [\n            (\n                \"rfr_payment_delay\",\n                DataFrame(\n                    {\n                        \"obs_dates\": [\n                            dt(2022, 12, 28),\n                            dt(2022, 12, 29),\n                            dt(2022, 12, 30),\n                            dt(2022, 12, 31),\n                            dt(2023, 1, 1),\n                        ],\n                        \"notional\": [\n                            0.0,\n                            0.0,\n                            0.0,\n                            -999821.37380,\n                            -999932.84380,\n                        ],\n                        \"risk\": [0.0, 0.0, 0.0, -0.26664737262, -0.26664737262],\n                        \"dcf\": [0.0027777777777777778] * 5,\n                        \"rates\": [1.19, 1.19, -8.81, 4.01364, 4.01364],\n                    },\n                ).set_index(\"obs_dates\"),\n            ),\n            (\n                \"rfr_payment_delay_avg\",\n                DataFrame(\n                    {\n                        \"obs_dates\": [\n                            dt(2022, 12, 28),\n                            dt(2022, 12, 29),\n                            dt(2022, 12, 30),\n                            dt(2022, 12, 31),\n                            dt(2023, 1, 1),\n                        ],\n                        \"notional\": [\n                            0.0,\n                            0.0,\n                            0.0,\n                            -999888.52252,\n                            -1000000.00000,\n                        ],\n                        \"risk\": [0.0, 0.0, 0.0, -0.26666528084917104, -0.26666528084917104],\n                        \"dcf\": [0.0027777777777777778] * 5,\n                        \"rates\": [1.19, 1.19, -8.81, 4.01364, 4.01364],\n                    },\n                ).set_index(\"obs_dates\"),\n            ),\n        ],\n    )\n    def test_rfr_fixings_table(self, curve, meth, exp) -> None:\n        exp.columns = MultiIndex.from_tuples(\n            [(curve.id, \"notional\"), (curve.id, \"risk\"), (curve.id, \"dcf\"), (curve.id, \"rates\")]\n        )\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            f\"{name}_1B\",\n            Series(\n                index=[dt(2022, 12, 28), dt(2022, 12, 29), dt(2022, 12, 30)],\n                data=[1.19, 1.19, -8.81],\n            ),\n        )\n        float_period = FloatPeriod(\n            start=dt(2022, 12, 28),\n            end=dt(2023, 1, 2),\n            payment=dt(2023, 1, 2),\n            frequency=Frequency.Months(1, None),\n            rate_fixings=name,\n            fixing_method=meth,\n        )\n        result = float_period.try_unindexed_reference_fixings_exposure(rate_curve=curve).unwrap()\n        assert_frame_equal(result, exp, rtol=1e-4)\n\n        curve._set_ad_order(order=1)\n        # assert values are unchanged even if curve can calculate derivatives\n        result = float_period.try_unindexed_reference_fixings_exposure(rate_curve=curve).unwrap()\n\n        fixings.pop(f\"{name}_1B\")\n        assert_frame_equal(result, exp)\n\n    @pytest.mark.skip(reason=\"`right` removed by v2.5\")\n    @pytest.mark.parametrize(\n        (\"right\", \"exp\"),\n        [\n            (dt(2021, 1, 1), 0),\n            (dt(2022, 12, 31), 4),\n        ],\n    )\n    def test_rfr_fixings_table_right(self, curve, right, exp) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            f\"{name}_1B\",\n            Series(\n                index=[dt(2022, 12, 28), dt(2022, 12, 29), dt(2022, 12, 30)],\n                data=[1.19, 1.19, -8.81],\n            ),\n        )\n\n        float_period = FloatPeriod(\n            start=dt(2022, 12, 28),\n            end=dt(2023, 1, 2),\n            payment=dt(2023, 1, 2),\n            frequency=Frequency.Months(1, None),\n            rate_fixings=name,\n            fixing_method=\"rfr_payment_delay\",\n        )\n        result = float_period.try_unindexed_reference_fixings_exposure(curve, right=right).unwrap()\n        assert isinstance(result, DataFrame)\n        assert len(result.index) == exp\n\n    @pytest.mark.skip(reason=\"`right` removed by v2.5\")\n    def test_rfr_fixings_table_right_non_bus_day(self) -> None:\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2022, 11, 19): 0.98}, calendar=\"tgt\")\n        float_period = FloatPeriod(\n            start=dt(2022, 2, 1),\n            end=dt(2022, 2, 28),\n            payment=dt(2022, 2, 28),\n            frequency=Frequency.Months(1, None),\n            fixing_method=\"rfr_payment_delay\",\n            fixing_series=FloatRateSeries(\n                calendar=\"tgt\",\n                lag=0,\n                convention=\"act360\",\n                modifier=\"F\",\n                eom=False,\n            ),\n        )\n        result = float_period.try_unindexed_reference_fixings_exposure(\n            rate_curve=curve, right=dt(2022, 2, 13)\n        ).unwrap()\n        assert isinstance(result, DataFrame)\n        assert len(result.index) == 9\n\n    # @pytest.mark.skip(reason=\"PERMANENT REMOVAL due to approximate method removed in v2.2.\")\n    # @pytest.mark.parametrize(\n    #     (\"method\", \"param\"),\n    #     [\n    #         (\"rfr_payment_delay\", NoInput(0)),\n    #         (\"rfr_lookback\", 4),\n    #         (\"rfr_lockout\", 1),\n    #         (\"rfr_observation_shift\", 2),\n    #     ],\n    # )\n    # @pytest.mark.parametrize(\n    #     (\"scm\", \"spd\"),\n    #     [\n    #         (\"none_simple\", 1000.0),\n    #         (\"isda_compounding\", 1000.0),\n    #         (\"isda_flat_compounding\", 1000.0),\n    #     ],\n    # )\n    # @pytest.mark.parametrize(\n    #     \"crv\",\n    #     [\n    #         Curve(\n    #             {\n    #                 dt(2022, 1, 1): 1.00,\n    #                 dt(2022, 4, 1): 0.99,\n    #                 dt(2022, 7, 1): 0.98,\n    #                 dt(2022, 10, 1): 0.97,\n    #                 dt(2023, 6, 1): 0.96,\n    #             },\n    #             interpolation=\"log_linear\",\n    #             calendar=\"bus\",\n    #         ),\n    #     ],\n    # )\n    # def test_rfr_fixings_table_fast(self, method, param, scm, spd, crv) -> None:\n    #     float_period = FloatPeriod(\n    #         start=dt(2022, 12, 28),\n    #         end=dt(2023, 1, 3),\n    #         payment=dt(2023, 1, 3),\n    #         frequency=Frequency.Months(1, None),\n    #         fixing_method=method,\n    #         method_param=param,\n    #         spread_compound_method=scm,\n    #         float_spread=spd,\n    #     )\n    #     expected = float_period.fixings_table(crv)\n    #     result = float_period.fixings_table(crv, approximate=True)\n    #     assert_frame_equal(result, expected, rtol=1e-2)\n    #\n    # @pytest.mark.skip(reason=\"PERMANENT REMOVAL due to approximate method removed in v2.2.\")\n    # @pytest.mark.parametrize(\n    #     \"right\",\n    #     [\n    #         dt(2022, 12, 31),\n    #         dt(2021, 1, 1),\n    #     ],\n    # )\n    # def test_rfr_fixings_table_fast_right(self, curve, right) -> None:\n    #     float_period = FloatPeriod(\n    #         start=dt(2022, 12, 28),\n    #         end=dt(2023, 1, 3),\n    #         payment=dt(2023, 1, 3),\n    #         frequency=Frequency.Months(1, None),\n    #         fixing_method=\"rfr_payment_delay\",\n    #     )\n    #     expected = float_period.fixings_table(curve, right=right)\n    #     result = float_period.fixings_table(curve, approximate=True, right=right)\n    #     assert_frame_equal(result, expected, rtol=1e-2, check_dtype=False)\n    #\n    # @pytest.mark.skip(reason=\"PERMANENT REMOVAL due to approximate method removed in v2.2.\")\n    # @pytest.mark.parametrize(\n    #     (\"method\", \"param\"),\n    #     [\n    #         (\"rfr_payment_delay_avg\", None),\n    #         (\"rfr_lookback_avg\", 4),\n    #         (\"rfr_lockout_avg\", 1),\n    #         (\"rfr_observation_shift_avg\", 2),\n    #     ],\n    # )\n    # @pytest.mark.parametrize(\n    #     \"crv\",\n    #     [\n    #         Curve(\n    #             {\n    #                 dt(2022, 1, 1): 1.00,\n    #                 dt(2022, 4, 1): 0.99,\n    #                 dt(2022, 7, 1): 0.98,\n    #                 dt(2022, 10, 1): 0.97,\n    #                 dt(2023, 6, 1): 0.96,\n    #             },\n    #             interpolation=\"log_linear\",\n    #             calendar=\"bus\",\n    #         ),\n    #     ],\n    # )\n    # def test_rfr_fixings_table_fast_avg(self, method, param, crv) -> None:\n    #     float_period = FloatPeriod(\n    #         start=dt(2022, 12, 28),\n    #         end=dt(2023, 1, 3),\n    #         payment=dt(2023, 1, 3),\n    #         frequency=Frequency.Months(1, None),\n    #         fixing_method=method,\n    #         method_param=param,\n    #         spread_compound_method=\"none_simple\",\n    #         float_spread=100.0,\n    #     )\n    #     expected = float_period.fixings_table(crv)\n    #     result = float_period.fixings_table(crv, approximate=True)\n    #     assert_frame_equal(result, expected, rtol=1e-2)\n\n    # @pytest.mark.skip(reason=\"Series are not recommended inputs. Testing is removed.\")\n    # def test_rfr_rate_fixings_series_monotonic_error(self) -> None:\n    #     nodes = {\n    #         dt(2022, 1, 1): 1.00,\n    #         dt(2022, 4, 1): 0.99,\n    #         dt(2022, 7, 1): 0.98,\n    #         dt(2022, 10, 1): 0.97,\n    #     }\n    #     curve = Curve(nodes=nodes, interpolation=\"log_linear\")\n    #     fixings = Series(\n    #         [99, 2.25, 2.375, 2.5],\n    #         index=[dt(1995, 12, 1), dt(2021, 12, 30), dt(2022, 12, 31), dt(2020, 1, 1)],\n    #     )\n    #     period = FloatPeriod(\n    #         start=dt(2021, 12, 30),\n    #         end=dt(2022, 1, 3),\n    #         payment=dt(2022, 1, 3),\n    #         frequency=Frequency.Months(3, None),\n    #         fixing_method=\"rfr_payment_delay\",\n    #         float_spread=100,\n    #         rate_fixings=fixings,\n    #         convention=\"act365F\",\n    #         fixing_series=FloatRateSeries(\n    #             calendar=\"all\",\n    #             convention=\"act360\",\n    #             lag=0,\n    #             modifier=\"F\",\n    #             eom=True,\n    #         ),\n    #     )\n    #     # with pytest.raises(ValueError, match=\"`fixings` as a Series\"):\n    #     with pytest.raises(ValueError, match=err.VE02_5[:20]):\n    #         period.rate(curve)\n\n    @pytest.mark.parametrize(\n        (\"scm\", \"exp\"),\n        [\n            (\"none_simple\", True),\n            (\"isda_compounding\", False),\n        ],\n    )\n    def test_float_spread_affects_fixing_exposure(self, scm, exp) -> None:\n        nodes = {\n            dt(2022, 1, 1): 1.00,\n            dt(2022, 4, 1): 0.99,\n            dt(2022, 7, 1): 0.98,\n            dt(2022, 10, 1): 0.97,\n        }\n        curve = Curve(nodes=nodes, interpolation=\"log_linear\", convention=\"act360\")\n        period = FloatPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 7, 1),\n            payment=dt(2022, 7, 1),\n            frequency=Frequency.Months(6, None),\n            fixing_method=\"rfr_payment_delay\",\n            float_spread=0,\n            convention=\"act365F\",\n            spread_compound_method=scm,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\", convention=\"act360\", eom=True, lag=0, modifier=\"F\"\n            ),\n        )\n        table = period.local_analytic_rate_fixings(rate_curve=curve)\n        period.rate_params.float_spread = 200\n        table2 = period.local_analytic_rate_fixings(rate_curve=curve)\n        assert (table.iloc[0, 0] == table2.iloc[0, 0]) == exp\n\n    def test_custom_interp_rate_nan(self) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            f\"{name}_1B\", Series(index=[dt(2022, 12, 28), dt(2022, 12, 29)], data=[1.19, 1.19])\n        )\n        float_period = FloatPeriod(\n            start=dt(2022, 12, 28),\n            end=dt(2023, 1, 2),\n            payment=dt(2023, 1, 2),\n            frequency=Frequency.Months(1, None),\n            rate_fixings=name,\n        )\n\n        def interp(date, nodes):\n            if date < dt(2023, 1, 1):\n                return None\n            return 2.0\n\n        line_curve = LineCurve({dt(2023, 1, 1): 3.0, dt(2023, 2, 1): 2.0}, interpolation=interp)\n        curve = Curve({dt(2023, 1, 1): 1.0, dt(2023, 2, 1): 0.999})\n        with pytest.raises(ValueError, match=\"The Curve initial node date is after the \"):\n            float_period.local_analytic_rate_fixings(rate_curve=line_curve, disc_curve=curve)\n\n    def test_method_param_raises(self) -> None:\n        with pytest.raises(ValueError, match='`method_param` must be >0 for \"RFRLockout'):\n            FloatPeriod(\n                start=dt(2022, 1, 4),\n                end=dt(2022, 4, 4),\n                payment=dt(2022, 4, 4),\n                frequency=Frequency.Months(3, None),\n                fixing_method=\"rfr_lockout(0)\",\n                rate_fixings=[1.00],\n            )\n\n        # test obsolete with FloatFixingMethod enum\n        # with pytest.raises(ValueError, match=\"`method_param` should not be used\"):\n        #     FloatPeriod(\n        #         start=dt(2022, 1, 4),\n        #         end=dt(2022, 4, 4),\n        #         payment=dt(2022, 4, 4),\n        #         frequency=Frequency.Months(3, None),\n        #         fixing_method=\"rfr_payment_delay\",\n        #         rate_fixings=[1.00],\n        #     )\n\n    def test_analytic_delta_no_curve_raises(self) -> None:\n        name = str(hash(os.urandom(9)))\n        fixings.add(f\"{name}_1B\", Series(index=[dt(2022, 12, 28)], data=1.19))\n        float_period = FloatPeriod(\n            start=dt(2022, 12, 28),\n            end=dt(2023, 1, 2),\n            payment=dt(2023, 1, 2),\n            frequency=Frequency.Months(1, None),\n            rate_fixings=name,\n            spread_compound_method=\"isda_compounding\",\n            float_spread=1.0,\n        )\n        with pytest.raises(ValueError, match=\"`disc_curve` is required but it has not been pr\"):\n            float_period.analytic_delta()\n\n    def test_more_series_fixings_than_calendar_from_curve_raises(self) -> None:\n        fixings = Series(\n            [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0],\n            index=[\n                dt(2022, 1, 4),\n                dt(2022, 1, 5),\n                dt(2022, 1, 6),\n                dt(2022, 1, 7),\n                dt(2022, 1, 8),\n                dt(2022, 1, 9),\n                dt(2022, 1, 10),\n            ],\n        )\n        with pytest.warns(UserWarning, match=err.W02_0[:20]):\n            FloatPeriod(\n                start=dt(2022, 1, 4),\n                end=dt(2022, 1, 11),\n                frequency=Frequency.Months(3, None),\n                fixing_method=\"rfr_payment_delay\",\n                payment=dt(2022, 1, 9),\n                float_spread=10.0,\n                rate_fixings=fixings,\n                fixing_series=FloatRateSeries(\n                    calendar=\"bus\",\n                    convention=\"act360\",\n                    lag=0,\n                    eom=True,\n                    modifier=\"F\",\n                ),\n            )\n\n    def test_series_fixings_not_applicable_to_period(self) -> None:\n        # if a series is historic and of no relevance all fixings are forecast from crv\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, calendar=\"bus\")\n        fixings = Series([1.0, 2.0, 3.0], index=[dt(2021, 1, 4), dt(2021, 1, 5), dt(2021, 1, 6)])\n        period = FloatPeriod(\n            start=dt(2022, 1, 4),\n            end=dt(2022, 1, 11),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"rfr_payment_delay\",\n            payment=dt(2022, 1, 9),\n            float_spread=10.0,\n            rate_fixings=fixings,\n        )\n        result = period.rate(curve)\n        expected = 1.09136153  # series fixings are completely ignored\n        assert abs(result - expected) < 1e-5\n\n    @pytest.mark.parametrize(\n        (\"meth\", \"exp\"),\n        [\n            (\"rfr_payment_delay\", 3.1183733605),\n            (\"rfr_observation_shift(2)\", 3.085000395),\n            (\"rfr_lookback(2)\", 3.05163645),\n            (\"rfr_lockout(7)\", 3.00157855),\n        ],\n    )\n    def test_norges_bank_nowa_calc_same(self, meth, exp) -> None:\n        # https://app.norges-bank.no/nowa/#/en/\n        curve = Curve({dt(2023, 8, 4): 1.0}, calendar=\"osl\", convention=\"act365f\")\n        fixings.add(\"nowa_1B\", fixings[\"nowa\"][1])\n        period = FloatPeriod(\n            start=dt(2023, 4, 27),\n            end=dt(2023, 5, 12),\n            payment=dt(2023, 5, 16),\n            frequency=Frequency.Months(12, None),\n            fixing_method=meth,\n            float_spread=0.0,\n            rate_fixings=\"nowa\",\n            fixing_series=FloatRateSeries(\n                calendar=\"osl\",\n                convention=\"act365f\",\n                lag=0,\n                modifier=\"F\",\n                eom=True,\n            ),\n        )\n        result = period.rate(curve)\n        assert abs(result - exp) < 1e-7\n        fixings.pop(\"nowa_1B\")\n\n    def test_interpolated_ibor_warns(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 4, 27),\n            end=dt(2023, 6, 12),\n            payment=dt(2023, 6, 16),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"ibor(1)\",\n            float_spread=0.0,\n            stub=True,\n        )\n        curve1 = LineCurve({dt(2022, 1, 1): 1.0, dt(2024, 2, 1): 1.0})\n        with pytest.warns(UserWarning):\n            period.rate({\"1m\": curve1})\n        with pytest.warns(UserWarning):\n            period.rate({\"3m\": curve1})\n\n    def test_interpolated_ibor_rate_line(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"ibor(1)\",\n            float_spread=0.0,\n            stub=True,\n        )\n        curve3 = LineCurve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0})\n        curve1 = LineCurve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        result = period.rate({\"1M\": curve1, \"3m\": curve3})\n        expected = 1.0 + (3.0 - 1.0) * (dt(2023, 4, 1) - dt(2023, 3, 1)) / (\n            dt(2023, 5, 1) - dt(2023, 3, 1)\n        )\n        assert abs(result - expected) < 1e-8\n\n    def test_interpolated_ibor_rate_df(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"ibor(1)\",\n            float_spread=0.0,\n            stub=True,\n        )\n        curve3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 0.97})\n        curve1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 0.99})\n        result = period.rate({\"1M\": curve1, \"3m\": curve3})\n        a, b = 0.91399161, 2.778518365\n        expected = a + (b - a) * (dt(2023, 4, 1) - dt(2023, 3, 1)) / (\n            dt(2023, 5, 1) - dt(2023, 3, 1)\n        )\n        assert abs(result - expected) < 1e-8\n\n    def test_rfr_period_curve_dict_raises(self, curve) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"rfr_payment_delay\",\n            float_spread=0.0,\n            stub=True,\n        )\n        with pytest.raises(ValueError, match=\"A `rate_curve` supplied as dict to an RFR ba\"):\n            period.rate({\"bad_index\": curve})\n\n    def test_rfr_period_curve_dict_allowed(self, curve) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"rfr_payment_delay\",\n            float_spread=0.0,\n            stub=True,\n        )\n        expected = 4.02664128485892\n        result = period.rate({\"rfr\": curve})\n        assert result == expected\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping for fixings exposure not implemented.\")\n    def test_ibor_stub_book2(self):\n        curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.94},\n            calendar=\"tgt\",\n            convention=\"act360\",\n            id=\"euribor3m\",\n        )\n        curve2 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.94},\n            calendar=\"tgt\",\n            convention=\"act360\",\n            id=\"euribor1m\",\n        )\n        stub_fp = FloatPeriod(\n            start=dt(2022, 3, 14),\n            end=dt(2022, 5, 14),\n            payment=dt(2022, 5, 14),\n            frequency=\"Q\",\n            calendar=\"tgt\",\n            convention=\"act360\",\n            fixing_method=\"ibor\",\n            method_param=2,\n            notional=-1e6,\n            stub=True,\n        )\n        result = stub_fp.try_unindexed_reference_fixings_exposure(\n            rate_curve={\"1m\": curve2, \"3m\": curve}, disc_curve=curve\n        ).unwrap()\n        assert abs(result.iloc[0, 0] - 998307) < 1\n        assert abs(result.iloc[0, 4] - 326658) < 1\n        assert abs(result.iloc[0, 1] - 8.5467) < 1e-4\n        assert abs(result.iloc[0, 5] - 8.2710) < 1e-4\n\n    def test_ibor_stub_book2_substitute(self):\n        curve = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.94},\n            calendar=\"tgt\",\n            convention=\"act360\",\n            id=\"euribor3m\",\n        )\n        curve2 = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2025, 1, 1): 0.94},\n            calendar=\"tgt\",\n            convention=\"act360\",\n            id=\"euribor1m\",\n        )\n        stub_fp = FloatPeriod(\n            start=dt(2022, 3, 14),\n            end=dt(2022, 5, 14),\n            payment=dt(2022, 5, 14),\n            frequency=\"Q\",\n            calendar=\"tgt\",\n            convention=\"act360\",\n            fixing_method=\"ibor(2)\",\n            notional=-1e6,\n            stub=True,\n        )\n        result = stub_fp.local_analytic_rate_fixings(\n            rate_curve={\"1m\": curve2, \"3m\": curve}, disc_curve=curve\n        )\n        assert abs(result.iloc[0, 0] - 8.5467) < 1e-4\n        assert abs(result.iloc[0, 1] - 8.2710) < 1e-4\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping for fixings exposure not implemented.\")\n    def test_ibor_stub_fixings_table(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"ibor(1)\",\n            float_spread=0.0,\n            stub=True,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\", convention=\"act360\", lag=1, eom=False, modifier=\"mf\"\n            ),\n        )\n        curve3 = LineCurve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0})\n        curve1 = LineCurve({dt(2022, 1, 1): 2.0, dt(2023, 2, 1): 2.0})\n        dc = Curve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        result = period.try_unindexed_reference_fixings_exposure(\n            rate_curve={\"1M\": curve1, \"3m\": curve3}, disc_curve=dc\n        ).unwrap()\n        assert isinstance(result, DataFrame)\n        assert abs(result.iloc[0, 0] + 1036300) < 1\n        assert abs(result.iloc[0, 4] + 336894) < 1\n        assert abs(result.iloc[0, 1] + 8.0601) < 1e-4\n        assert abs(result.iloc[0, 5] + 8.32877) < 1e-4\n\n    def test_ibor_stub_fixings_table_substitute(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"ibor(1)\",\n            float_spread=0.0,\n            stub=True,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\", convention=\"act360\", lag=1, eom=False, modifier=\"mf\"\n            ),\n        )\n        curve3 = LineCurve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0})\n        curve1 = LineCurve({dt(2022, 1, 1): 2.0, dt(2023, 2, 1): 2.0})\n        dc = Curve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        result = period.local_analytic_rate_fixings(\n            rate_curve={\"1M\": curve1, \"3m\": curve3}, disc_curve=dc\n        )\n        assert isinstance(result, DataFrame)\n        assert abs(result.iloc[0, 0] + 8.0601) < 1e-4\n        assert abs(result.iloc[0, 1] + 8.32877) < 1e-4\n\n    @pytest.mark.skip(reason=\"NOTIONAL mapping for fixings exposure not implemented.\")\n    def test_ibor_stub_fixings_rfr_in_dict_ignored(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"ibor\",\n            method_param=1,\n            float_spread=0.0,\n            stub=True,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\", convention=\"act360\", lag=1, eom=False, modifier=\"mf\"\n            ),\n        )\n        curve3 = LineCurve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0})\n        curve1 = LineCurve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        dc = Curve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        result = period.try_unindexed_reference_fixings_exposure(\n            rate_curve={\"1M\": curve1, \"3m\": curve3, \"rfr\": curve1}, disc_curve=dc\n        ).unwrap()\n        assert isinstance(result, DataFrame)\n        assert abs(result.iloc[0, 0] + 1036300) < 1\n        assert abs(result.iloc[0, 4] + 336894) < 1\n        assert abs(result.iloc[0, 1] + 8.0601) < 1e-4\n        assert abs(result.iloc[0, 5] + 8.32877) < 1e-4\n\n    def test_ibor_stub_fixings_rfr_in_dict_ignored_substitute(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"ibor(1)\",\n            float_spread=0.0,\n            stub=True,\n            fixing_series=FloatRateSeries(\n                calendar=\"all\", convention=\"act360\", lag=1, eom=False, modifier=\"mf\"\n            ),\n        )\n        curve3 = LineCurve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0})\n        curve1 = LineCurve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        dc = Curve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        result = period.local_analytic_rate_fixings(\n            rate_curve={\"1M\": curve1, \"3m\": curve3, \"rfr\": curve1}, disc_curve=dc\n        )\n        assert isinstance(result, DataFrame)\n        assert abs(result.iloc[0, 0] + 8.0601) < 1e-4\n        assert abs(result.iloc[0, 1] + 8.32877) < 1e-4\n\n    @pytest.mark.skip(reason=\"`right` removed by v2.5\")\n    def test_ibor_stub_fixings_table_right(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 4, 1),\n            payment=dt(2023, 4, 1),\n            frequency=Frequency.Months(12, None),\n            fixing_method=\"ibor\",\n            method_param=1,\n            float_spread=0.0,\n            stub=True,\n        )\n        curve3 = LineCurve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0})\n        curve1 = LineCurve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        result = period.try_unindexed_reference_fixings_exposure(\n            rate_curve={\"1M\": curve1, \"3m\": curve3}, disc_curve=curve1, right=dt(2022, 1, 1)\n        ).unwrap()\n        assert isinstance(result, DataFrame)\n        assert len(result.index) == 0\n\n    def test_ibor_non_stub_fixings_table(self) -> None:\n        period = FloatPeriod(\n            start=dt(2023, 2, 1),\n            end=dt(2023, 5, 1),\n            payment=dt(2023, 5, 1),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(1)\",\n            float_spread=0.0,\n        )\n        curve3 = LineCurve({dt(2022, 1, 1): 3.0, dt(2023, 2, 1): 3.0})\n        curve1 = LineCurve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        curved = Curve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 1.0})\n        result = period.local_analytic_rate_fixings(\n            rate_curve={\"1M\": curve1, \"3M\": curve3}, disc_curve=curved\n        )\n        expected = DataFrame(\n            data=[[-24.722222222222]],\n            index=Index([dt(2023, 1, 31)], name=\"obs_dates\"),\n            columns=MultiIndex.from_tuples(\n                [(curve3.id, \"usd\", \"usd\", \"3M\")],\n                names=[\"identifier\", \"local_ccy\", \"display_ccy\", \"frequency\"],\n            ),\n        )\n        assert_frame_equal(result, expected)\n\n    def test_ibor_fixings_no_bad_curves_raises(self):\n        curve1 = LineCurve({dt(2022, 1, 1): 2.0, dt(2023, 2, 1): 2.0})\n        disc_curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 2, 1): 0.96})\n        float_period = FloatPeriod(\n            start=dt(2023, 3, 6),\n            end=dt(2023, 6, 6),\n            payment=dt(2023, 6, 6),\n            frequency=Frequency.Months(3, None),\n            fixing_method=\"ibor(2)\",\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\",\n                convention=\"act360\",\n                lag=2,\n                modifier=\"mf\",\n                eom=False,\n            ),\n        )\n        with pytest.raises(ValueError, match=\"A `rate_curve` must be provided to this method\"):\n            float_period.local_analytic_rate_fixings(rate_curve=NoInput(0), disc_curve=disc_curve)\n\n        with pytest.raises(ValueError, match=\"`disc_curve` cannot be inferred from a non-DF base\"):\n            float_period.local_analytic_rate_fixings(rate_curve=curve1, disc_curve=NoInput(0))\n\n    def test_local_historical_pay_date_issue(self, curve) -> None:\n        period = FloatPeriod(\n            start=dt(2021, 1, 1),\n            end=dt(2021, 4, 1),\n            payment=dt(2021, 4, 1),\n            frequency=Frequency.Months(3, None),\n        )\n        result = period.npv(rate_curve=curve, local=True)\n        assert result == {\"usd\": 0.0}\n\n    @pytest.mark.parametrize(\n        \"curve\", [NoInput(0), LineCurve({dt(2000, 1, 1): 2.0, dt(2001, 1, 1): 2.0})]\n    )\n    @pytest.mark.parametrize(\"fixing_method\", [\"ibor(2)\", \"rfr_payment_delay_avg\"])\n    @pytest.mark.parametrize(\"fixings\", [3.0, NoInput(0)])\n    def test_rate_optional_curve(self, fixings, fixing_method, curve) -> None:\n        # GH530. Allow forecasting rates without necessarily providing curve if unnecessary\n        period = FloatPeriod(\n            start=dt(2000, 1, 12),\n            end=dt(2000, 4, 12),\n            fixing_method=fixing_method,\n            frequency=Frequency.Months(3, None),\n            rate_fixings=fixings,\n            payment=dt(2000, 4, 12),\n        )\n        if (\n            isinstance(curve, NoInput)\n            and isinstance(fixings, NoInput)\n            and fixing_method != \"ibor(2)\"\n        ):\n            # then no data to price\n            msg = \"A `rate_curve` is required to forecast missing RFR\"\n            with pytest.raises(FixingMissingForecasterError, match=msg):\n                period.rate(curve)\n        elif (\n            isinstance(curve, NoInput)\n            and isinstance(fixings, NoInput)\n            and fixing_method == \"ibor(2)\"\n        ):\n            msg = \"A `rate_curve` is required to forecast missing IBOR\"\n            with pytest.raises(ValueError, match=msg):\n                period.rate(curve)\n        elif isinstance(fixings, NoInput):\n            result = period.rate(curve)\n            assert abs(result - 2.0) < 1e-8  # uses curve\n        else:\n            result = period.rate(curve)\n            assert abs(result - 3.0) < 1e-8  # uses fixing\n\n    @pytest.mark.parametrize(\n        \"rate_fixings\",\n        [\n            Series(\n                index=[dt(2000, 1, 1), dt(2000, 1, 2), dt(2000, 1, 3)], data=[2.0, 2.0, 2.0]\n            ),  # some unknown\n            Series(\n                index=Cal.from_name(\"all\").bus_date_range(dt(2000, 1, 1), dt(2000, 1, 31)), data=2.0\n            ),  # exhaustive\n            Series(2.0, index=date_range(dt(2000, 1, 1), dt(2001, 1, 1))),\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"curve\", [NoInput(0), LineCurve({dt(2000, 1, 1): 2.0, dt(2001, 1, 1): 2.0})]\n    )\n    def test_rate_optional_curve_rfr(self, curve, rate_fixings) -> None:\n        # GH530. Test RFR periods what happens when supply/not supply a Curve and fixings\n        # are either exhaustive/ not exhaustive\n        name = str(hash(os.urandom(8)))\n        fixings.add(f\"{name}_1B\", rate_fixings)\n        period = FloatPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 2, 1),\n            fixing_method=\"rfr_payment_delay_avg\",\n            frequency=Frequency.Months(1, None),\n            calendar=\"all\",\n            rate_fixings=name,\n            payment=dt(2000, 2, 1),\n        )\n\n        # When a curve is not supplied for RFR period currently it will still fail\n        # even if exhaustive fixings are available. There is currently no branching handling this.\n        if isinstance(curve, NoInput) and len(rate_fixings) == 3:\n            with pytest.raises(\n                FixingMissingForecasterError, match=\"A `rate_curve` is required to forecast mi\"\n            ):\n                period.rate(curve)\n        else:\n            # it will conclude without fail, the exhaustive case is captured.\n            period.rate(curve)\n\n        fixings.pop(f\"{name}_1B\")\n\n    def test_rfr_lockout_calculation_is_accurate(self):\n        # this is an additional test to ensure the validity of the lockout rate\n        # it combines multiple features such as weekends and changing rates.\n        # it ensures that the DCF is handled correctly for the locked out days\n        name = str(hash(os.urandom(8)))\n        fixings.add(\n            f\"{name}_1B\",\n            Series(\n                index=[\n                    dt(2024, 6, 7),  # 1\n                    dt(2024, 6, 10),\n                    dt(2024, 6, 11),  # 3\n                    dt(2024, 6, 12),\n                    dt(2024, 6, 13),  # 5\n                    dt(2024, 6, 14),  # 5\n                    dt(2024, 6, 17),\n                    dt(2024, 6, 18),\n                    dt(2024, 6, 19),\n                ],\n                data=[1.0, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0, 1.0],\n            ),\n        )\n        p = FloatPeriod(\n            start=dt(2024, 6, 7),\n            end=dt(2024, 6, 20),\n            payment=dt(2024, 6, 21),\n            frequency=\"A\",\n            fixing_method=FloatFixingMethod.RFRLockout(4),\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\", convention=\"act360\", lag=0, eom=False, modifier=\"F\"\n            ),\n            spread_compound_method=\"NoneSimple\",\n            float_spread=50.0,\n            rate_fixings=name,\n        )\n        result = p.rate(rate_curve=NoInput(0))\n        fixings.pop(f\"{name}_1B\")\n        d = 1.0 / 36000.0\n        expected = (\n            (1 + 1 * 3 * d)\n            * (1 + 2 * d)\n            * (1 + 3 * d)\n            * (1 + 4 * d)\n            * (1 + 3 * d * 5)\n            * (1 + d * 5) ** 4\n        )\n        expected = (expected - 1) * 1 / (13 * d) + 0.50\n\n        not_expected = (1 + 1 * 3 * d) * (1 + 2 * d) * (1 + 3 * d) * (1 + 4 * d) * (1 + 7 * d * 5)\n        not_expected = (not_expected - 1) * 1 / (13 * d) + 0.50\n\n        assert abs(result - not_expected) > 1e-14\n        assert abs(result - expected) < 1e-14\n\n    def test_analytic_delta_raises(self, curve):\n        p = FloatPeriod(\n            start=dt(2024, 6, 7),\n            end=dt(2024, 6, 20),\n            payment=dt(2024, 6, 21),\n            frequency=\"A\",\n            fixing_method=FloatFixingMethod.RFRLockout(4),\n            fixing_series=FloatRateSeries(\n                calendar=\"bus\", convention=\"act360\", lag=0, eom=False, modifier=\"F\"\n            ),\n            spread_compound_method=\"ISDACompounding\",\n            float_spread=50.0,\n        )\n        assert p.try_unindexed_reference_cashflow_analytic_delta(\n            rate_curve=NoInput(0), disc_curve=curve\n        ).is_err\n\n    def test_ibor_param_mismatch(self):\n        with pytest.raises(\n            ValueError, match=\"A `fixing_series` has been provided with a publication `lag` that\"\n        ):\n            FloatPeriod(\n                start=dt(2000, 1, 1),\n                end=dt(2000, 4, 1),\n                payment=dt(2000, 4, 1),\n                fixing_method=\"ibor(1)\",\n                fixing_series=\"eur_ibor\",\n                frequency=\"Q\",\n            )\n\n\nclass TestFixedPeriod:\n    def test_frequency_as_str(self):\n        p = FixedPeriod(\n            start=dt(2000, 1, 1),\n            end=dt(2000, 4, 1),\n            payment=dt(2000, 4, 1),\n            frequency=\"Q\",\n            roll=1,\n        )\n        assert p.period_params.frequency == Frequency.Months(3, RollDay.Day(1))\n\n    def test_fixed_period_analytic_delta(self, curve, fxr) -> None:\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        result = fixed_period.analytic_delta(rate_curve=curve)\n        assert abs(result - 24744.478172244584) < 1e-7\n\n        result = fixed_period.analytic_delta(rate_curve=curve, fx=fxr, base=\"nok\")\n        assert abs(result - 247444.78172244584) < 1e-7\n\n    def test_fixed_period_analytic_delta_raises(self, curve, fxr) -> None:\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        assert fixed_period.try_immediate_local_analytic_delta(rate_curve=dict()).is_err\n\n    def test_fixed_period_analytic_delta_fxr_base(self, curve, fxr) -> None:\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        fxr = FXRates({\"usdnok\": 10.0}, base=\"NOK\")\n        result = fixed_period.analytic_delta(rate_curve=curve, fx=fxr, base=\"NOK\")\n        assert abs(result - 247444.78172244584) < 1e-7\n\n    @pytest.mark.parametrize(\n        (\"rate\", \"crv\", \"fx\"),\n        [\n            (4.00, True, 2.0),\n            (NoInput(0), False, 2.0),\n            (4.00, True, 10.0),\n            (NoInput(0), False, 10.0),\n        ],\n    )\n    def test_fixed_period_cashflows(self, curve, fxr, rate, crv, fx) -> None:\n        # also test the inputs to fx as float and as FXRates (10 is for\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=rate,\n        )\n\n        cashflow = (\n            None if rate is NoInput.blank else rate * -1e9 * fixed_period.period_params.dcf / 100\n        )\n        expected = {\n            defaults.headers[\"base\"]: \"UNSPECIFIED\",\n            defaults.headers[\"type\"]: \"FixedPeriod\",\n            defaults.headers[\"stub_type\"]: \"Regular\",\n            defaults.headers[\"a_acc_start\"]: dt(2022, 1, 1),\n            defaults.headers[\"a_acc_end\"]: dt(2022, 4, 1),\n            defaults.headers[\"payment\"]: dt(2022, 4, 3),\n            defaults.headers[\"notional\"]: 1e9,\n            defaults.headers[\"currency\"]: \"USD\",\n            defaults.headers[\"convention\"]: \"Act360\",\n            defaults.headers[\"dcf\"]: fixed_period.period_params.dcf,\n            defaults.headers[\"df\"]: 0.9897791268897856 if crv else None,\n            defaults.headers[\"rate\"]: _drb(None, rate),\n            defaults.headers[\"spread\"]: None,\n            defaults.headers[\"npv\"]: -9897791.268897856 if crv else None,\n            defaults.headers[\"cashflow\"]: cashflow,\n            defaults.headers[\"fx\"]: fx,\n            defaults.headers[\"npv_fx\"]: -9897791.268897855 * fx if crv else None,\n            defaults.headers[\"collateral\"]: None,\n        }\n        if fx == 2.0:\n            with pytest.warns(UserWarning):\n                # supplying `fx` as numeric\n                result = fixed_period.cashflows(\n                    rate_curve=curve if crv else NoInput(0),\n                    fx=2.0,\n                    base=NoInput(0),\n                )\n        else:\n            result = fixed_period.cashflows(\n                rate_curve=curve if crv else NoInput(0), fx=fxr, base=\"nok\"\n            )\n            expected[defaults.headers[\"base\"]] = \"NOK\"\n        assert result == expected\n\n    def test_fixed_period_npv(self, curve, fxr) -> None:\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n        )\n        result = fixed_period.npv(rate_curve=curve)\n        assert abs(result + 9897791.268897833) < 1e-7\n\n        result = fixed_period.npv(rate_curve=curve, disc_curve=curve, fx=fxr, base=\"nok\")\n        assert abs(result + 98977912.68897833) < 1e-6\n\n    def test_fixed_period_npv_raises(self, curve) -> None:\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n        )\n        with pytest.raises(\n            TypeError,\n            match=re.escape(\"`curves` have not been supplied correctly\"),\n        ):\n            fixed_period.npv()\n\n    def test_npv_no_fixed_rate(self, curve):\n        period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        with pytest.raises(ValueError, match=\"A `fixed_rate` must be set for a cashflow to be de\"):\n            period.npv(rate_curve=curve)\n\n\nclass TestCreditPremiumPeriod:\n    @pytest.mark.parametrize(\n        (\"accrued\", \"exp\"), [(True, -9892843.47762896), (False, -9887893.477628957)]\n    )\n    def test_period_npv(self, hazard_curve, curve, fxr, accrued, exp) -> None:\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.0,\n            currency=\"usd\",\n            premium_accrued=accrued,\n        )\n        result = premium_period.npv(rate_curve=hazard_curve, disc_curve=curve)\n        assert abs(result - exp) < 1e-7\n\n        result = premium_period.npv(rate_curve=hazard_curve, disc_curve=curve, fx=fxr, base=\"nok\")\n        assert abs(result - exp * 10.0) < 1e-6\n\n    def test_period_npv_raises(self, curve, hazard_curve) -> None:\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n        )\n        with pytest.raises(\n            TypeError,\n            match=re.escape(\"`curves` have not been supplied correctly.\"),\n        ):\n            premium_period.npv(rate_curve=hazard_curve)\n        with pytest.raises(\n            TypeError,\n            match=re.escape(\"`curves` have not been supplied correctly.\"),\n        ):\n            premium_period.npv(rate_curve=NoInput(0), disc_curve=curve)\n\n    def test_period_npv_no_spread_raises(self, curve, hazard_curve) -> None:\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        with pytest.raises(\n            ValueError,\n            match=re.escape(\"A `fixed_rate` must be set for a cashfl\"),\n        ):\n            premium_period.npv(rate_curve=hazard_curve, disc_curve=curve)\n\n    @pytest.mark.parametrize(\n        (\"accrued\", \"exp\"), [(True, 24732.108694072398), (False, 24719.733694072398)]\n    )\n    def test_period_analytic_delta(self, hazard_curve, curve, fxr, accrued, exp) -> None:\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            premium_accrued=accrued,\n        )\n        result = premium_period.analytic_delta(rate_curve=hazard_curve, disc_curve=curve)\n        assert abs(result - exp) < 1e-7\n\n        result = premium_period.analytic_delta(\n            rate_curve=hazard_curve, disc_curve=curve, fx=fxr, base=\"nok\"\n        )\n        assert abs(result - exp * 10.0) < 1e-7\n\n    def test_period_analytic_delta_fxr_base(self, hazard_curve, curve, fxr) -> None:\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n        )\n        fxr = FXRates({\"usdnok\": 10.0}, base=\"NOK\")\n        result = premium_period.analytic_delta(\n            rate_curve=hazard_curve,\n            disc_curve=curve,\n            fx=fxr,\n            base=\"nok\",\n        )\n        assert abs(result - 247321.086941) < 1e-6\n\n    def test_period_cashflows(self, hazard_curve, curve, fxr) -> None:\n        # also test the inputs to fx as float and as FXRates (10 is for\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n        )\n\n        cashflow = 400 * -1e9 * premium_period.period_params.dcf / 10000\n        expected = {\n            defaults.headers[\"type\"]: \"CreditPremiumPeriod\",\n            defaults.headers[\"base\"]: \"NOK\",\n            defaults.headers[\"stub_type\"]: \"Regular\",\n            defaults.headers[\"a_acc_start\"]: dt(2022, 1, 1),\n            defaults.headers[\"a_acc_end\"]: dt(2022, 4, 1),\n            defaults.headers[\"payment\"]: dt(2022, 4, 3),\n            defaults.headers[\"notional\"]: 1e9,\n            defaults.headers[\"currency\"]: \"USD\",\n            defaults.headers[\"convention\"]: \"Act360\",\n            defaults.headers[\"dcf\"]: premium_period.period_params.dcf,\n            defaults.headers[\"df\"]: 0.9897791268897856,\n            defaults.headers[\"rate\"]: 4.0,\n            defaults.headers[\"survival\"]: 0.999,\n            defaults.headers[\"recovery\"]: 0.40,\n            defaults.headers[\"spread\"]: None,\n            defaults.headers[\"npv\"]: -9892843.47762896,\n            defaults.headers[\"cashflow\"]: cashflow,\n            defaults.headers[\"fx\"]: 10.0,\n            defaults.headers[\"npv_fx\"]: -9892843.47762896 * 10.0,\n            defaults.headers[\"collateral\"]: None,\n        }\n        result = premium_period.cashflows(\n            rate_curve=hazard_curve, disc_curve=curve, fx=fxr, base=\"nok\"\n        )\n        assert result == expected\n\n    def test_period_cashflows_no_curves(self, fxr) -> None:\n        # also test the inputs to fx as float and as FXRates (10 is for\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n        )\n\n        cashflow = 400 * -1e9 * premium_period.period_params.dcf / 10000\n        expected = {\n            defaults.headers[\"type\"]: \"CreditPremiumPeriod\",\n            defaults.headers[\"base\"]: \"NOK\",\n            defaults.headers[\"stub_type\"]: \"Regular\",\n            defaults.headers[\"a_acc_start\"]: dt(2022, 1, 1),\n            defaults.headers[\"a_acc_end\"]: dt(2022, 4, 1),\n            defaults.headers[\"payment\"]: dt(2022, 4, 3),\n            defaults.headers[\"notional\"]: 1e9,\n            defaults.headers[\"currency\"]: \"USD\",\n            defaults.headers[\"convention\"]: \"Act360\",\n            defaults.headers[\"dcf\"]: premium_period.period_params.dcf,\n            defaults.headers[\"df\"]: None,\n            defaults.headers[\"rate\"]: 4.0,\n            defaults.headers[\"survival\"]: None,\n            defaults.headers[\"recovery\"]: None,\n            defaults.headers[\"spread\"]: None,\n            defaults.headers[\"npv\"]: None,\n            defaults.headers[\"cashflow\"]: cashflow,\n            defaults.headers[\"fx\"]: 10.0,\n            defaults.headers[\"npv_fx\"]: None,\n            defaults.headers[\"collateral\"]: None,\n        }\n        result = premium_period.cashflows(fx=fxr, base=\"nok\")\n        assert result == expected\n\n    def test_mid_period_accrued(self, hazard_curve, curve):\n        p1 = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"ActActICMA\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            adjuster=\"F\",\n        )\n        p2 = CreditPremiumPeriod(\n            start=dt(2021, 10, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"ActActICMA\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(6, None),\n            fixed_rate=2.00,\n            currency=\"usd\",\n            adjuster=\"F\",\n        )\n        r1 = p1.npv(rate_curve=hazard_curve, disc_curve=curve)\n        r2 = p2.npv(rate_curve=hazard_curve, disc_curve=curve)\n\n        assert 2505 > r1 - r2 > 2500\n\n    def test_null_cashflow(self):\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        result = premium_period.try_cashflow()\n        assert result.is_err\n\n    def test_no_accrued(self):\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        assert premium_period.try_accrued(dt(2022, 2, 1)).is_err\n\n    def test_accrued_out_of_range(self):\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n            fixed_rate=2.0,\n        )\n        assert premium_period.accrued(dt(2022, 9, 1)) == 0.0\n        assert premium_period.accrued(dt(2021, 9, 1)) == 0.0\n\n    def test_accrued(self):\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"ActActICMA\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n            fixed_rate=2.0,\n            adjuster=\"F\",\n        )\n        assert abs(premium_period.accrued(dt(2022, 2, 1)) - (-1e9 * 0.25 * 31 / 90 * 0.02)) < 1e-9\n\n    def test_analytic_delta_bad_curve(self):\n        premium_period = CreditPremiumPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"ActActICMA\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n            fixed_rate=2.0,\n            adjuster=\"F\",\n        )\n        assert premium_period.try_local_analytic_delta(rate_curve=dict()).is_err\n\n\nclass TestCreditProtectionPeriod:\n    def test_period_npv(self, hazard_curve, curve, fxr) -> None:\n        period = CreditProtectionPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            # convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        period.discretization = 1\n        result = period.npv(\n            rate_curve=hazard_curve,\n            disc_curve=curve,\n        )  # discounted properly this is -596962.1422873045\n        assert abs(result - -596962.1422873045) < 34\n\n        period.discretization = 23\n        result = period.npv(rate_curve=hazard_curve, disc_curve=curve)\n        exp = -596995.7591843301\n        assert abs(result - exp) < 1e-7\n\n        result = period.npv(rate_curve=hazard_curve, disc_curve=curve, fx=fxr, base=\"nok\")\n        assert abs(result - exp * 10.0) < 1e-6\n\n    def test_period_npv_raises(self, curve, hazard_curve) -> None:\n        period = CreditProtectionPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            # convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        with pytest.raises(\n            TypeError,\n            match=re.escape(\"`curves` have not been supplied correctly.\"),\n        ):\n            period.npv(rate_curve=hazard_curve)\n        with pytest.raises(\n            TypeError,\n            match=re.escape(\"`curves` have not been supplied correctly.\"),\n        ):\n            period.npv(rate_curve=NoInput(0), disc_curve=curve)\n\n    def test_period_analytic_delta(self, hazard_curve, curve, fxr) -> None:\n        period = CreditProtectionPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            # convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        result = period.analytic_delta(rate_curve=hazard_curve, disc_curve=curve)\n        assert abs(result - 0.0) < 1e-7\n\n        result = period.analytic_delta(\n            rate_curve=hazard_curve, disc_curve=curve, fx=fxr, base=\"nok\"\n        )\n        assert abs(result - 0.0 * 10.0) < 1e-7\n\n    def test_period_analytic_delta_fxr_base(self, hazard_curve, curve, fxr) -> None:\n        period = CreditProtectionPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            # convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        fxr = FXRates({\"usdnok\": 10.0}, base=\"NOK\")\n        result = period.analytic_delta(rate_curve=hazard_curve, disc_curve=curve, fx=fxr)\n        assert abs(result - 0.0) < 1e-7\n\n    def test_period_cashflows(self, hazard_curve, curve, fxr) -> None:\n        # also test the inputs to fx as float and as FXRates (10 is for\n        period = CreditProtectionPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            # convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n\n        cashflow = -period.settlement_params.notional * (1 - hazard_curve.meta.credit_recovery_rate)\n        expected = {\n            defaults.headers[\"type\"]: \"CreditProtectionPeriod\",\n            defaults.headers[\"stub_type\"]: \"Regular\",\n            defaults.headers[\"a_acc_start\"]: dt(2022, 1, 1),\n            defaults.headers[\"a_acc_end\"]: dt(2022, 4, 1),\n            defaults.headers[\"payment\"]: dt(2022, 4, 3),\n            defaults.headers[\"notional\"]: 1e9,\n            defaults.headers[\"currency\"]: \"USD\",\n            defaults.headers[\"convention\"]: \"One\",\n            defaults.headers[\"dcf\"]: period.period_params.dcf,\n            defaults.headers[\"df\"]: 0.9897791268897856,\n            defaults.headers[\"recovery\"]: 0.4,\n            defaults.headers[\"survival\"]: 0.999,\n            defaults.headers[\"npv\"]: -596995.7591843301,\n            defaults.headers[\"cashflow\"]: cashflow,\n            defaults.headers[\"fx\"]: 10.0,\n            defaults.headers[\"npv_fx\"]: -596995.7591843301 * 10.0,\n            defaults.headers[\"collateral\"]: None,\n        }\n        result = period.cashflows(rate_curve=hazard_curve, disc_curve=curve, fx=fxr, base=\"nok\")\n\n        for key in expected:\n            assert key in result\n            assert result[key] == expected[key] or abs(result[key] - expected[key]) < 1e-6\n\n    def test_period_cashflows_no_curves(self, fxr) -> None:\n        # also test the inputs to fx as float and as FXRates (10 is for\n        period = CreditProtectionPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            # convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n        )\n        cashflow = None\n        expected = {\n            defaults.headers[\"type\"]: \"CreditProtectionPeriod\",\n            defaults.headers[\"stub_type\"]: \"Regular\",\n            defaults.headers[\"base\"]: \"NOK\",\n            defaults.headers[\"a_acc_start\"]: dt(2022, 1, 1),\n            defaults.headers[\"a_acc_end\"]: dt(2022, 4, 1),\n            defaults.headers[\"payment\"]: dt(2022, 4, 3),\n            defaults.headers[\"notional\"]: 1e9,\n            defaults.headers[\"currency\"]: \"USD\",\n            defaults.headers[\"convention\"]: \"One\",\n            defaults.headers[\"dcf\"]: period.period_params.dcf,\n            defaults.headers[\"df\"]: None,\n            defaults.headers[\"recovery\"]: None,\n            defaults.headers[\"survival\"]: None,\n            defaults.headers[\"npv\"]: None,\n            defaults.headers[\"cashflow\"]: cashflow,\n            defaults.headers[\"fx\"]: 10.0,\n            defaults.headers[\"npv_fx\"]: None,\n            defaults.headers[\"collateral\"]: None,\n        }\n        result = period.cashflows(fx=fxr, base=\"nok\")\n        assert result == expected\n\n    def test_discretization_period(self, hazard_curve, curve):\n        p1 = CreditProtectionPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 1),\n            notional=1e9,\n            frequency=Frequency.Months(3, None),\n        )\n        h1 = hazard_curve.copy()\n        h2 = hazard_curve.copy()\n        h1._meta = replace(h1.meta, _credit_discretization=1)\n        h2._meta = replace(h2.meta, _credit_discretization=31)\n        r1 = p1.npv(rate_curve=h1, disc_curve=curve)\n        r2 = p1.npv(rate_curve=h2, disc_curve=curve)\n        assert 0.1 < abs(r1 - r2) < 1.0  # very similar result but not identical\n\n    def test_mid_period(self, hazard_curve, curve):\n        period = CreditProtectionPeriod(\n            start=dt(2021, 10, 4),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            notional=1e9,\n            frequency=Frequency.Months(3, None),\n        )\n        r1 = period.npv(rate_curve=hazard_curve, disc_curve=curve)\n        exp = -20006.321837529074\n        assert abs(r1 - exp) < 1e-7\n\n    def test_recovery_risk(self, hazard_curve, curve):\n        period = CreditProtectionPeriod(\n            start=dt(2021, 10, 4),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            notional=1e9,\n            frequency=Frequency.Months(3, None),\n        )\n\n        result = period.analytic_rec_risk(hazard_curve, curve)\n        p1 = period.npv(rate_curve=hazard_curve, disc_curve=curve)\n        hazard_curve.update_meta(\"credit_recovery_rate\", 0.41)\n        p2 = period.npv(rate_curve=hazard_curve, disc_curve=curve)\n        expected = p2 - p1\n        assert abs(result - expected) < 1e-9\n\n    def test_recovery_risk_raises(self, hazard_curve, curve):\n        period = CreditProtectionPeriod(\n            start=dt(2021, 10, 4),\n            end=dt(2022, 1, 4),\n            payment=dt(2022, 1, 4),\n            notional=1e9,\n            frequency=Frequency.Months(3, None),\n        )\n        with pytest.raises(TypeError, match=\"`curves` have not been supplied cor\"):\n            period.analytic_rec_risk(rate_curve=dict())\n\n\nclass TestCashflow:\n    def test_cashflow_analytic_delta(self, curve) -> None:\n        cashflow = Cashflow(notional=1e6, payment=dt(2022, 1, 1))\n        assert cashflow.analytic_delta(rate_curve=curve) == 0.0\n\n    @pytest.mark.parametrize(\n        (\"crv\", \"fx\"),\n        [\n            (True, 2.0),\n            (False, 2.0),\n            (True, 10.0),\n            (False, 10.0),\n        ],\n    )\n    def test_cashflow_cashflows(self, curve, fxr, crv, fx) -> None:\n        cashflow = Cashflow(notional=1e9, payment=dt(2022, 4, 3))\n        curve = curve if crv else NoInput(0)\n        expected = {\n            defaults.headers[\"base\"]: \"UNSPECIFIED\" if fx == 2.0 else \"NOK\",\n            defaults.headers[\"type\"]: \"Cashflow\",\n            # defaults.headers[\"a_acc_start\"]: None,\n            # defaults.headers[\"a_acc_end\"]: None,\n            defaults.headers[\"payment\"]: dt(2022, 4, 3),\n            defaults.headers[\"currency\"]: \"USD\",\n            defaults.headers[\"notional\"]: 1e9,\n            # defaults.headers[\"convention\"]: None,\n            # defaults.headers[\"dcf\"]: None,\n            defaults.headers[\"df\"]: 0.9897791268897856 if crv else None,\n            # defaults.headers[\"spread\"]: None,\n            defaults.headers[\"npv\"]: -989779126.8897856 if crv else None,\n            defaults.headers[\"cashflow\"]: -1e9,\n            defaults.headers[\"fx\"]: fx,\n            defaults.headers[\"npv_fx\"]: -989779126.8897856 * fx if crv else None,\n            defaults.headers[\"collateral\"]: None,\n        }\n        if fx == 2.0:\n            with pytest.warns(UserWarning):\n                # supplying `fx` as numeric\n                result = cashflow.cashflows(\n                    rate_curve=curve if crv else NoInput(0),\n                    fx=2.0,\n                    base=NoInput(0),\n                )\n        else:\n            result = cashflow.cashflows(\n                rate_curve=curve if crv else NoInput(0),\n                fx=fxr,\n                base=\"nok\",\n            )\n        assert result == expected\n\n    def test_cashflow_npv_raises(self, curve) -> None:\n        with pytest.raises(TypeError, match=\"`curves` have not been supplied correctly.\"):\n            Cashflow(notional=1e6, payment=dt(2022, 1, 1)).npv()\n        cashflow = Cashflow(notional=1e6, payment=dt(2022, 1, 1))\n        assert cashflow.analytic_delta(rate_curve=curve) == 0\n\n    def test_cashflow_npv_local(self, curve) -> None:\n        cashflow = Cashflow(notional=1e9, payment=dt(2022, 4, 3), currency=\"nok\")\n        result = cashflow.npv(rate_curve=curve, local=True)\n        expected = {\"nok\": -989779126.8897856}\n        assert result == expected\n\n\nclass TestIndexFixedPeriod:\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\"),\n        [(\"daily\", 201.00502512562812), (\"monthly\", 200.98317675333183)],\n    )\n    def test_period_rate(self, method, expected) -> None:\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n            index_method=method,\n        )\n        index_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 3): 0.995},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        _, result, _ = index_period.index_params.index_ratio(index_curve)\n        assert abs(result - expected) < 1e-8\n\n    def test_period_cashflow(self) -> None:\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n            index_lag=3,\n        )\n        index_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 3): 0.995},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        result = index_period.try_unindexed_reference_cashflow()\n        expected = -1e7 * ((dt(2022, 4, 1) - dt(2022, 1, 1)) / timedelta(days=360)) * 4\n        assert abs(result.unwrap() - expected) < 1e-8\n\n        result = index_period.try_cashflow(index_curve=index_curve)\n        expected = expected * index_curve.index_value(dt(2022, 4, 3), 3) / 100.0\n        assert abs(result.unwrap() - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"method\", [\"daily\", \"curve\"])\n    def test_period_curve_interp_method(self, method) -> None:\n        # both these methods of interpolation should give the same result with the way\n        # the curve and period are configured.\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n            index_lag=0,\n            index_method=method,\n        )\n        index_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 3): 0.995},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=0,\n        )\n        result = index_period.try_unindexed_reference_cashflow()\n        expected = -1e7 * ((dt(2022, 4, 1) - dt(2022, 1, 1)) / timedelta(days=360)) * 4\n        assert abs(result.unwrap() - expected) < 1e-8\n\n        result = index_period.try_cashflow(index_curve=index_curve)\n        assert abs(result.unwrap() + 20100502.512562) < 1e-6\n        expected = expected * index_curve.index_value(dt(2022, 4, 3), 0) / 100.0\n        assert abs(result.unwrap() - expected) < 1e-8\n\n    def test_period_analytic_delta(self, fxr, curve) -> None:\n        index_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 3): 0.995},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n        )\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n            index_base=200.0,\n            index_fixings=300.0,\n        )\n        result = fixed_period.analytic_delta(index_curve=index_curve, rate_curve=curve)\n        assert abs(result - 24744.478172244584 * 300.0 / 200.0) < 1e-7\n\n        result = fixed_period.analytic_delta(\n            index_curve=index_curve, rate_curve=curve, fx=fxr, base=\"nok\"\n        )\n        assert abs(result - 247444.78172244584 * 300.0 / 200.0) < 1e-7\n\n    @pytest.mark.parametrize((\"fixings\", \"method\"), [(300.0, \"daily\")])\n    def test_period_fixings_float(self, fixings, method, curve) -> None:\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n            index_base=200.0,\n            index_fixings=fixings,\n            index_method=method,\n        )\n        result = fixed_period.analytic_delta(index_curve=None, rate_curve=curve)\n        assert abs(result - 24744.478172244584 * 300.0 / 200.0) < 1e-7\n\n    @pytest.mark.skip(reason=\"`index_fixings` as Series removed for Period in 2.0\")\n    @pytest.mark.parametrize(\n        (\"fixings\", \"method\"),\n        [\n            (\n                Series([1.0, 300, 5], index=[dt(2022, 4, 2), dt(2022, 4, 3), dt(2022, 4, 4)]),\n                \"daily\",\n            ),\n            (Series([100.0, 500], index=[dt(2022, 4, 2), dt(2022, 4, 4)]), \"daily\"),\n            (Series([300.0, 500], index=[dt(2022, 4, 1), dt(2022, 4, 5)]), \"monthly\"),\n        ],\n    )\n    def test_period_fixings_series(self, fixings, method, curve) -> None:\n        fixed_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            currency=\"usd\",\n            index_base=200.0,\n            index_fixings=fixings,\n            index_method=method,\n        )\n        result = fixed_period.analytic_delta(index_curve=None, rate_curve=curve)\n        assert abs(result - 24744.478172244584 * 300.0 / 200.0) < 1e-7\n\n    def test_period_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"`index_method` as string: 'BAD' is not a val\"):\n            FixedPeriod(\n                start=dt(2022, 1, 1),\n                end=dt(2022, 4, 1),\n                payment=dt(2022, 4, 3),\n                notional=1e9,\n                convention=\"Act360\",\n                termination=dt(2022, 4, 1),\n                frequency=Frequency.Months(3, None),\n                currency=\"usd\",\n                index_base=200.0,\n                index_method=\"BAD\",\n            )\n\n    def test_period_npv(self, curve) -> None:\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n            index_lag=3,\n        )\n        index_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 3): 0.995},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=3,\n        )\n        result = index_period.npv(index_curve=index_curve, rate_curve=curve)\n        expected = -19895057.826930363\n        assert abs(result - expected) < 1e-8\n\n        result = index_period.npv(index_curve=index_curve, rate_curve=curve, local=True)\n        assert abs(result[\"usd\"] - expected) < 1e-8\n\n    def test_period_npv_raises(self, curve) -> None:\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n        )\n        with pytest.raises(\n            ValueError,\n            match=re.escape(\"`index_value` must be forecast from a `index_curve`\"),\n        ):\n            index_period.npv(disc_curve=curve)\n\n    @pytest.mark.parametrize(\"curve_\", [True, False])\n    def test_period_cashflows(self, curve, curve_) -> None:\n        curve = curve if curve_ else NoInput(0)\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 4, 1),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 1),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n            index_fixings=200.0,\n        )\n        result = index_period.cashflows(rate_curve=curve)\n        expected = {\n            \"Type\": \"FixedPeriod\",\n            \"Period\": \"Regular\",\n            \"Ccy\": \"USD\",\n            \"Base Ccy\": \"USD\",\n            \"Acc Start\": dt(2022, 1, 1),\n            \"Acc End\": dt(2022, 4, 1),\n            \"Payment\": dt(2022, 4, 3),\n            \"Convention\": \"Act360\",\n            \"DCF\": 0.25,\n            \"DF\": 0.9897791268897856 if curve_ else None,\n            \"Notional\": 1e9,\n            \"Rate\": 4.0,\n            \"Spread\": None,\n            \"Cashflow\": -20000000.0,\n            \"Unindexed Cashflow\": -10e6,\n            \"Index Fix Date\": dt(2022, 4, 1),\n            \"Index Base\": 100.0,\n            \"Index Val\": 200.0,\n            \"Index Ratio\": 2.0,\n            \"NPV\": -19795582.53779571 if curve_ else None,\n            \"FX Rate\": 1.0,\n            \"NPV Ccy\": -19795582.53779571 if curve_ else None,\n            defaults.headers[\"collateral\"]: None,\n        }\n        assert result == expected\n\n    def test_cashflow_returns_err(self) -> None:\n        i_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 2, 1),\n            payment=dt(2022, 2, 1),\n            frequency=Frequency.Months(1, None),\n            index_base=100.0,\n        )\n        assert i_period.try_cashflow().is_err\n        assert i_period.try_unindexed_cashflow().is_err\n\n    def test_cashflow_no_index_rate(self) -> None:\n        i_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 2, 1),\n            payment=dt(2022, 2, 1),\n            frequency=Frequency.Months(1, None),\n            index_base=100.0,\n        )\n        result = i_period.cashflows()\n        assert result[defaults.headers[\"index_ratio\"]] is None\n\n    def test_bad_curve(self) -> None:\n        i_period = FixedPeriod(\n            start=dt(2022, 1, 1),\n            end=dt(2022, 2, 1),\n            payment=dt(2022, 2, 1),\n            frequency=Frequency.Months(1, None),\n            index_base=100.0,\n        )\n        curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n        with pytest.raises(ValueError, match=\"Curve must be initialised with an `index_base`\"):\n            i_period.index_params.index_ratio(curve)\n\n    def test_index_fixings_linear_interp(self) -> None:\n        i_fixings = Series([173.1, 174.2], index=[dt(2001, 6, 1), dt(2001, 7, 1)])\n        result = _try_index_value(\n            index_fixings=i_fixings,\n            index_curve=NoInput(0),\n            index_date=dt(2001, 7, 20),\n            index_lag=1,\n            index_method=IndexMethod.Daily,\n        )\n        expected = 173.1 + 19 / 31 * (174.2 - 173.1)\n        assert abs(result.unwrap() - expected) < 1e-6\n\n    def test_composite_curve(self) -> None:\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n        )\n        index_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 3): 0.995},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n        )\n        composite_curve = CompositeCurve([index_curve])\n        _, result, _ = index_period.index_params.index_ratio(composite_curve)\n\n    def test_composite_curve_raises(self) -> None:\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e9,\n            convention=\"Act360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n        )\n        curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 3): 0.995},\n        )\n        composite_curve = CompositeCurve([curve])\n        with pytest.raises(ValueError, match=\"Curve must be initialised with an `index_base`\"):\n            _, result, _ = index_period.index_params.index_ratio(composite_curve)\n\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\"),\n        [(\"daily\", 201.00573790940518), (\"monthly\", 200.9836416123169)],\n    )\n    def test_index_lag_on_period_zero_curve(self, method, expected):\n        # test if a period can calculate the correct value by referencing a curve with\n        # zero index lag.\n        index_period = FixedPeriod(\n            start=dt(2022, 1, 3),\n            end=dt(2022, 4, 3),\n            payment=dt(2022, 4, 3),\n            notional=1e6,\n            convention=\"30360\",\n            termination=dt(2022, 4, 3),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=4.00,\n            currency=\"usd\",\n            index_base=100.0,\n            index_method=method,\n            index_lag=3,\n        )\n        index_curve = Curve(\n            nodes={dt(2021, 10, 1): 1.0, dt(2022, 1, 3): 0.995},\n            index_base=200.0,\n            interpolation=\"linear_index\",\n            index_lag=0,\n        )\n        discount_curve = Curve(\n            nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 3): 0.99},\n        )\n        _, result, _ = index_period.index_params.index_ratio(index_curve)\n        npv = index_period.npv(index_curve=index_curve, rate_curve=discount_curve)\n        assert abs(result - expected) < 1e-8\n        expected_npv = -1e6 * 0.04 * 0.25 * result * 0.99 / 100.0\n        assert abs(npv - expected_npv) < 1e-5\n\n    def test_cashflows_available_with_series_fixings(self):\n        RPI = DataFrame(\n            [\n                [dt(2024, 2, 1), 381.0],\n                [dt(2024, 3, 1), 383.0],\n                [dt(2024, 4, 1), 385.0],\n                [dt(2024, 5, 1), 386.4],\n                [dt(2024, 6, 1), 387.3],\n                [dt(2024, 7, 1), 387.5],\n                [dt(2024, 8, 1), 389.9],\n                [dt(2024, 9, 1), 388.6],\n                [dt(2024, 10, 1), 390.7],\n                [dt(2024, 11, 1), 390.9],\n                [dt(2024, 12, 1), 392.1],\n                [dt(2025, 1, 1), 391.7],\n                [dt(2025, 2, 1), 394.0],\n                [dt(2025, 3, 1), 395.3],\n            ],\n            columns=[\"month\", \"rate\"],\n        ).set_index(\"month\")[\"rate\"]\n        fixings.add(\"CPI_INDEX\", RPI)\n        period = FixedPeriod(\n            start=dt(2024, 11, 27),\n            end=dt(2025, 5, 27),\n            fixed_rate=2.0,\n            index_lag=3,\n            index_fixings=\"CPI_INDEX\",\n            index_base_date=dt(2024, 11, 27),\n            frequency=Frequency.Months(6, None),\n            payment=dt(2025, 5, 27),\n        )\n        result = period.cashflows()\n\n        fixings.pop(\"CPI_INDEX\")\n        assert result[\"Index Base\"] == 389.9 + (388.6 - 389.9) * (27 - 1) / 30\n        assert result[\"Index Val\"] == 394 + (395.3 - 394) * (27 - 1) / 31\n\n\nclass TestIndexCashflow:\n    def test_cashflow_analytic_delta(self, curve) -> None:\n        cashflow = Cashflow(notional=1e6, payment=dt(2022, 1, 1), index_base=100, index_fixings=105)\n        assert cashflow.analytic_delta(disc_curve=curve) == 0\n\n    def test_index_cashflow(self) -> None:\n        cf = Cashflow(notional=1e6, payment=dt(2022, 1, 1), index_base=100, index_fixings=200)\n        assert cf.try_unindexed_reference_cashflow().unwrap() == -1e6\n\n        assert cf.try_cashflow().unwrap() == -2e6\n\n    def test_index_cashflow_npv(self, curve) -> None:\n        cf = Cashflow(notional=1e6, payment=dt(2022, 1, 1), index_base=100.0, index_fixings=200)\n        assert abs(cf.npv(rate_curve=curve) + 2e6) < 1e-6\n\n    def test_cashflow_no_index_rate(self) -> None:\n        i_period = Cashflow(\n            notional=200.0,\n            payment=dt(2022, 2, 1),\n            index_base=100.0,\n        )\n        result = i_period.cashflows()\n        assert result[defaults.headers[\"index_ratio\"]] is None\n\n    def test_index_only(self, curve) -> None:\n        cf = Cashflow(\n            notional=1e6,\n            payment=dt(2022, 1, 1),\n            index_base=100,\n            index_fixings=200,\n            index_only=True,\n        )\n        assert abs(cf.npv(rate_curve=curve) + 1e6) < 1e-6\n\n    def test_index_cashflow_floats(self, curve) -> None:\n        icurve = Curve(\n            nodes={\n                dt(2022, 1, 1): 1.00,\n                dt(2022, 4, 1): 0.99,\n                dt(2022, 7, 1): 0.98,\n                dt(2022, 10, 1): 0.97,\n            },\n            index_base=100.0,\n            interpolation=\"linear_index\",\n        )\n        icurve._set_ad_order(1)\n        curve._set_ad_order(1)\n        cf = Cashflow(notional=1e6, payment=dt(2022, 7, 1), index_base=100)\n        result = cf.cashflows(index_curve=icurve, disc_curve=curve)\n        assert isinstance(result[\"Cashflow\"], float)\n\n\nclass TestMtmCashflow:\n    def test_cashflow(self):\n        p = MtmCashflow(\n            currency=\"usd\",\n            notional=2e6,\n            payment=dt(2000, 1, 10),\n            pair=\"eurusd\",\n            fx_fixings_start=2.0,\n            fx_fixings_end=2.2,\n            start=dt(2000, 1, 1),\n            end=dt(2000, 1, 10),\n        )\n        result = p.try_unindexed_reference_cashflow().unwrap()\n        expected = -0.2 * 2e6\n        assert abs(result - expected) < 1e-9\n\n    def test_cashflow_reversed(self):\n        p = MtmCashflow(\n            currency=\"usd\",\n            notional=2e6,\n            payment=dt(2000, 1, 10),\n            pair=\"usdeur\",\n            fx_fixings_start=0.5,\n            fx_fixings_end=1.0 / 2.2,\n            start=dt(2000, 1, 1),\n            end=dt(2000, 1, 10),\n        )\n        result = p.try_unindexed_reference_cashflow()\n        expected = -0.2 * 2e6\n        assert abs(result.unwrap() - expected) < 1e-9\n\n\nclass TestNonDeliverableCashflow:\n    @pytest.fixture(scope=\"class\")\n    def fxf_ndf(self):\n        fxr = FXRates({\"brlusd\": 0.200}, settlement=dt(2025, 1, 23))\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\n                \"brlbrl\": Curve({dt(2025, 1, 21): 1.0, dt(2026, 1, 23): 0.98}),\n                \"usdusd\": Curve({dt(2025, 1, 21): 1.0, dt(2026, 1, 23): 0.96}),\n                \"brlusd\": Curve({dt(2025, 1, 21): 1.0, dt(2026, 1, 23): 0.978}),\n            },\n        )\n        return fxf\n\n    def test_npv(self, fxf_ndf):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n        )\n        result = ndf.npv(disc_curve=fxf_ndf.curve(\"usd\", \"usd\"), fx=fxf_ndf)\n        expected = -1e6 * 0.20131018767289705 * 0.9855343095437953\n        assert abs(result - expected) < 1e-8\n\n    def test_npv_reversed(self, fxf_ndf):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"usdbrl\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n        )\n        result = ndf.npv(disc_curve=fxf_ndf.curve(\"usd\", \"usd\"), fx=fxf_ndf)\n        expected = -1e6 * 0.20131018767289705 * 0.9855343095437953\n        assert abs(result - expected) < 1e-8\n\n    def test_npv_fixing(self, fxf_ndf):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n            fx_fixings=0.25,\n        )\n        result = ndf.npv(disc_curve=fxf_ndf.curve(\"usd\", \"usd\"), fx=fxf_ndf)\n        expected = -1e6 * 0.25 * 0.9855343095437953\n        assert abs(result - expected) < 1e-8\n\n    def test_rate_as_fixing(self, fxf_ndf):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n            fx_fixings=0.25,\n        )\n        result = ndf.non_deliverable_params.fx_fixing.value\n        expected = 0.25\n        assert abs(result - expected) < 1e-8\n\n    def test_forecast_as_fixing(self, fxf_ndf):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n            fx_fixings=0.25,\n        )\n        result = ndf.non_deliverable_params.fx_fixing.try_value_or_forecast(fx=fxf_ndf).unwrap()\n        expected = 0.25\n        assert abs(result - expected) < 1e-8\n\n    def test_rate(self, fxf_ndf):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n        )\n        result = ndf.non_deliverable_params.fx_fixing.try_value_or_forecast(fx=fxf_ndf).unwrap()\n        expected = fxf_ndf.rate(ndf.non_deliverable_params.pair, dt(2025, 6, 1))\n        assert abs(result - expected) < 1e-8\n\n    def test_forecast_rate(self, fxf_ndf):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n        )\n        result = ndf.non_deliverable_params.fx_fixing.try_value_or_forecast(fx=fxf_ndf).unwrap()\n        expected = fxf_ndf.rate(ndf.non_deliverable_params.pair, dt(2025, 6, 1))\n        assert abs(result - expected) < 1e-8\n\n    def test_cashflows_priced(self, fxf_ndf):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n            fx_fixings=0.25,\n        )\n        result = ndf.cashflows(disc_curve=fxf_ndf.curve(\"usd\", \"usd\"), fx=fxf_ndf)\n        expected = {\n            \"Base Ccy\": \"USD\",\n            \"Cashflow\": -250000.0,\n            \"Ccy\": \"USD\",\n            \"Collateral\": \"usd\",\n            \"DF\": 0.9855343095437953,\n            \"FX Rate\": 1.0,\n            \"NPV\": -246383.57738594883,\n            \"NPV Ccy\": -246383.57738594883,\n            \"Notional\": 1000000.0,\n            \"Payment\": dt(2025, 6, 1, 0, 0),\n            \"FX Fix Date\": dt(2025, 6, 1),\n            \"FX Fixing\": 0.25,\n            \"Reference Ccy\": \"BRL\",\n            \"Type\": \"Cashflow\",\n        }\n        assert result == expected\n\n    def test_cashflows_no_args(self):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n        )\n        result = ndf.cashflows()\n        expected = {\n            \"Base Ccy\": \"USD\",\n            \"Cashflow\": None,\n            \"Ccy\": \"USD\",\n            \"Collateral\": None,\n            \"DF\": None,\n            \"FX Rate\": 1.0,\n            \"FX Fixing\": None,\n            \"FX Fix Date\": dt(2025, 6, 1),\n            \"NPV\": None,\n            \"NPV Ccy\": None,\n            \"Notional\": 1000000.0,\n            \"Reference Ccy\": \"BRL\",\n            \"Payment\": dt(2025, 6, 1),\n            \"Type\": \"Cashflow\",\n        }\n        assert result == expected\n\n    def test_analytic_delta(self, curve):\n        ndf = Cashflow(\n            notional=1e6,\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            payment=dt(2025, 6, 1),\n            fx_fixings=0.25,\n        )\n        assert ndf.analytic_delta(disc_curve=curve) == 0.0\n\n\nclass TestNonDeliverableFixedPeriod:\n    @pytest.fixture(scope=\"class\")\n    def fxf_ndf(self):\n        fxr = FXRates({\"brlusd\": 0.200}, settlement=dt(2025, 1, 23))\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\n                \"brlbrl\": Curve({dt(2025, 1, 21): 1.0, dt(2026, 1, 23): 0.98}),\n                \"usdusd\": Curve({dt(2025, 1, 21): 1.0, dt(2026, 1, 23): 0.96}),\n                \"brlusd\": Curve({dt(2025, 1, 21): 1.0, dt(2026, 1, 23): 0.978}),\n            },\n        )\n        return fxf\n\n    @pytest.mark.parametrize(\"fx_fixing\", [NoInput(0), 5.00])\n    def test_cashflow_reversed(self, fx_fixing, fxf_ndf):\n        ndfp = FixedPeriod(\n            start=dt(2025, 2, 1),\n            end=dt(2025, 5, 1),\n            payment=dt(2025, 5, 1),\n            convention=\"30e360\",\n            currency=\"usd\",\n            pair=FXIndex(\"usdbrl\", \"all\", 0),\n            notional=1e6,\n            fx_fixings=fx_fixing,\n            frequency=Frequency.Months(3, None),\n            fixed_rate=3.0,\n        )\n        cf = ndfp.try_cashflow(fx=fxf_ndf).unwrap()\n        fx_fixing = ndfp.non_deliverable_params.fx_fixing.try_value_or_forecast(fx=fxf_ndf).unwrap()\n        expected = -1e6 * 0.25 * 0.03 / fx_fixing  # in USD\n        assert abs(cf - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"fx_fixing\", [NoInput(0), 0.2])\n    def test_cashflow(self, fx_fixing, fxf_ndf):\n        ndfp = FixedPeriod(\n            start=dt(2025, 2, 1),\n            end=dt(2025, 5, 1),\n            payment=dt(2025, 5, 1),\n            convention=\"30e360\",\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            notional=0.2e6,\n            fx_fixings=fx_fixing,\n            frequency=Frequency.Months(3, None),\n            fixed_rate=3.0,\n        )\n        cf = ndfp.try_cashflow(fx=fxf_ndf).unwrap()\n        fx_fixing = ndfp.non_deliverable_params.fx_fixing.try_value_or_forecast(fx=fxf_ndf).unwrap()\n        expected = -0.2e6 * 0.25 * 0.03 * fx_fixing  # in USD\n        assert abs(cf - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"fx_fixing\", [NoInput(0), 0.20])\n    def test_cashflow_err(self, fx_fixing, fxf_ndf):\n        ndfp = FixedPeriod(\n            start=dt(2025, 2, 1),\n            end=dt(2025, 5, 1),\n            payment=dt(2025, 5, 1),\n            convention=\"30e360\",\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            notional=1e6,\n            fx_fixings=fx_fixing,\n            frequency=Frequency.Months(3, None),\n        )\n        assert ndfp.try_cashflow(fx=fxf_ndf).is_err\n\n    @pytest.mark.parametrize(\"fx_fixing\", [NoInput(0), 5.0])\n    def test_analytic_delta(self, fx_fixing, fxf_ndf):\n        ndfp = FixedPeriod(\n            start=dt(2025, 2, 1),\n            end=dt(2025, 5, 1),\n            payment=dt(2025, 5, 1),\n            convention=\"30e360\",\n            currency=\"usd\",\n            pair=FXIndex(\"usdbrl\", \"all\", 0),\n            notional=1e9,\n            fx_fixings=fx_fixing,\n            frequency=Frequency.Months(3, None),\n            fixed_rate=3.0,\n        )\n        curve = fxf_ndf.curve(\"usd\", \"usd\")\n        result = ndfp.analytic_delta(rate_curve=curve, fx=fxf_ndf)\n        fx_fixing = ndfp.non_deliverable_params.fx_fixing.try_value_or_forecast(fx=fxf_ndf).unwrap()\n        expected = 1e9 * 0.25 * 0.0001 * curve[dt(2025, 5, 1)] / fx_fixing  # in USD\n        assert abs(result - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"fx_conv\", [FXRates({\"usdeur\": 105.0}), 105.0])\n    def test_analytic_delta_base(self, fx_conv, fxf_ndf):\n        ndfp = FixedPeriod(\n            start=dt(2025, 2, 1),\n            end=dt(2025, 5, 1),\n            payment=dt(2025, 5, 1),\n            convention=\"30e360\",\n            currency=\"usd\",\n            pair=FXIndex(\"usdbrl\", \"all\", 0),\n            notional=1e9,\n            fx_fixings=5.0,\n            frequency=Frequency.Months(3, None),\n            fixed_rate=3.0,\n        )\n        curve = fxf_ndf.curve(\"usd\", \"usd\")\n        result = ndfp.analytic_delta(rate_curve=curve, fx=fx_conv, base=\"eur\")\n        fx_fixing = 5.0\n        expected = 105 * 1e9 * 0.25 * 0.0001 * curve[dt(2025, 5, 1)] / fx_fixing  # in USD\n        assert abs(result - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"fx_fixing\", [NoInput(0), 5.0])\n    def test_npv_reversed(self, fx_fixing, fxf_ndf):\n        ndfp = FixedPeriod(\n            start=dt(2025, 2, 1),\n            end=dt(2025, 5, 1),\n            payment=dt(2025, 5, 1),\n            convention=\"30e360\",\n            currency=\"usd\",\n            pair=FXIndex(\"usdbrl\", \"all\", 0),\n            notional=1e9,\n            fx_fixings=fx_fixing,\n            frequency=Frequency.Months(3, None),\n            fixed_rate=3.0,\n        )\n        curve = fxf_ndf.curve(\"usd\", \"usd\")\n        result = ndfp.npv(rate_curve=curve, fx=fxf_ndf)\n        fx_fixing = ndfp.non_deliverable_params.fx_fixing.try_value_or_forecast(fx=fxf_ndf).unwrap()\n        expected = -1e9 * 0.25 * 0.03 * curve[dt(2025, 5, 1)] / fx_fixing  # in USD\n        assert abs(result - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"fx_fixing\", [NoInput(0), 0.20])\n    def test_npv(self, fx_fixing, fxf_ndf):\n        ndfp = FixedPeriod(\n            start=dt(2025, 2, 1),\n            end=dt(2025, 5, 1),\n            payment=dt(2025, 5, 1),\n            convention=\"30e360\",\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            notional=1e9,\n            fx_fixings=fx_fixing,\n            frequency=Frequency.Months(3, None),\n            fixed_rate=3.0,\n        )\n        curve = fxf_ndf.curve(\"usd\", \"usd\")\n        result = ndfp.npv(rate_curve=curve, fx=fxf_ndf)\n        fx_fixing = ndfp.non_deliverable_params.fx_fixing.try_value_or_forecast(fx=fxf_ndf).unwrap()\n        expected = -1e9 * 0.25 * 0.03 * curve[dt(2025, 5, 1)] * fx_fixing  # in USD\n        assert abs(result - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"curve\", [True, False])\n    @pytest.mark.parametrize(\"fixed_rate\", [3.0])\n    def test_cashflows(self, curve, fixed_rate, fxf_ndf):\n        curve_ = fxf_ndf.curve(\"usd\", \"usd\") if curve else NoInput(0)\n        ndfp = FixedPeriod(\n            start=dt(2025, 2, 1),\n            end=dt(2025, 5, 1),\n            payment=dt(2025, 5, 1),\n            convention=\"30e360\",\n            currency=\"usd\",\n            pair=FXIndex(\"brlusd\", \"all\", 0),\n            notional=1e9,\n            fx_fixings=NoInput(0),\n            frequency=Frequency.Months(3, None),\n            fixed_rate=fixed_rate,\n        )\n        result = ndfp.cashflows(rate_curve=curve_, fx=fxf_ndf)\n        expected = {\n            \"Acc End\": dt(2025, 5, 1, 0, 0),\n            \"Acc Start\": dt(2025, 2, 1, 0, 0),\n            \"Cashflow\": -1507459.1627133065,\n            \"Base Ccy\": \"USD\",\n            \"Ccy\": \"USD\",\n            \"Collateral\": \"usd\" if curve else None,\n            \"Convention\": \"30e360\",\n            \"DCF\": 0.25,\n            \"DF\": 0.9889384743344495 if curve else None,\n            \"FX Rate\": 1.0,\n            \"FX Fixing\": 0.20099455502844088,\n            \"FX Fix Date\": dt(2025, 5, 1),\n            \"NPV\": -1490784.364495184 if curve else None,\n            \"NPV Ccy\": -1490784.364495184 if curve else None,\n            \"Notional\": 1000000000.0,\n            \"Reference Ccy\": \"BRL\",\n            \"Payment\": dt(2025, 5, 1, 0, 0),\n            \"Period\": \"Regular\",\n            \"Rate\": 3.0,\n            \"Spread\": None,\n            \"Type\": \"FixedPeriod\",\n        }\n        assert result == expected\n\n\nclass TestZeroFixedPeriod:\n    def test_cashflows(self):\n        zp = ZeroFixedPeriod(\n            schedule=Schedule(\n                effective=dt(2000, 1, 1),\n                termination=dt(2003, 6, 1),\n                frequency=\"A\",\n            ),\n            convention=\"1\",\n            fixed_rate=1.0,\n        )\n        cf = zp.cashflows()\n        assert cf[defaults.headers[\"dcf\"]] == 4.0\n        assert cf[defaults.headers[\"cashflow\"]] == ((1 + 0.01) ** 4 - 1) * -1e6\n\n\ndef test_base_period_dates_raise() -> None:\n    with pytest.raises(ValueError):\n        _ = FixedPeriod(\n            start=dt(2023, 1, 1),\n            end=dt(2022, 1, 1),\n            payment=dt(2024, 1, 1),\n            frequency=Frequency.Months(3, None),\n        )\n\n\n@pytest.fixture\ndef fxfo():\n    # FXForwards for FX Options tests\n    eureur = Curve(\n        {dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.9851909811629752},\n        calendar=\"tgt\",\n        id=\"eureur\",\n    )\n    usdusd = Curve(\n        {dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.976009366603271},\n        calendar=\"nyc\",\n        id=\"usdusd\",\n    )\n    eurusd = Curve({dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.987092591908283}, id=\"eurusd\")\n    fxr = FXRates({\"eurusd\": 1.0615}, settlement=dt(2023, 3, 20))\n    fxf = FXForwards(fx_curves={\"eureur\": eureur, \"eurusd\": eurusd, \"usdusd\": usdusd}, fx_rates=fxr)\n    # fxf.swap(\"eurusd\", [dt(2023, 3, 20), dt(2023, 6, 20)]) = 60.10\n    return fxf\n\n\n@pytest.fixture\ndef fxvs():\n    vol_ = FXDeltaVolSmile(\n        nodes={\n            0.25: 8.9,\n            0.5: 8.7,\n            0.75: 10.15,\n        },\n        eval_date=dt(2023, 3, 16),\n        expiry=dt(2023, 6, 16),\n        delta_type=\"forward\",\n    )\n    return vol_\n\n\nclass TestFXOption:\n    # replicate https://quant.stackexchange.com/a/77802/29443\n    @pytest.mark.parametrize(\n        (\"pay\", \"k\", \"exp_pts\", \"exp_prem\", \"dlty\", \"exp_dl\"),\n        [\n            (dt(2023, 3, 20), 1.101, 69.378, 138756.54, \"spot\", 0.250124),\n            (dt(2023, 3, 20), 1.101, 69.378, 138756.54, \"forward\", 0.251754),\n            (dt(2023, 6, 20), 1.101, 70.226, 140451.53, \"spot\", 0.250124),\n            (dt(2023, 6, 20), 1.101, 70.226, 140451.53, \"forward\", 0.251754),\n            (dt(2023, 6, 20), 1.10101922, 70.180, 140360.17, \"spot\", 0.250000),\n        ],\n    )\n    @pytest.mark.parametrize(\"smile\", [False, True])\n    def test_premium_big_usd_pips(\n        self,\n        fxfo,\n        fxvs,\n        pay,\n        k,\n        exp_pts,\n        exp_prem,\n        dlty,\n        exp_dl,\n        smile,\n    ) -> None:\n        vol_ = (\n            8.9\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=dlty,\n            )\n        )\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=pay,\n            strike=k,\n            notional=20e6,\n            delta_type=dlty,\n        )\n        result = fxo.try_rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n            forward=pay,\n        ).unwrap()\n        expected = exp_pts\n        assert abs(result - expected) < 1e-3\n\n        result = 20e6 * result / 10000\n        expected = exp_prem\n        assert abs(result - expected) < 1e-2\n\n        result = fxo.analytic_greeks(\n            fxfo.curve(\"eur\", \"usd\"),\n            fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )[\"delta\"]\n        expected = exp_dl\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"pay\", \"k\", \"exp_pts\", \"exp_prem\", \"dlty\", \"exp_dl\"),\n        [\n            (dt(2023, 3, 20), 1.101, 0.6536, 130717.44, \"spot_pa\", 0.243588),\n            (dt(2023, 3, 20), 1.101, 0.6536, 130717.44, \"forward_pa\", 0.245175),\n            (dt(2023, 6, 20), 1.101, 0.6578, 131569.29, \"spot_pa\", 0.243548),\n            (dt(2023, 6, 20), 1.101, 0.6578, 131569.29, \"forward_pa\", 0.245178),\n        ],\n    )\n    @pytest.mark.parametrize(\"smile\", [False, True])\n    def test_premium_big_eur_pc(self, fxfo, pay, k, exp_pts, exp_prem, dlty, exp_dl, smile) -> None:\n        vol_ = (\n            8.9\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=dlty,\n            )\n        )\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=pay,\n            strike=k,\n            notional=20e6,\n            delta_type=dlty,\n            metric=\"percent\",\n        )\n        result = fxo.try_rate(\n            fxfo.curve(\"eur\", \"usd\"),\n            fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n            forward=pay,\n        ).unwrap()\n        expected = exp_pts\n        assert abs(result - expected) < 1e-3\n\n        result = 20e6 * result / 100\n        expected = exp_prem\n        assert abs(result - expected) < 1e-1\n\n        result = fxo.analytic_greeks(\n            fxfo.curve(\"eur\", \"usd\"),\n            fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n            premium=exp_prem,\n            premium_payment=pay,\n        )[\"delta\"]\n        expected = exp_dl\n        assert abs(result - expected) < 5e-5\n\n    @pytest.mark.parametrize(\"smile\", [False, True])\n    def test_npv(self, fxfo, smile) -> None:\n        vol_ = (\n            8.9\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"forward\",\n            )\n        )\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n        )\n        result = fxo.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )\n        result /= fxfo.curve(\"usd\", \"usd\")[dt(2023, 6, 20)]\n        expected = 140451.5273  # 140500 USD premium according to Tullets calcs (may be rounded)\n        assert abs(result - expected) < 1e-3\n\n    @pytest.mark.parametrize(\"smile\", [False, True])\n    def test_npv_in_past(self, fxfo, smile) -> None:\n        vol_ = (\n            8.9\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"forward\",\n            )\n        )\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2022, 6, 16),\n            delivery=dt(2022, 6, 20),\n            # payment=dt(2022, 6, 20),\n            strike=1.101,\n            notional=20e6,\n        )\n        result = fxo.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )\n        assert result == 0.0\n\n    def test_npv_option_fixing(self, fxfo) -> None:\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 3, 15),\n            delivery=dt(2023, 3, 17),\n            # payment=dt(2023, 3, 17),\n            strike=1.101,\n            notional=20e6,\n            option_fixings=1.102,\n        )\n        result = fxo.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )\n        expected = (1.102 - 1.101) * 20e6 * fxfo.curve(\"usd\", \"usd\")[dt(2023, 3, 17)]\n        assert abs(result - expected) < 1e-9\n\n        # valuable put\n        fxo = FXPutPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 3, 15),\n            delivery=dt(2023, 3, 17),\n            # payment=dt(2023, 3, 17),\n            strike=1.101,\n            notional=20e6,\n            option_fixings=1.100,\n        )\n        result = fxo.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )\n        expected = (1.101 - 1.100) * 20e6 * fxfo.curve(\"usd\", \"usd\")[dt(2023, 3, 17)]\n        assert abs(result - expected) < 1e-9\n\n        # worthless option\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 3, 15),\n            delivery=dt(2023, 3, 17),\n            # payment=dt(2023, 3, 17),\n            strike=1.101,\n            notional=20e6,\n            option_fixings=1.100,\n        )\n        result = fxo.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )\n        expected = 0.0\n        assert abs(result - expected) < 1e-9\n\n    def test_rate_metric_raises(self, fxfo) -> None:\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n        )\n        with pytest.raises(ValueError, match=\"FXOption `metric` as string: 'bad' i\"):\n            fxo.rate(\n                rate_curve=fxfo.curve(\"eur\", \"usd\"),\n                disc_curve=fxfo.curve(\"usd\", \"usd\"),\n                fx=fxfo,\n                fx_vol=8.9,\n                metric=\"bad\",\n            )\n\n    @pytest.mark.parametrize(\"smile\", [False, True])\n    def test_premium_points(self, fxfo, smile) -> None:\n        vol_ = (\n            8.9\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"forward\",\n            )\n        )\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n        )\n        result = fxo.rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )\n        expected = 70.225764  # 70.25 premium according to Tullets calcs (may be rounded)\n        assert abs(result - expected) < 1e-6\n\n    def test_implied_vol(self, fxfo) -> None:\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n        )\n        result = fxo.implied_vol(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            premium=70.25,\n        )\n        expected = 8.90141775  # Tullets have trade confo at 8.9%\n        assert abs(expected - result) < 1e-8\n\n        premium_pc = 0.007025 / fxfo.rate(\"eurusd\", fxo.fx_option_params.delivery) * 100.0\n        result = fxo.implied_vol(\n            fxfo.curve(\"eur\", \"usd\"),\n            fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            premium=premium_pc,\n            metric=\"percent\",\n        )\n        assert abs(expected - result) < 1e-8\n\n    @pytest.mark.parametrize(\"smile\", [False, True])\n    def test_premium_put(self, fxfo, smile) -> None:\n        vol_ = (\n            10.15\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 10.15},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"forward\",\n            )\n        )\n        fxo = FXPutPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.033,\n            notional=20e6,\n        )\n        result = fxo.rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )\n        expected = 83.836959  # Tullets trade confo has 83.75\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\"smile\", [False, True])\n    def test_npv_put(self, fxfo, smile) -> None:\n        vol_ = (\n            10.15\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 10.15},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"forward\",\n            )\n        )\n        fxo = FXPutPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.033,\n            notional=20e6,\n        )\n        result = (\n            fxo.npv(\n                rate_curve=fxfo.curve(\"eur\", \"usd\"),\n                disc_curve=fxfo.curve(\"usd\", \"usd\"),\n                fx=fxfo,\n                fx_vol=vol_,\n            )\n            / fxfo.curve(\"usd\", \"usd\")[dt(2023, 6, 20)]\n        )\n        expected = 167673.917818  # Tullets trade confo has 167 500\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"dlty\", \"delta\", \"exp_k\"),\n        [\n            (FXDeltaMethod.Forward, 0.25, 1.101271021340),\n            (FXDeltaMethod.ForwardPremiumAdjusted, 0.25, 1.10023348001),\n            (FXDeltaMethod.Forward, 0.251754, 1.100999951),\n            (FXDeltaMethod.ForwardPremiumAdjusted, 0.8929, 0.9748614298),\n            # close to peak of premium adjusted delta graph.\n            (FXDeltaMethod.Spot, 0.25, 1.10101920113408),\n            (FXDeltaMethod.SpotPremiumAdjusted, 0.25, 1.099976469786),\n            (FXDeltaMethod.Spot, 0.251754, 1.10074736155),\n            (FXDeltaMethod.SpotPremiumAdjusted, 0.8870, 0.97543175409),\n            # close to peak of premium adjusted delta graph.\n        ],\n    )\n    @pytest.mark.parametrize(\"smile\", [False, True])\n    def test_strike_from_delta(self, fxfo, dlty, delta, exp_k, smile) -> None:\n        # https://quant.stackexchange.com/a/77802/29443\n        vol_ = (\n            8.9\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=dlty,\n            )\n        )\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n            delta_type=dlty,\n        )\n        result = fxo._index_vol_and_strike_from_delta(\n            delta,\n            dlty,\n            vol_,\n            fxfo.curve(\"eur\", \"usd\")[fxo.fx_option_params.delivery],\n            fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)],\n            fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n            fxo.fx_option_params.time_to_expiry(fxfo.curve(\"usd\", \"usd\").nodes.initial),\n        )[2]\n        expected = exp_k\n        assert abs(result - expected) < 1e-8\n\n        ## Round trip test\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=float(result),\n            notional=20e6,\n            delta_type=dlty,\n        )\n        result2 = fxo.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )[\"delta\"]\n        assert abs(result2 - delta) < 1e-8\n\n    def test_payoff_at_expiry(self, fxfo) -> None:\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n        )\n        result = fxo._payoff_at_expiry(rng=[1.07, 1.13])\n        assert result[0][0] == 1.07\n        assert result[0][-1] == 1.13\n        assert result[1][0] == 0.0\n        assert result[1][-1] == (1.13 - 1.101) * 20e6\n\n    def test_payoff_at_expiry_put(self, fxfo) -> None:\n        fxo = FXPutPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n        )\n        result = fxo._payoff_at_expiry(rng=[1.07, 1.13])\n        assert result[0][0] == 1.07\n        assert result[0][-1] == 1.13\n        assert result[1][0] == (1.101 - 1.07) * 20e6\n        assert result[1][-1] == 0.0\n\n    @pytest.mark.parametrize(\n        \"delta_type\",\n        [\n            FXDeltaMethod.Spot,\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.Forward,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"smile_type\",\n        [\n            FXDeltaMethod.Spot,\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.Forward,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ],\n    )\n    @pytest.mark.parametrize(\"delta\", [-0.1, -0.25, -0.75, -0.9, -1.5])\n    @pytest.mark.parametrize(\"vol_smile\", [True, False])\n    def test_strike_and_delta_idx_multisolve_from_delta_put(\n        self,\n        fxfo,\n        delta_type,\n        smile_type,\n        delta,\n        vol_smile,\n    ) -> None:\n        if delta < -1.0 and delta_type not in [\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ]:\n            pytest.skip(\"Put delta cannot be below -1.0 in unadjusted cases.\")\n        fxo = FXPutPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.033,\n            notional=20e6,\n            delta_type=delta_type,\n        )\n        if vol_smile:\n            vol_ = FXDeltaVolSmile(\n                nodes={\n                    0.25: 8.9,\n                    0.5: 8.7,\n                    0.75: 10.15,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=smile_type,\n            )\n        else:\n            vol_ = 9.00\n\n        result = fxo._index_vol_and_strike_from_delta(\n            delta,\n            delta_type,\n            vol_,\n            fxfo.curve(\"eur\", \"usd\")[dt(2023, 6, 20)],\n            fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)],\n            fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n            fxo.fx_option_params.time_to_expiry(fxfo.curve(\"eur\", \"usd\").nodes.initial),\n        )\n\n        fxo.fx_option_params.strike = result[2]\n\n        if vol_smile:\n            vol_ = result[1]\n\n        expected = fxo.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )[\"delta\"]\n\n        assert abs(delta - expected) < 1e-8\n\n    @pytest.mark.parametrize(\n        \"delta_type\",\n        [\n            FXDeltaMethod.Spot,\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.Forward,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"smile_type\",\n        [\n            FXDeltaMethod.Spot,\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.Forward,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ],\n    )\n    @pytest.mark.parametrize(\"delta\", [0.1, 0.25, 0.65, 0.9])\n    @pytest.mark.parametrize(\"vol_smile\", [True, False])\n    def test_strike_and_delta_idx_multisolve_from_delta_call(\n        self,\n        fxfo,\n        delta_type,\n        smile_type,\n        delta,\n        vol_smile,\n    ) -> None:\n        if delta > 0.65 and delta_type in [\n            FXDeltaMethod.SpotPremiumAdjusted,\n            FXDeltaMethod.ForwardPremiumAdjusted,\n        ]:\n            pytest.skip(\"Premium adjusted call delta cannot be above the peak ~0.7?.\")\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.033,\n            notional=20e6,\n            delta_type=delta_type,\n        )\n        if vol_smile:\n            vol_ = FXDeltaVolSmile(\n                nodes={\n                    0.25: 8.9,\n                    0.5: 8.7,\n                    0.75: 10.15,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=smile_type,\n            )\n        else:\n            vol_ = 9.00\n        result = fxo._index_vol_and_strike_from_delta(\n            delta,\n            delta_type,\n            vol_,\n            fxfo.curve(\"eur\", \"usd\")[dt(2023, 6, 20)],\n            fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)],\n            fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n            fxo.fx_option_params.time_to_expiry(fxfo.curve(\"eur\", \"usd\").nodes.initial),\n        )\n\n        fxo.fx_option_params.strike = result[2]\n        if vol_smile:\n            vol_ = result[1]\n\n        expected = fxo.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )[\"delta\"]\n        assert abs(delta - expected) < 1e-8\n\n    @pytest.mark.parametrize(\"delta_type\", [\"spot_pa\", \"forward_pa\"])\n    @pytest.mark.parametrize(\"smile_type\", [\"spot\", \"spot_pa\", \"forward\", \"forward_pa\"])\n    @pytest.mark.parametrize(\"delta\", [0.9])\n    @pytest.mark.parametrize(\"vol_smile\", [True, False])\n    def test_strike_and_delta_idx_multisolve_from_delta_call_out_of_bounds(\n        self,\n        fxfo,\n        delta_type,\n        smile_type,\n        delta,\n        vol_smile,\n    ) -> None:\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.033,\n            notional=20e6,\n            delta_type=delta_type,\n        )\n        if vol_smile:\n            vol_ = FXDeltaVolSmile(\n                nodes={\n                    0.25: 8.9,\n                    0.5: 8.7,\n                    0.75: 10.15,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=smile_type,\n            )\n        else:\n            vol_ = 9.00\n        with pytest.raises(ValueError, match=\"Newton root solver failed\"):\n            fxo._index_vol_and_strike_from_delta(\n                delta,\n                delta_type,\n                vol_,\n                fxfo.curve(\"eur\", \"usd\")[dt(2023, 6, 20)],\n                fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)],\n                fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n                fxo.fx_option_params.time_to_expiry(fxfo.curve(\"eur\", \"usd\").nodes.initial),\n            )\n\n    @pytest.mark.parametrize(\"delta_type\", [\"forward\", \"spot\"])\n    def test_analytic_gamma_fwd_diff(self, delta_type, fxfo) -> None:\n        # test not suitable for pa because of the assumption of a fixed premium amount\n        fxc = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 3, 16),\n            notional=20e6,\n            strike=1.101,\n            delta_type=delta_type,\n        )\n        base = fxc.analytic_greeks(\n            fxfo.curve(\"eur\", \"usd\"),\n            fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )\n        f_d = fxfo.rate(\"eurusd\", dt(2023, 6, 20))\n        f_t = fxfo.rate(\"eurusd\", dt(2023, 3, 20))\n        fxfo.fx_rates.update({\"eurusd\": 1.0615001})\n        fxfo.update()\n        f_d2 = fxfo.rate(\"eurusd\", dt(2023, 6, 20))\n        f_t2 = fxfo.rate(\"eurusd\", dt(2023, 3, 20))\n        base_1 = fxc.analytic_greeks(\n            fxfo.curve(\"eur\", \"usd\"),\n            fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )\n        denomn = (f_d2 - f_d) if \"forward\" in delta_type else (f_t2 - f_t)\n        fwd_diff = -(base[\"delta\"] - base_1[\"delta\"]) / denomn\n        assert abs(base[\"gamma\"] - fwd_diff) < 1e-5\n\n    def test_analytic_vega(self, fxfo) -> None:\n        fxc = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 3, 16),\n            notional=20e6,\n            strike=1.101,\n            delta_type=\"forward\",\n        )\n        result = fxc.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )[\"vega\"]\n        assert abs(result * 20e6 / 100 - 33757.945) < 1e-2\n\n        p0 = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )\n        p1 = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.91,\n        )\n        fwd_diff = (p1 - p0) / 20e6 * 10000.0\n        assert abs(result - fwd_diff) < 1e-4\n\n    def test_analytic_vomma(self, fxfo) -> None:\n        fxc = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 3, 16),\n            notional=1,\n            strike=1.101,\n            delta_type=\"forward\",\n        )\n        result = fxc.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )[\"vomma\"]\n\n        p0 = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.9,\n        )\n        p1 = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.91,\n        )\n        p_1 = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=8.89,\n        )\n        fwd_diff = (p1 - p0 - p0 + p_1) * 1e4 * 1e4\n        assert abs(result - fwd_diff) < 1e-6\n\n    @pytest.mark.parametrize(\"payment\", [dt(2023, 3, 16), dt(2023, 6, 20)])\n    def test_vega_and_vomma_example(self, fxfo, payment) -> None:\n        fxc = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=payment,\n            notional=10e6,\n            strike=1.10,\n            delta_type=\"forward\",\n        )\n        npv = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=10.0,\n        )\n        npv2 = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=10.1,\n        )\n        greeks = fxc.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=Dual(10.0, [\"vol\"], [100.0]),\n        )\n        taylor_vega = 10e6 * greeks[\"vega\"] * 0.1 / 100.0\n        taylor_vomma = 10e6 * 0.5 * greeks[\"vomma\"] * 0.1**2 / 10000.0\n        expected = npv2 - npv\n        assert abs(taylor_vega + taylor_vomma - expected) < 0.2\n\n    @pytest.mark.parametrize(\"payment\", [dt(2023, 3, 16), dt(2023, 6, 20)])\n    @pytest.mark.parametrize(\"delta_type\", [\"spot\", \"forward\"])\n    def test_delta_and_gamma_example(self, fxfo, payment, delta_type) -> None:\n        fxc = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=payment,\n            notional=10e6,\n            strike=1.10,\n            delta_type=delta_type,\n        )\n        npv = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=10.0,\n        )\n        greeks = fxc.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=10.0,\n        )\n        f_d = fxfo.rate(\"eurusd\", dt(2023, 6, 20))\n        fxfo.fx_rates.update({\"eurusd\": 1.0625})\n        fxfo.update()\n        f_d2 = fxfo.rate(\"eurusd\", dt(2023, 6, 20))\n        npv2 = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=10.0,\n        )\n        if delta_type == \"forward\":\n            fwd_diff = f_d2 - f_d\n            discount_date = fxc.fx_option_params.delivery\n        else:\n            fwd_diff = 0.001\n            discount_date = dt(2023, 3, 20)\n        taylor_delta = 10e6 * greeks[\"delta\"] * fwd_diff\n        taylor_gamma = 10e6 * 0.5 * greeks[\"gamma\"] * fwd_diff**2\n        expected = npv2 - npv\n        taylor = (taylor_delta + taylor_gamma) * fxfo.curve(\"usd\", \"usd\")[discount_date]\n        assert abs(taylor - expected) < 0.5\n\n    @pytest.mark.parametrize(\"payment\", [dt(2023, 6, 20), dt(2023, 3, 16)])\n    @pytest.mark.parametrize(\"delta_type\", [\"spot\", \"forward\"])\n    def test_all_5_greeks_example(self, fxfo, payment, delta_type) -> None:\n        fxc = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=payment,\n            notional=10e6,\n            strike=1.10,\n            delta_type=delta_type,\n        )\n        npv = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=10.0,\n        )\n        greeks = fxc.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=Dual(10.0, [\"vol\"], [100.0]),\n        )\n        f_d = fxfo.rate(\"eurusd\", dt(2023, 6, 20))\n        fxfo.fx_rates.update({\"eurusd\": 1.0625})\n        fxfo.update()\n        f_d2 = fxfo.rate(\"eurusd\", dt(2023, 6, 20))\n        if delta_type == \"forward\":\n            fwd_diff = f_d2 - f_d\n            discount_date = fxc.fx_option_params.delivery\n        else:\n            fwd_diff = 0.001\n            discount_date = dt(2023, 3, 20)\n        npv2 = fxc.npv(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=10.1,\n        )\n        fxc.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=Dual(10.1, [\"vol\"], [100.0]),\n        )\n        expected = npv2 - npv\n        taylor_delta = fwd_diff * greeks[\"delta\"] * 10e6\n        taylor_gamma = 0.5 * fwd_diff**2 * greeks[\"gamma\"] * 10e6\n        taylor_vega = 0.1 / 100.0 * greeks[\"vega\"] * 10e6\n        taylor_vomma = 0.5 * 0.1**2 / 10000.0 * greeks[\"vomma\"] * 10e6\n        taylor_vanna = 0.1 / 100.0 * fwd_diff * greeks[\"vanna\"] * 10e6\n        taylor = (\n            fxfo.curve(\"usd\", \"usd\")[discount_date] * (taylor_delta + taylor_gamma + taylor_vanna)\n            + taylor_vomma\n            + taylor_vega\n        )\n        assert abs(taylor - expected) < 5e-1\n\n    def test_kega(self, fxfo) -> None:\n        fxc = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            notional=10e6,\n            strike=1.10,\n            delta_type=\"spot_pa\",\n        )\n\n        d_eta = _OptionModelBlack76._d_plus_min_u(1.10 / 1.065, 0.10 * 0.5, -0.5)\n        result = fxc._analytic_kega(1.10 / 1.065, 0.99, -0.5, 0.10, 0.50, 1.065, 1.0, 1.10, d_eta)\n        expected = 0.355964619118249\n        assert abs(result - expected) < 1e-12\n\n    def test_bad_expiries_raises(self, fxfo) -> None:\n        fxc = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            notional=10e6,\n            strike=1.10,\n            delta_type=\"forward\",\n        )\n        vol_ = FXDeltaVolSmile(\n            nodes={\n                0.25: 8.9,\n                0.5: 8.7,\n                0.75: 10.15,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 18),\n            delta_type=\"forward\",\n        )\n        with pytest.raises(ValueError, match=\"`expiry` of VolSmile and OptionPeriod do not match\"):\n            fxc.npv(\n                rate_curve=fxfo.curve(\"eur\", \"usd\"),\n                disc_curve=fxfo.curve(\"usd\", \"usd\"),\n                fx=fxfo,\n                fx_vol=vol_,\n            )\n\n    @pytest.mark.parametrize(\"smile\", [True, False])\n    def test_call_cashflows(self, fxfo, smile) -> None:\n        vol_ = (\n            8.9\n            if not smile\n            else FXDeltaVolSmile(\n                nodes={0.5: 8.9},\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                delta_type=\"forward\",\n            )\n        )\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n        )\n        result = fxo.cashflows(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n            base=\"eur\",\n        )\n        assert isinstance(result, dict)\n        expected = 140451.5273\n        assert (result[defaults.headers[\"cashflow\"]] - expected) < 1e-3\n        assert result[defaults.headers[\"currency\"]] == \"USD\"\n        assert result[defaults.headers[\"type\"]] == \"FXCallPeriod\"\n\n    @pytest.mark.parametrize(\"delta_type\", [\"spot\", \"forward\"])\n    def test_sticky_delta_delta_vol_smile_against_ad(self, fxfo, delta_type) -> None:\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n            delta_type=delta_type,\n        )\n        vol_ = FXDeltaVolSmile(\n            nodes={\n                0.25: 8.9,\n                0.5: 8.7,\n                0.75: 10.15,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"spot\",\n        )\n        gks = fxo.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )\n\n        v_deli = fxfo.curve(\"usd\", \"usd\")[fxo.fx_option_params.delivery]\n        v_spot = fxfo.curve(\"usd\", \"usd\")[dt(2023, 3, 20)]\n\n        # this is the actual derivative of vol with respect to either spot or forward via AD\n        if \"spot\" in delta_type:\n            z_v_0 = v_deli / v_spot\n            expected = gradient(gks[\"__vol\"], [\"fx_eurusd\"])[0]\n        else:\n            z_v_0 = 1.0\n            w_deli = fxfo.curve(\"eur\", \"usd\")[fxo.fx_option_params.delivery]\n            w_spot = fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)]\n            expected = (\n                gradient(gks[\"__vol\"], [\"fx_eurusd\"])[0] * v_deli * w_spot / (v_spot * w_deli)\n            )\n\n        # this is the reverse engineered part of the sticky delta formula to get dsigma_dfspot\n        result = (gks[\"delta_sticky\"] - gks[\"delta\"]) * v_deli / (z_v_0 * gks[\"vega\"])\n        # delta is\n        assert abs(result - expected) < 1e-3\n\n    @pytest.mark.parametrize(\n        (\"smile\", \"expected\"),\n        [\n            (\n                FXSabrSmile(\n                    nodes={\"alpha\": 0.05, \"beta\": 1.0, \"rho\": 0.01, \"nu\": 0.03},\n                    eval_date=dt(2024, 5, 7),\n                    expiry=dt(2024, 5, 28),\n                    id=\"smile\",\n                    pair=\"eurusd\",\n                ),\n                0.700594,\n            ),\n            (\n                FXSabrSurface(\n                    expiries=[dt(2024, 5, 23), dt(2024, 6, 4)],\n                    node_values=[[0.05, 1.0, 0.01, 0.03], [0.052, 1.0, 0.03, 0.05]],\n                    eval_date=dt(2024, 5, 7),\n                    id=\"smile\",\n                    pair=\"eurusd\",\n                ),\n                0.701191,\n            ),\n            (\n                FXDeltaVolSmile(\n                    nodes={0.25: 10, 0.5: 9, 0.75: 11},\n                    eval_date=dt(2024, 5, 7),\n                    expiry=dt(2024, 5, 28),\n                    delta_type=\"forward\",\n                    id=\"smile\",\n                ),\n                0.704091,\n            ),\n        ],\n    )\n    def test_sticky_delta_calculation(self, smile, expected) -> None:\n        from rateslib import IRS, FXBrokerFly, FXCall, FXRiskReversal, FXStraddle, FXSwap, Solver\n\n        usd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"nyc\", id=\"usd\")\n        eur = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"tgt\", id=\"eur\")\n        eurusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, id=\"eurusd\")\n        # Create an FX Forward market with spot FX rate data\n        spot = dt(2024, 5, 9)\n        fxr = FXRates({\"eurusd\": 1.0760}, settlement=spot)\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\"eureur\": eur, \"usdusd\": usd, \"eurusd\": eurusd},\n        )\n        # Solve the Curves to market\n        pre_solver = Solver(\n            curves=[eur, eurusd, usd],\n            instruments=[\n                IRS(spot, \"3W\", spec=\"eur_irs\", curves=\"eur\"),\n                IRS(spot, \"3W\", spec=\"usd_irs\", curves=\"usd\"),\n                FXSwap(spot, \"3W\", pair=\"eurusd\", curves=[None, \"eurusd\", None, \"usd\"]),\n            ],\n            s=[3.90, 5.32, 8.85],\n            fx=fxf,\n            id=\"fxf\",\n        )\n\n        option_args = dict(\n            pair=\"eurusd\",\n            expiry=dt(2024, 5, 28),\n            calendar=\"tgt|fed\",\n            delta_type=\"spot\",\n            curves=[\"eurusd\", \"usd\"],\n            vol=\"smile\",\n        )\n\n        # Calibrate the Smile to market option data\n        solver = Solver(\n            pre_solvers=[pre_solver],\n            curves=[smile],\n            instruments=[\n                FXStraddle(strike=\"atm_delta\", **option_args),\n                FXRiskReversal(strike=(\"-25d\", \"25d\"), **option_args),\n                FXRiskReversal(strike=(\"-10d\", \"10d\"), **option_args),\n                FXBrokerFly(strike=((\"-25d\", \"25d\"), \"atm_delta\"), **option_args),\n                FXBrokerFly(strike=((\"-10d\", \"10d\"), \"atm_delta\"), **option_args),\n            ],\n            s=[5.493, -0.157, -0.289, 0.071, 0.238],\n            fx=fxf,\n            id=\"smile\",\n        )\n\n        fxc = FXCall(**option_args, notional=100e6, strike=1.07, premium=982144.59)\n\n        result = fxc.analytic_greeks(solver=solver)[\"delta_sticky\"]\n        assert abs(result - expected) < 1e-6\n\n    def test_sticky_delta_direct_from_ad(self, fxfo) -> None:\n        # this test will use AD to directly measure dP_dfs and compare that with the\n        # analytical derivation of sticky delta.\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.101,\n            notional=20e6,\n            delta_type=\"spot\",\n        )\n        vol_ = FXDeltaVolSmile(\n            nodes={\n                0.25: 8.9,\n                0.5: 8.7,\n                0.75: 10.15,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"spot\",\n        )\n        gks = fxo.analytic_greeks(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n        )\n\n        P = 20e6 * gks[\"__bs76\"]\n        dP_dfs = gradient(P, [\"fx_eurusd\"])[0]\n        v_spot = fxfo.curve(\"usd\", \"usd\")[dt(2023, 3, 20)]\n        result = dP_dfs / (20e6 * v_spot)\n        expected = gks[\"delta_sticky\"]\n        assert abs(result - expected) < 1e-8\n\n    def test_no_strike_raises(self):\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=NoInput(0),\n            notional=20e6,\n            delta_type=\"spot\",\n        )\n        with pytest.raises(ValueError, match=err.VE_NEEDS_STRIKE):\n            fxo.try_unindexed_reference_cashflow().unwrap()\n\n    def test_try_rate_with_metric(self, fxfo):\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=1.1,\n            notional=20e6,\n            delta_type=\"spot\",\n        )\n        vol_ = FXDeltaVolSmile(\n            nodes={\n                0.25: 8.9,\n                0.5: 8.7,\n                0.75: 10.15,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"spot\",\n        )\n        result1 = fxo.try_rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n            metric=\"Pips\",\n        )\n        result2 = fxo.try_rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx_vol=vol_,\n            fx=fxfo,\n            metric=\"Percent\",\n        )\n        result3 = fxo.try_rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx_vol=vol_,\n            fx=fxfo,\n        )\n        assert result1.unwrap() != result2.unwrap()\n        assert result1.unwrap() == result3.unwrap()  # default is Pips\n\n    def test_try_rate_errs(self, fxfo):\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=dt(2023, 6, 16),\n            delivery=dt(2023, 6, 20),\n            # payment=dt(2023, 6, 20),\n            strike=NoInput(0),\n            notional=20e6,\n            delta_type=\"spot\",\n        )\n        vol_ = FXDeltaVolSmile(\n            nodes={\n                0.25: 8.9,\n                0.5: 8.7,\n                0.75: 10.15,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            delta_type=\"spot\",\n        )\n        assert fxo.try_rate(\n            rate_curve=fxfo.curve(\"eur\", \"usd\"),\n            disc_curve=fxfo.curve(\"usd\", \"usd\"),\n            fx=fxfo,\n            fx_vol=vol_,\n            metric=\"Pips\",\n        ).is_err\n\n    @pytest.mark.skip(reason=\"non-deliverability of FXOption period not implemented in v2.5\")\n    def test_non_deliverable_fx_option_third_currency_raises(self, fxfo):\n        # this is an NOKSEK FX option with notional in NOK, normal value in SEK but non-deliverable\n        # requiring conversion to USD\n        with pytest.raises(ValueError, match=err.VE_MISMATCHED_FX_PAIR_ND_PAIR[:15]):\n            FXCallPeriod(\n                delivery=dt(2000, 3, 1),\n                pair=\"NOKSEK\",\n                nd_pair=\"SEKUSD\",\n                strike=1.0,\n                expiry=dt(2000, 2, 28),\n            )\n        # assert fxo.settlement_params.notional_currency == \"nok\"\n        # assert fxo.settlement_params.currency == \"usd\"\n        # assert fxo.non_deliverable_params.reference_currency == \"sek\"\n        #\n        # fxo = FXCallPeriod(\n        #     delivery=dt(2000, 3, 1),\n        #     pair=\"NOKSEK\",\n        #     strike=1.0,\n        #     expiry=dt(2000, 2, 28),\n        # )\n        # assert fxo.settlement_params.notional_currency == \"nok\"\n        # assert fxo.settlement_params.currency == \"sek\"\n        # assert fxo.non_deliverable_params is None\n\n    @pytest.mark.skip(reason=\"non-deliverability of FXOption period not implemented in v2.5\")\n    @pytest.mark.parametrize(\"ndpair\", [\"usdbrl\", \"brlusd\"])\n    def test_non_deliverable_fx_option_npv_vol_given(self, ndpair):\n        # this is an USDBRL FX option period non-deliverable into USD.\n        fxf = FXForwards(\n            fx_rates=FXRates({\"usdbrl\": 5.0}, settlement=dt(2000, 1, 1)),\n            fx_curves={\n                \"usdusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.98}),\n                \"brlusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.983}),\n                \"brlbrl\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.984}),\n            },\n        )\n        fxo = FXCallPeriod(\n            delivery=dt(2000, 3, 1),\n            pair=\"USDBRL\",\n            strike=1.0,\n            expiry=dt(2000, 2, 28),\n        )\n        fxond = FXCallPeriod(\n            delivery=dt(2000, 3, 1),\n            pair=\"USDBRL\",\n            nd_pair=ndpair,\n            strike=1.0,\n            expiry=dt(2000, 2, 28),\n        )\n\n        npv = fxo.local_npv(fx=fxf, fx_vol=10.0, disc_curve=fxf.curve(\"brl\", \"usd\"))\n        npv_nd = fxond.local_npv(fx=fxf, fx_vol=10.0, disc_curve=fxf.curve(\"usd\", \"usd\"))\n\n        # local NPV should be expressed in USD for ND type\n        result = npv / 5.0 - npv_nd\n        assert abs(result) < 1e-9\n\n    @pytest.mark.skip(reason=\"non-deliverability of FXOption period not implemented in v2.5\")\n    @pytest.mark.parametrize((\"ndpair\", \"fxfix\"), [(\"usdbrl\", 5.25), (\"brlusd\", 1 / 5.25)])\n    def test_non_deliverable_fx_option_npv_vol_given_fx_fixing(self, ndpair, fxfix):\n        # this is an USDBRL FX option period non-deliverable into USD.\n        fxf = FXForwards(\n            fx_rates=FXRates({\"usdbrl\": 5.0}, settlement=dt(2000, 1, 1)),\n            fx_curves={\n                \"usdusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.98}),\n                \"brlusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.983}),\n                \"brlbrl\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 6, 1): 0.984}),\n            },\n        )\n        fxv = FXDeltaVolSmile(\n            nodes={0.4: 10.0, 0.6: 11.0},\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2000, 2, 28),\n            delta_type=\"spot\",\n        )\n        fxo = FXCallPeriod(\n            delivery=dt(2000, 3, 1),\n            pair=\"USDBRL\",\n            strike=1.0,\n            expiry=dt(2000, 2, 28),\n        )\n        fxond = FXCallPeriod(\n            delivery=dt(2000, 3, 1),\n            pair=\"USDBRL\",\n            nd_pair=ndpair,\n            fx_fixings=fxfix,\n            strike=1.0,\n            expiry=dt(2000, 2, 28),\n        )\n\n        npv = fxo.local_npv(\n            fx=fxf,\n            fx_vol=fxv,\n            rate_curve=fxf.curve(\"usd\", \"usd\"),\n            disc_curve=fxf.curve(\"brl\", \"usd\"),\n        )\n        npv_nd = fxond.local_npv(\n            fx=fxf,\n            fx_vol=fxv,\n            rate_curve=fxf.curve(\"usd\", \"usd\"),\n            disc_curve=fxf.curve(\"usd\", \"usd\"),\n        )\n\n        # local NPV should be expressed in USD for ND type\n        result = (\n            npv_nd\n            * 5.25\n            / fxf.curve(\"usd\", \"usd\")[dt(2000, 3, 1)]\n            * fxf.curve(\"brl\", \"usd\")[dt(2000, 3, 1)]\n            - npv\n        )\n        # these should be different beucase of the fix: compare with test above\n        assert abs(result) < 1e-8\n\n    def test_cashflow_no_pricing_objects(self):\n        # this is an NOKSEK FX option with notional in NOK, normal value in SEK but non-deliverable\n        # requiring conversion to USD\n        fxo = FXCallPeriod(\n            delivery=dt(2000, 3, 1),\n            pair=\"NOKSEK\",\n            strike=1.0,\n            expiry=dt(2000, 2, 28),\n        )\n        cf = fxo.cashflows()\n        assert isinstance(cf, dict)\n\n\nclass TestIROption:\n    @pytest.mark.parametrize(\"today\", [dt(2026, 1, 3), dt(2026, 4, 15)])\n    @pytest.mark.parametrize(\n        (\"strike\", \"fixing\", \"klass\"), [(2.0, 2.5, IRSCallPeriod), (2.0, 1.5, IRSPutPeriod)]\n    )\n    def test_cashflow_known_exercise(self, today, strike, fixing, klass):\n        # if we know that the exercise will occur (from the fixing_value) value the cashflow\n        curve = Curve({today: 1.0, dt(2028, 4, 15): 0.95}, calendar=\"nyc\")\n        ir_period = klass(\n            expiry=dt(2027, 2, 3),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=strike,\n            notional=100e6,\n            option_fixings=fixing,\n        )\n        immediate_npv = ir_period.ir_option_params.option_fixing.irs.npv(curves=curve)\n        forward_npv = immediate_npv / curve[dt(2027, 2, 5)] * 100.0\n\n        result = ir_period.unindexed_reference_cashflow(rate_curve=curve, index_curve=curve)\n        assert abs(abs(result) - abs(forward_npv)) < 1e-8\n\n    def test_cashflow_option_value(self):\n        # if we know that the exercise will occur (from the fixing_value) value the cashflow\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"tgt\"\n        )\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n        )\n        cashflow = ir_period.unindexed_reference_cashflow(\n            rate_curve=curve, ir_vol=25.16, index_curve=curve\n        )\n        expected = cashflow * curve[dt(2027, 2, 18)]\n        result = ir_period.npv(rate_curve=curve, ir_vol=25.16, index_curve=curve)\n        assert abs(result - expected) < 1e-8\n        assert abs(result - 145000) < 500.0\n\n    def test_option_npv_different_csa(self):\n        # test that a forward NPV alignbed with cashflow does not change, but an NPV does.\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"tgt\"\n        )\n        alt_disc_curve = Curve(nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.91}, calendar=\"tgt\")\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.000,\n            notional=100e6,\n        )\n        fwd_result = ir_period.npv(\n            rate_curve=curve,\n            ir_vol=25.16,\n            index_curve=curve,\n            disc_curve=alt_disc_curve,\n            forward=dt(2027, 2, 18),\n        )\n        imm_exp = fwd_result * alt_disc_curve[dt(2027, 2, 18)]\n        imm_res = ir_period.npv(\n            rate_curve=curve, ir_vol=25.16, index_curve=curve, disc_curve=alt_disc_curve\n        )\n        assert abs(imm_exp - imm_res) < 1e-6\n\n        fwd_result2 = ir_period.npv(\n            rate_curve=curve, ir_vol=25.16, index_curve=curve, forward=dt(2027, 2, 18)\n        )\n        imm_exp2 = fwd_result * curve[dt(2027, 2, 18)]\n        imm_res2 = ir_period.npv(rate_curve=curve, ir_vol=25.16, index_curve=curve)\n        assert abs(imm_exp2 - imm_res2) < 1e-6\n\n        assert abs(fwd_result - fwd_result2) < 1e-6\n        assert abs(imm_res - imm_res2) > 2000.0\n\n    @pytest.mark.parametrize(\n        (\"metric\", \"expected\"),\n        [\n            (\"NormalVol\", 75.792872),\n            (\"Premium\", 149725.796514),\n            (\"PercentNotional\", 0.149725),\n            (\"black_vol_shift_0\", 25.16),\n            (\"Black_vol_shift_100\", 18.880156),\n            (\"Black_vol_shift_200\", 15.111396),\n            (\"Black_vol_shift_300\", 12.597702),\n            (\"Black_vol_shift_117\", 18.112063),\n        ],\n    )\n    def test_option_rate(self, metric, expected):\n        # if we know that the exercise will occur (from the fixing_value) value the cashflow\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n        )\n        result = ir_period.rate(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=25.16,\n            metric=metric,\n        )\n        assert abs(result - expected) < 1e-5\n\n    @pytest.mark.parametrize(\n        (\"smile\", \"expected\"),\n        [\n            (\n                IRSabrSmile(\n                    eval_date=dt(2026, 2, 16),\n                    expiry=dt(2027, 2, 16),\n                    tenor=\"6m\",\n                    beta=0.5,\n                    nodes={\"alpha\": 0.4, \"rho\": -0.05, \"nu\": 0.4},\n                    irs_series=\"usd_irs\",\n                ),\n                70.27947168577464,\n            ),\n            (\n                IRSabrSmile(\n                    eval_date=dt(2026, 2, 16),\n                    expiry=dt(2027, 2, 16),\n                    tenor=\"6m\",\n                    beta=0.5,\n                    nodes={\"alpha\": 0.4, \"rho\": -0.05, \"nu\": 0.4},\n                    irs_series=\"usd_irs\",\n                    shift=200.0,\n                ),\n                90.68148269529259,\n            ),\n            (\n                IRSabrSmile(\n                    eval_date=dt(2026, 2, 16),\n                    expiry=dt(2027, 2, 16),\n                    tenor=\"6m\",\n                    beta=0.5,\n                    nodes={\"alpha\": 0.3, \"rho\": -0.05, \"nu\": 0.4},\n                    irs_series=\"usd_irs\",\n                    shift=50.0,\n                ),\n                56.96593721292377,\n            ),\n        ],\n    )\n    def test_ir_option_rate_from_sabr(self, smile, expected):\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n        )\n        result = ir_period.rate(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=smile,\n            metric=\"normal_vol\",\n        )\n        assert abs(result - expected) < 1e-5\n\n    def test_cashflows(self):\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n        )\n        result = ir_period.cashflows(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=25.16,\n        )\n        expected = {\n            \"Base Ccy\": \"USD\",\n            \"Cashflow\": 149725.7965143448,\n            \"Ccy\": \"USD\",\n            \"Collateral\": None,\n            \"DF\": 0.969902553602701,\n            \"FX Rate\": 1.0,\n            \"NPV\": 145219.43237946142,\n            \"NPV Ccy\": 145219.43237946142,\n            \"Notional\": 100000000.0,\n            \"Payment\": dt(2027, 2, 18, 0, 0),\n            \"Type\": \"IRSCallPeriod\",\n        }\n        assert result == expected\n\n    def test_analytic_greeks(self):\n        from rateslib.instruments import IRS\n        from rateslib.solver import Solver\n\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        curve_solver = Solver(\n            curves=[curve],\n            instruments=[IRS(dt(2026, 2, 16), \"1y\", spec=\"usd_irs\", curves=curve)],\n            s=[3.0],\n        )\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n        )\n        result = ir_period.analytic_greeks(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=25.16,\n        )\n        expected = {\n            \"__bs76\": 0.2792463326582493,\n            \"__forward\": 2.9774664970728626,\n            \"__sqrt_t\": 1.0,\n            \"__strike\": 3.020383,\n            \"__vol\": 0.2516,\n            \"__notional\": 100e6,\n            \"delta\": 0.5274735620216011,\n            \"gamma\": 0.5312770889914765,\n            \"vanna\": 0.28897329599293436,\n            \"vega\": 1.185019484592725,\n            \"delta_usd\": 2534.939100519541,\n            \"gamma_usd\": 25.532181384278392,\n            \"vega_usd\": 5694.981592743021,\n            \"vanna_usd\": 13.88751512418925,\n            \"delta_sticky\": 0.5274735620216011,\n        }\n\n        # forward rate is increased by 1bp. Check the gamma and vanna values.\n        curve_solver.s = [3.01]\n        curve_solver.iterate()\n        result2 = ir_period.analytic_greeks(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=25.16,\n        )\n        assert abs(result2[\"delta_usd\"] - result[\"delta_usd\"] - result[\"gamma_usd\"]) < 1.0\n        assert abs(result2[\"vega_usd\"] - result[\"vega_usd\"] - result[\"vanna_usd\"]) < 2.0\n        assert all(abs(v - result[k]) < 1e-5 for k, v in expected.items())\n\n    def test_analytic_greeks_bachelier(self):\n        # this test compares the analytic_greeks results with a Solver framework (i,e, independent\n        # calculations) configured about a normal_vol metric.\n        from rateslib.instruments import IRS, IRVolValue\n        from rateslib.solver import Solver\n        from rateslib.volatility import IRSplineCube\n\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225},\n            calendar=\"nyc\",\n            id=\"r\",\n        )\n        curve_solver = Solver(\n            curves=[curve],\n            instruments=[IRS(dt(2026, 2, 16), \"1y\", spec=\"usd_irs\", curves=curve)],\n            s=[3.0],\n        )\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n        )\n        cube = IRSplineCube(\n            eval_date=dt(2026, 2, 16),\n            expiries=[dt(2027, 2, 16)],\n            tenors=[\"6m\"],\n            strikes=[0.0],\n            parameters=75.0,\n            irs_series=\"usd_irs\",\n            id=\"v\",\n        )\n        ir_vol_solver = Solver(\n            pre_solvers=[curve_solver],\n            surfaces=[cube],\n            instruments=[\n                IRVolValue(\n                    expiry=dt(2027, 2, 16),\n                    tenor=\"6m\",\n                    strike=3.0,\n                    irs_series=\"usd_irs\",\n                    metric=\"normal_vol\",\n                    curves=curve,\n                    vol=cube,\n                )\n            ],\n            s=[75.0],\n            instrument_labels=[\"vol\"],\n        )\n        result = ir_period.analytic_greeks(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=cube,\n        )\n        expected = {\n            \"__bachelier\": 0.27823818012037993,\n            \"__forward\": 2.9774664970728626,\n            \"__sqrt_t\": 1.0,\n            \"__strike\": 3.020383,\n            \"__vol\": 0.75,\n            \"__notional\": 100e6,\n            \"delta\": 0.47718417514818345,\n            \"gamma\": 0.5310528998576186,\n            \"vanna\": 0.030387911108272263,\n            \"vega\": 0.398289674893214,\n            \"vomma\": 0.001738857168037003,\n            \"delta_usd\": 2293.2577304846204,\n            \"gamma_usd\": 25.521407274593162,\n            \"vega_usd\": 1914.1055455944868,\n            \"vanna_usd\": 1.4603860666729847,\n            \"vomma_usd\": 0.08356621720682876,\n            \"delta_sticky\": 0.47718417514818345,\n        }\n\n        # first test that the calculations are generally static, i.e. quantities are obtainable\n        assert all(abs(v - result[k]) < 1e-5 for k, v in expected.items())\n\n        p = ir_period.npv(\n            rate_curve=curve,\n            index_curve=curve,\n            disc_curve=curve,\n            ir_vol=cube,\n            local=True,\n        )\n        _ = ir_vol_solver.delta(p)\n        exp_delta = _.iloc[0, 0]\n        exp_vega = _.iloc[1, 0]\n\n        for res, exp in zip([\"delta_usd\", \"vega_usd\"], [exp_delta, exp_vega]):\n            percent_diff = abs(result[res] - exp) / abs(exp)\n            assert percent_diff < 0.025\n\n        ir_vol_solver._set_ad_order(2)\n        p2 = ir_period.npv(\n            rate_curve=curve,\n            index_curve=curve,\n            disc_curve=curve,\n            ir_vol=cube,\n            local=True,\n        )\n        _ = ir_vol_solver.gamma(p2)\n        ir_vol_solver._set_ad_order(1)\n        exp_gamma = _.iloc[0, 0]\n        exp_vomma = _.iloc[1, 1]\n        exp_vanna = _.iloc[1, 0]\n\n        for res, exp in zip(\n            [\"gamma_usd\", \"vanna_usd\", \"vomma_usd\"], [exp_gamma, exp_vanna, exp_vomma]\n        ):\n            percent_diff2 = abs(result[res] - exp) / abs(exp)\n            assert percent_diff2 < 0.07 or abs(result[res] - exp) < 0.5\n\n        # test finite difference\n\n        # forward rate is increased by 1bp. Check the gamma and vanna values.\n        curve_solver.s = [3.01]\n        curve_solver.iterate()\n        ir_vol_solver.iterate()\n        result2 = ir_period.analytic_greeks(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=cube,\n        )\n        assert abs(result2[\"delta_usd\"] - result[\"delta_usd\"] - result[\"gamma_usd\"]) < 1e-0\n        assert abs(result2[\"vega_usd\"] - result[\"vega_usd\"] - result[\"vanna_usd\"]) < 5e-1\n\n        curve_solver.s = [3.00]\n        ir_vol_solver.s = [76.0]\n        curve_solver.iterate()\n        ir_vol_solver.iterate()\n        result3 = ir_period.analytic_greeks(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=cube,\n        )\n        assert abs(result3[\"delta_usd\"] - result[\"delta_usd\"] - result[\"vanna_usd\"]) < 2e-2\n        assert abs(result3[\"vega_usd\"] - result[\"vega_usd\"] - result[\"vomma_usd\"]) < 2e-3\n\n    @pytest.mark.parametrize(\n        (\"ir_vol\", \"expected\"),\n        [\n            (\n                IRSplineCube(\n                    eval_date=dt(2000, 1, 1),\n                    expiries=[\"1y\"],\n                    tenors=[\"1y\"],\n                    strikes=[-25, 0.0, 25.0],\n                    irs_series=\"usd_irs\",\n                    parameters=[[[33.5, 32.5, 34.1]]],\n                    k=2,\n                    pricing_model=\"bachelier\",\n                ),\n                0.5657673654151706,\n            ),\n            (\n                IRSplineCube(\n                    eval_date=dt(2000, 1, 1),\n                    expiries=[\"1y\"],\n                    tenors=[\"1y\"],\n                    strikes=[-25, 0.0, 25.0],\n                    irs_series=\"usd_irs\",\n                    parameters=[[[33.5, 32.5, 34.1]]],\n                    k=2,\n                    pricing_model=\"black76\",\n                ),\n                0.6338221418100394,\n            ),\n            (\n                IRSabrCube(\n                    eval_date=dt(2000, 1, 1),\n                    expiries=[\"1y\"],\n                    tenors=[\"1y\"],\n                    beta=0.5,\n                    irs_series=\"usd_irs\",\n                    alpha=0.5,\n                    rho=-0.05,\n                    nu=0.65,\n                ),\n                0.5538666266910927,\n            ),\n        ],\n    )\n    def test_analytic_sticky_delta(self, ir_vol, expected):\n        ir_period = IRSCallPeriod(\n            expiry=dt(2001, 1, 1),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.45,\n            notional=100e6,\n        )\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2003, 1, 1): 0.9})\n        result = ir_period.analytic_greeks(\n            disc_curve=curve, rate_curve=curve, index_curve=curve, ir_vol=ir_vol\n        )\n        assert abs(result[\"delta\"] - result[\"delta_sticky\"]) > 0.01\n\n        assert abs(result[\"delta_sticky\"] - expected) < 1e-5\n\n    def test_repr(self):\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n        )\n        assert ir_period.__repr__() == f\"<rl.IRSCallPeriod at {hex(id(ir_period))}>\"\n\n    def test_raise_on_no_strike(self):\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            notional=100e6,\n        )\n        with pytest.raises(\n            ValueError,\n            match=\"An FXOptionPeriod cashflow cannot be determined without setting a `strike`.\",\n        ):\n            ir_period.unindexed_cashflow()\n\n    def test_cash_collateralized_settlement_with_fixing(self):\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            notional=100e6,\n            strike=3.0,\n            option_fixings=3.05,\n            settlement_method=\"CashCollateralized\",\n        )\n        curve = Curve({dt(2027, 2, 16): 1.0, dt(2028, 2, 16): 0.98})\n        result = ir_period.unindexed_cashflow(index_curve=curve)\n        expected = 24885.54  #  approx 5 * 0.993 * 5000\n        assert abs(result - expected) < 1e-2\n\n    def test_try_rate(self):\n        # if we know that the exercise will occur (from the fixing_value) value the cashflow\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n        )\n        result = ir_period.try_rate(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=25.16,\n            metric=\"normal_vol\",\n        )\n        assert isinstance(result, Ok)\n\n        result = ir_period.try_rate(\n            rate_curve=NoInput(0),\n            disc_curve=NoInput(0),\n            index_curve=NoInput(0),\n            metric=\"normal_vol\",\n        )\n        assert isinstance(result, Err)\n\n    def test_rate_bachelier_metric(self):\n        curve = Curve(\n            nodes={dt(2026, 2, 16): 1.0, dt(2028, 2, 16): 0.941024343401225}, calendar=\"nyc\"\n        )\n        ir_period = IRSCallPeriod(\n            expiry=dt(2027, 2, 16),\n            irs_series=\"usd_irs\",\n            tenor=\"6m\",\n            strike=3.020383,\n            notional=100e6,\n            metric=\"normal_vol\",\n        )\n        smile = IRSplineSmile(\n            nodes={0: 50.0},\n            eval_date=dt(2026, 2, 16),\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            irs_series=\"usd_irs\",\n        )\n        smile2 = IRSabrSmile(\n            nodes={\"alpha\": 0.5, \"rho\": 0.01, \"nu\": 0.03},\n            beta=0.5,\n            eval_date=dt(2026, 2, 16),\n            expiry=dt(2027, 2, 16),\n            tenor=\"6m\",\n            irs_series=\"usd_irs\",\n        )\n        result = ir_period.try_rate(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=smile,\n        )\n        expected = 50.0\n        assert abs(result.unwrap() - expected) < 1e-2\n\n        result2 = ir_period.try_rate(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=smile2,\n        )\n        expected = 86.6790263475833\n        assert abs(result2.unwrap() - expected) < 1e-5\n\n        result3 = ir_period.try_rate(\n            rate_curve=curve,\n            disc_curve=curve,\n            index_curve=curve,\n            ir_vol=smile,\n            metric=\"black_vol_shift_50\",\n        )\n        expected = 14.2149591308255\n        assert abs(result3.unwrap() - expected) < 1e-5\n"
  },
  {
    "path": "python/tests/periods/test_static_npv.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.curves import Curve\nfrom rateslib.periods import Cashflow\n\n\nclass TestStaticNPV:\n    @pytest.mark.parametrize(\n        (\"settlement\", \"forward\", \"expected\"),\n        [\n            (dt(2000, 1, 1), dt(2000, 1, 1), 80.0),\n            (dt(2000, 1, 1), dt(2000, 1, 6), 100.0 * 0.8 / 0.75),\n            (dt(2000, 1, 2), dt(2000, 1, 5), 100.0),\n            (dt(2000, 1, 4), dt(2000, 1, 5), 0.0),\n        ],\n    )\n    def test_settlement_forward(self, settlement, forward, expected):\n        # test the example in the book\n        curve = Curve(\n            nodes={\n                dt(2000, 1, 1): 1.0,\n                dt(2000, 1, 2): 0.95,\n                dt(2000, 1, 3): 0.90,\n                dt(2000, 1, 4): 0.85,\n                dt(2000, 1, 5): 0.80,\n                dt(2000, 1, 6): 0.75,\n            }\n        )\n        cf = Cashflow(\n            currency=\"usd\",\n            notional=-100.0,\n            payment=dt(2000, 1, 5),\n            ex_dividend=dt(2000, 1, 3),\n        )\n        result = cf.npv(disc_curve=curve, settlement=settlement, forward=forward)\n        assert abs(result - expected) < 1e-7\n"
  },
  {
    "path": "python/tests/scheduling/test_calendars.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib import calendars, defaults, fixings\nfrom rateslib.curves import Curve\nfrom rateslib.default import NoInput\nfrom rateslib.instruments import IRS\nfrom rateslib.scheduling import (\n    Adjuster,\n    Cal,\n    Convention,\n    Frequency,\n    RollDay,\n    UnionCal,\n    add_tenor,\n    dcf,\n    get_calendar,\n    get_imm,\n    next_imm,\n)\nfrom rateslib.scheduling.calendars import (\n    _adjust_date,\n    _get_years_and_months,\n    _is_day_type_tenor,\n)\nfrom rateslib.scheduling.frequency import _get_frequency, _get_fx_expiry_and_delivery_and_payment\n\n\n@pytest.fixture\ndef cal_():\n    return Cal([dt(_, 1, 3) for _ in range(1970, 2200)], [5, 6])\n\n\n@pytest.mark.parametrize(\n    (\"date\", \"expected\"),\n    [\n        (dt(2022, 1, 1), True),  # sat\n        (dt(2022, 1, 2), True),  # sun\n        (dt(2022, 1, 3), True),  # mon new year hol\n        (dt(2022, 1, 4), False),  # tues\n        (dt(2022, 1, 5), False),  # wed\n    ],\n)\ndef test_is_non_bus_day(date, expected, cal_) -> None:\n    result = cal_.is_non_bus_day(date)\n    assert result == expected\n\n\ndef test_is_non_bus_day_raises() -> None:\n    obj = \"not a cal object\"\n    with pytest.raises(AttributeError):\n        obj._is_non_bus_day(dt(2022, 1, 1))\n\n\n@pytest.mark.parametrize(\n    \"date\",\n    [\n        dt(2021, 12, 29),\n        dt(2021, 12, 30),\n        dt(2021, 12, 31),\n        dt(2021, 1, 1),\n        dt(2021, 1, 2),\n        dt(2021, 1, 3),\n        dt(2021, 1, 4),\n        dt(2021, 1, 5),\n    ],\n)\ndef test_cal_no_hols(date) -> None:\n    cal_no_hols = Cal([], [])\n    assert not cal_no_hols.is_non_bus_day(date)\n\n\ndef test_named_cal() -> None:\n    ldn_cal = get_calendar(\"ldn\")\n    assert ldn_cal.is_non_bus_day(dt(2022, 1, 1))\n    assert ldn_cal.is_bus_day(dt(2022, 1, 5))\n\n\ndef test_multiple_named_cal() -> None:\n    ldn_cal = get_calendar(\"ldn\")\n    stk_cal = get_calendar(\"stk\")\n\n    assert ldn_cal.is_non_bus_day(dt(2023, 1, 2))\n    assert stk_cal.is_bus_day(dt(2023, 1, 2))\n\n    assert ldn_cal.is_bus_day(dt(2023, 1, 6))\n    assert stk_cal.is_non_bus_day(dt(2023, 1, 6))\n\n    merged_cal = get_calendar(\"LDN,stk\")\n    assert merged_cal.is_non_bus_day(dt(2023, 1, 2))\n    assert merged_cal.is_non_bus_day(dt(2023, 1, 6))\n\n\ndef test_add_tenor_raises() -> None:\n    # this raise is superfluous by the design principles of private methods\n    with pytest.raises(ValueError):\n        add_tenor(dt(2022, 1, 1), \"1X\", \"mf\", None)\n\n\n@pytest.mark.parametrize(\n    (\"tenor\", \"expected\"),\n    [\n        (\"1M\", dt(2022, 1, 31)),\n        (\"2m\", dt(2022, 2, 28)),\n        (\"6M\", dt(2022, 6, 30)),\n        (\"1d\", dt(2022, 1, 1)),\n        (\"32d\", dt(2022, 2, 1)),\n        (\"1y\", dt(2022, 12, 31)),\n        (\"0.5y\", dt(2022, 6, 30)),\n    ],\n)\ndef test_add_tenor(tenor, expected) -> None:\n    result = add_tenor(dt(2021, 12, 31), tenor, \"NONE\", NoInput(0))\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    (\"tenor\", \"expected\", \"roll\"),\n    [\n        (\"-1M\", dt(2022, 1, 31), \"eom\"),\n        (\"-1M\", dt(2022, 1, 28), NoInput(0)),\n        (\"-2m\", dt(2021, 12, 31), 31),\n        (\"-2m\", dt(2021, 12, 28), NoInput(0)),\n        (\"-1Y\", dt(2021, 2, 28), NoInput(0)),\n        (\"-1d\", dt(2022, 2, 27), NoInput(0)),\n        (\"-2y\", dt(2020, 2, 29), \"eom\"),\n        (\"-2y\", dt(2020, 2, 28), NoInput(0)),\n    ],\n)\ndef test_add_negative_tenor(tenor, expected, roll) -> None:\n    result = add_tenor(dt(2022, 2, 28), tenor, \"NONE\", NoInput(0), roll)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    (\"date\", \"tenor\", \"mod\", \"roll\", \"cal\", \"expected\"),\n    [\n        (dt(1990, 9, 28), \"-6m\", \"NONE\", 31, NoInput(0), dt(1990, 3, 31)),\n        (dt(1990, 9, 28), \"-6m\", \"NONE\", 29, NoInput(0), dt(1990, 3, 29)),\n        (dt(1990, 5, 29), \"3m\", \"NONE\", NoInput(0), NoInput(0), dt(1990, 8, 29)),\n        (dt(1990, 5, 29), \"3m\", \"NONE\", 31, NoInput(0), dt(1990, 8, 31)),\n        (dt(1990, 3, 31), \"6m\", \"MF\", 31, \"nyc\", dt(1990, 9, 28)),\n        (dt(2023, 4, 21), \"-3m\", \"P\", 23, \"bus\", dt(2023, 1, 23)),\n        (dt(2023, 6, 23), \"-3m\", \"P\", 25, \"bus\", dt(2023, 3, 24)),\n    ],\n)\ndef test_add_tenor_special_cases(date, tenor, mod, roll, cal, expected) -> None:\n    end = add_tenor(date, tenor, mod, cal, roll)\n    assert end == expected\n\n\n@pytest.mark.parametrize(\n    (\"date\", \"modifier\", \"expected\"),\n    [\n        (dt(2022, 1, 3), \"NONE\", dt(2022, 1, 3)),\n        (dt(2022, 1, 3), \"F\", dt(2022, 1, 4)),\n        (dt(2022, 1, 3), \"MF\", dt(2022, 1, 4)),\n        (dt(2022, 1, 3), \"P\", dt(2021, 12, 31)),\n        (dt(2022, 1, 3), \"MP\", dt(2022, 1, 4)),\n        (dt(2022, 7, 30), \"NONE\", dt(2022, 7, 30)),\n        (dt(2022, 7, 30), \"f\", dt(2022, 8, 1)),\n        (dt(2022, 7, 30), \"mf\", dt(2022, 7, 29)),\n        (dt(2022, 7, 30), \"p\", dt(2022, 7, 29)),\n        (dt(2022, 7, 30), \"mp\", dt(2022, 7, 29)),\n    ],\n)\ndef test_adjust_date(date, modifier, cal_, expected) -> None:\n    result = _adjust_date(date, modifier, cal_)\n    assert result == expected\n\n\ndef test_adjust_date_cal() -> None:\n    result = _adjust_date(dt(2022, 10, 1), \"F\", NoInput(0))\n    assert result == dt(2022, 10, 1)\n\n\ndef test_adjust_date_raises() -> None:\n    with pytest.raises(KeyError):\n        _adjust_date(dt(2000, 1, 1), \"BAD_STRING\", NoInput(0))\n\n\n@pytest.mark.parametrize(\n    (\"modifier\", \"expected\"),\n    [\n        (\"None\", dt(2022, 1, 3)),\n        (\"F\", dt(2022, 1, 4)),\n        (\"MF\", dt(2022, 1, 4)),\n        (\"P\", dt(2021, 12, 31)),\n        (\"MP\", dt(2022, 1, 4)),\n    ],\n)\ndef test_modifiers_som(cal_, modifier, expected) -> None:\n    result = add_tenor(dt(2021, 12, 3), \"1M\", modifier, cal_)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    (\"modifier\", \"expected\"),\n    [\n        (\"None\", dt(2021, 2, 28)),\n        (\"F\", dt(2021, 3, 1)),\n        (\"MF\", dt(2021, 2, 26)),\n        (\"P\", dt(2021, 2, 26)),\n        (\"MP\", dt(2021, 2, 26)),\n    ],\n)\ndef test_modifiers_eom(cal_, modifier, expected) -> None:\n    result = add_tenor(dt(2020, 12, 31), \"2M\", modifier, cal_)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    (\"start\", \"end\", \"conv\", \"expected\"),\n    [\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"ACT365F\", 0.2465753424657534),\n        (dt(2021, 1, 1), dt(2022, 4, 1), \"ACT365F+\", 1.2465753424657535),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"ACT365F+\", 0.2465753424657534),\n        (dt(2020, 6, 1), dt(2022, 4, 1), \"ACT365F+\", 1.832876712328767),\n        (dt(2020, 1, 1), dt(2052, 1, 2), \"ACT365F\", 32.02465753424657),\n        (dt(2020, 1, 1), dt(2052, 1, 2), \"ACT365F+\", 32.0027397260274),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"1\", 1.0),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"ACT360\", 0.2465753424657534 * 365 / 360),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"30360\", 0.250),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"30E360\", 0.250),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"ACTACTISDA\", 0.2465753424657534),\n        (dt(2022, 1, 1), dt(2022, 1, 1), \"ACTACTISDA\", 0.0),\n        (dt(2022, 1, 1), dt(2023, 1, 31), \"1+\", 1.0),\n        (dt(2022, 1, 1), dt(2024, 2, 28), \"1+\", 2 + 1 / 12),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"BUS252\", 0.35714285714285715),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"30U360\", 0.25),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"ACT365_25\", 0.2464065708418891),\n        (dt(2022, 1, 1), dt(2022, 4, 1), \"ACT364\", 0.24725274725274726),\n    ],\n)\ndef test_dcf(start, end, conv, expected) -> None:\n    result = dcf(start, end, conv, calendar=\"all\", frequency=\"Q\")\n    assert abs(result - expected) < 1e-14\n\n\n@pytest.mark.parametrize(\n    (\"start\", \"end\", \"conv\", \"expected\", \"freq\", \"term\", \"stub\"),\n    [\n        (dt(2022, 6, 30), dt(2022, 7, 31), \"30360\", 1 / 12, NoInput(0), None, None),\n        (dt(2022, 6, 30), dt(2022, 7, 31), \"30E360\", 1 / 12, NoInput(0), None, None),\n        (dt(2022, 6, 30), dt(2022, 7, 31), \"30E360ISDA\", 1 / 12, \"A\", dt(2022, 7, 31), None),\n        (dt(2022, 6, 29), dt(2022, 7, 31), \"30360\", 1 / 12 + 2 / 360, NoInput(0), None, None),\n        (dt(2022, 6, 29), dt(2022, 7, 31), \"30E360\", 1 / 12 + 1 / 360, NoInput(0), None, None),\n        (dt(2022, 2, 28), dt(2022, 3, 31), \"30E360\", 1 / 12 + 2 / 360, NoInput(0), None, None),\n        (dt(2022, 2, 28), dt(2022, 3, 31), \"30E360ISDA\", 1 / 12, \"A\", dt(2022, 3, 3), None),\n        (\n            dt(1999, 2, 1),\n            dt(1999, 7, 1),\n            \"ACTACTICMA\",\n            150 / 365,\n            \"A\",\n            dt(2000, 7, 1),\n            True,\n        ),  # short first\n        (\n            dt(2002, 8, 15),\n            dt(2003, 7, 15),\n            \"ACTACTICMA\",\n            0.5 + 153 / 368,\n            \"S\",\n            dt(2004, 1, 15),\n            True,\n        ),  # long first\n        (\n            dt(2000, 1, 30),\n            dt(2000, 6, 30),\n            \"ACTACTICMA\",\n            152 / 364,\n            \"S\",\n            dt(2000, 6, 30),\n            True,\n        ),  # short back\n        (\n            dt(1999, 11, 30),\n            dt(2000, 4, 30),\n            \"ACTACTICMA\",\n            0.25 + 61 / 368,\n            Frequency.Months(3, RollDay.Day(31)),\n            dt(2000, 4, 30),\n            True,\n        ),\n        (\n            dt(1999, 11, 30),\n            dt(2000, 4, 30),\n            \"ACTACTICMA\",\n            0.25 + 61 / 364,\n            Frequency.Months(3, RollDay.Day(30)),\n            dt(2000, 4, 30),\n            True,\n        ),\n        # long back : with and without month end roll here\n        (\n            dt(1999, 11, 15),\n            dt(2000, 4, 15),\n            \"ACTACTICMA\",\n            0.25 + 60 / 360,\n            \"Q\",\n            dt(2000, 4, 15),\n            True,\n        ),  # long back\n        (dt(2002, 8, 31), dt(2002, 11, 30), \"ACTACTICMA\", 0.25, \"Q\", dt(2004, 11, 30), False),\n        (\n            dt(1999, 2, 1),\n            dt(1999, 7, 1),\n            \"ACTACTICMA_STUB365F\",\n            150 / 365,\n            \"A\",\n            dt(2000, 7, 1),\n            True,\n        ),  # short first\n        (\n            dt(2002, 8, 15),\n            dt(2003, 7, 15),\n            \"ACTACTICMA_STUB365F\",\n            0.5 + 153 / 365,\n            \"S\",\n            dt(2004, 1, 15),\n            True,\n        ),  # long first\n        (\n            dt(2000, 1, 30),\n            dt(2000, 6, 30),\n            \"ACTACTICMA_STUB365F\",\n            152 / 365,\n            \"S\",\n            dt(2000, 6, 30),\n            True,\n        ),  # short back\n        (\n            dt(1999, 11, 15),\n            dt(2000, 4, 15),\n            \"ACTACTICMA_STUB365F\",\n            0.25 + 60 / 365,\n            \"Q\",\n            dt(2000, 4, 15),\n            True,\n        ),  # long back\n        (\n            dt(2002, 8, 31),\n            dt(2002, 11, 30),\n            \"ACTACTICMA_STUB365F\",\n            0.25,\n            \"Q\",\n            dt(2004, 11, 30),\n            False,\n        ),\n    ],\n)\ndef test_dcf_special(start, end, conv, expected, freq, term, stub) -> None:\n    # The 4 ActICMA tests match short/long first/final stubs in 1998-ISDA-memo-EMU pdf\n    result = dcf(start, end, conv, term, freq, stub)\n    assert abs(result - expected) < 1e-12\n\n\n@pytest.mark.parametrize(\n    (\"conv\", \"freq\", \"term\", \"stub\"),\n    [\n        (\"ACTACTICMA\", NoInput(0), NoInput(0), NoInput(0)),\n        (\"ACTACTICMA\", \"Q\", NoInput(0), NoInput(0)),\n        (\"BadConv\", NoInput(0), NoInput(0), NoInput(0)),\n    ],\n)\ndef test_dcf_raises(conv, freq, term, stub) -> None:\n    with pytest.raises(ValueError):\n        _ = dcf(\n            dt(2022, 1, 1),\n            dt(2022, 4, 1),\n            conv,\n            term,\n            freq,\n            stub=stub,\n        )\n\n\ndef test_dcf_30e360_isda_raises():\n    # needs a termination if end februrary\n    with pytest.raises(ValueError, match=\"`termination` must be provided for '30e360ISDA' conv\"):\n        _ = dcf(\n            dt(2022, 2, 28),\n            dt(2023, 2, 28),\n            \"30e360isda\",\n            NoInput(0),\n        )\n\n\ndef test_dcf_30u360_raises():\n    # needs a termination if end februrary\n    with pytest.raises(ValueError, match=\"`frequency` must be provided or has no `roll`. A roll-d\"):\n        _ = dcf(\n            dt(2022, 2, 28),\n            dt(2023, 2, 28),\n            \"30u360\",\n        )\n\n\ndef test_dcf_actacticma_raises():\n    with pytest.raises(ValueError, match=\"Stub periods under ActActICMA require `termination`, `a\"):\n        _ = dcf(\n            dt(2022, 2, 28),\n            dt(2023, 2, 28),\n            \"actacticma\",\n            NoInput(0),\n            \"Q\",\n            True,\n            Cal.from_name(\"tgt\"),\n            NoInput(0),\n        )\n\n\ndef test_dcf_actact_raises():\n    with pytest.raises(ValueError, match=r\"`ActAct` must be directly specified as `ActActICMA` \"):\n        _ = dcf(\n            dt(2022, 2, 28),\n            dt(2023, 2, 28),\n            \"actact\",\n        )\n\n\n@pytest.mark.parametrize(\n    (\"start\", \"end\", \"expected\"),\n    [\n        (dt(2000, 1, 1), dt(2000, 1, 4), 1.0 / 252.0),\n        (dt(2000, 1, 2), dt(2000, 1, 4), 1.0 / 252.0),\n        (dt(2000, 1, 2), dt(2000, 1, 5), 2.0 / 252.0),\n        (dt(2000, 1, 1), dt(2000, 1, 5), 2.0 / 252.0),\n        (dt(2000, 1, 3), dt(2000, 1, 5), 1.0 / 252.0),\n        (dt(2000, 1, 3), dt(2000, 1, 4), 0.0 / 252.0),\n        (dt(2000, 1, 4), dt(2000, 1, 5), 1.0 / 252.0),\n        (dt(2000, 1, 5), dt(2000, 1, 6), 0.0 / 252.0),\n        (dt(2000, 1, 5), dt(2000, 1, 5), 0.0 / 252.0),\n    ],\n)\ndef test_bus252(start, end, expected) -> None:\n    cal = Cal(\n        [\n            dt(2000, 1, 1),\n            dt(2000, 1, 3),\n            dt(2000, 1, 5),\n            dt(2000, 1, 6),\n        ],\n        [],\n    )\n    assert dcf(start, end, \"BUS252\", calendar=cal) == expected\n\n\n@pytest.mark.parametrize(\n    (\"start\", \"end\", \"roll\", \"expected\"),\n    [\n        (dt(2024, 2, 29), dt(2025, 2, 28), \"eom\", 1.00),\n        (dt(2024, 2, 29), dt(2025, 2, 28), 29, 0.99722222222222),\n        (dt(2024, 2, 28), dt(2025, 2, 28), \"eom\", 1.0),\n        (dt(2024, 2, 28), dt(2025, 2, 28), 28, 1.0),\n        (dt(2024, 2, 29), dt(2025, 2, 27), \"eom\", 0.99166666666666),\n        (dt(2024, 2, 29), dt(2025, 2, 27), 27, 0.99444444444444),\n        (dt(2024, 2, 28), dt(2025, 2, 27), \"eom\", 0.99722222222222),\n        (dt(2024, 2, 28), dt(2025, 2, 27), 27, 0.99722222222222),\n        (dt(2024, 9, 30), dt(2024, 12, 31), None, 0.25),\n        (dt(2024, 3, 31), dt(2024, 6, 30), None, 0.25),\n        (dt(2024, 3, 31), dt(2024, 12, 31), None, 0.75),\n        (dt(2024, 12, 1), dt(2024, 12, 31), None, 30 / 360),\n        (dt(2024, 11, 30), dt(2024, 12, 31), None, 30 / 360),\n        (dt(2024, 2, 29), dt(2024, 3, 31), 29, 32 / 360),\n        (dt(2024, 2, 29), dt(2024, 3, 31), \"eom\", 30 / 360),\n        (dt(2024, 2, 28), dt(2024, 3, 31), \"eom\", 33 / 360),\n        (dt(2025, 2, 28), dt(2025, 3, 31), \"eom\", 30 / 360),\n    ],\n)\ndef test_30u360(start, end, roll, expected):\n    freq = _get_frequency(\"M\", roll, \"all\")\n    result = dcf(start, end, \"30U360\", frequency=freq)\n    assert abs(result - expected) < 1e-10\n\n\n@pytest.mark.parametrize(\n    (\"d1\", \"d2\", \"exp\"),\n    [\n        (dt(2009, 3, 1), dt(2012, 1, 15), (2, 10)),\n        (dt(2008, 12, 1), dt(2013, 10, 31), (4, 10)),\n        (dt(2008, 12, 1), dt(2018, 11, 15), (9, 11)),\n        (dt(2008, 12, 1), dt(2038, 5, 15), (29, 5)),\n    ],\n)\ndef test_get_years_and_months(d1, d2, exp) -> None:\n    result = _get_years_and_months(d1, d2)\n    assert result == exp\n\n\n@pytest.mark.parametrize(\n    (\"s\", \"e\", \"t\", \"exp\"),\n    [\n        (dt(2024, 2, 29), dt(2024, 5, 29), dt(2024, 5, 29), 0.24657534),\n        (dt(2021, 2, 28), dt(2024, 5, 29), dt(2024, 5, 29), 3.24863387),\n        (dt(2021, 2, 28), dt(2024, 5, 29), dt(2026, 5, 28), 3.24657534),\n    ],\n)\ndef test_act_act_icma_z_freq(s, e, t, exp) -> None:\n    with pytest.warns(UserWarning, match=\"`frequency` cannot be 'Zero' variant in combination wit\"):\n        result = dcf(\n            start=s,\n            end=e,\n            convention=\"ActActICMA\",\n            termination=t,\n            frequency=Frequency.Zero(),  # Z Frequency\n            stub=True,\n            calendar=Cal([], []),\n            adjuster=Adjuster.Actual(),\n        )\n    assert abs(result - exp) < 1e-6\n\n\ndef test_calendar_aligns_with_fixings_tyo() -> None:\n    # using this test in a regular way, and with \"-W error\" for error on warn ensures that:\n    #  - Curve cal is a business  day and fixings cal has no fixing: is a warn\n    #  - Curve cal is not a business day and fixings cal has a fixing: errors\n    curve = Curve(\n        {dt(2015, 6, 10): 1.0, dt(2024, 6, 3): 1.0},\n        calendar=\"tyo\",\n    )\n    fixings_ = fixings[\"jpy_rfr\"][1]\n    irs = IRS(dt(2015, 6, 10), dt(2024, 6, 3), \"A\", leg2_rate_fixings=fixings_, calendar=\"tyo\")\n    irs.rate(curves=curve)\n\n\ndef test_calendar_aligns_with_fixings_syd() -> None:\n    # using this test in a regular way, and with \"-W error\" for error on warn ensures that:\n    #  - Curve cal is a business  day and fixings cal has no fixing: is a warn\n    #  - Curve cal is not a business day and fixings cal has a fixing: errors\n    curve = Curve(\n        {dt(2015, 6, 10): 1.0, dt(2024, 6, 3): 1.0},\n        calendar=\"syd\",\n    )\n    fixings_ = fixings[\"aud_rfr\"][1]\n    irs = IRS(dt(2015, 6, 10), dt(2024, 6, 3), \"A\", leg2_rate_fixings=fixings_, calendar=\"syd\")\n    irs.rate(curves=curve)\n\n\ndef test_book_example() -> None:\n    res = add_tenor(dt(2001, 9, 28), \"-6M\", modifier=\"MF\", calendar=\"ldn\")\n    assert res == dt(2001, 3, 28, 0, 0)\n    res = add_tenor(dt(2001, 9, 28), \"-6M\", modifier=\"MF\", calendar=\"ldn\", roll=31)\n    assert res == dt(2001, 3, 30, 0, 0)\n    res = add_tenor(dt(2001, 9, 28), \"-6M\", modifier=\"MF\", calendar=\"ldn\", roll=29)\n    assert res == dt(2001, 3, 29, 0, 0)\n\n\ndef test_book_example2() -> None:\n    cal = get_calendar(\"tgt|nyc\")\n    cal2 = get_calendar(\"tgt,nyc\")\n    # 11th Nov 09 is a US holiday: test that the holiday is ignored in the settlement cal\n    result = cal.add_bus_days(dt(2009, 11, 10), 2, True)\n    result2 = cal2.add_bus_days(dt(2009, 11, 10), 2, True)\n    assert result == dt(2009, 11, 12)\n    assert result2 == dt(2009, 11, 13)\n\n    # test that the US settlement is honoured\n    result = cal.add_bus_days(dt(2009, 11, 9), 2, True)\n    result2 = cal.add_bus_days(dt(2009, 11, 9), 2, False)\n    assert result == dt(2009, 11, 12)\n    assert result2 == dt(2009, 11, 11)\n\n\ndef test_pipe_vectors() -> None:\n    get_calendar(\"tgt,stk|nyc,osl\")\n\n\ndef test_pipe_raises() -> None:\n    with pytest.raises(\n        ValueError, match=\"The calendar cannot be parsed. Is there more than one pipe character?\"\n    ):\n        get_calendar(\"tgt|nyc|stk\")\n\n\ndef test_add_and_get_custom_calendar() -> None:\n    cal = Cal([dt(2023, 1, 2)], [5, 6])\n    calendars.add(\"custom\", cal)\n    result = get_calendar(\"custom\")\n    assert result == cal\n    calendars.pop(\"custom\")\n\n\ndef test_add_and_get_custom_calendar_combination() -> None:\n    cal = Cal([dt(2023, 1, 2)], [5, 6])\n    cal2 = Cal([dt(2023, 1, 3)], [1, 2, 5, 6])\n    calendars.add(\"custom\", cal)\n    calendars.add(\"custom2\", cal2)\n    result = get_calendar(\"custom,custom2\")\n    assert result == UnionCal([cal, cal2], [])\n    calendars.pop(\"custom\")\n    calendars.pop(\"custom2\")\n\n\n@pytest.mark.parametrize(\"name\", [\"abc,def\", \"abc|def\"])\ndef test_add_fails_on_comma_or_pipe(name):\n    with pytest.raises(\n        ValueError, match=r\"`name` cannot contain the comma \\(','\\) or pipe \\('|'\\) cha\"\n    ):\n        calendars.add(name, Cal([], []))\n\n\n@pytest.mark.parametrize(\"name\", [\"tgt\", \"nyc\"])\ndef test_add_fails_on_existing(name):\n    with pytest.raises(KeyError, match=r\"'`name` already exists in calendars.\\\\nCannot overwri\"):\n        calendars.add(name, Cal([], []))\n\n\ndef test_calendar_pop_all_combinations() -> None:\n    cal = Cal([dt(2023, 1, 2)], [5, 6])\n    cal2 = Cal([dt(2023, 1, 3)], [1, 2, 5, 6])\n    cal3 = Cal([dt(2023, 1, 3)], [1, 2, 4, 6])\n    calendars.add(\"custom1\", cal)\n    calendars.add(\"custom2\", cal2)\n    calendars.add(\"custom3\", cal3)\n    _ = get_calendar(\"custom1,custom2\")\n    _ = get_calendar(\"custom1,custom3\")\n    _ = get_calendar(\"custom2,custom3\")\n    calendars.pop(\"custom1\")\n\n    assert \"custom1,custom2\" not in calendars\n    assert \"custom1,custom3\" not in calendars\n    assert \"custom2,custom3\" in calendars\n    calendars.pop(\"custom2\")\n    calendars.pop(\"custom3\")\n\n\ndef test_doc_union_cal() -> None:\n    calendars.add(\"mondays-off\", Cal([], [0, 5, 6]))\n    calendars.add(\"fridays-off\", Cal([], [4, 5, 6]))\n    result = get_calendar(\"mondays-off, fridays-off\").print(2026, 1)\n    expected = \"\"\"        January 2026\nSu Mo Tu We Th Fr Sa\n             1  *  .\n .  *  6  7  8  *  .\n .  * 13 14 15  *  .\n .  * 20 21 22  *  .\n .  * 27 28 29  *  .\n                    \n\"\"\"  # noqa: W293\n    assert result == expected\n    calendars.pop(\"mondays-off\")\n    calendars.pop(\"fridays-off\")\n\n\n@pytest.mark.parametrize(\n    (\"evald\", \"delivery\", \"expiry\", \"expected_expiry\"),\n    [\n        (dt(2024, 5, 2), 2, \"2m\", dt(2024, 7, 4)),\n        (dt(2024, 4, 30), 2, \"2m\", dt(2024, 7, 1)),\n        (dt(2024, 5, 31), 2, \"1m\", dt(2024, 7, 3)),\n        (dt(2024, 5, 31), 2, \"2w\", dt(2024, 6, 14)),\n    ],\n)\ndef test_expiries_delivery(evald, delivery, expiry, expected_expiry) -> None:\n    result_expiry, _, _ = _get_fx_expiry_and_delivery_and_payment(\n        evald, expiry, delivery, \"tgt|fed\", \"mf\", False, 0\n    )\n    assert result_expiry == expected_expiry\n\n\ndef test_expiries_delivery_raises() -> None:\n    with pytest.raises(ValueError, match=\"Cannot determine FXOption expiry and delivery\"):\n        _get_fx_expiry_and_delivery_and_payment(\n            dt(2000, 1, 1),\n            \"3m\",\n            dt(2000, 3, 2),\n            \"tgt|fed\",\n            \"mf\",\n            False,\n            0,\n        )\n\n\n@pytest.mark.parametrize(\n    (\"val\", \"exp\"),\n    [\n        (\"Z24\", dt(2024, 12, 18)),\n        (\"X89\", dt(2089, 11, 16)),\n    ],\n)\ndef test_get_imm_api(val, exp):\n    result = get_imm(month=1, year=1, code=val)\n    assert result == exp\n\n\ndef test_get_imm_api_no_code():\n    result = get_imm(month=11, year=2089)\n    assert result == dt(2089, 11, 16)\n\n\n@pytest.mark.parametrize(\"tenor\", [\"1B\", \"1b\", \"3D\", \"3d\", \"2W\", \"2w\"])\ndef test_is_day_type_tenor(tenor):\n    assert _is_day_type_tenor(tenor)\n\n\n@pytest.mark.parametrize(\"tenor\", [\"1M\", \"1m\", \"4Y\", \"4y\"])\ndef test_is_not_day_type_tenor(tenor):\n    assert not _is_day_type_tenor(tenor)\n\n\n@pytest.mark.parametrize(\n    (\"start\", \"method\", \"expected\"),\n    [\n        (dt(2025, 1, 1), \"wed3_hmuz\", dt(2025, 3, 19)),\n        (dt(2025, 1, 1), \"wed3\", dt(2025, 1, 15)),\n        (dt(2025, 1, 1), \"day20_hmuz\", dt(2025, 3, 20)),\n        (dt(2025, 1, 1), \"day20_HU\", dt(2025, 3, 20)),\n        (dt(2025, 1, 1), \"day20_MZ\", dt(2025, 6, 20)),\n        (dt(2025, 1, 15), \"wed3\", dt(2025, 2, 19)),\n        (dt(2025, 3, 19), \"wed3_hmuz\", dt(2025, 6, 18)),\n        (dt(2025, 3, 20), \"day20_hmuz\", dt(2025, 6, 20)),\n        (dt(2025, 3, 20), \"day20_HU\", dt(2025, 9, 20)),\n        (dt(2025, 3, 20), \"day20_MZ\", dt(2025, 6, 20)),\n        (dt(2025, 9, 20), \"day20_HU\", dt(2026, 3, 20)),\n        (dt(2025, 12, 1), \"wed3_hmuz\", dt(2025, 12, 17)),\n        (dt(2025, 12, 1), \"wed3\", dt(2025, 12, 17)),\n        (dt(2025, 12, 1), \"day20_hmuz\", dt(2025, 12, 20)),\n        (dt(2025, 12, 1), \"day20_HU\", dt(2026, 3, 20)),\n        (dt(2025, 12, 1), \"day20_MZ\", dt(2025, 12, 20)),\n        (dt(2025, 12, 17), \"wed3_hmuz\", dt(2026, 3, 18)),\n        (dt(2025, 12, 17), \"wed3\", dt(2026, 1, 21)),\n        (dt(2025, 12, 20), \"day20_hmuz\", dt(2026, 3, 20)),\n        (dt(2025, 12, 20), \"day20_HU\", dt(2026, 3, 20)),\n        (dt(2025, 12, 20), \"day20_MZ\", dt(2026, 6, 20)),\n    ],\n)\ndef test_next_imm(start, method, expected):\n    result = next_imm(start, method)\n    assert result == expected\n\n\ndef test_next_imm_depr():\n    with pytest.warns(DeprecationWarning):\n        next_imm(dt(2000, 1, 1), \"imm\")\n\n\ndef test_get_imm_depr():\n    with pytest.warns(DeprecationWarning):\n        get_imm(3, 2000, definition=\"imm\")\n\n\ndef test_fed_nyc_good_friday():\n    assert not get_calendar(\"nyc\").is_bus_day(dt(2024, 3, 29))\n    assert get_calendar(\"fed\").is_bus_day(dt(2024, 3, 29))\n\n\ndef test_fed_sunday_to_monday():\n    fed = get_calendar(\"fed\")\n    assert fed.is_bus_day(dt(2021, 12, 24))\n    assert not fed.is_bus_day(dt(2022, 12, 26))\n\n\ndef test_syd_nsw_holidays():\n    cal = get_calendar(\"nsw\")\n    assert not cal.is_bus_day(dt(1970, 8, 3))\n    assert not cal.is_bus_day(dt(1970, 10, 5))\n\n\ndef test_wlg_changes():\n    cal = get_calendar(\"wlg\")\n    assert not cal.is_bus_day(dt(2022, 9, 26))\n    assert not cal.is_bus_day(dt(2025, 1, 20))\n    assert not cal.is_bus_day(dt(2025, 1, 27))\n\n\ndef test_busdayslag_reverse():\n    # test that reverse operates over settleable days also\n    a = Adjuster.BusDaysLagSettle(2)\n    cal = Cal([dt(2026, 1, 1)], [5, 6])\n    union = UnionCal([Cal([], [])], [cal])\n    assert a.adjust(dt(2025, 12, 30), union) == dt(2026, 1, 2)\n    assert a.adjust(dt(2025, 12, 31), union) == dt(2026, 1, 2)\n    assert a.reverse(dt(2026, 1, 2), union) == [dt(2025, 12, 31), dt(2025, 12, 30)]\n\n\ndef test_mex_loads():\n    cal = get_calendar(\"mex\")\n    assert not cal.is_bus_day(dt(2026, 3, 16))\n    assert cal.is_bus_day(dt(2026, 3, 17))\n\n\ndef test_bjs_loads():\n    cal = get_calendar(\"bjs\")\n    assert not cal.is_bus_day(dt(2026, 9, 19))\n    assert cal.is_bus_day(dt(2026, 9, 20))\n\n\ndef test_replace_whitespace():\n    cal1 = get_calendar(\"nyc, tgt\")\n    cal2 = get_calendar(\"nyc,tgt\")\n    assert cal1 == cal2\n\n\ndef test_print_month():\n    cal = get_calendar(\"nyc,tgt\")\n    output = cal.print(2026, 1)\n    assert (\n        output\n        == r\"\"\"        January 2026\nSu Mo Tu We Th Fr Sa\n             *  2  .\n .  5  6  7  8  9  .\n . 12 13 14 15 16  .\n .  * 20 21 22 23  .\n . 26 27 28 29 30  .\n                    \n\"\"\"  # noqa: W291, W293\n    )\n\n\ndef test_print_calendar():\n    cal = get_calendar(\"bjs\")\n    output = cal.print(2026)\n    expected = \"\"\"\n        January 2026             April 2026              July 2026           October 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n             *  *  .             1  2  3  .             1  2  3  .                *  *  .\n 4  5  6  7  8  9  .    .  *  7  8  9 10  .    .  6  7  8  9 10  .    .  *  *  *  8  9 10\n . 12 13 14 15 16  .    . 13 14 15 16 17  .    . 13 14 15 16 17  .    . 12 13 14 15 16  .\n . 19 20 21 22 23  .    . 20 21 22 23 24  .    . 20 21 22 23 24  .    . 19 20 21 22 23  .\n . 26 27 28 29 30  .    . 27 28 29 30          . 27 28 29 30 31       . 26 27 28 29 30  .\n                                                                                         \n       February 2026               May 2026            August 2026          November 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n .  2  3  4  5  6  .                   *  .                      .    .  2  3  4  5  6  .\n .  9 10 11 12 13 14    .  *  *  6  7  8  9    .  3  4  5  6  7  .    .  9 10 11 12 13  .\n .  *  *  *  *  *  .    . 11 12 13 14 15  .    . 10 11 12 13 14  .    . 16 17 18 19 20  .\n .  * 24 25 26 27 28    . 18 19 20 21 22  .    . 17 18 19 20 21  .    . 23 24 25 26 27  .\n                        . 25 26 27 28 29  .    . 24 25 26 27 28  .    . 30               \n                        .                      . 31                                      \n          March 2026              June 2026         September 2026          December 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n .  2  3  4  5  6  .       1  2  3  4  5  .          1  2  3  4  .          1  2  3  4  .\n .  9 10 11 12 13  .    .  8  9 10 11 12  .    .  7  8  9 10 11  .    .  7  8  9 10 11  .\n . 16 17 18 19 20  .    . 15 16 17 18  *  .    . 14 15 16 17 18  .    . 14 15 16 17 18  .\n . 23 24 25 26 27  .    . 22 23 24 25 26  .   20 21 22 23 24  *  .    . 21 22 23 24 25  .\n . 30 31                . 29 30                . 28 29 30             . 28 29 30 31      \n                                                                                         \nLegend:\n'1-31': Settleable business day         'X': Non-settleable business day\n   '.': Non-business weekend            '*': Non-business day\n\"\"\"  # noqa: W291, W293\n    assert output == expected\n\n\ndef test_print_compare_calendar():\n    cal = get_calendar(\"nyc\")\n    cal2 = get_calendar(\"fed\")\n    output = cal.print_compare(cal2, 2026)\n    expected = \"\"\"\n        January 2026             April 2026              July 2026           October 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n             _  _  _             _  _ []  _             _  _ []  _                _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _          _  _  _  _  _  _       _  _  _  _  _  _  _ \n                                                                                          \n       February 2026               May 2026            August 2026          November 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n _  _  _  _  _  _  _                   _  _                      _    _  _  _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n                        _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _                \n                        _                      _  _                                       \n          March 2026              June 2026         September 2026          December 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n _  _  _  _  _  _  _       _  _  _  _  _  _          _  _  _  _  _          _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _    _  _  _  _  _  _  _ \n _  _  _                _  _  _                _  _  _  _             _  _  _  _  _       \n                                                                                          \n\"\"\"  # noqa: W291, W293\n    assert output == expected\n\n\ndef test_union_cal_try_from_name():\n    uc = UnionCal.from_name(\"ldn,tgt|fed\")\n    assert isinstance(uc, UnionCal)\n\n\n@pytest.mark.parametrize(\"number\", [-3, -2, -1, 0, 1, 2, 3])\n@pytest.mark.parametrize(\n    \"start\", [dt(2026, 2, 13), dt(2026, 2, 14), dt(2026, 2, 15), dt(2026, 2, 16)]\n)\ndef test_add_bus_days_BusDaysLagSettle_equivalence(number, start):\n    cal = Cal([], [5, 6])\n    adj = Adjuster.BusDaysLagSettle(number)\n    result = cal.adjust(start, adj)\n    expected = cal.lag_bus_days(start, number, True)\n    assert result == expected\n"
  },
  {
    "path": "python/tests/scheduling/test_calendarsrs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom pandas import Index\nfrom rateslib import fixings\nfrom rateslib.rs import Adjuster, Cal, CalendarManager, Modifier, NamedCal, RollDay, UnionCal\nfrom rateslib.scheduling import get_calendar\nfrom rateslib.serialization import from_json\n\n\nclass TestRollDay:\n    @pytest.mark.parametrize(\n        (\"left\", \"right\", \"expected\"),\n        [\n            (RollDay.IMM(), RollDay.IMM(), True),\n            (RollDay.Day(20), RollDay.Day(20), True),\n            (RollDay.Day(20), RollDay.Day(30), False),\n            (RollDay.Day(31), RollDay.IMM(), False),\n        ],\n    )\n    def test_equality(self, left, right, expected):\n        result = left == right\n        assert result is expected\n\n\n@pytest.mark.parametrize(\n    \"modifier\",\n    [\n        Modifier.Act,\n        Modifier.F,\n        Modifier.ModF,\n        Modifier.P,\n        Modifier.ModP,\n    ],\n)\ndef test_modifier_pickle(modifier) -> None:\n    import pickle\n\n    assert modifier == pickle.loads(pickle.dumps(modifier))\n\n\n@pytest.fixture\ndef simple_cal():\n    return Cal([dt(2015, 9, 5), dt(2015, 9, 7)], [5, 6])  # Saturday and Monday\n\n\n@pytest.fixture\ndef simple_union(simple_cal):\n    return UnionCal([simple_cal], None)\n\n\n@pytest.fixture\ndef multi_union(simple_cal):\n    add_cal = Cal([dt(2015, 9, 3), dt(2015, 9, 8)], [5, 6])\n    return UnionCal([simple_cal, add_cal], None)\n\n\nclass TestCal:\n    def test_cal_construct(self) -> None:\n        cal = Cal([dt(2015, 9, 5), dt(2015, 9, 7)], [5, 6])\n        UnionCal([cal], None)\n\n    def test_cal_from_name(self):\n        cal1 = Cal.from_name(\"ldn\")\n        cal2 = NamedCal(\"ldn\")\n        assert cal1 == cal2\n        assert type(cal1) is not type(cal2)\n\n    def test_is_business_day(self, simple_cal, simple_union) -> None:\n        assert not simple_cal.is_bus_day(dt(2015, 9, 7))  # Monday Holiday\n        assert simple_cal.is_bus_day(dt(2015, 9, 8))  # Tuesday\n        assert not simple_cal.is_bus_day(dt(2015, 9, 12))  # Saturday\n\n        assert not simple_union.is_bus_day(dt(2015, 9, 7))\n        assert simple_union.is_bus_day(dt(2015, 9, 8))\n\n    @pytest.mark.parametrize(\"cal\", [\"basic\", \"union\"])\n    def test_add_cal_days(self, simple_cal, simple_union, cal) -> None:\n        cal = simple_cal if cal == \"basic\" else simple_union\n        expected = dt(2015, 9, 8)\n        result = cal.add_cal_days(dt(2015, 9, 4), 2, Adjuster.FollowingSettle())\n        assert result == expected\n\n        expected = dt(2015, 9, 6)\n        result = cal.add_cal_days(dt(2015, 9, 5), 1, Adjuster.Actual())\n        assert result == expected\n\n    @pytest.mark.parametrize(\"cal\", [\"basic\", \"union\"])\n    @pytest.mark.parametrize(\n        (\"start\", \"days\", \"expected\"),\n        [\n            (dt(2015, 9, 4), 0, dt(2015, 9, 4)),\n            (dt(2015, 9, 4), 1, dt(2015, 9, 8)),\n            (dt(2015, 9, 8), -1, dt(2015, 9, 4)),\n            (dt(2015, 9, 4), -1, dt(2015, 9, 3)),\n            (dt(2015, 9, 8), 1, dt(2015, 9, 9)),\n        ],\n    )\n    def test_add_bus_days(self, simple_cal, simple_union, cal, start, days, expected) -> None:\n        cal = simple_cal if cal == \"basic\" else simple_union\n\n        result = cal.add_bus_days(start, days, True)\n        assert result == expected\n\n    def test_add_bus_days_raises(self, simple_cal, simple_union) -> None:\n        with pytest.raises(ValueError, match=\"Cannot add business days\"):\n            simple_cal.add_bus_days(dt(2015, 9, 5), 1, True)\n\n    @pytest.mark.parametrize(\"cal\", [\"basic\", \"union\"])\n    @pytest.mark.parametrize(\n        (\"start\", \"months\", \"expected\"),\n        [\n            (dt(2015, 9, 4), 2, dt(2015, 11, 4)),\n            (dt(2015, 9, 4), 36, dt(2018, 9, 4)),\n        ],\n    )\n    def test_add_months(self, cal, simple_cal, simple_union, start, months, expected) -> None:\n        cal = simple_cal if cal == \"basic\" else simple_union\n        result = cal.add_months(start, months, Adjuster.FollowingSettle(), None)\n        assert result == expected\n\n    def test_pickle_cal(self, simple_cal) -> None:\n        import pickle\n\n        pickled_cal = pickle.dumps(simple_cal)\n        pickle.loads(pickled_cal)\n\n    def test_pickle_union(self, simple_union) -> None:\n        import pickle\n\n        pickled_cal = pickle.dumps(simple_union)\n        pickle.loads(pickled_cal)\n\n    @pytest.mark.parametrize(\n        (\"cal\", \"exp\"),\n        [\n            (\"basic\", [dt(2015, 9, 5), dt(2015, 9, 7)]),\n            (\"union\", [dt(2015, 9, 3), dt(2015, 9, 5), dt(2015, 9, 7), dt(2015, 9, 8)]),\n        ],\n    )\n    def test_holidays(self, cal, exp, simple_cal, multi_union) -> None:\n        cal = simple_cal if cal == \"basic\" else multi_union\n        assert cal.holidays == exp\n\n    # def test_rules(self):\n    #     rules = get_calendar(\"tyo\").rules\n    #     assert rules[:10] == \"Jan 1 (New\"\n\n    def test_tyo_cal(self) -> None:\n        tokyo = get_calendar(\"tyo\")\n        assert tokyo.holidays[0] == dt(1970, 1, 1)\n\n    def test_fed_cal(self) -> None:\n        cal = get_calendar(\"fed\")\n        assert cal.holidays[0] == dt(1970, 1, 1)\n\n    def test_wlg_cal(self):\n        cal = get_calendar(\"wlg\")\n        assert cal.holidays[0] == dt(1970, 1, 1)\n\n    def test_mum_cal(self):\n        cal = get_calendar(\"mum\")\n        assert cal.holidays[0] == dt(1970, 1, 26)\n\n    def test_json_round_trip(self, simple_cal) -> None:\n        json = simple_cal.to_json()\n        from_cal = from_json(json)\n        assert simple_cal == from_cal\n\n    def test_json_round_trip_union(self, multi_union) -> None:\n        json = multi_union.to_json()\n        from_cal = from_json(json)\n        assert multi_union == from_cal\n\n    def test_json_raises(self) -> None:\n        with pytest.raises(ValueError, match=\"Could not create Class or Struct from given JSON\"):\n            from_json('{\"Cal\":{\"holidays\":[]}}')\n\n        with pytest.raises(ValueError, match=\"Could not create Class or Struct from given JSON\"):\n            from_json('{\"UnionCal\":{\"settlement_calendars\":[]}}')\n\n    @pytest.mark.parametrize(\n        (\"left\", \"right\", \"expected\"),\n        [\n            (Cal([], [5, 6]), Cal([], [5, 6]), True),\n            (Cal([dt(2006, 1, 2)], [5, 6]), Cal([dt(2006, 1, 2)], [5, 6]), True),\n            (Cal([dt(2006, 1, 2)], [5, 6]), Cal([dt(2007, 1, 2)], [5, 6]), False),\n            (Cal([], [4, 6]), Cal([], [5, 6]), False),\n            (UnionCal([Cal([], [5, 6])]), Cal([], [5, 6]), True),\n            (UnionCal([Cal([dt(2006, 1, 2)], [5, 6])]), Cal([], [5, 6]), False),\n            (\n                UnionCal([Cal([dt(2006, 1, 2)], [5, 6])]),\n                Cal([dt(2006, 1, 2)], [5, 6]),\n                True,\n            ),\n            (\n                UnionCal([Cal([dt(2006, 1, 2)], [5, 6]), Cal([dt(2006, 1, 3)], [5, 6])]),\n                Cal([dt(2006, 1, 2), dt(2006, 1, 3)], [5, 6]),\n                True,\n            ),\n            (\n                UnionCal([Cal([dt(2006, 1, 2)], [5, 6]), Cal([dt(2006, 1, 3)], [5, 6])]),\n                UnionCal([Cal([dt(2006, 1, 2), dt(2006, 1, 3)], [5, 6])]),\n                True,\n            ),\n        ],\n    )\n    def test_equality(self, left, right, expected) -> None:\n        assert (left == right) is expected\n        assert (right == left) is expected\n\n    def test_attributes(self) -> None:\n        ncal = get_calendar(\"tgt,LDN|Fed\")\n        assert ncal.name == \"ldn,tgt|fed\"\n        assert isinstance(ncal.inner, UnionCal)\n        assert len(ncal.inner.calendars) == 2\n        assert len(ncal.inner.settlement_calendars) == 1\n\n        ncal = get_calendar(\"tgt\")\n        assert isinstance(ncal.inner, Cal)\n\n    def test_adjusts(self, simple_cal):\n        dates = [dt(2015, 9, 4), dt(2015, 9, 5), dt(2015, 9, 6), dt(2015, 9, 7)]\n        result = simple_cal.adjusts(dates, Adjuster.Following())\n        expected = [dt(2015, 9, 4), dt(2015, 9, 8), dt(2015, 9, 8), dt(2015, 9, 8)]\n        assert result == expected\n\n    def test_roll(self, simple_cal):\n        result = simple_cal.roll(dt(2015, 9, 5), \"F\", False)\n        assert result == dt(2015, 9, 8)\n\n\nclass TestUnionCal:\n    def test_week_mask(self, multi_union) -> None:\n        result = multi_union.week_mask\n        assert result == {5, 6}\n\n    def test_adjusts(self, simple_union):\n        dates = [dt(2015, 9, 4), dt(2015, 9, 5), dt(2015, 9, 6), dt(2015, 9, 7)]\n        result = simple_union.adjusts(dates, Adjuster.Following())\n        expected = [dt(2015, 9, 4), dt(2015, 9, 8), dt(2015, 9, 8), dt(2015, 9, 8)]\n        assert result == expected\n\n    def test_roll(self, simple_union):\n        result = simple_union.roll(dt(2015, 9, 5), \"F\", False)\n        assert result == dt(2015, 9, 8)\n\n\nclass TestNamedCal:\n    def test_equality_named_cal(self) -> None:\n        cal = Cal.from_name(\"fed\")\n        ncal = NamedCal(\"fed\")\n        assert cal == ncal\n        assert ncal == cal\n\n        ucal = UnionCal.from_name(\"ldn,tgt|fed\")\n        ncal = NamedCal(\"ldn,tgt|fed\")\n        assert ucal == ncal\n        assert ncal == ucal\n\n    def test_adjusts(self):\n        ncal = NamedCal(\"fed\")\n        dates = [dt(2015, 9, 4), dt(2015, 9, 5), dt(2015, 9, 6), dt(2015, 9, 7)]\n        result = ncal.adjusts(dates, Adjuster.Following())\n        expected = [dt(2015, 9, 4), dt(2015, 9, 8), dt(2015, 9, 8), dt(2015, 9, 8)]\n        assert result == expected\n\n    def test_roll(self):\n        ncal = NamedCal(\"fed\")\n        result = ncal.roll(dt(2015, 9, 5), \"F\", False)\n        assert result == dt(2015, 9, 8)\n\n\n@pytest.mark.parametrize(\n    (\"datafile\", \"calendar\", \"known_exceptions\"),\n    [\n        (\"usd_rfr\", \"nyc\", []),\n        (\"gbp_rfr\", \"ldn\", []),\n        (\"cad_rfr\", \"tro\", []),\n        (\"eur_rfr\", \"tgt\", []),\n        (\"jpy_rfr\", \"tyo\", []),\n        (\"sek_rfr\", \"stk\", []),\n        (\"nok_rfr\", \"osl\", []),\n        (\"aud_rfr\", \"syd\", []),\n        (\"inr_rfr\", \"mum\", []),\n    ],\n)\ndef test_calendar_against_historical_fixings(datafile, calendar, known_exceptions):\n    fixings_ = fixings[datafile][1]\n    calendar_ = get_calendar(calendar)\n    bus_days = Index(calendar_.bus_date_range(fixings_.index[0], fixings_.index[-1]))\n    diff = fixings_.index.symmetric_difference(bus_days)\n\n    errors = 0\n    if len(diff) != 0:\n        print(f\"{calendar} for {datafile}\")\n        for i, date in enumerate(diff):\n            if date in known_exceptions:\n                continue\n            elif date in fixings_.index:\n                print(f\"{date} exists in fixings: does calendar wrongly classify as a holiday?\")\n            else:\n                # print(f'Holiday(\"adhoc{i}\", year={date.year}, month={date.month}, day={date.day}),')  # noqa: E501\n                print(f\"{date} exists in calendar: should this date be classified as a holiday?\")\n            errors += 1\n\n    assert errors == 0\n\n\nclass TestAdjuster:\n    def test_adjusts(self, simple_cal):\n        dates = [dt(2015, 9, 4), dt(2015, 9, 5), dt(2015, 9, 6), dt(2015, 9, 7)]\n        result = Adjuster.Following().adjusts(dates, simple_cal)\n        expected = [dt(2015, 9, 4), dt(2015, 9, 8), dt(2015, 9, 8), dt(2015, 9, 8)]\n        assert result == expected\n\n\nclass TestCalendarManager:\n    def test_add_and_pop(self):\n        c = CalendarManager()\n        c.add(\"mycalendar\", Cal([], [2]))\n        nc = c.get(\"mycalendar\")\n        assert isinstance(nc, NamedCal)\n        assert nc == Cal([], [2])\n        pop = c.pop(\"mycalendar\")\n        assert pop == Cal([], [2])\n        assert isinstance(pop, Cal)\n        with pytest.raises(KeyError):\n            c.get(\"mycalendar\")\n\n    def test_add_union_cal_raises(self):\n        c = CalendarManager()\n        with pytest.raises(TypeError, match=\"argument 'calendar': 'UnionCal' object is not\"):\n            c.add(\"mycalendar\", UnionCal([Cal([], [])], None))\n\n    def test_add_and_get_composition(self):\n        c = CalendarManager()\n        x = c.get(\"ldn,tgt\")\n        y = c.get(\"tgt,ldn\")\n        assert x == y\n        assert x.inner_ptr_eq(y)\n\n    def test_get_raises(self):\n        c = CalendarManager()\n        with pytest.raises(KeyError, match=\"`name` does not exist in calendars.\"):\n            c.get(\"bad_calendar\")\n\n        with pytest.raises(KeyError, match=\"`name` does not exist in calendars.\"):\n            c.get(\"ldn,bad_calendar\")\n"
  },
  {
    "path": "python/tests/scheduling/test_frequency.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.rs import Adjuster, Cal, Frequency, RollDay\n\n\n@pytest.mark.parametrize(\n    (\"method\", \"args\", \"exp\"),\n    [\n        (\"unext\", (dt(2000, 1, 1),), dt(2000, 1, 11)),\n        (\"uprevious\", (dt(2000, 1, 11),), dt(2000, 1, 1)),\n        (\n            \"uregular\",\n            (dt(2000, 1, 1), dt(2000, 1, 21)),\n            [dt(2000, 1, 1), dt(2000, 1, 11), dt(2000, 1, 21)],\n        ),\n        (\"infer_ustub\", (dt(2000, 1, 1), dt(2000, 1, 17), True, True), dt(2000, 1, 7)),\n        (\"infer_ustub\", (dt(2000, 1, 1), dt(2000, 1, 27), False, True), dt(2000, 1, 17)),\n        (\"infer_ustub\", (dt(2000, 1, 1), dt(2000, 1, 17), True, False), dt(2000, 1, 11)),\n        (\"infer_ustub\", (dt(2000, 1, 1), dt(2000, 1, 27), False, False), dt(2000, 1, 11)),\n    ],\n)\ndef test_frequency_cal_days(method, args, exp):\n    f = Frequency.CalDays(10)\n    result = getattr(f, method)(*args)\n    assert result == exp\n\n\n@pytest.mark.parametrize(\n    (\"method\", \"args\", \"exp\"),\n    [\n        (\"unext\", (dt(2025, 1, 1),), dt(2025, 1, 8)),\n        (\"uprevious\", (dt(2025, 1, 8),), dt(2025, 1, 1)),\n        (\n            \"uregular\",\n            (dt(2025, 1, 1), dt(2025, 1, 15)),\n            [dt(2025, 1, 1), dt(2025, 1, 8), dt(2025, 1, 15)],\n        ),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 1, 23), True, True), dt(2025, 1, 2)),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 1, 23), False, True), dt(2025, 1, 9)),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 1, 23), True, False), dt(2025, 1, 22)),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 1, 23), False, False), dt(2025, 1, 15)),\n    ],\n)\ndef test_frequency_bus_days(method, args, exp):\n    cal = Cal([], [5, 6])\n    f = Frequency.BusDays(5, cal)\n    result = getattr(f, method)(*args)\n    assert result == exp\n\n\n@pytest.mark.parametrize(\n    (\"method\", \"args\", \"exp\"),\n    [\n        (\"unext\", (dt(2025, 1, 15),), dt(2025, 2, 15)),\n        (\"uprevious\", (dt(2025, 2, 15),), dt(2025, 1, 15)),\n        (\n            \"uregular\",\n            (dt(2025, 1, 15), dt(2025, 3, 15)),\n            [dt(2025, 1, 15), dt(2025, 2, 15), dt(2025, 3, 15)],\n        ),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 4, 15), True, True), dt(2025, 1, 15)),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 4, 15), False, True), dt(2025, 2, 15)),\n        (\"infer_ustub\", (dt(2025, 1, 15), dt(2025, 4, 1), True, False), dt(2025, 3, 15)),\n        (\"infer_ustub\", (dt(2025, 1, 15), dt(2025, 4, 1), False, False), dt(2025, 2, 15)),\n    ],\n)\ndef test_frequency_months(method, args, exp):\n    f = Frequency.Months(1, RollDay.Day(15))\n    result = getattr(f, method)(*args)\n    assert result == exp\n\n\n@pytest.mark.parametrize(\n    (\"method\", \"args\", \"exp\"),\n    [\n        (\"unext\", (dt(2025, 1, 1),), dt(2025, 2, 1)),\n        (\"uprevious\", (dt(2025, 2, 1),), dt(2025, 1, 1)),\n        (\n            \"uregular\",\n            (dt(2025, 1, 1), dt(2025, 3, 1)),\n            [dt(2025, 1, 1), dt(2025, 2, 1), dt(2025, 3, 1)],\n        ),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 4, 15), True, True), dt(2025, 1, 15)),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 4, 15), False, True), dt(2025, 2, 15)),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 4, 15), True, False), dt(2025, 4, 1)),\n        (\"infer_ustub\", (dt(2025, 1, 1), dt(2025, 4, 15), False, False), dt(2025, 3, 1)),\n    ],\n)\ndef test_frequency_months_undefined(method, args, exp):\n    with pytest.raises(ValueError, match=\"`udate` cannot be validated since RollDay is None.\"):\n        getattr(Frequency.Months(1, None), method)(*args)\n\n\n@pytest.mark.parametrize(\n    (\"method\", \"args\", \"exp\"),\n    [\n        (\"unext\", (dt(2025, 1, 1),), dt(9999, 1, 1)),\n        (\"uprevious\", (dt(2025, 1, 8),), dt(1500, 1, 1)),\n        (\"uregular\", (dt(2025, 1, 1), dt(2025, 1, 15)), [dt(2025, 1, 1), dt(2025, 1, 15)]),\n    ],\n)\ndef test_frequency_zero(method, args, exp):\n    f = Frequency.Zero()\n    result = getattr(f, method)(*args)\n    assert result == exp\n\n\n@pytest.mark.parametrize(\"front\", [True, False])\ndef test_frequency_zero_raise(front):\n    f = Frequency.Zero()\n    result = f.infer_ustub(dt(2000, 1, 1), dt(2001, 1, 1), True, front)\n    assert result is None\n\n\ndef test_equality():\n    f = Frequency.Zero()\n    assert f == Frequency.Zero()\n\n    f = Frequency.CalDays(10)\n    assert isinstance(f, Frequency.CalDays)\n    assert not isinstance(f, Frequency.BusDays)\n\n\ndef test_rollday_equality():\n    assert RollDay.Day(15) == RollDay.Day(15)\n    assert RollDay.Day(15) != RollDay.Day(16)\n    assert RollDay.Day(15) != RollDay.IMM()\n    assert RollDay.IMM() == RollDay.IMM()\n\n\ndef test_string():\n    assert Frequency.Zero().string() == \"Z\"\n    assert Frequency.CalDays(10).string() == \"10D\"\n    assert Frequency.Months(3, None).string() == \"Q\"\n\n\ndef test_adjuster_reverse():\n    cal = Cal([dt(2010, 1, 1)], [])\n    result = Adjuster.Following().reverse(dt(2010, 1, 2), cal)\n    assert result == [dt(2010, 1, 2), dt(2010, 1, 1)]\n"
  },
  {
    "path": "python/tests/scheduling/test_imm.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.rs import Imm\nfrom rateslib.scheduling import get_imm\n\n\n@pytest.mark.parametrize(\n    (\"date\", \"expected\"),\n    [\n        (dt(2022, 3, 16), True),\n        (dt(2022, 6, 15), True),\n        (dt(2022, 9, 25), False),\n        (dt(2022, 8, 17), False),\n    ],\n)\ndef test_is_imm(date, expected) -> None:\n    result = Imm.Wed3_HMUZ.validate(date)\n    assert result is expected\n\n\ndef test_is_imm_serial() -> None:\n    result = Imm.Wed3.validate(dt(2022, 8, 17))  # imm in Aug\n    assert result\n\n\n@pytest.mark.parametrize(\n    (\"month\", \"year\", \"expected\"),\n    [\n        (3, 2022, dt(2022, 3, 16)),\n        (6, 2022, dt(2022, 6, 15)),\n        (9, 2022, dt(2022, 9, 21)),\n        (12, 2022, dt(2022, 12, 21)),\n    ],\n)\ndef test_get_imm(month, year, expected) -> None:\n    result = Imm.Wed3.get(year, month)\n    assert result == expected\n\n\ndef test_get_imm_namespace():\n    from rateslib import get_imm as f\n\n    f(code=\"h24\")\n\n\n@pytest.mark.parametrize(\n    (\"month\", \"year\", \"expected\"),\n    [\n        (2, 2022, dt(2022, 2, 28)),\n        (2, 2024, dt(2024, 2, 29)),\n        (8, 2022, dt(2022, 8, 31)),\n    ],\n)\ndef test_get_eom(month, year, expected) -> None:\n    result = Imm.Eom.get(year, month)\n    assert result == expected\n\n\ndef test_get_som() -> None:\n    assert Imm.Som.get(2000, 3) == dt(2000, 3, 1)\n    assert get_imm(code=\"H25\", definition=\"som\") == dt(2025, 3, 1)\n"
  },
  {
    "path": "python/tests/scheduling/test_schedule.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport numpy as np\nimport pytest\nfrom pandas import DataFrame, DatetimeIndex, date_range\nfrom pandas.testing import assert_index_equal\nfrom pandas.tseries.holiday import Holiday\nfrom rateslib import defaults\nfrom rateslib.default import NoInput\nfrom rateslib.rs import Adjuster, Frequency, RollDay\nfrom rateslib.scheduling import Cal\nfrom rateslib.scheduling.schedule import Schedule\n\n\n@pytest.fixture\ndef cal_():\n    return Cal([dt(_, 1, 3) for _ in range(1970, 2200)], [5, 6])\n\n\n@pytest.mark.parametrize(\n    (\"dt1\", \"dt2\", \"fm\", \"expected\"),\n    [\n        (dt(2022, 3, 16), dt(2022, 6, 30), 3, False),\n        (dt(2022, 3, 16), dt(2024, 9, 16), 3, True),\n        (dt(2022, 3, 16), dt(2028, 9, 16), 6, True),\n        (dt(2022, 3, 16), dt(2029, 3, 16), 12, True),\n        (dt(2022, 3, 16), dt(2022, 10, 16), 3, False),\n        (dt(2022, 3, 31), dt(2024, 4, 1), 12, False),\n    ],\n)\ndef test_is_divisible_months(dt1, dt2, fm, expected) -> None:\n    f = Frequency.Months(fm, RollDay.Day(16))\n    try:\n        f.uregular(dt1, dt2)\n    except ValueError:\n        assert not expected\n    else:\n        assert expected\n\n\n@pytest.mark.parametrize(\n    (\"effective\", \"termination\", \"expected\", \"expected2\"),\n    [\n        (dt(2022, 2, 22), dt(2024, 2, 22), 22, 22),\n        (dt(2022, 2, 22), dt(2024, 2, 15), 15, 15),\n        (dt(2022, 2, 28), dt(2024, 2, 29), 29, 31),\n        (dt(2022, 6, 30), dt(2024, 9, 30), 30, 31),\n        (dt(2022, 6, 30), dt(2024, 12, 30), 30, 30),\n        (dt(2022, 2, 28), dt(2024, 9, 30), 30, 31),\n        (dt(2024, 3, 31), dt(2024, 9, 30), 31, 31),\n    ],\n)\ndef test_get_unspecified_roll(effective, termination, expected, expected2) -> None:\n    result = Schedule(\n        effective,\n        termination,\n        Frequency.Months(1, None),\n        eom=False,\n    )\n    assert result.frequency_obj.roll == RollDay.Day(expected)\n\n    result = Schedule(\n        effective,\n        termination,\n        Frequency.Months(1, None),\n        eom=True,\n    )\n    assert result.frequency_obj.roll == RollDay.Day(expected2)\n\n\n@pytest.mark.parametrize(\n    (\"e\", \"t\", \"stub\", \"exp_roll\", \"exp_stub\"),\n    [\n        (dt(2022, 2, 26), dt(2024, 4, 22), \"SHORTFRONT\", 22, dt(2022, 4, 22)),\n        (dt(2022, 2, 26), dt(2024, 4, 22), \"LONGFRONT\", 22, dt(2022, 7, 22)),\n        (dt(2022, 2, 26), dt(2024, 4, 22), \"SHORTBACK\", 26, dt(2024, 2, 26)),\n        (dt(2022, 2, 26), dt(2024, 4, 22), \"LONGBACK\", 26, dt(2023, 11, 26)),\n    ],\n)\ndef test_infer_stub_date(e, t, stub, exp_roll, exp_stub, cal_) -> None:\n    result = Schedule(\n        e,\n        t,\n        \"Q\",\n        eom=False,\n        stub=stub,\n        calendar=cal_,\n    )\n    if \"FRONT\" in stub:\n        assert result.ufront_stub == exp_stub\n        assert result.roll == exp_roll\n    else:\n        assert result.uback_stub == exp_stub\n        assert result.roll == exp_roll\n\n\n@pytest.mark.parametrize(\n    (\"e\", \"t\", \"stub\", \"exp_roll\", \"exp_stub\"),\n    [\n        (dt(2022, 2, 26), dt(2024, 2, 26), \"SHORTFRONT\", 26, NoInput(0)),\n        (dt(2022, 2, 26), dt(2024, 2, 26), \"LONGFRONT\", 26, NoInput(0)),\n        (dt(2022, 2, 26), dt(2024, 2, 26), \"SHORTBACK\", 26, NoInput(0)),\n        (dt(2022, 2, 26), dt(2024, 2, 26), \"LONGBACK\", 26, NoInput(0)),\n    ],\n)\ndef test_infer_stub_date_no_inference_on_regular(e, t, stub, exp_roll, exp_stub, cal_) -> None:\n    result = Schedule(\n        e,\n        t,\n        \"Q\",\n        stub=stub,\n        eom=False,\n        calendar=cal_,\n    )\n    assert result.is_regular()\n\n\ndef test_infer_stub_date_no_inference_on_regular_dual(cal_) -> None:\n    result = Schedule(\n        dt(2022, 2, 26),\n        dt(2024, 4, 26),\n        \"Q\",\n        stub=\"SHORTFRONT\",\n        front_stub=NoInput(0),\n        back_stub=dt(2024, 2, 26),\n        calendar=cal_,\n    )\n    assert result.ufront_stub is None\n    assert result.roll == 26\n\n    result = Schedule(\n        dt(2022, 2, 26),\n        dt(2024, 4, 26),\n        \"Q\",\n        stub=\"SHORTBACK\",\n        front_stub=dt(2022, 4, 26),\n        back_stub=NoInput(0),\n        calendar=cal_,\n    )\n    assert result.uback_stub is None\n    assert result.roll == 26\n\n\n@pytest.mark.parametrize(\n    (\"e\", \"t\", \"stub\"),\n    [\n        (dt(2022, 2, 26), dt(2024, 4, 22), \"SHORTFRONT\"),\n        (dt(2022, 2, 26), dt(2024, 4, 22), \"LONGFRONT\"),\n        (dt(2022, 2, 26), dt(2024, 4, 22), \"SHORTBACK\"),\n        (dt(2022, 2, 26), dt(2024, 4, 22), \"LONGBACK\"),\n    ],\n)\ndef test_infer_stub_date_invalid_roll(e, t, stub, cal_) -> None:\n    with pytest.raises(ValueError, match=\"A Schedule could not be generated from\"):\n        Schedule(e, t, \"Q\", stub=stub, roll=14, calendar=cal_)\n\n\n@pytest.mark.parametrize(\n    (\"e\", \"fs\", \"t\", \"stub\", \"exp_roll\", \"exp_stub\"),\n    [\n        (dt(2022, 1, 1), dt(2022, 2, 26), dt(2024, 4, 26), \"FRONTSHORTBACK\", 26, dt(2024, 2, 26)),\n        (dt(2022, 1, 1), dt(2022, 2, 26), dt(2024, 4, 26), \"FRONTLONGBACK\", 26, dt(2023, 11, 26)),\n    ],\n)\ndef test_infer_stub_date_dual_sided(e, fs, t, stub, exp_roll, exp_stub, cal_) -> None:\n    result = Schedule(e, t, \"Q\", stub=stub, front_stub=fs, calendar=cal_)\n    assert result.ueffective == e\n    assert result.uback_stub == exp_stub\n    assert result.utermination == t\n    assert result.roll == exp_roll\n\n\n@pytest.mark.parametrize(\n    (\"e\", \"bs\", \"t\", \"stub\", \"exp_roll\", \"exp_stub\"),\n    [\n        (dt(2022, 1, 1), dt(2024, 2, 26), dt(2024, 4, 26), \"SHORTFRONT\", 26, dt(2022, 2, 26)),\n        (dt(2022, 1, 1), dt(2024, 2, 26), dt(2024, 4, 26), \"LONGFRONT\", 26, dt(2022, 5, 26)),\n    ],\n)\ndef test_infer_stub_date_dual_sided2(e, bs, t, stub, exp_roll, exp_stub, cal_) -> None:\n    result = Schedule(e, t, \"Q\", stub=stub, back_stub=bs, calendar=cal_)\n    assert result.ueffective == e\n    assert result.ufront_stub == exp_stub\n    assert result.uback_stub == bs\n    assert result.utermination == t\n    assert result.roll == exp_roll\n\n\ndef test_infer_stub_date_dual_sided_invalid(cal_) -> None:\n    with pytest.raises(ValueError, match=\"A Schedule could not be generated from\"):\n        Schedule(\n            dt(2022, 1, 1),\n            dt(2022, 12, 31),\n            \"Q\",\n            stub=\"FRONTSHORT\",\n            front_stub=dt(2022, 2, 13),\n            calendar=cal_,\n        )\n\n\ndef test_infer_stub_date_eom(cal_) -> None:\n    result = Schedule(\n        dt(2022, 1, 1),\n        dt(2023, 2, 28),\n        \"Q\",\n        stub=\"LONGFRONT\",\n        eom=True,  # <- the EOM parameter forces the stub to be 31 May and not 28 May\n        calendar=cal_,\n    )\n    assert result.ufront_stub == dt(2022, 5, 31)\n\n\ndef test_repr():\n    schedule = Schedule(\n        dt(2022, 1, 1),\n        \"2M\",\n        \"M\",\n    )\n    expected = f\"<rl.Schedule at {hex(id(schedule))}>\"\n    assert expected == schedule.__repr__()\n\n\ndef test_schedule_str(cal_) -> None:\n    schedule = Schedule(dt(2022, 1, 1), \"2M\", \"M\", eom=False, calendar=cal_, roll=1, payment_lag=1)\n    expected = \"freq: 1M (roll: 1), accrual adjuster: MF, payment adjuster: 1B,\\n\"\n    df = DataFrame(\n        {\n            defaults.headers[\"stub_type\"]: [\"Regular\", \"Regular\"],\n            defaults.headers[\"u_acc_start\"]: [dt(2022, 1, 1), dt(2022, 2, 1)],\n            defaults.headers[\"u_acc_end\"]: [dt(2022, 2, 1), dt(2022, 3, 1)],\n            defaults.headers[\"a_acc_start\"]: [dt(2022, 1, 4), dt(2022, 2, 1)],\n            defaults.headers[\"a_acc_end\"]: [dt(2022, 2, 1), dt(2022, 3, 1)],\n            defaults.headers[\"payment\"]: [dt(2022, 2, 2), dt(2022, 3, 2)],\n        },\n    )\n    result = schedule.__str__()\n    assert result == expected + df.__repr__()\n\n\ndef test_schedule_raises(cal_) -> None:\n    with pytest.raises(ValueError, match=\"Frequency can not be determined from `frequency` input.\"):\n        _ = Schedule(dt(2022, 1, 1), dt(2022, 12, 31), \"Unknown\")\n\n    with pytest.raises(ValueError, match=\"`termination` must be after\"):\n        _ = Schedule(dt(2022, 1, 1), dt(2021, 12, 31), \"Q\")\n\n    with pytest.raises(ValueError):\n        _ = Schedule(\n            dt(2022, 1, 1),\n            dt(2022, 12, 31),\n            \"Q\",\n            stub=\"SHORTFRONT\",\n            front_stub=None,\n            back_stub=dt(2022, 11, 15),\n            eom=False,\n            modifier=\"MF\",\n            calendar=cal_,\n            roll=1,\n        )\n\n    with pytest.raises(ValueError):\n        _ = Schedule(\n            dt(2022, 1, 1),\n            dt(2022, 12, 31),\n            \"Q\",\n            stub=\"SHORTBACK\",\n            front_stub=dt(2022, 3, 15),\n            eom=False,\n            calendar=cal_,\n            roll=1,\n        )\n\n    with pytest.raises(ValueError):\n        _ = Schedule(\n            dt(2022, 1, 1),\n            dt(2022, 12, 31),\n            \"Q\",\n            stub=\"SBLB\",\n            front_stub=dt(2022, 3, 15),\n            eom=False,\n            calendar=cal_,\n            roll=1,\n        )\n\n\n@pytest.mark.parametrize(\n    (\"eff\", \"term\", \"f\", \"roll\"),\n    [\n        (\n            dt(2022, 3, 16),\n            dt(2024, 9, 10),\n            \"Q\",\n            \"imm\",\n        ),  # cannot build because termination does not align with IMM and no back stub specified.\n        (\n            dt(2022, 3, 1),\n            dt(2023, 3, 2),\n            \"A\",\n            \"som\",\n        ),  # fails because roll is explicit and a short stub to 1st march 2022 this does not align.\n        (\n            dt(2022, 2, 20),\n            dt(2025, 8, 21),\n            \"S\",\n            20,\n        ),  # fails because a short stub cannot be generated aligned with specified roll.\n        (\n            dt(2022, 2, 28),\n            dt(2024, 2, 28),\n            \"S\",\n            30,\n        ),  # is leap year 2024 and front stub is specified so 28th Feb '24 does not align with roll\n    ],\n)\ndef test_unadjusted_regular_swap_dead_stubs(eff, term, f, roll) -> None:\n    # this test isn't really about dead stubs more about misalignment with rolls.\n    with pytest.raises(ValueError, match=\"A Schedule could not be generated from the parameter c\"):\n        # the default `stub` is SHORTFRONT\n        Schedule(eff, term, f, eom=False, roll=roll)\n\n\n@pytest.mark.parametrize(\n    (\"eff\", \"term\", \"f\", \"roll\", \"stub\"),\n    [\n        (\n            dt(2022, 3, 31),\n            dt(2023, 3, 30),\n            \"A\",\n            \"eom\",\n            \"shortfront\",\n        ),  # this builds because it is a single period short stub.\n        (\n            dt(2022, 3, 1),\n            dt(2023, 3, 2),\n            \"A\",\n            \"som\",\n            \"shortback\",\n        ),  # corrects the above test issue by specifying a back stub.\n        (\n            dt(2022, 2, 20),\n            dt(2025, 8, 21),\n            \"S\",\n            20,\n            \"longback\",\n        ),  # corrects to provide a single period stub\n        (\n            dt(2022, 2, 20),\n            dt(2025, 8, 21),\n            \"S\",\n            20,\n            \"shortback\",\n        ),  # corrects to provide a shoprt back stub with regular rolling on 20th\n        (\n            dt(2022, 2, 28),\n            dt(2024, 2, 28),\n            \"S\",\n            30,\n            \"shortback\",\n        ),  # corrects the above to specify a back stub\n        (\n            dt(2022, 2, 28),\n            dt(2024, 2, 28),\n            \"S\",\n            30,\n            \"longback\",\n        ),  # or alternatively with a long back stub\n    ],\n)\ndef test_unadjusted_regular_swap_dead_stubs_corrections(eff, term, f, roll, stub) -> None:\n    Schedule(eff, term, f, eom=False, roll=roll, stub=stub)\n\n\n@pytest.mark.parametrize(\n    (\"eff\", \"term\", \"f\", \"roll\", \"exp\"),\n    [\n        (dt(2022, 3, 16), dt(2022, 6, 30), \"S\", NoInput(0), False),  # frequency\n        (dt(2022, 3, 15), dt(2022, 9, 21), \"Q\", \"imm\", False),  # non-imm eff\n        (dt(2022, 3, 30), dt(2029, 3, 31), \"A\", \"eom\", False),  # non-eom eff\n        (dt(2022, 3, 2), dt(2029, 3, 1), \"A\", \"som\", False),  # non-som eff\n        (dt(2022, 3, 30), dt(2023, 9, 30), \"S\", 31, False),  # non-eom\n        (dt(2024, 2, 28), dt(2025, 8, 30), \"S\", 30, False),  # is leap\n        (dt(2024, 2, 29), dt(2025, 8, 30), \"S\", 30, True),  # is leap\n        (dt(2022, 2, 28), dt(2025, 8, 29), \"S\", 29, True),  # is end feb\n        (dt(2022, 2, 20), dt(2025, 8, 20), \"S\", 20, True),  # OK\n        (dt(2022, 2, 21), dt(2025, 8, 20), \"S\", 20, False),  # roll\n        (dt(2022, 2, 22), dt(2024, 2, 15), \"S\", NoInput(0), False),  # no valid roll\n        (dt(2022, 2, 28), dt(2024, 2, 29), \"S\", NoInput(0), True),  # 29 or eom\n        (dt(2022, 6, 30), dt(2024, 12, 30), \"S\", NoInput(0), True),  # 30\n    ],\n)\ndef test_unadjusted_regular_swap(eff, term, f, roll, exp) -> None:\n    result = Schedule(eff, term, f, eom=False, roll=roll)\n    assert result.is_regular() is exp\n\n\n# 12th and 13th of Feb and March are Saturday and Sunday\n@pytest.mark.parametrize(\n    (\"eff\", \"term\", \"roll\", \"e_ueff\", \"e_uterm\", \"e_roll\"),\n    [\n        (dt(2022, 2, 11), dt(2022, 3, 11), 11, dt(2022, 2, 11), dt(2022, 3, 11), 11),\n        (dt(2022, 2, 14), dt(2022, 3, 14), 14, dt(2022, 2, 14), dt(2022, 3, 14), 14),\n        (dt(2022, 2, 14), dt(2022, 3, 14), NoInput(0), dt(2022, 2, 14), dt(2022, 3, 14), 14),\n        (dt(2022, 2, 13), dt(2022, 3, 14), NoInput(0), dt(2022, 2, 13), dt(2022, 3, 13), 13),\n        (dt(2022, 2, 12), dt(2022, 3, 14), NoInput(0), dt(2022, 2, 12), dt(2022, 3, 12), 12),\n        (dt(2022, 2, 14), dt(2022, 3, 12), NoInput(0), dt(2022, 2, 12), dt(2022, 3, 12), 12),\n        (dt(2022, 2, 14), dt(2022, 3, 14), 12, dt(2022, 2, 12), dt(2022, 3, 12), 12),\n        (dt(2022, 2, 28), dt(2022, 3, 31), NoInput(0), dt(2022, 2, 28), dt(2022, 3, 31), 31),\n        (dt(2022, 2, 28), dt(2022, 3, 31), \"eom\", dt(2022, 2, 28), dt(2022, 3, 31), 31),\n        (\n            dt(2022, 2, 12),\n            dt(2022, 3, 13),\n            NoInput(0),\n            dt(2022, 2, 12),\n            dt(2022, 3, 13),\n            13,\n        ),  # dead stub converts to long stub\n    ],\n)\ndef test_check_regular_swap_mf(eff, term, roll, e_ueff, e_uterm, e_roll, cal_) -> None:\n    result = Schedule(eff, term, \"M\", modifier=\"MF\", eom=False, roll=roll, calendar=cal_)\n    assert result.ueffective == e_ueff\n    assert result.utermination == e_uterm\n    assert result.roll == e_roll\n\n\n# 12th and 13th of Feb and March are Saturday and Sunday\n@pytest.mark.parametrize(\n    (\"eff\", \"term\", \"roll\"),\n    [\n        (dt(2022, 2, 14), dt(2022, 3, 14), 11),  # fails due to roll misalignment\n        (dt(2022, 2, 28), dt(2022, 3, 31), 28),  # fails due to wrong stub side\n    ],\n)\ndef test_check_regular_swap_mf_failures(eff, term, roll, cal_) -> None:\n    with pytest.raises(ValueError):\n        Schedule(eff, term, \"M\", modifier=\"MF\", eom=False, roll=roll, calendar=cal_)\n\n\n@pytest.mark.parametrize(\n    (\"effective\", \"termination\", \"uf\", \"ub\", \"roll\", \"expected\"),\n    [\n        (\n            dt(2023, 2, 4),\n            dt(2023, 9, 4),\n            dt(2023, 3, 4),\n            NoInput(0),\n            4,\n            [dt(2023, 2, 4), dt(2023, 3, 4), dt(2023, 6, 4), dt(2023, 9, 4)],\n        ),\n        (\n            dt(2023, 2, 4),\n            dt(2023, 9, 4),\n            NoInput(0),\n            dt(2023, 8, 4),\n            4,\n            [dt(2023, 2, 4), dt(2023, 5, 4), dt(2023, 8, 4), dt(2023, 9, 4)],\n        ),\n        (\n            dt(2023, 3, 4),\n            dt(2023, 9, 4),\n            NoInput(0),\n            NoInput(0),\n            4,\n            [dt(2023, 3, 4), dt(2023, 6, 4), dt(2023, 9, 4)],\n        ),\n        (\n            dt(2023, 2, 4),\n            dt(2023, 10, 4),\n            dt(2023, 3, 4),\n            dt(2023, 9, 4),\n            4,\n            [dt(2023, 2, 4), dt(2023, 3, 4), dt(2023, 6, 4), dt(2023, 9, 4), dt(2023, 10, 4)],\n        ),\n    ],\n)\ndef test_generate_irregular_uschedule(effective, termination, uf, ub, roll, expected) -> None:\n    result = Schedule(effective, termination, \"Q\", roll=roll, front_stub=uf, back_stub=ub)\n    assert result.uschedule == expected\n\n\n@pytest.mark.parametrize(\n    (\"effective\", \"termination\", \"roll\", \"expected\"),\n    [\n        (dt(2023, 3, 4), dt(2023, 9, 4), 4, [dt(2023, 3, 4), dt(2023, 6, 4), dt(2023, 9, 4)]),\n        (dt(2023, 3, 6), dt(2023, 9, 6), 6, [dt(2023, 3, 6), dt(2023, 6, 6), dt(2023, 9, 6)]),\n        (\n            dt(2023, 4, 30),\n            dt(2023, 10, 31),\n            31,\n            [dt(2023, 4, 30), dt(2023, 7, 31), dt(2023, 10, 31)],\n        ),\n        (\n            dt(2022, 2, 28),\n            dt(2022, 8, 31),\n            \"eom\",\n            [dt(2022, 2, 28), dt(2022, 5, 31), dt(2022, 8, 31)],\n        ),\n        (\n            dt(2021, 11, 30),\n            dt(2022, 5, 31),\n            31,\n            [dt(2021, 11, 30), dt(2022, 2, 28), dt(2022, 5, 31)],\n        ),\n        (\n            dt(2023, 4, 30),\n            dt(2023, 10, 30),\n            30,\n            [dt(2023, 4, 30), dt(2023, 7, 30), dt(2023, 10, 30)],\n        ),\n        (\n            dt(2022, 3, 16),\n            dt(2022, 9, 21),\n            \"imm\",\n            [dt(2022, 3, 16), dt(2022, 6, 15), dt(2022, 9, 21)],\n        ),\n        (dt(2022, 12, 1), dt(2023, 6, 1), \"som\", [dt(2022, 12, 1), dt(2023, 3, 1), dt(2023, 6, 1)]),\n    ],\n)\ndef test_generate_regular_uschedule(effective, termination, roll, expected) -> None:\n    result = Schedule(effective, termination, \"Q\", roll=roll)\n    assert result.uschedule == expected\n\n\n@pytest.mark.parametrize(\n    (\"effective\", \"termination\", \"frequency\", \"expected\"),\n    [\n        (dt(2022, 2, 15), dt(2022, 8, 15), \"M\", 6),\n        (dt(2022, 2, 15), dt(2022, 8, 15), \"Q\", 2),\n        (dt(2022, 2, 15), dt(2032, 2, 15), \"Q\", 40),\n        (dt(2022, 2, 15), dt(2032, 2, 15), \"Z\", 1),\n    ],\n)\ndef test_regular_n_periods(effective, termination, frequency, expected) -> None:\n    result = Schedule(effective, termination, frequency)\n    assert result.n_periods == expected\n\n\n@pytest.mark.parametrize(\n    (\"eff\", \"term\", \"freq\", \"ss\", \"eom\", \"roll\", \"expected\"),\n    [\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"M\", \"SHORTFRONT\", False, NoInput(0), dt(2022, 1, 15)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"Q\", \"SHORTFRONT\", False, NoInput(0), dt(2022, 2, 15)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"S\", \"SHORTFRONT\", False, NoInput(0), dt(2022, 2, 15)),\n        (dt(2022, 2, 15), dt(2023, 2, 1), \"S\", \"SHORTFRONT\", False, NoInput(0), dt(2022, 8, 1)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"M\", \"SHORTBACK\", False, NoInput(0), dt(2023, 2, 1)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"Q\", \"SHORTBACK\", False, NoInput(0), dt(2023, 1, 1)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"S\", \"SHORTBACK\", False, NoInput(0), dt(2023, 1, 1)),\n        (dt(2022, 2, 15), dt(2023, 2, 1), \"S\", \"SHORTBACK\", False, NoInput(0), dt(2022, 8, 15)),\n        (dt(2022, 1, 1), dt(2023, 2, 28), \"M\", \"SHORTFRONT\", True, NoInput(0), dt(2022, 1, 31)),\n        (dt(2022, 3, 1), dt(2023, 2, 28), \"Q\", \"SHORTFRONT\", True, NoInput(0), dt(2022, 5, 31)),\n        (dt(2022, 3, 1), dt(2023, 2, 17), \"Q\", \"SHORTFRONT\", False, 17, dt(2022, 5, 17)),\n    ],\n)\ndef test_get_unadjusted_short_stub_date(eff, term, freq, ss, eom, roll, expected) -> None:\n    result = Schedule(eff, term, freq, stub=ss, eom=eom, roll=roll)\n    if ss == \"SHORTFRONT\":\n        assert result.ufront_stub == expected\n    else:\n        assert result.uback_stub == expected\n\n\n@pytest.mark.parametrize(\n    (\"eff\", \"term\", \"freq\", \"stub\", \"eom\", \"roll\", \"expected\"),\n    [\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"M\", \"LONGFRONT\", False, NoInput(0), dt(2022, 2, 15)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"Q\", \"LONGFRONT\", False, NoInput(0), dt(2022, 5, 15)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"S\", \"LONGFRONT\", False, NoInput(0), dt(2022, 8, 15)),\n        (dt(2022, 2, 15), dt(2024, 2, 1), \"S\", \"LONGFRONT\", False, NoInput(0), dt(2023, 2, 1)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"M\", \"LONGBACK\", False, NoInput(0), dt(2023, 1, 1)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"Q\", \"LONGBACK\", False, NoInput(0), dt(2022, 10, 1)),\n        (dt(2022, 1, 1), dt(2023, 2, 15), \"S\", \"LONGBACK\", False, NoInput(0), dt(2022, 7, 1)),\n        (dt(2022, 2, 15), dt(2024, 2, 1), \"S\", \"LONGBACK\", False, NoInput(0), dt(2023, 2, 15)),\n        (dt(2022, 1, 1), dt(2023, 2, 28), \"M\", \"LONGFRONT\", True, NoInput(0), dt(2022, 2, 28)),\n        (dt(2022, 3, 1), dt(2023, 2, 28), \"Q\", \"LONGFRONT\", True, NoInput(0), dt(2022, 8, 31)),\n        (dt(2022, 3, 1), dt(2023, 2, 17), \"Q\", \"LONGFRONT\", False, 17, dt(2022, 8, 17)),\n        (dt(2022, 4, 30), dt(2023, 2, 18), \"Q\", \"LONGBACK\", True, NoInput(0), dt(2022, 10, 31)),\n    ],\n)\ndef test_get_unadjusted_stub_date_long(eff, term, freq, stub, eom, roll, expected) -> None:\n    result = Schedule(eff, term, freq, stub=stub, eom=eom, roll=roll)\n    if stub == \"LONGFRONT\":\n        assert result.ufront_stub == expected\n    else:\n        assert result.uback_stub == expected\n\n\n@pytest.mark.parametrize(\n    (\"e\", \"t\", \"r\", \"exp_roll\", \"exp_ue\", \"exp_ut\"),\n    [\n        (\n            dt(2020, 8, 31),\n            dt(2021, 2, 26),\n            NoInput(0),\n            31,\n            dt(2020, 8, 31),\n            dt(2021, 2, 28),\n        ),\n        (\n            dt(2021, 2, 26),\n            dt(2021, 8, 31),\n            NoInput(0),\n            31,\n            dt(2021, 2, 28),\n            dt(2021, 8, 31),\n        ),\n        (dt(2021, 2, 26), dt(2021, 8, 30), 29, 29, dt(2021, 2, 28), dt(2021, 8, 29)),\n    ],\n)\ndef test_schedule_eom(e, t, r, exp_roll, exp_ue, exp_ut, cal_) -> None:\n    sched = Schedule(e, t, \"S\", roll=r, modifier=\"MF\", calendar=cal_)\n    assert sched.ueffective == exp_ue\n    assert sched.utermination == exp_ut\n    assert sched.roll == exp_roll\n\n\ndef test_payment_lag_is_business_days() -> None:\n    sched = Schedule(dt(2022, 11, 16), \"1M\", \"M\", modifier=\"MF\", calendar=\"ldn\")\n    assert sched.pschedule[1] == dt(2022, 12, 20)\n    # not 19th Dec which is adjusted(16th Dec + 2 days)\n\n\ndef test_schedule_bad_stub_combinations_raise() -> None:\n    with pytest.raises(ValueError, match=\"Must supply at least one stub date\"):\n        _ = Schedule(\n            effective=dt(2022, 1, 1),\n            termination=dt(2023, 1, 1),\n            frequency=\"S\",\n            stub=\"SHORTFRONTSHORTBACK\",\n        )\n\n\n@pytest.mark.skip(reason=\"StubInference enum behaves differently to versions <= 2.0\")\ndef test_schedule_bad_stub_combinations_raise2() -> None:\n    with pytest.raises(ValueError, match=\"`stub` is only front sided but `back_stub` given\"):\n        _ = Schedule(\n            effective=dt(2022, 1, 1),\n            termination=dt(2023, 1, 1),\n            frequency=\"S\",\n            stub=\"FRONT\",\n            front_stub=dt(2022, 2, 1),\n            back_stub=dt(2022, 12, 1),\n        )\n\n\n@pytest.mark.parametrize(\n    (\"st\", \"fs\", \"bs\"),\n    [\n        (\"SHORTFRONTSHORTBACK\", NoInput(0), dt(2023, 1, 1)),\n        (\"SHORTFRONTLONGBACK\", dt(2022, 2, 1), NoInput(0)),\n        (\"SHORTFRONTSHORTBACK\", dt(2022, 4, 15), dt(2022, 10, 15)),\n        (\"SHORTFRONT\", NoInput(0), NoInput(0)),\n        (\"SHORTFRONT\", dt(2022, 2, 1), NoInput(0)),\n        (\"SHORTBACK\", NoInput(0), dt(2023, 1, 1)),\n        (\"SHORTBACK\", NoInput(0), NoInput(0)),\n    ],\n)\ndef test_schedule_combinations_valid(st, fs, bs) -> None:\n    Schedule(\n        effective=dt(2022, 1, 1),\n        termination=dt(2023, 2, 1),\n        frequency=\"S\",\n        stub=st,\n        back_stub=bs,\n        front_stub=fs,\n    )\n\n\n@pytest.mark.parametrize(\n    (\"st\", \"fs\", \"bs\", \"roll\"),\n    [\n        (\"FRONTBACK\", NoInput(0), dt(2023, 1, 15), 20),\n        (\"FRONTBACK\", dt(2022, 2, 1), NoInput(0), 20),\n        (\"FRONTBACK\", dt(2022, 4, 15), dt(2023, 11, 25), NoInput(0)),\n        (\"FRONT\", NoInput(0), NoInput(0), 20),\n        (\"FRONT\", dt(2022, 3, 12), NoInput(0), 20),\n        (\"BACK\", NoInput(0), dt(2022, 12, 5), 20),\n        (\"BACK\", NoInput(0), NoInput(0), 20),\n    ],\n)\ndef test_schedule_combinations_invalid(st, fs, bs, roll) -> None:\n    with pytest.raises(ValueError, match=\"A Schedule could not be generated from the parameter co\"):\n        Schedule(\n            effective=dt(2022, 1, 1),\n            termination=dt(2023, 2, 1),\n            frequency=\"S\",\n            stub=st,\n            back_stub=bs,\n            front_stub=fs,\n            roll=roll,\n        )\n\n\ndef test_schedule_n_periods() -> None:\n    result = Schedule(\n        effective=dt(2022, 1, 1),\n        termination=dt(2023, 2, 1),\n        frequency=\"S\",\n        stub=\"SHORTFRONT\",\n    )\n    assert result.n_periods == 3\n\n\n@pytest.mark.parametrize(\n    (\"ue\", \"ut\", \"exp\"),\n    [\n        (dt(2023, 3, 17), dt(2023, 12, 20), dt(2023, 9, 20)),\n        (dt(2022, 12, 19), dt(2023, 12, 20), dt(2023, 3, 15)),\n    ],  # PR #9\n)\ndef test_get_unadjusted_long_stub_imm(ue, ut, exp) -> None:\n    result = Schedule(ue, ut, \"Q\", stub=\"LONGFRONT\", eom=False, roll=\"imm\")\n    assert result.ufront_stub == exp\n\n\n@pytest.mark.parametrize(\n    (\"ue\", \"ut\"),\n    [\n        (dt(2023, 3, 15), dt(2023, 12, 20)),\n    ],\n)\ndef test_get_unadjusted_short_stub_imm(ue, ut) -> None:\n    result = Schedule(ue, ut, \"Q\", stub=\"SHORTFRONT\", eom=False)\n    assert result.is_regular()\n    assert result.roll == \"IMM\"\n\n\ndef test_dead_stubs() -> None:\n    # this was a bug detected in performance testing which generated a 1d invalid stub.\n    # this failed originally because a 1D stub between Sun 2nd May 27 and Mon 3rd May 27\n    # was invalid since the adjusted accrual schedule modified the sunday to be\n    # equal to the Monday giving a 0 day period.\n    s = Schedule(\n        dt(2027, 5, 2),\n        dt(2046, 5, 3),\n        \"A\",\n        stub=\"LONGFRONT\",\n        calendar=\"bus\",\n    )\n    assert s.uschedule[0:2] == [dt(2027, 5, 2), dt(2028, 5, 3)]\n    assert s.aschedule[0:2] == [dt(2027, 5, 3), dt(2028, 5, 3)]\n\n    # manipulate this test to cover the case for dual sided stubs\n    s = Schedule(\n        dt(2027, 5, 2),\n        dt(2046, 6, 3),\n        \"A\",\n        stub=\"LONGFRONTSHORTBACK\",\n        back_stub=dt(2046, 5, 3),  # back stub means front stub is inferred\n        calendar=\"bus\",\n    )\n    assert s.uschedule[0:2] == [dt(2027, 5, 2), dt(2028, 5, 3)]\n    assert s.aschedule[0:2] == [dt(2027, 5, 3), dt(2028, 5, 3)]\n\n    # this was a bug detected in performance testing which generated a 1d invalid stub.\n    # this failed originally because the ueffective date of Sat 20-dec-25 and the\n    # inferred front stub of Sun 21-dec-25 both adjusted forwards to 22-dec-25\n    # giving a 0 day period.\n    s = Schedule(\n        dt(2025, 12, 20),\n        dt(2069, 12, 21),\n        \"A\",\n        stub=\"LONGFRONT\",\n        calendar=\"bus\",\n    )\n    assert s.uschedule[0:2] == [dt(2025, 12, 20), dt(2026, 12, 21)]\n    assert s.aschedule[0:2] == [dt(2025, 12, 22), dt(2026, 12, 21)]\n\n    # this was a bug detected in performance testing which generated a 1d invalid stub.\n    # this failed originally because the utermination date of Sat 20-dec-25 and the\n    # inferred front stub of Sun 21-dec-25 both adjusted forwards to 22-dec-25\n    # giving a 0 day period.\n    s = Schedule(\n        dt(2027, 10, 19),\n        dt(2047, 10, 20),\n        \"A\",\n        stub=\"LONGBACK\",\n        calendar=\"bus\",\n    )\n    assert s.uschedule[-2:] == [dt(2046, 10, 19), dt(2047, 10, 20)]\n    assert s.aschedule[-2:] == [dt(2046, 10, 19), dt(2047, 10, 21)]\n\n    # manipulate this test for dual sided stubs\n    s = Schedule(\n        dt(2027, 8, 19),\n        dt(2047, 10, 20),\n        \"A\",\n        stub=\"SHORTFRONTLONGBACK\",\n        front_stub=dt(2027, 10, 19),\n        calendar=\"bus\",\n    )\n    assert s.uschedule[-2:] == [dt(2046, 10, 19), dt(2047, 10, 20)]\n    assert s.aschedule[-2:] == [dt(2046, 10, 19), dt(2047, 10, 21)]\n\n\n@pytest.mark.parametrize(\n    (\"mode\", \"end\", \"roll\"),\n    [\n        (NoInput(0), dt(2025, 8, 17), 17),\n        (\"swaps_align\", dt(2025, 8, 17), 17),\n        (\"swaptions_align\", dt(2025, 8, 19), 19),\n    ],\n)\ndef test_eval_mode(mode, end, roll) -> None:\n    sch = Schedule(\n        effective=\"1Y\",\n        termination=\"1Y\",\n        frequency=\"S\",\n        calendar=\"tgt\",\n        eval_date=dt(2023, 8, 17),\n        eval_mode=mode,\n    )\n    assert sch.roll == roll\n    assert sch.utermination == end\n\n\ndef test_eval_date_raises() -> None:\n    with pytest.raises(ValueError, match=\"For `effective` given as string tenor, must\"):\n        Schedule(\n            effective=\"1Y\",\n            termination=\"1Y\",\n            frequency=\"S\",\n        )\n\n\ndef test_single_period_imm_roll():\n    s = Schedule(\n        effective=dt(2024, 12, 18),\n        termination=dt(2025, 3, 19),\n        roll=\"imm\",\n        frequency=\"a\",\n        calendar=\"stk\",\n    )\n    assert len(s.aschedule) == 2\n\n\ndef test_deviate_from_effective_in_inference() -> None:\n    # 28th and 30th are both valid rolls for this schedule\n    # test that 30th is inferred since it deviates the least from effective input.\n    s = Schedule(\n        effective=dt(2024, 12, 30),\n        termination=dt(2025, 11, 28),\n        frequency=\"m\",\n        eom=False,\n        calendar=\"bus\",\n    )\n    assert s.ueffective == dt(2024, 12, 30)\n    assert s.utermination == dt(2025, 11, 30)\n    assert s.roll == 30\n\n\n@pytest.mark.parametrize(\n    (\"f\", \"expected\"),\n    [\n        (Frequency.CalDays(10), NoInput(0)),\n        (Frequency.Months(1, None), 16),\n        (Frequency.Months(1, RollDay.Day(16)), 16),\n    ],\n)\ndef test_roll_property(f, expected) -> None:\n    s = Schedule(dt(2000, 1, 16), dt(2001, 1, 16), f)\n    result = s.roll\n    assert result == expected\n\n\ndef test_day_type_tenor() -> None:\n    # should convert MF to Following only\n    s = Schedule(\n        dt(2024, 12, 30),\n        \"1d\",\n        \"A\",\n        modifier=\"mf\",\n        calendar=\"stk\",\n    )\n    assert s.utermination == dt(2025, 1, 2)\n\n\ndef test_cds_standard_example() -> None:\n    # https://www.cdsmodel.com/documentation.html?# standard example\n    # use Adjuster.FollowingExLast to avoid adjusting the final accrual date.\n    s = Schedule(\n        dt(2008, 12, 20),\n        dt(2010, 3, 20),\n        \"Q\",\n        modifier=\"fex\",\n        calendar=\"bus\",\n        payment_lag=0,\n    )\n    expected = [\n        dt(2008, 12, 22),\n        dt(2009, 3, 20),\n        dt(2009, 6, 22),\n        dt(2009, 9, 21),\n        dt(2009, 12, 21),\n        dt(2010, 3, 20),\n    ]\n    assert s.aschedule == expected\n\n    expected = [\n        dt(2008, 12, 22),\n        dt(2009, 3, 20),\n        dt(2009, 6, 22),\n        dt(2009, 9, 21),\n        dt(2009, 12, 21),\n        dt(2010, 3, 22),\n    ]\n    assert s.pschedule == expected\n\n\n@pytest.mark.parametrize(\n    \"frequency\",\n    [\n        \"M\",  # monthly,\n        \"Q\",  # quarterly,\n        \"S\",  # semi-annually,\n        \"A\",  # annually,\n        \"10D\",  # 10-cal-days\n        \"10B\",  # 10-bus-days\n        \"2W\",  # 14-cal-days\n        \"8M\",  # 8-months\n        \"1Y\",  # 1-year\n    ],\n)\ndef test_all_frequency_as_str(frequency):\n    s = Schedule(\n        dt(2000, 1, 1),\n        dt(2010, 1, 1),\n        frequency=frequency,\n        stub=\"ShortFront\",\n        calendar=\"bus\",\n    )\n    s.__str__()\n\n\ndef test_inference_busdays():\n    # the effective is given adjusted whilst termination is unadjusted\n    s = Schedule(\n        effective=dt(2000, 1, 6),\n        termination=dt(2000, 3, 1),\n        frequency=Frequency.Months(1, None),\n        modifier=Adjuster.BusDaysLagSettle(5),\n    )\n    assert s.uschedule == [dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 3, 1)]\n    assert s.aschedule == [dt(2000, 1, 6), dt(2000, 2, 6), dt(2000, 3, 6)]\n\n\ndef test_payment_adjuster_2_and_3():\n    s = Schedule(\n        dt(2000, 1, 1),\n        dt(2000, 3, 1),\n        \"M\",\n        calendar=\"all\",\n        modifier=\"none\",\n        payment_lag=1,\n        payment_lag_exchange=2,\n        extra_lag=-2,\n    )\n    assert s.pschedule == [dt(2000, 1, 2), dt(2000, 2, 2), dt(2000, 3, 2)]\n    assert s.pschedule2 == [dt(2000, 1, 3), dt(2000, 2, 3), dt(2000, 3, 3)]\n    assert s.pschedule3 == [dt(1999, 12, 30), dt(2000, 1, 30), dt(2000, 2, 28)]\n\n\n@pytest.mark.parametrize(\n    (\"eff\", \"front\", \"back\", \"term\"),\n    [\n        # All unadjusted\n        (dt(2025, 1, 15), NoInput(0), NoInput(0), dt(2025, 4, 15)),\n        (dt(2025, 1, 15), dt(2025, 2, 15), NoInput(0), dt(2025, 4, 15)),\n        (dt(2025, 1, 15), NoInput(0), dt(2025, 3, 15), dt(2025, 4, 15)),\n        (dt(2025, 1, 15), dt(2025, 2, 15), dt(2025, 3, 15), dt(2025, 4, 15)),\n        # Stub given as adjusted\n        (dt(2025, 1, 15), dt(2025, 2, 17), NoInput(0), dt(2025, 4, 15)),\n        (dt(2025, 1, 15), NoInput(0), dt(2025, 3, 17), dt(2025, 4, 15)),\n        (dt(2025, 1, 15), dt(2025, 2, 17), dt(2025, 3, 17), dt(2025, 4, 15)),\n        # Stub given as mixed\n        (dt(2025, 1, 15), dt(2025, 2, 17), dt(2025, 3, 15), dt(2025, 4, 15)),\n    ],\n)\ndef test_schedule_when_stub_input_is_regular(eff, front, back, term):\n    # GH-dev 142\n    s_base = Schedule(\n        effective=dt(2025, 1, 15),\n        termination=dt(2025, 3, 17),\n        calendar=\"bus\",\n        frequency=\"M\",\n        modifier=\"mf\",\n    )\n    assert s_base.uschedule == [dt(2025, 1, 15), dt(2025, 2, 15), dt(2025, 3, 15)]\n    assert s_base.aschedule == [dt(2025, 1, 15), dt(2025, 2, 17), dt(2025, 3, 17)]\n\n    s = Schedule(\n        effective=eff,\n        termination=term,\n        front_stub=front,\n        back_stub=back,\n        calendar=\"bus\",\n        frequency=\"M\",\n        modifier=\"mf\",\n    )\n    assert s._stubs == [False, False, False]\n\n\n@pytest.mark.skip(reason=\"multiple stubs, where one may be a genuine stub is not implemented.\")\n@pytest.mark.parametrize(\"fs\", [dt(2025, 2, 15), dt(2025, 2, 17)])\ndef test_schedule_when_one_front_stub_of_two_is_regular(fs):\n    # GH-dev 142\n    # this tests that one stub might be genuine whilst the other is a regular period and\n    # the schedule still generates correctly.\n\n    # this requires additional branching in the Rust scheduling code in the pre-check which has\n    # not been developed. The most common use case for this pre-check is when only a front stub,\n    # i.e. the first coupon date of a bond is provided.\n\n    s = Schedule(\n        effective=dt(2025, 1, 15),\n        termination=dt(2025, 4, 25),\n        front_stub=fs,\n        back_stub=dt(2025, 4, 15),\n        calendar=\"bus\",\n        frequency=\"M\",\n        modifier=\"mf\",\n    )\n    assert s._stubs == [False, False, False, True]\n\n\ndef test_schedule_in_advance_payment():\n    # used by FRA constructor\n    from rateslib.scheduling import Adjuster\n\n    s = Schedule(\n        effective=dt(2024, 3, 20),\n        termination=dt(2024, 12, 18),\n        calendar=\"bus\",\n        frequency=\"Q\",\n        modifier=\"mf\",\n        payment_lag=Adjuster.BusDaysLagSettleInAdvance(1),\n    )\n    assert s.aschedule == [dt(2024, 3, 20), dt(2024, 6, 19), dt(2024, 9, 18), dt(2024, 12, 18)]\n    assert s.pschedule == [dt(2024, 3, 21), dt(2024, 3, 21), dt(2024, 6, 20), dt(2024, 9, 19)]\n    assert s.pschedule3 == s.pschedule\n\n\n@pytest.mark.parametrize(\"tenor\", [\"3b\", \"3d\", \"7d\", \"14d\", \"2w\", \"1m\", \"6m\", \"12m\", \"18m\", \"2y\"])\ndef test_single_period_from_str_matching_frequency(tenor):\n    # test was introduced for a Bill that derives a termination from a string tenor.\n    # When the frequency matches the tenor it should generate only a single period.\n    s = Schedule(effective=dt(2025, 1, 15), termination=tenor, frequency=tenor)\n    assert s.n_periods == 1\n\n\n@pytest.mark.parametrize(\"stub\", [\"shortfront\", \"shortback\"])\ndef test_dead_stub_failures(stub) -> None:\n    # this test attempts to build a schedule from unadjusted saturday to unadjusted sunday\n    # using a 7d frequency. This converts the dead short stub to a long stub and thereby\n    # defines only one period.\n    s = Schedule(\n        effective=dt(2026, 1, 3),  # saturday\n        termination=dt(2026, 1, 11),  # sunday\n        frequency=\"7d\",\n        calendar=\"bus\",\n        modifier=\"f\",\n        stub=stub,\n    )\n    assert s.uschedule == [dt(2026, 1, 3), dt(2026, 1, 11)]\n"
  },
  {
    "path": "python/tests/scheduling/test_schedulers.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.rs import Adjuster, Cal, Frequency, RollDay, Schedule, StubInference\n\n\n@pytest.mark.parametrize(\n    (\"ueff\", \"uterm\", \"si\", \"exp\"),\n    [\n        (\n            dt(2000, 1, 1),\n            dt(2000, 7, 1),\n            StubInference.NeitherSide,\n            [dt(2000, 1, 1), dt(2000, 4, 1), dt(2000, 7, 1)],\n        ),\n        (\n            dt(2000, 1, 1),\n            dt(2000, 8, 1),\n            StubInference.ShortFront,\n            [dt(2000, 1, 1), dt(2000, 2, 1), dt(2000, 5, 1), dt(2000, 8, 1)],\n        ),\n        (\n            dt(2000, 1, 1),\n            dt(2000, 8, 1),\n            StubInference.LongFront,\n            [dt(2000, 1, 1), dt(2000, 5, 1), dt(2000, 8, 1)],\n        ),\n        (\n            dt(2000, 1, 1),\n            dt(2000, 8, 1),\n            StubInference.ShortBack,\n            [dt(2000, 1, 1), dt(2000, 4, 1), dt(2000, 7, 1), dt(2000, 8, 1)],\n        ),\n        (\n            dt(2000, 1, 1),\n            dt(2000, 8, 1),\n            StubInference.LongBack,\n            [dt(2000, 1, 1), dt(2000, 4, 1), dt(2000, 8, 1)],\n        ),\n    ],\n)\ndef test_schedule(ueff, uterm, si, exp):\n    s = Schedule(\n        effective=ueff,\n        termination=uterm,\n        frequency=Frequency.Months(3, RollDay.Day(1)),\n        calendar=Cal([], [5, 6]),\n        accrual_adjuster=Adjuster.ModifiedFollowing(),\n        payment_adjuster=Adjuster.BusDaysLagSettle(2),\n        payment_adjuster2=Adjuster.Actual(),\n        eom=True,\n        front_stub=None,\n        back_stub=None,\n        stub_inference=si,\n    )\n    assert s.uschedule == exp\n\n\ndef test_imm_schedule():\n    # test that IMM rolls are automatically determined.\n    s = Schedule(\n        effective=dt(2025, 3, 19),\n        termination=dt(2025, 9, 17),\n        frequency=Frequency.Months(3, None),\n        calendar=Cal([], [5, 6]),\n        accrual_adjuster=Adjuster.ModifiedFollowing(),\n        payment_adjuster=Adjuster.BusDaysLagSettle(2),\n        payment_adjuster2=Adjuster.Actual(),\n        eom=True,\n        front_stub=None,\n        back_stub=None,\n        stub_inference=StubInference.NeitherSide,\n    )\n    assert s.frequency == Frequency.Months(3, RollDay.IMM())\n\n\ndef test_single_period_schedule():\n    s = Schedule(\n        effective=dt(2025, 3, 19),\n        termination=dt(2025, 9, 19),\n        frequency=Frequency.Months(12, RollDay.Day(19)),\n        calendar=Cal([], [5, 6]),\n        accrual_adjuster=Adjuster.ModifiedFollowing(),\n        payment_adjuster=Adjuster.BusDaysLagSettle(2),\n        payment_adjuster2=Adjuster.Actual(),\n        eom=True,\n        front_stub=None,\n        back_stub=None,\n        stub_inference=StubInference.NeitherSide,\n    )\n    assert s.uschedule == [dt(2025, 3, 19), dt(2025, 9, 19)]\n\n\ndef test_single_period_schedule2():\n    from rateslib import IRS\n\n    IRS(dt(2022, 7, 1), \"3M\", \"A\", curves=\"eureur\", notional=1e6)\n\n\n@pytest.mark.parametrize(\n    (\"a\", \"b\", \"expected\"),\n    [\n        (Adjuster.ModifiedFollowing(), Adjuster.ModifiedFollowing(), True),\n        (Adjuster.Following(), Adjuster.ModifiedFollowing(), False),\n        (Adjuster.BusDaysLagSettleInAdvance(3), Adjuster.BusDaysLagSettleInAdvance(3), True),\n        (Adjuster.BusDaysLagSettleInAdvance(3), Adjuster.Following(), False),\n        (Adjuster.BusDaysLagSettle(2), Adjuster.BusDaysLagSettle(1), False),\n    ],\n)\ndef test_adjuster_equality(a, b, expected):\n    result = a == b\n    assert result is expected\n"
  },
  {
    "path": "python/tests/serialization/test_json.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pytest\nfrom rateslib import Curve, Dual, Dual2, FXForwards, FXRates, dt, from_json\nfrom rateslib.enums import FloatFixingMethod, IROptionMetric, LegIndexBase\nfrom rateslib.rs import Schedule as ScheduleRs\nfrom rateslib.scheduling import (\n    Adjuster,\n    Convention,\n    Frequency,\n    Imm,\n    NamedCal,\n    RollDay,\n    Schedule,\n    StubInference,\n)\nfrom rateslib.splines import PPSplineDual, PPSplineDual2, PPSplineF64\n\n\n@pytest.mark.parametrize(\n    \"obj\",\n    [\n        Dual(2, vars=[\"v0\", \"v2\"], dual=[0, 3]),\n        Dual2(2.5, [\"a\", \"bb\"], [1.2, 3.4], []),\n        FXRates({\"usdnok\": 8.0, \"eurusd\": 1.05}),\n        Imm.Wed1_Post9_HMUZ,\n        StubInference.LongFront,\n        RollDay.Day(31),\n        RollDay.IMM(),\n        Frequency.Zero(),\n        Frequency.CalDays(3),\n        Frequency.BusDays(3, NamedCal(\"tgt\")),\n        Frequency.Months(4, None),\n        Frequency.Months(3, RollDay.IMM()),\n        Adjuster.ModifiedFollowing(),\n        Adjuster.BusDaysLagSettle(2),\n        Convention.ActActICMA,\n        Convention.ActActISDA,\n        ScheduleRs(\n            effective=dt(2000, 1, 1),\n            termination=dt(2001, 1, 1),\n            frequency=Frequency.Months(6, None),\n            calendar=NamedCal(\"tgt\"),\n            accrual_adjuster=Adjuster.Actual(),\n            payment_adjuster=Adjuster.BusDaysLagSettle(2),\n            payment_adjuster2=Adjuster.Actual(),\n            front_stub=None,\n            back_stub=None,\n            eom=False,\n            stub_inference=StubInference.NeitherSide,\n        ),\n        Schedule(\n            effective=dt(2000, 1, 1),\n            termination=dt(2001, 1, 1),\n            frequency=\"S\",\n            calendar=\"tgt\",\n        ),\n        PPSplineF64(3, [0, 0, 0, 1, 1, 1], [0.1, 0.2, 0.3]),\n        PPSplineDual(\n            3, [0, 0, 0, 1, 1, 1], [Dual(0.1, [], []), Dual(0.2, [], []), Dual(0.3, [], [])]\n        ),\n        PPSplineDual2(\n            3,\n            [0, 0, 0, 1, 1, 1],\n            [Dual2(0.1, [], [], []), Dual2(0.2, [], [], []), Dual2(0.3, [], [], [])],\n        ),\n        FloatFixingMethod.RFRPaymentDelay(),\n        FloatFixingMethod.RFRPaymentDelayAverage(),\n        FloatFixingMethod.RFRObservationShift(2),\n        FloatFixingMethod.RFRObservationShiftAverage(2),\n        FloatFixingMethod.RFRLookback(3),\n        FloatFixingMethod.RFRLookbackAverage(3),\n        FloatFixingMethod.RFRLockout(4),\n        FloatFixingMethod.RFRLockoutAverage(4),\n        FloatFixingMethod.IBOR(2),\n        IROptionMetric.Premium(),\n        IROptionMetric.PercentNotional(),\n        IROptionMetric.NormalVol(),\n        IROptionMetric.BlackVolShift(25),\n        LegIndexBase.Initial,\n        LegIndexBase.PeriodOnPeriod,\n    ],\n)\ndef test_json_round_trip(obj) -> None:\n    jstring = obj.to_json()\n    reconstituted = from_json(jstring)\n    assert obj == reconstituted\n"
  },
  {
    "path": "python/tests/serialization/test_pickle.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pickle\n\nimport pytest\nfrom rateslib import (\n    ADOrder,\n    Dual,\n    Dual2,\n    FXForwards,\n    FXRates,\n    Imm,\n    NamedCal,\n    Variable,\n    dt,\n)\nfrom rateslib.curves import (\n    CompositeCurve,\n    CreditImpliedCurve,\n    Curve,\n    LineCurve,\n    MultiCsaCurve,\n    ProxyCurve,\n)\nfrom rateslib.enums import FloatFixingMethod, IROptionMetric, LegIndexBase\nfrom rateslib.rs import Schedule as ScheduleRs\nfrom rateslib.scheduling import (\n    Adjuster,\n    Cal,\n    Convention,\n    Frequency,\n    RollDay,\n    Schedule,\n    StubInference,\n    UnionCal,\n)\nfrom rateslib.splines import PPSplineDual, PPSplineDual2, PPSplineF64\n\n\n@pytest.mark.parametrize(\n    \"obj\",\n    [\n        # core\n        dt(2000, 1, 1),\n        # ad\n        Dual(1.2, [\"x\"], [2.3]),\n        Dual2(1.3, [\"y\"], [1.0], [2.0]),\n        Variable(2.0, [\"r\"]),\n        # calendars\n        Cal.from_name(\"bus\"),\n        UnionCal([Cal.from_name(\"bus\")], []),\n        NamedCal(\"bus\"),\n        # scheduling\n        # fx\n        FXRates({\"eurusd\": 1.0}, dt(2000, 1, 1)),\n        # curves\n        Curve(\n            {dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98, dt(2002, 1, 1): 0.96},\n            interpolation=\"spline\",\n        ),\n        LineCurve({dt(2000, 1, 1): 2.0, dt(2000, 1, 2): 3.0}),\n        CompositeCurve(\n            [\n                Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n                Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n            ]\n        ),\n        MultiCsaCurve(\n            [\n                Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n                Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n            ],\n        ),\n        CreditImpliedCurve(\n            Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n            Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n        ),\n        ProxyCurve(\n            \"usd\",\n            \"eur\",\n            FXForwards(\n                fx_rates=FXRates({\"eurusd\": 1.0}, dt(2000, 1, 1)),\n                fx_curves={\n                    \"eureur\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n                    \"eurusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n                    \"usdusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 1, 2): 0.98}),\n                },\n            ),\n        ),\n        Curve({dt(2000, 1, 1): 1.0, dt(2000, 7, 1): 0.98}).shift(10),\n        Curve({dt(2000, 1, 1): 1.0, dt(2000, 7, 1): 0.98}).roll(\"1m\"),\n        Curve({dt(2000, 1, 1): 1.0, dt(2000, 7, 1): 0.98}).translate(dt(2000, 1, 15)),\n        ScheduleRs(\n            effective=dt(2000, 1, 1),\n            termination=dt(2001, 1, 10),\n            frequency=Frequency.Months(6, RollDay.Day(1)),\n            calendar=NamedCal(\"tgt\"),\n            accrual_adjuster=Adjuster.ModifiedFollowing(),\n            payment_adjuster=Adjuster.BusDaysLagSettle(2),\n            payment_adjuster2=Adjuster.Actual(),\n            front_stub=None,\n            back_stub=None,\n            eom=False,\n            stub_inference=StubInference.ShortBack,\n        ),\n        Schedule(\n            effective=dt(2000, 1, 1),\n            termination=dt(2001, 1, 10),\n            frequency=Frequency.Months(6, RollDay.Day(1)),\n            calendar=NamedCal(\"tgt\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            payment_lag=Adjuster.BusDaysLagSettle(2),\n            stub=StubInference.ShortBack,\n        ),\n        Schedule(\n            effective=dt(2000, 1, 1),\n            termination=dt(2001, 1, 1),\n            frequency=Frequency.Months(6, RollDay.Day(1)),\n            calendar=NamedCal(\"tgt\"),\n            modifier=Adjuster.ModifiedFollowing(),\n            payment_lag=Adjuster.BusDaysLagSettle(2),\n            stub=StubInference.NeitherSide,\n        ),\n        PPSplineF64(3, [0, 0, 0, 1, 1, 1], [0.1, 0.2, 0.3]),\n        PPSplineDual(\n            3, [0, 0, 0, 1, 1, 1], [Dual(0.1, [], []), Dual(0.2, [], []), Dual(0.3, [], [])]\n        ),\n        PPSplineDual2(\n            3,\n            [0, 0, 0, 1, 1, 1],\n            [Dual2(0.1, [], [], []), Dual2(0.2, [], [], []), Dual2(0.3, [], [], [])],\n        ),\n    ],\n)\ndef test_pickle_round_trip_obj_via_equality(obj):\n    pickled = pickle.dumps(obj)\n    loaded = pickle.loads(pickled)\n    assert obj == loaded\n\n\n@pytest.mark.parametrize(\n    (\"a1\", \"a2\", \"b1\"),\n    [\n        (Imm.Eom, Imm.Eom, Imm.Leap),\n        (StubInference.LongBack, StubInference.LongBack, StubInference.ShortFront),\n        (ADOrder.Zero, ADOrder.Zero, ADOrder.One),\n        (RollDay.Day(21), RollDay.Day(21), RollDay.Day(16)),\n        (RollDay.Day(21), RollDay.Day(21), RollDay.IMM),\n        (Adjuster.Actual(), Adjuster.Actual(), Adjuster.BusDaysLagSettle(5)),\n        (\n            Frequency.Months(4, RollDay.Day(2)),\n            Frequency.Months(4, RollDay.Day(2)),\n            Frequency.CalDays(3),\n        ),\n        (\n            Frequency.Months(4, RollDay.Day(2)),\n            Frequency.Months(4, RollDay.Day(2)),\n            Frequency.Months(4, None),\n        ),\n        (Convention.ActActICMA, Convention.ActActICMA, Convention.ActActISDA),\n        (FloatFixingMethod.IBOR(2), FloatFixingMethod.IBOR(2), FloatFixingMethod.RFRLookback(2)),\n        (FloatFixingMethod.IBOR(2), FloatFixingMethod.IBOR(2), FloatFixingMethod.IBOR(5)),\n        (IROptionMetric.Premium(), IROptionMetric.Premium(), IROptionMetric.BlackVolShift(200)),\n        (\n            IROptionMetric.BlackVolShift(200),\n            IROptionMetric.BlackVolShift(200),\n            IROptionMetric.BlackVolShift(100),\n        ),\n        (LegIndexBase.Initial, LegIndexBase.Initial, LegIndexBase.PeriodOnPeriod),\n    ],\n)\ndef test_enum_equality(a1, a2, b1):\n    assert a1 == a2\n    assert a2 != b1\n\n\n@pytest.mark.parametrize(\n    (\"enum\", \"klass\"),\n    [\n        (FloatFixingMethod.IBOR(2), FloatFixingMethod.IBOR),\n        (IROptionMetric.BlackVolShift(2), IROptionMetric.BlackVolShift),\n    ],\n)\ndef test_complex_enum_isinstance(enum, klass):\n    assert isinstance(enum, klass)\n    type_enum = type(enum)\n    assert type_enum is klass\n    assert type_enum in [klass]\n    assert not isinstance(enum, FloatFixingMethod.RFRLookback)\n    assert type(enum) is not FloatFixingMethod.RFRLookback\n    assert enum != FloatFixingMethod.RFRLookback(2)\n\n\n@pytest.mark.parametrize(\n    (\"enum\", \"method_filter\"),\n    [\n        (Imm, [\"next\", \"get\", \"validate\", \"to_json\"]),\n        (StubInference, [\"to_json\"]),\n        (ADOrder, []),\n        (Convention, [\"dcf\", \"to_json\"]),\n        (LegIndexBase, [\"to_json\"]),\n    ],\n)\ndef test_simple_enum_pickle(enum, method_filter):\n    variants = [v for v in enum.__dict__ if \"__\" not in v and v not in method_filter]\n    for v in variants:\n        obj = enum.__dict__[v]\n        pickled = pickle.dumps(obj)\n        unpickled = pickle.loads(pickled)\n        assert unpickled == enum.__dict__[v]\n\n\n@pytest.mark.parametrize(\n    (\"enum\"),\n    [\n        RollDay.Day(31),\n        RollDay.IMM(),\n        Adjuster.Actual(),\n        Adjuster.Following(),\n        Adjuster.ModifiedFollowing(),\n        Adjuster.Previous(),\n        Adjuster.ModifiedPrevious(),\n        Adjuster.FollowingSettle(),\n        Adjuster.ModifiedFollowingSettle(),\n        Adjuster.PreviousSettle(),\n        Adjuster.ModifiedPreviousSettle(),\n        Adjuster.BusDaysLagSettle(4),\n        Adjuster.CalDaysLagSettle(2),\n        Adjuster.FollowingExLast(),\n        Adjuster.FollowingExLastSettle(),\n        Adjuster.BusDaysLagSettleInAdvance(2),\n        Frequency.Months(4, RollDay.Day(2)),\n        Frequency.Months(4, None),\n        Frequency.BusDays(2, NamedCal(\"tgt\")),\n        Frequency.Zero(),\n        Frequency.CalDays(3),\n        FloatFixingMethod.RFRPaymentDelay(),\n        FloatFixingMethod.RFRPaymentDelayAverage(),\n        FloatFixingMethod.RFRObservationShift(2),\n        FloatFixingMethod.RFRObservationShiftAverage(2),\n        FloatFixingMethod.RFRLookback(3),\n        FloatFixingMethod.RFRLookbackAverage(3),\n        FloatFixingMethod.RFRLockout(4),\n        FloatFixingMethod.RFRLockoutAverage(4),\n        FloatFixingMethod.IBOR(2),\n    ],\n)\ndef test_complex_enum_pickle(enum):\n    pickled = pickle.dumps(enum)\n    unpickled = pickle.loads(pickled)\n    assert unpickled == enum\n"
  },
  {
    "path": "python/tests/serialization/test_repr.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pytest\nfrom rateslib import dt\nfrom rateslib.dual import Dual, Dual2\nfrom rateslib.enums import FloatFixingMethod, LegIndexBase\nfrom rateslib.scheduling import (\n    Adjuster,\n    Cal,\n    Frequency,\n    Imm,\n    NamedCal,\n    RollDay,\n    Schedule,\n    StubInference,\n    UnionCal,\n)\nfrom rateslib.splines import PPSplineDual, PPSplineDual2, PPSplineF64\n\n\n@pytest.mark.parametrize(\n    (\"obj\", \"expected\"),\n    [\n        (Imm.Wed1_Post9_HMUZ, \"Imm.Wed1_Post9_HMUZ\"),\n        (StubInference.ShortFront, \"StubInference.ShortFront\"),\n        (RollDay.Day(31), \"RollDay.Day(31)\"),\n        (RollDay.IMM(), \"RollDay.IMM\"),\n        (Frequency.Zero(), \"Frequency.Zero\"),\n        (Frequency.CalDays(2), \"Frequency.CalDays(2)\"),\n        (Frequency.BusDays(3, NamedCal(\"tgt\")), \"Frequency.BusDays(3, ...)\"),\n        (Frequency.Months(2, RollDay.Day(31)), \"Frequency.Months(2, Day(31))\"),\n        (Frequency.Months(4, None), \"Frequency.Months(4, None)\"),\n        (Adjuster.ModifiedFollowing(), \"Adjuster.ModifiedFollowing\"),\n        (Adjuster.BusDaysLagSettle(4), \"Adjuster.BusDaysLagSettle(4)\"),\n        (Schedule(dt(2000, 1, 1), dt(2001, 2, 1), \"M\"), \"Schedule\"),\n        (PPSplineF64(3, [0, 0, 0, 1, 1, 1], [0.1, 0.2, 0.3]), \"PPSplineF64\"),\n        (\n            PPSplineDual(\n                3, [0, 0, 0, 1, 1, 1], [Dual(0.1, [], []), Dual(0.2, [], []), Dual(0.3, [], [])]\n            ),\n            \"PPSplineDual\",\n        ),\n        (\n            PPSplineDual2(\n                3,\n                [0, 0, 0, 1, 1, 1],\n                [Dual2(0.1, [], [], []), Dual2(0.2, [], [], []), Dual2(0.3, [], [], [])],\n            ),\n            \"PPSplineDual2\",\n        ),\n        (Cal([], []), \"Cal\"),\n        (UnionCal([Cal([], []), Cal([], [])], []), \"UnionCal\"),\n        (NamedCal(\"tgt,ldn|fed\"), \"NamedCal:'tgt,ldn|fed'\"),\n        (FloatFixingMethod.IBOR(2), \"FloatFixingMethod.IBOR(2)\"),\n        (FloatFixingMethod.RFRPaymentDelay(), \"FloatFixingMethod.RFRPaymentDelay\"),\n        (LegIndexBase.Initial, \"LegIndexBase.Initial\"),\n    ],\n)\ndef test_repr_strings(obj, expected) -> None:\n    repr_ = obj.__repr__()\n    assert f\"<rl.{expected} at\" in repr_\n\n\ndef test_unique_repr_simple_enum():\n    a = Imm.Wed1_Post9_HMUZ\n    b = Imm.Wed1_Post9_HMUZ\n    assert a.__repr__() == b.__repr__()\n    assert hex(id(a)) == hex(id(b))\n"
  },
  {
    "path": "python/tests/test_default.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\n\nimport pytest\nfrom rateslib import (\n    Licence,\n    NamedCal,\n    __version__,\n    default_context,\n    defaults,\n    dt,\n    fixings,\n    licence,\n)\nfrom rateslib.verify import LicenceNotice, _LicenceStatus\n\n\ndef test_version() -> None:\n    assert __version__ == \"2.7.1\"\n\n\ndef test_context_raises() -> None:\n    with pytest.raises(ValueError, match=\"Need to invoke as \"):\n        default_context(\"only 1 arg\")\n\n\ndef test_reset_defaults() -> None:\n    defaults.modifier = \"MP\"\n    defaults.base_currency = \"gbp\"\n    assert defaults.modifier == \"MP\"\n    assert defaults.base_currency == \"gbp\"\n\n    defaults.reset_defaults()\n    assert defaults.modifier == \"MF\"\n    assert defaults.base_currency == \"usd\"\n\n\ndef test_defaults_singleton() -> None:\n    from rateslib.default import Defaults\n\n    other = Defaults()\n    assert id(other) == id(defaults)\n\n\ndef test_fixings_singleton() -> None:\n    from rateslib.data.loader import Fixings\n\n    other = Fixings()\n    assert id(other) == id(fixings)\n\n\ndef test_fx_index_change() -> None:\n    # test that default fx indexes can be overwritten and are loaded by constructed objects\n    from rateslib.data.fixings import FXFixing, FXIndex\n    from rateslib.scheduling import Adjuster\n\n    eurusd = FXFixing(\"eurusd\", dt(2000, 1, 1))\n    assert eurusd.fx_index.calendar == NamedCal(\"tgt|fed\")\n    assert eurusd.fx_index.settle == Adjuster.BusDaysLagSettle(2)\n    defaults.fx_index[\"eurusd\"] = {\"pair\": \"eurusd\", \"calendar\": \"stk\", \"settle\": 3}\n    eurusd = FXFixing(\"eurusd\", dt(2000, 1, 1))\n    assert eurusd.fx_index.calendar == NamedCal(\"stk\")\n    assert eurusd.fx_index.settle == Adjuster.BusDaysLagSettle(3)\n\n    defaults.reset_defaults()\n    assert defaults.fx_index[\"eurusd\"][\"calendar\"] == NamedCal(\"tgt|fed\")\n\n\ndef test_float_series_change():\n    from rateslib import IRS\n\n    with pytest.raises(ValueError, match=\"The FloatRateSeries: 'monkey' was not found \"):\n        IRS(dt(2000, 1, 1), \"1y\", \"A\", leg2_fixing_series=\"monkey\")\n\n    defaults.float_series[\"monkey\"] = dict(\n        lag=0, calendar=\"nyc\", modifier=\"f\", eom=False, convention=\"act360\"\n    )\n    IRS(dt(2000, 1, 1), \"1y\", \"A\", leg2_fixing_series=\"monkey\")\n\n    defaults.reset_defaults()\n    assert \"monkey\" not in defaults.float_series\n\n\ndef collect_and_remove_licence() -> tuple[str | None, str | None]:\n    env_licence = os.getenv(\"RATESLIB_LICENCE\")\n    if env_licence is not None:\n        del os.environ[\"RATESLIB_LICENCE\"]\n    try:\n        file_licence = licence.print_licence()\n        licence.remove_licence()\n    except ValueError:\n        file_licence = None\n    return env_licence, file_licence\n\n\ndef replace_collected_licence(env_licence, file_licence) -> None:\n    if env_licence is not None:\n        os.environ[\"RATESLIB_LICENCE\"] = env_licence\n    if file_licence is not None:\n        licence.add_licence(file_licence)\n\n\nclass TestLicence:\n    def test_valid_licence(self):\n        # test that this system has a valid licence\n        assert licence.status == _LicenceStatus.VALID\n\n    @pytest.mark.skipif(\n        os.getenv(\"RATESLIB_LICENCE\") is not None, reason=\"env licence already tested.\"\n    )\n    def test_env_licence(self):\n        # this test relies on `test_valid_licence`\n        assert licence.status == _LicenceStatus.VALID  # licence is loaded from file.\n        os.environ[\"RATESLIB_LICENCE\"] = licence.print_licence()\n        licence.remove_licence()  # remove the file licence\n        x = Licence()\n        assert x.status == _LicenceStatus.VALID\n        licence.add_licence(os.environ[\"RATESLIB_LICENCE\"])\n        del os.environ[\"RATESLIB_LICENCE\"]\n\n    def test_licence_no_licence_warning(self):\n        # test just the\n        env_licence, file_licence = collect_and_remove_licence()\n        with pytest.warns(LicenceNotice, match=\"No commercial licence is registered\"):\n            Licence()\n        replace_collected_licence(env_licence, file_licence)\n\n    def test_licence_warning_for_expired_as_file(self):\n        env_licence, file_licence = collect_and_remove_licence()\n        licence.add_licence(\n            '{\"expiry\": \"1900-01-01\", \"id\": \"Rateslib Tests\", \"xkey\": \"0x2cec1be74d8b2d2bdfa41aec384a4a8ede06c8c7873d6130035c19fcf244b5b92e29c7087a5e51c453a1fe7da345a689ef3d0953b8841ab1b3895a69a209aa529ff3e4d6b8217ce16b37c5572d737ece0a7f381696a3f3901bced9f843b48504930b25d204d910955f52c76eccd208a975a3a0e4433d70dd090ef5adb8de83cb\", \"name\": \"System\"}'  # noqa: E501\n        )\n        with pytest.warns(LicenceNotice, match=\"expired on 1900-01-01\"):\n            Licence()\n        licence.remove_licence()\n        replace_collected_licence(env_licence, file_licence)\n\n    def test_licence_warning_for_expired_as_env_var(self):\n        env_licence, file_licence = collect_and_remove_licence()\n        os.environ[\"RATESLIB_LICENCE\"] = (\n            '{\"expiry\": \"1900-01-01\", \"id\": \"Rateslib Tests\", \"xkey\": \"0x2cec1be74d8b2d2bdfa41aec384a4a8ede06c8c7873d6130035c19fcf244b5b92e29c7087a5e51c453a1fe7da345a689ef3d0953b8841ab1b3895a69a209aa529ff3e4d6b8217ce16b37c5572d737ece0a7f381696a3f3901bced9f843b48504930b25d204d910955f52c76eccd208a975a3a0e4433d70dd090ef5adb8de83cb\", \"name\": \"System\"}'  # noqa: E501\n        )\n        with pytest.warns(LicenceNotice, match=\"expired on 1900-01-01\"):\n            Licence()\n        del os.environ[\"RATESLIB_LICENCE\"]\n        replace_collected_licence(env_licence, file_licence)\n\n    def test_invalid_signature(self):\n        env_licence, file_licence = collect_and_remove_licence()\n        os.environ[\"RATESLIB_LICENCE\"] = (\n            '{\"expiry\": \"2100-01-01\", \"id\": \"Rateslib Tests\", \"xkey\": \"0x2cec1be74d8b2d2bdfa41aec384a4a8ede06c8c7873d6130035c19fcf244b5b92e29c7087a5e51c453a1fe7da345a689ef3d0953b8841ab1b3895a69a209aa529ff3e4d6b8217ce16b37c5572d737ece0a7f381696a3f3901bced9f843b48504930b25d204d910955f52c76eccd208a975a3a0e4433d70dd090ef5adb8de83cb\", \"name\": \"System\"}'  # noqa: E501\n        )\n        with pytest.warns(LicenceNotice, match=\"An invalid licence file is detected\"):\n            Licence()\n        del os.environ[\"RATESLIB_LICENCE\"]\n        replace_collected_licence(env_licence, file_licence)\n\n    @pytest.mark.parametrize(\n        \"licence_text\",\n        [\n            \"garbage\",\n            '{\"expiry\": \"1900-01-01\", \"id\": \"Rateslib Tests\", \"xkey\": \"0x2cec1\", \"name\": \"System\"}',\n        ],\n    )\n    def test_add_invalid_licence(self, licence_text):\n        with pytest.raises(ValueError):\n            licence.add_licence(licence_text)\n\n    @pytest.mark.parametrize(\n        \"licence_text\",\n        [\n            '{\"name\": \"RL Expiry Test\", \"xkey\": \"0x68178a21511a36f8270bb4f73451bf3a6575e23e11bc9d0ebead841fa77bfef16cbae1341ad2e6d80f0b717923a48fbd3580eb6cc216a31c0d23618a32e8b2773cc52998e6bcb0315a8f46d003ce04f7ddeb8c19e66a16c73d2e925218dff044ba5f43f7d05503626e89fadbf85751807737f73c55b2048f96fd331b202abe45\"}',  # noqa: E501\n            '{\"name\": \"RL xkey missing\"}',\n        ],\n    )\n    def test_licence_missing_keys(self, licence_text):\n        from rateslib.verify import _verify_licence\n\n        assert _verify_licence(licence_text) is None\n"
  },
  {
    "path": "python/tests/test_dual.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport math\nfrom statistics import NormalDist\n\nimport numpy as np\nimport pytest\nfrom packaging import version\nfrom rateslib import IRS, Curve, FXRates, Solver, default_context, dt\nfrom rateslib.dual import (\n    Dual,\n    Dual2,\n    Variable,\n    dual_exp,\n    dual_inv_norm_cdf,\n    dual_log,\n    dual_norm_cdf,\n    dual_norm_pdf,\n    dual_solve,\n    gradient,\n    set_order,\n)\nfrom rateslib.dual.utils import _abs_float, _set_ad_order_objects\n\nDUAL_CORE_PY = False\n\n\n@pytest.fixture\ndef x_1():\n    return Dual(1, vars=[\"v0\", \"v1\"], dual=[1, 2])\n\n\n@pytest.fixture\ndef x_2():\n    return Dual(2, vars=[\"v0\", \"v2\"], dual=[0, 3])\n\n\n@pytest.fixture\ndef y_1():\n    return Dual2(1, vars=[\"v0\", \"v1\"], dual=[1, 2], dual2=[])\n\n\n@pytest.fixture\ndef y_2():\n    return Dual2(1, vars=[\"v0\", \"v1\"], dual=[1, 2], dual2=[1.0, 1.0, 1.0, 1.0])\n\n\n@pytest.fixture\ndef y_3():\n    return Dual2(2, vars=[\"v0\", \"v2\"], dual=[0, 3], dual2=[1.0, 1.0, 1.0, 1.0])\n\n\n@pytest.fixture\ndef A():\n    return np.random.randn(25).reshape(5, 5)\n\n\n@pytest.fixture\ndef A_sparse():\n    return np.array(\n        [\n            [24, -36, 12, 0, 0, 0, 0, 0, 0],\n            [1, 0, 0, 0, 0, 0, 0, 0, 0],\n            [0, 0.25, 0.583333333333, 0.1666666666, 0, 0, 0, 0, 0],\n            [0, 0, 0.1666666666, 0.6666666666, 0.1666666666, 0, 0, 0, 0],\n            [0, 0, 0, 0.1666666666, 0.6666666666, 0.1666666666, 0, 0, 0],\n            [0, 0, 0, 0, 0.1666666666, 0.6666666666, 0.1666666666, 0, 0],\n            [0, 0, 0, 0, 0, 0.1666666666, 0.583333333333, 0.25, 0],\n            [0, 0, 0, 0, 0, 0, 0, 0, 1],\n            [0, 0, 0, 0, 0, 0, 12, -36, 24],\n        ],\n    )\n\n\n@pytest.fixture\ndef b():\n    return np.random.randn(5).reshape(5, 1)\n\n\ndef test_zero_init() -> None:\n    x = Dual(1, [\"x\"], [])\n    assert np.all(x.dual == np.ones(1))\n\n    y = Dual2(1, [\"x\"], [], [])\n    assert np.all(y.dual == np.ones(1))\n    assert np.all(y.dual2 == np.zeros((1, 1)))\n\n\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        \"__add__\",\n        \"__sub__\",\n        \"__mul__\",\n        \"__truediv__\",\n        \"__eq__\",\n    ],\n)\ndef test_no_type_crossing_on_ops(x_1, y_1, op) -> None:\n    # getattr(x_1, op)(y_1)\n    with pytest.raises(TypeError):\n        getattr(x_1, op)(y_1)\n\n    with pytest.raises(TypeError):\n        getattr(y_1, op)(x_1)\n\n\ndef test_functions_of_two_duals_analytic_formula():\n    # test the analytic formula for determining the resultant dual number of a function of\n    # 2 dual numbers\n\n    a = Dual2(2.0, [\"a\"], [], [])\n    b = Dual2(3.0, [\"b\"], [], [])\n\n    # z and p contain 2nd order manifolds\n    z = a**2 * b  # = 12\n    p = b**2 * a  # = 18\n    p = Dual2.vars_from(z, p.real, p.vars, p.dual, np.ravel(p.dual2))\n\n    # f is the actual expected result, calculated using dual number arithmetic\n    expected = z**2 * p**3\n\n    # result is pieced together using the analytic formula\n    f_0 = 12**2 * 18**3\n    f_z = 2 * 12 * 18**3\n    f_p = 3 * 12**2 * 18**2\n    f_zz = 2 * 18**3\n    f_zp = 6 * 12 * 18**2\n    f_pp = 6 * 12**2 * 18\n\n    real = f_0\n    dual = z.dual * f_z + p.dual * f_p\n    dual2 = f_z * z.dual2 + f_p * p.dual2\n    dual2 += 0.5 * f_zz * np.outer(z.dual, z.dual)\n    dual2 += 0.5 * f_pp * np.outer(p.dual, p.dual)\n    dual2 += 0.5 * f_zp * (np.outer(z.dual, p.dual) + np.outer(p.dual, z.dual))\n    result = Dual2.vars_from(z, real, z.vars, dual, np.ravel(dual2))\n\n    assert result == expected\n\n\ndef test_dual_repr(x_1, y_2) -> None:\n    result = x_1.__repr__()\n    assert result == \"<Dual: 1.000000, (v0, v1), [1.0, 2.0]>\"\n\n    result = y_2.__repr__()\n    assert result == \"<Dual2: 1.000000, (v0, v1), [1.0, 2.0], [[...]]>\"\n\n\n@pytest.mark.skipif(not DUAL_CORE_PY, reason=\"Rust Dual does not format string in this way.\")\ndef test_dual_str(x_1, y_2) -> None:\n    result = x_1.__str__()\n    assert result == \" val = 1.00000000\\n  dv0 = 1.000000\\n  dv1 = 2.000000\\n\"\n\n    result = y_2.__str__()\n    assert (\n        result == \" val = 1.00000000\\n\"\n        \"  dv0 = 1.000000\\n\"\n        \"  dv1 = 2.000000\\n\"\n        \"dv0dv0 = 2.000000\\n\"\n        \"dv0dv1 = 2.000000\\n\"\n        \"dv1dv1 = 2.000000\\n\"\n    )\n\n\n@pytest.mark.parametrize(\n    (\"vars_\", \"expected\"),\n    [\n        ([\"v0\"], 1.00),\n        ([\"v1\", \"v0\"], np.array([2.0, 1.0])),\n    ],\n)\ndef test_gradient_method(vars_, expected, x_1, y_2) -> None:\n    result = gradient(x_1, vars_)\n    assert np.all(result == expected)\n\n    result = gradient(y_2, vars_)\n    assert np.all(result == expected)\n\n\ndef test_gradient_on_float():\n    result = gradient(1.0, [\"v0\", \"s\"])\n    assert np.all(result == np.array([0.0, 0.0]))\n\n    result = gradient(1.0, [\"s\"], order=2)\n    assert np.all(result == np.array([[0.0, 0.0], [0.0, 0.0]]))\n\n\n@pytest.mark.parametrize(\n    (\"vars_\", \"expected\"),\n    [\n        ([\"v0\"], 2.00),\n        ([\"v1\", \"v0\"], np.array([[2.0, 2.0], [2.0, 2.0]])),\n    ],\n)\ndef test_gradient_method2(vars_, expected, y_2) -> None:\n    result = gradient(y_2, vars_, 2)\n    assert np.all(result == expected)\n\n\ndef test_rdiv_raises(x_1, y_1) -> None:\n    with pytest.raises(TypeError):\n        _ = \"string\" / x_1\n\n    with pytest.raises(TypeError):\n        _ = \"string\" / y_1\n\n\ndef test_neg(x_1, y_2) -> None:\n    assert -x_1 == Dual(-1, [\"v0\", \"v1\"], [-1.0, -2.0])\n    assert -y_2 == Dual2(-1, [\"v0\", \"v1\"], [-1.0, -2.0], [-1.0, -1.0, -1.0, -1.0])\n\n\ndef test_eq_ne(x_1, y_1, y_2) -> None:\n    # non-matching types\n    assert Dual(0, [\"single_var\"], []) != 0\n    assert Dual2(0, [\"single_var\"], [], []) != 0\n    # ints\n    assert Dual(2, [], []) == 2\n    assert Dual2(2, [], [], []) == 2\n    # floats\n    assert Dual(3.3, [], []) == 3.3\n    assert Dual2(3.3, [], [], []) == 3.3\n    # no type crossing\n    with pytest.raises(TypeError):\n        assert x_1 != y_1\n    # equality\n    assert x_1 == Dual(1, [\"v0\", \"v1\"], [1, 2])\n    assert y_1 == Dual2(1, [\"v0\", \"v1\"], [1, 2], [])\n    assert y_2 == Dual2(1, [\"v0\", \"v1\"], [1, 2], [1.0, 1.0, 1.0, 1.0])\n    # non-matching elements\n    assert x_1 != Dual(2, [\"v0\", \"v1\"], [1, 2])\n    assert x_1 != Dual(1, [\"v0\", \"v1\"], [2, 2])\n    assert x_1 != Dual(1, [\"v2\", \"v1\"], [1, 2])\n    # non-matching elements\n    assert y_1 != Dual2(2, [\"v0\", \"v1\"], [1, 2], [])\n    assert y_1 != Dual2(1, [\"v0\", \"v1\"], [2, 2], [])\n    assert y_1 != Dual2(1, [\"v2\", \"v1\"], [1, 2], [])\n    # non-matching dual2\n    assert y_2 != Dual2(1, [\"v0\", \"v1\"], [1, 2], [2.0, 2.0, 2.0, 2.0])\n\n\ndef test_lt() -> None:\n    assert Dual(1, [\"x\"], []) < Dual(2, [\"y\"], [])\n    assert Dual2(1, [\"z\"], [], []) < Dual2(2, [\"x\"], [], [])\n    assert Dual(1, [\"x\"], []) < 10\n    assert not Dual(1, [\"x\"], []) < 0\n\n\ndef test_lt_raises() -> None:\n    with pytest.raises(TypeError, match=\"Cannot compare\"):\n        assert Dual(1, [\"x\"], []) < Dual2(2, [\"y\"], [], [])\n\n\ndef test_gt() -> None:\n    assert Dual(2, [\"x\"], []) > Dual(1, [\"y\"], [])\n    assert Dual2(2, [\"z\"], [], []) > Dual2(1, [\"x\"], [], [])\n    assert Dual(1, [\"x\"], []) > 0\n    assert not Dual(1, [\"x\"], []) > 10\n\n\ndef test_gt_raises() -> None:\n    with pytest.raises(TypeError, match=\"Cannot compare\"):\n        assert Dual(2, [\"x\"], []) > Dual2(1, [\"y\"], [], [])\n\n\ndef test_dual2_abs_float(x_1, y_1, y_2) -> None:\n    assert _abs_float(x_1) == 1\n    assert _abs_float(y_1) == 1\n    assert _abs_float(y_2) == 1\n    assert float(x_1) == float(1)\n    assert float(y_1) == float(1)\n    assert float(y_2) == float(1)\n    assert abs(-x_1) == x_1\n    assert abs(-y_1) == y_1\n    assert abs(-y_2) == y_2\n\n\n@pytest.mark.parametrize(\"op\", [\"__add__\", \"__sub__\", \"__mul__\", \"__truediv__\"])\ndef test_dual2_immutable(y_1, y_2, op) -> None:\n    _ = getattr(y_1, op)(y_2)\n    assert y_1 == Dual2(1, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]), dual2=[])\n    assert y_2 == Dual2(1, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]), dual2=[1.0, 1.0, 1.0, 1.0])\n\n\n@pytest.mark.parametrize(\"op\", [\"__add__\", \"__sub__\", \"__mul__\", \"__truediv__\"])\ndef test_dual_immutable(x_1, op) -> None:\n    _ = getattr(x_1, op)(Dual(2, vars=[\"new\"], dual=np.array([4])))\n    assert x_1 == Dual(1, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))\n\n\ndef test_dual_raises(x_1) -> None:\n    with pytest.raises(ValueError, match=\"`Dual` variable cannot possess `dual2`\"):\n        x_1.dual2\n\n\ndef test_dual_is_not_iterable(x_1, y_1):\n    # do not want isinstance checks for Dual to identify them as a Sequence kind\n    assert getattr(x_1, \"__iter__\", None) is None\n    assert getattr(y_1, \"__iter__\", None) is None\n\n\ndef test_dual_has_no_len(x_1, y_1):\n    # do not want isinstance checks for Dual to identify them as a Sequence kind\n    assert getattr(x_1, \"__len__\", None) is None\n    assert getattr(y_1, \"__len__\", None) is None\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual(3, vars=[\"v0\", \"v1\", \"v2\"], dual=np.array([1, 2, 3]))),\n        (\"__sub__\", Dual(-1, vars=[\"v0\", \"v1\", \"v2\"], dual=np.array([1, 2, -3]))),\n        (\"__mul__\", Dual(2, vars=[\"v0\", \"v1\", \"v2\"], dual=np.array([2, 4, 3]))),\n        (\"__truediv__\", Dual(0.5, vars=[\"v0\", \"v1\", \"v2\"], dual=np.array([0.5, 1, -0.75]))),\n    ],\n)\ndef test_ops(x_1, x_2, op, expected) -> None:\n    result = getattr(x_1, op)(x_2)\n    assert result == expected\n\n\ndef test_op_inversions(x_1, x_2) -> None:\n    assert (x_1 + x_2) - (x_2 + x_1) == 0\n    assert (x_1 / x_2) * (x_2 / x_1) == 1\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual2(3, [\"v0\", \"v1\", \"v2\"], [1, 2, 3], [2, 1, 1, 1, 1, 0, 1, 0, 1])),\n        (\"__sub__\", Dual2(-1, [\"v0\", \"v1\", \"v2\"], [1, 2, -3], [0, 1, -1, 1, 1, 0, -1, 0, -1])),\n        (\"__mul__\", Dual2(2, [\"v0\", \"v1\", \"v2\"], [2, 4, 3], [3, 2, 2.5, 2, 2, 3, 2.5, 3, 1])),\n        (\n            \"__truediv__\",\n            Dual2(\n                0.5,\n                [\"v0\", \"v1\", \"v2\"],\n                [0.5, 1.0, -0.75],\n                [0.25, 0.5, -0.625, 0.5, 0.5, -0.75, -0.625, -0.75, 0.875],\n            ),\n        ),\n    ],\n)\ndef test_ops2(y_2, y_3, op, expected) -> None:\n    result = getattr(y_2, op)(y_3)\n    assert result == expected\n\n\ndef test_op_inversions2(y_2, y_3) -> None:\n    assert (y_2 + y_3) - (y_3 + y_2) == 0\n    assert (y_2 / y_3) * (y_3 / y_2) == 1\n\n\ndef test_inverse(x_1, y_2) -> None:\n    assert x_1 * x_1**-1 == 1\n    assert y_2 * y_2**-1 == 1\n\n\ndef test_power_identity(x_1, y_2) -> None:\n    result = x_1**1\n    assert result == x_1\n\n    result = y_2**1\n    assert result == y_2\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual(1 + 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))),\n        (\"__sub__\", Dual(1 - 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))),\n        (\"__mul__\", Dual(1 * 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]) * 2.5)),\n        (\"__truediv__\", Dual(1 / 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]) / 2.5)),\n    ],\n)\ndef test_left_op_with_float(x_1, op, expected) -> None:\n    result = getattr(x_1, op)(2.5)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual2(1 + 2.5, [\"v0\", \"v1\"], [1, 2], [1.0, 1.0, 1.0, 1.0])),\n        (\n            \"__sub__\",\n            Dual2(1 - 2.5, [\"v0\", \"v1\"], [1, 2], [1.0, 1.0, 1.0, 1.0]),\n        ),\n        (\"__mul__\", Dual2(1 * 2.5, [\"v0\", \"v1\"], [2.5, 5.0], [2.5, 2.5, 2.5, 2.5])),\n        (\n            \"__truediv__\",\n            Dual2(1 / 2.5, [\"v0\", \"v1\"], [1 / 2.5, 2 / 2.5], [1 / 2.5, 1 / 2.5, 1 / 2.5, 1 / 2.5]),\n        ),\n    ],\n)\ndef test_left_op_with_float2(y_2, op, expected) -> None:\n    result = getattr(y_2, op)(2.5)\n    assert result == expected\n\n\ndef test_right_op_with_float(x_1) -> None:\n    assert 2.5 + x_1 == Dual(1 + 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))\n    assert 2.5 - x_1 == Dual(2.5 - 1, vars=[\"v0\", \"v1\"], dual=-np.array([1, 2]))\n    assert 2.5 * x_1 == x_1 * 2.5\n    assert 2.5 / x_1 == (x_1 / 2.5) ** -1\n\n\ndef test_right_op_with_float2(y_2) -> None:\n    assert 2.5 + y_2 == Dual2(\n        1 + 2.5,\n        vars=[\"v0\", \"v1\"],\n        dual=[1.0, 2.0],\n        dual2=[1.0, 1.0, 1.0, 1.0],\n    )\n    assert 2.5 - y_2 == Dual2(\n        2.5 - 1,\n        vars=[\"v0\", \"v1\"],\n        dual=[-1.0, -2.0],\n        dual2=[-1.0, -1.0, -1.0, -1.0],\n    )\n    assert 2.5 * y_2 == y_2 * 2.5\n    assert 2.5 / y_2 == (y_2 / 2.5) ** -1\n\n\ndef test_dual2_second_derivatives() -> None:\n    \"test power, multiplication, addition\"\n\n    def f(x, y, z):\n        \"\"\"\n        f_x = 4x^3 y^2, f_y = 2y x^4 + z, f_z = 3z^2 +y\n        f_xx = 12x^2 y^2, f_xy = 8 x^3 y, f_xz = 0,\n        f_yx = 8x^3 y, f_yy = 2 x^4, f_yz = 1,\n        f_zx = 0, f_zy = 1, f_zz = 6z\n        \"\"\"\n        return x**4 * y**2 + z**3 + y * z\n\n    x_, y_, z_ = 3, 2, 1\n\n    x = Dual2(x_, vars=[\"x\"], dual=[1], dual2=[])\n    y = Dual2(y_, vars=[\"y\"], dual=[1], dual2=[])\n    z = Dual2(z_, vars=[\"z\"], dual=[1], dual2=[])\n\n    result = f(x, y, z)\n    assert result.dual[0] == 4 * x_**3 * y_**2  # 432\n    assert result.dual[1] == 2 * y_ * x_**4 + z_  # 325\n    assert result.dual[2] == 3 * z_**2 + y_  # 5\n\n    assert result.dual2[0, 0] * 2 == 12 * x_**2 * y_**2\n    assert result.dual2[0, 1] * 2 == 8 * x_**3 * y_\n    assert result.dual2[0, 2] * 2 == 0\n    assert result.dual2[1, 0] * 2 == 8 * x_**3 * y_\n    assert result.dual2[1, 1] * 2 == 2 * x_**4\n    assert result.dual2[1, 2] * 2 == 1\n    assert result.dual2[2, 0] * 2 == 0\n    assert result.dual2[2, 1] * 2 == 1\n    assert result.dual2[2, 2] * 2 == 6 * z_\n\n\ndef test_dual2_second_derivatives2() -> None:\n    \"test dual_exp, multiplication, division, dual_log\"\n\n    def f(x, y, z):\n        return (x / z).__exp__() + (x * y).__log__()\n\n    x_, y_, z_ = 3, 2, 1\n\n    x = Dual2(x_, vars=[\"x\"], dual=[1], dual2=[])\n    y = Dual2(y_, vars=[\"y\"], dual=[1], dual2=[])\n    z = Dual2(z_, vars=[\"z\"], dual=[1], dual2=[])\n\n    result = f(x, y, z)\n    xi = result.vars.index(\"x\")\n    yi = result.vars.index(\"y\")\n    zi = result.vars.index(\"z\")\n    assert result.dual[xi] == math.exp(x_ / z_) / z_ + 1 / x_\n    assert result.dual[yi] == 1 / y_\n    assert result.dual[zi] == -x_ * math.exp(x_ / z_) / z_**2\n\n    assert result.dual2[xi, xi] * 2 == math.exp(x_ / z_) / z_**2 - 1 / x_**2\n    assert result.dual2[xi, yi] * 2 == 0\n    assert result.dual2[xi, zi] * 2 == math.exp(x_ / z_) * (-1 / z_**2 - x_ / z_**3)\n    assert result.dual2[yi, xi] * 2 == 0\n    assert result.dual2[yi, yi] * 2 == -1 / y_**2\n    assert result.dual2[yi, zi] * 2 == 0\n    assert result.dual2[zi, xi] * 2 == math.exp(x_ / z_) * (-1 / z_**2 - x_ / z_**3)\n    assert result.dual2[zi, yi] * 2 == 0\n    assert result.dual2[zi, zi] * 2 == math.exp(x_ / z_) * (x_**2 / z_**4 + 2 * x_ / z_**3)\n\n\ndef test_dual2_second_derivatives3() -> None:\n    \"\"\"\n    h, f = dual_log(f), x^3y+y\n    f_x = 1/f 3x^2y, f_y = 1/f (x^3+1),\n    f_xx = -1/f^2 (3x^2y)^2 + 1/f 6xy, f_xy = -1/f^2 (3x^2y)(x^3+1),\n    f_yy = -1/f^2 (x^3+1)^2 +1/f (0)\n    \"\"\"\n    x_, y_ = 2, 1\n    x = Dual2(x_, vars=[\"x\"], dual=[1], dual2=[])\n    y = Dual2(y_, vars=[\"y\"], dual=[1], dual2=[])\n\n    f = y * x**3 + y\n    f_, fx_, fy_ = f.real, 3 * y_ * x_**2, x_**3 + 1\n    fxx_, fxy_, fyy_ = 6 * x_ * y_, 3 * x_**2, 0\n\n    xi = f.vars.index(\"x\")\n    yi = f.vars.index(\"y\")\n\n    assert f.dual[xi] == fx_\n    assert f.dual[yi] == fy_\n    assert f.dual2[xi, xi] * 2 == fxx_\n    assert f.dual2[xi, yi] * 2 == fxy_\n    assert f.dual2[yi, yi] * 2 == 0\n\n    h = f.__log__()\n    assert h.real == math.log(y_ * x_**3 + y_)\n    assert h.dual[xi] == 1 / f_ * fx_\n    assert h.dual[yi] == 1 / f_ * fy_\n    assert h.dual2[xi, xi] * 2 == -1 / f_**2 * fx_**2 + 1 / f_ * fxx_\n    assert h.dual2[xi, yi] * 2 == -1 / f_**2 * fx_ * fy_ + 1 / f_ * fxy_\n    assert h.dual2[yi, xi] * 2 == -1 / f_**2 * fx_ * fy_ + 1 / f_ * fxy_\n    assert h.dual2[yi, yi] * 2 == -1 / f_**2 * fy_**2 + 1 / f_ * fyy_\n\n\n@pytest.mark.parametrize(\n    (\"power\", \"expected\"),\n    [\n        (1, (2, 1, 0)),\n        (2, (4, 4, 2)),\n        (3, (8, 12, 12)),\n        (4, (16, 32, 48)),\n        (5, (32, 80, 160)),\n        (6, (64, 192, 480)),\n    ],\n)\ndef test_dual_power_1d(power, expected) -> None:\n    x = Dual(2, vars=[\"x\"], dual=[1])\n    y = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    f, g = x**power, y**power\n    assert f.real == expected[0]\n    assert f.dual[0] == expected[1]\n\n    assert g.real == expected[0]\n    assert g.dual[0] == expected[1]\n    assert g.dual2[0, 0] * 2 == expected[2]\n\n\ndef test_dual2_power2_1d() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    assert (x**2) * (x ** (-2)) == 1\n    assert (x**5) * (x ** (-5)) == 1\n    z = (x**7.35) * (x ** (-7.35))\n    assert abs(z - 1.0) < 1e-12\n\n\ndef test_dual2_power_2d() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    y = Dual2(3, vars=[\"y\"], dual=[1], dual2=[])\n    f = (x**4 * y**3) ** 2\n    assert f.dual2[0, 1] * 2 == 1492992\n    assert f.dual2[1, 0] * 2 == 1492992\n\n\ndef test_dual2_inv_specific() -> None:\n    z = Dual2(2, vars=[\"x\", \"y\"], dual=[2, 3], dual2=[])\n    result = z**-1\n    expected = Dual2(\n        0.5,\n        vars=[\"x\", \"y\"],\n        dual=[-0.5, -0.75],\n        dual2=[0.5, 0.75, 0.75, 9 / 8],\n    )\n    assert result == expected\n\n\ndef test_dual_truediv(x_1) -> None:\n    expected = Dual(1, [], [])\n    result = x_1 / x_1\n    assert result == expected\n\n\ndef test_dual2_exp_1d() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    f = x.__exp__()\n    assert f.real == math.exp(2)\n    assert f.dual[0] == math.exp(2)\n    assert f.dual2[0, 0] * 2 == math.exp(2)\n\n\ndef test_dual2_log_1d() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    f = x.__log__()\n    assert f.real == math.log(2)\n    assert f.dual[0] == 0.5\n    assert f.dual2[0] * 2 == -0.25\n\n\ndef test_dual2_log_exp() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    y = x.__log__()\n    z = y.__exp__()\n    assert x == z\n\n\ndef test_combined_vars_sorted(y_3) -> None:\n    x = Dual2(2, vars=[\"a\", \"v0\", \"z\"], dual=[1, 1, 1], dual2=[])\n    result = x * y_3\n    assert set(result.vars) == {\"a\", \"v0\", \"v2\", \"z\"}\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        2,\n        Dual(2, [], []),\n        Dual2(2, [], [], []),\n    ],\n)\ndef test_log(x) -> None:\n    result = dual_log(x)\n    expected = math.log(2)\n    assert result == expected\n\n\ndef test_dual_log_base() -> None:\n    result = dual_log(16, 2)\n    assert result == 4\n\n    result = dual_log(Dual(16, [], []), 2)\n    assert result == Dual(4, [], [])\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        2,\n        Dual(2, [], []),\n        Dual2(2, [], [], []),\n    ],\n)\ndef test_exp(x) -> None:\n    result = dual_exp(x)\n    expected = math.exp(2)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        Dual(1.25, [\"x\"], []),\n        Dual2(1.25, [\"x\"], [], []),\n    ],\n)\ndef test_norm_cdf(x) -> None:\n    result = dual_norm_cdf(x)\n    expected = NormalDist().cdf(1.250)\n    assert abs(result - expected) < 1e-10\n\n    approx_grad = (NormalDist().cdf(1.25001) - NormalDist().cdf(1.25)) * 100000\n    assert abs(gradient(result, [\"x\"])[0] - approx_grad) < 1e-5\n\n    if isinstance(x, Dual2):\n        approx_grad2 = (NormalDist().cdf(1.25) - NormalDist().cdf(1.24999)) * 100000\n        approx_grad2 = (approx_grad - approx_grad2) * 100000\n        assert abs(gradient(result, [\"x\"], order=2)[0] - approx_grad2) < 1e-5\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        Dual(0.75, [\"x\"], []),\n        Dual2(0.75, [\"x\"], [], []),\n    ],\n)\ndef test_inv_norm_cdf(x) -> None:\n    result = dual_inv_norm_cdf(x)\n    expected = NormalDist().inv_cdf(0.75)\n    assert abs(result - expected) < 1e-10\n\n    approx_grad = (NormalDist().inv_cdf(0.75001) - NormalDist().inv_cdf(0.75)) * 100000\n    assert abs(gradient(result, [\"x\"])[0] - approx_grad) < 1e-4\n\n    if isinstance(x, Dual2):\n        approx_grad2 = (NormalDist().inv_cdf(0.75) - NormalDist().inv_cdf(0.74999)) * 100000\n        approx_grad2 = (approx_grad - approx_grad2) * 100000\n        assert abs(gradient(result, [\"x\"], order=2)[0] - approx_grad2) < 1e-4\n\n\ndef test_norm_cdf_value() -> None:\n    result = dual_norm_cdf(1.0)\n    expected = 0.8413\n    assert abs(result - expected) < 1e-4\n\n\ndef test_inv_norm_cdf_value() -> None:\n    result = dual_inv_norm_cdf(0.50)\n    expected = 0.0\n    assert abs(result - expected) < 1e-4\n\n\n@pytest.mark.skip(reason=\"downcast vars is not used within the library, kept only for compat.\")\ndef test_downcast_vars() -> None:\n    w = Dual(2, [\"x\", \"y\", \"z\"], [0, 1, 1])\n    assert w.__downcast_vars__().vars == (\"y\", \"z\")\n\n    x = Dual2(2, [\"x\", \"y\", \"z\"], [0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1])\n    assert x.__downcast_vars__().vars == (\"y\", \"z\")\n\n    y = Dual2(2, [\"x\", \"y\", \"z\"], [0, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 1])\n    assert y.__downcast_vars__().vars == (\"z\",)\n\n    z = Dual2(2, [\"x\", \"y\", \"z\"], [0, 0, 1], [0, 0, 0, 0, 0, 1, 0, 1, 1])\n    assert z.__downcast_vars__().vars == (\"y\", \"z\")\n\n\ndef test_gradient_of_non_present_vars(x_1) -> None:\n    result = gradient(x_1)\n    assert np.all(np.isclose(result, np.array([1, 2])))\n\n\n@pytest.mark.parametrize((\"base\", \"exponent\"), [(0, 1), (1, 0)])\ndef test_powers_bad_type(base, exponent, x_1, y_1) -> None:\n    base = x_1 if base else y_1\n    exponent = x_1 if exponent else y_1\n    with pytest.raises(TypeError):\n        base**exponent\n\n\ndef test_keep_manifold_gradient() -> None:\n    du2 = Dual2(\n        10,\n        [\"x\", \"y\", \"z\"],\n        dual=[1, 2, 3],\n        dual2=[2, 3, 4, 3, 4, 5, 4, 5, 6],\n    )\n    result = gradient(du2, [\"x\", \"z\"], 1, keep_manifold=True)\n    expected = np.array([Dual2(1, [\"x\", \"z\"], [4, 8], []), Dual2(3, [\"x\", \"z\"], [8, 12], [])])\n    assertions = result == expected\n    assert all(assertions)\n\n\ndef test_dual_set_order(x_1, y_1) -> None:\n    assert set_order(x_1, 1) == x_1\n    assert set_order(y_1, 2) == y_1\n    assert set_order(1.0, 2) == 1.0\n    assert set_order(x_1, 2) == y_1\n    assert set_order(y_1, 1) == x_1\n    assert set_order(x_1, 0) == 1.0\n\n\ndef test_variable_set_order() -> None:\n    x = Variable(2.0, [\"x\"])\n    x_dual = set_order(x, order=1)\n    assert isinstance(x_dual, Dual)\n    x_dual2 = set_order(x, order=2)\n    assert isinstance(x_dual2, Dual2)\n\n\ndef test_perturbation_confusion() -> None:\n    # https://www.bcl.hamilton.ie/~barak/papers/ifl2005.pdf\n\n    # Utilised tagged variables\n    x = Dual(1.0, [\"x\"], [])\n    y = Dual(1.0, [\"y\"], [])\n    z = gradient(x + y, [\"y\"])[0]\n    result = gradient(x * z, [\"x\"])\n    assert result == 1.0\n\n    # Replicates untagged variables\n    x = Dual(1.0, [\"x\"], [])\n    y = Dual(1.0, [\"x\"], [])\n    z = gradient(x + y, [\"x\"])[0]\n    result = gradient(x * z, [\"x\"])\n    assert result == 2.0\n\n\n# Linalg dual_solve tests\n\n\ndef test_solve(A, b) -> None:\n    x = dual_solve(A, b)\n    x_np = np.linalg.solve(A, b)\n    diff = x - x_np\n    assertions = [abs(diff[i, 0]) < 1e-10 for i in range(A.shape[0])]\n    assert all(assertions)\n\n\ndef test_solve_lsqrs() -> None:\n    A = np.array([[0, 1], [1, 1], [2, 1], [3, 1]])\n    b = np.array([[-1, 0.2, 0.9, 2.1]]).T\n    result = dual_solve(A, b, allow_lsq=True, types=(float, float))\n    assert abs(result[0, 0] - 1.0) < 1e-9\n    assert abs(result[1, 0] + 0.95) < 1e-9\n\n\ndef test_solve_dual() -> None:\n    A = np.array([[1, 0], [0, 1]], dtype=\"object\")\n    b = np.array([Dual(2, [\"x\"], np.array([1])), Dual(5, [\"x\", \"y\"], np.array([1, 1]))])[\n        :,\n        np.newaxis,\n    ]\n    x = dual_solve(A, b, types=(float, Dual))\n    assertions = abs(b - x) < 1e-10\n    assert all(assertions)\n\n\ndef test_solve_dual2() -> None:\n    A = np.array(\n        [\n            [Dual2(1, [], [], []), Dual2(0, [], [], [])],\n            [Dual2(0, [], [], []), Dual2(1, [], [], [])],\n        ],\n        dtype=\"object\",\n    )\n    b = np.array([Dual2(2, [\"x\"], [1], []), Dual2(5, [\"x\", \"y\"], [1, 1], [])])[:, np.newaxis]\n    x = dual_solve(A, b, types=(Dual2, Dual2))\n    assertions = abs(b - x) < 1e-10\n    assert all(assertions)\n\n\ndef test_sparse_solve(A_sparse) -> None:\n    b = np.array(\n        [0, 0.90929743, 0.14112001, -0.7568025, -0.95892427, -0.2794155, 0.6569866, 0.98935825, 0],\n    )\n    b = b[:, np.newaxis]\n    x = dual_solve(A_sparse, b)\n    x_np = np.linalg.solve(A_sparse, b)\n    diff = x - x_np\n    assertions = [abs(diff[i, 0]) < 1e-10 for i in range(A_sparse.shape[0])]\n    assert all(assertions)\n\n\n@pytest.mark.skipif(not DUAL_CORE_PY, reason=\"Rust Dual has not implemented Multi-Dim Solve\")\ndef test_multi_dim_solve() -> None:\n    A = np.array([[Dual(0.5, [], []), Dual(2, [\"y\"], [])], [Dual(2.5, [\"y\"], []), Dual(4, [], [])]])\n    b = np.array(\n        [[Dual(6.5, [], []), Dual(9, [\"z\"], [])], [Dual(14.5, [\"y\"], []), Dual(21, [\"z\"], [])]],\n    )\n\n    x = dual_solve(A, b)\n    result = np.matmul(A, x).flatten()\n    expected = b.flatten()\n    for i in range(4):\n        assert abs(result[i] - expected[i]) < 1e-13\n        assert all(np.isclose(gradient(result[i], [\"y\", \"z\"]), gradient(expected[i], [\"y\", \"z\"])))\n\n\n# Test numpy compat\n\n\ndef test_numpy_isclose(y_2) -> None:\n    # np.isclose not supported for non-numeric dtypes\n    a = np.array([y_2, y_2])\n    b = np.array([y_2, y_2])\n    with pytest.raises(TypeError):\n        assert np.isclose(a, b)\n\n\ndef test_numpy_equality(y_2) -> None:\n    # instead of isclose use == (which uses math.isclose elementwise) and then np.all\n    a = np.array([y_2, y_2])\n    b = np.array([y_2, y_2])\n    result = a == b\n    assert np.all(result)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        Dual2(3.0, [\"x\"], [1], [2]),\n    ],\n)\n@pytest.mark.parametrize(\n    \"arg\",\n    [\n        2.2,\n        Dual(3, [\"x\"], []),\n        Dual2(3, [\"x\"], [2], [3]),\n    ],\n)\n@pytest.mark.parametrize(\n    \"op_str\",\n    [\n        \"add\",\n        \"sub\",\n        \"mul\",\n        \"truediv\",\n    ],\n)\ndef test_numpy_broadcast_ops_types(z, arg, op_str) -> None:\n    op = \"__\" + op_str + \"__\"\n    if type(z) in [Dual, Dual2] and type(arg) in [Dual, Dual2] and type(arg) is not type(z):\n        pytest.skip(\"Cannot operate Dual and Dual2 together.\")\n    result = getattr(np.array([z, z]), op)(arg)\n    expected = np.array([getattr(z, op)(arg), getattr(z, op)(arg)])\n    assert np.all(result == expected)\n\n    result = getattr(arg, op)(np.array([z, z]))\n    if result is NotImplemented:\n        opr = \"__r\" + op_str + \"__\"\n        result = getattr(np.array([z, z]), opr)(arg)\n        expected = np.array([getattr(z, opr)(arg), getattr(z, opr)(arg)])\n    else:\n        expected = np.array([getattr(arg, op)(z), getattr(arg, op)(z)])\n    assert np.all(result == expected)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        Dual2(3.0, [\"x\"], [1], [2]),\n    ],\n)\ndef test_numpy_broadcast_pow_types(z) -> None:\n    result = np.array([z, z]) ** 3\n    expected = np.array([z**3, z**3])\n    assert np.all(result == expected)\n\n    result = z ** np.array([3, 4])\n    expected = np.array([z**3, z**4])\n    assert np.all(result == expected)\n\n\ndef test_numpy_matmul(y_2, y_1) -> None:\n    a = np.array([y_2, y_1])\n    result = np.matmul(a[:, np.newaxis], a[np.newaxis, :])\n    expected = np.array([[y_2 * y_2, y_2 * y_1], [y_2 * y_1, y_1 * y_1]])\n    assert np.all(result == expected)\n\n\n@pytest.mark.skipif(\n    version.parse(np.__version__) >= version.parse(\"1.25.0\"),\n    reason=\"Object dtypes accepted by NumPy in 1.25.0+\",\n)\ndef test_numpy_einsum(y_2, y_1) -> None:\n    # einsum does not work with object dtypes\n    a = np.array([y_2, y_1])\n    with pytest.raises(TypeError):\n        _ = np.einsum(\"i,j\", a, a, optimize=True)\n\n\n@pytest.mark.skipif(\n    version.parse(np.__version__) < version.parse(\"1.25.0\"),\n    reason=\"Object dtypes not accepted by NumPy in <1.25.0\",\n)\ndef test_numpy_einsum_works(y_2, y_1) -> None:\n    a = np.array([y_2, y_1])\n    result = np.einsum(\"i,j\", a, a, optimize=True)\n    expected = np.array([[y_2 * y_2, y_2 * y_1], [y_2 * y_1, y_1 * y_1]])\n    assert np.all(result == expected)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        Dual2(3.0, [\"x\"], [1], [2]),\n    ],\n)\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        np.int8,\n        np.int16,\n        np.int32,\n        np.int64,\n        np.float16,\n        np.float32,\n        np.float64,\n        np.longdouble,\n    ],\n)\ndef test_numpy_dtypes(z, dtype) -> None:\n    np.array([1, 2], dtype=dtype) + z\n    z + np.array([1, 2], dtype=dtype)\n\n    z + dtype(2)\n    dtype(2) + z\n\n\nclass TestVariable:\n    @pytest.mark.parametrize(\n        (\"op\", \"exp\"),\n        [\n            (\"__add__\", Variable(4.0, [\"x\"])),\n            (\"__radd__\", Variable(4.0, [\"x\"])),\n            (\"__sub__\", Variable(1.0, [\"x\"])),\n            (\"__rsub__\", -Variable(1.0, [\"x\"])),\n            (\"__mul__\", Variable(3.75, [\"x\"], [1.5])),\n            (\"__rmul__\", Variable(3.75, [\"x\"], [1.5])),\n            (\"__truediv__\", Variable(2.5 / 1.5, [\"x\"], [1.0 / 1.5])),\n            (\"__rtruediv__\", Dual(1.5, [], []) / Dual(2.5, [\"x\"], [])),\n        ],\n    )\n    def test_variable_f64(self, op, exp):\n        with default_context(\"_global_ad_order\", 1):\n            f = 1.5\n            v = Variable(2.5, (\"x\",))\n            result = getattr(v, op)(f)\n            assert result == exp\n\n    def test_variable_f64_reverse(self):\n        v = Variable(2.5, (\"x\",))\n        assert (1.5 + v) == Variable(4.0, [\"x\"], [])\n        assert (1.5 - v) == Variable(-1.0, [\"x\"], [-1.0])\n        assert (1.5 * v) == Variable(1.5 * 2.5, [\"x\"], [1.5])\n        assert (1.5 / v) == Dual(1.5, [], []) / Dual(2.5, [\"x\"], [])\n\n    def test_rtruediv_global_ad(self):\n        exp = Dual2(1.5, [], [], []) / Dual2(2.5, [\"x\"], [], [])\n        with default_context(\"_global_ad_order\", 2):\n            f = 1.5\n            v = Variable(2.5, (\"x\",))\n            result = f / v\n            assert result == exp\n\n    @pytest.mark.parametrize(\n        (\"op\", \"exp\"),\n        [\n            (\"__add__\", Dual(4.0, [\"x\"], [2])),\n            (\"__radd__\", Dual(4.0, [\"x\"], [2])),\n            (\"__sub__\", Dual(1.0, [\"x\"], [0])),\n            (\"__rsub__\", Dual(-1.0, [\"x\"], [0])),\n            (\"__mul__\", Dual(3.75, [\"x\"], [4.0])),\n            (\"__rmul__\", Dual(3.75, [\"x\"], [4.0])),\n            (\"__truediv__\", Dual(2.5, [\"x\"], []) / Dual(1.5, [\"x\"], [])),\n            (\"__rtruediv__\", Dual(1.5, [\"x\"], []) / Dual(2.5, [\"x\"], [])),\n        ],\n    )\n    def test_variable_dual(self, op, exp):\n        f = Dual(1.5, [\"x\"], [])\n        v = Variable(2.5, (\"x\",))\n        result = getattr(v, op)(f)\n        assert result == exp\n\n    def test_variable_dual_reverse(self):\n        f = Dual(1.5, [\"x\"], [])\n        v = Variable(2.5, (\"x\",))\n        assert f + v == Dual(4.0, [\"x\"], [2.0])\n        assert f - v == Dual(-1.0, [\"x\"], [0.0])\n        assert f * v == Dual(1.5 * 2.5, [\"x\"], [4.0])\n        assert f / v == Dual(1.5, [\"x\"], [1.0]) / Dual(2.5, [\"x\"], [1.0])\n\n    @pytest.mark.parametrize(\n        (\"op\", \"exp\"),\n        [\n            (\"__add__\", Dual2(4.0, [\"x\"], [2], [])),\n            (\"__radd__\", Dual2(4.0, [\"x\"], [2], [])),\n            (\"__sub__\", Dual2(1.0, [\"x\"], [0], [])),\n            (\"__rsub__\", Dual2(-1.0, [\"x\"], [0], [])),\n            (\"__mul__\", Dual2(1.5, [\"x\"], [1.0], []) * Dual2(2.5, [\"x\"], [1.0], [])),\n            (\"__rmul__\", Dual2(1.5, [\"x\"], [1.0], []) * Dual2(2.5, [\"x\"], [1.0], [])),\n            (\"__truediv__\", Dual2(2.5, [\"x\"], [], []) / Dual2(1.5, [\"x\"], [], [])),\n            (\"__rtruediv__\", Dual2(1.5, [\"x\"], [], []) / Dual2(2.5, [\"x\"], [], [])),\n        ],\n    )\n    def test_variable_dual2(self, op, exp):\n        f = Dual2(1.5, [\"x\"], [], [])\n        v = Variable(2.5, (\"x\",))\n        result = getattr(v, op)(f)\n        assert result == exp\n\n    def test_variable_dual2_reverse(self):\n        f = Dual2(1.5, [\"x\"], [], [])\n        v = Variable(2.5, (\"x\",))\n        assert f + v == Dual2(4.0, [\"x\"], [2.0], [])\n        assert f - v == Dual2(-1.0, [\"x\"], [0.0], [])\n        assert f * v == Dual2(1.5, [\"x\"], [], []) * Dual2(2.5, [\"x\"], [], [])\n        assert f / v == Dual2(1.5, [\"x\"], [], []) / Dual2(2.5, [\"x\"], [], [])\n\n    @pytest.mark.parametrize(\n        (\"op\", \"exp\"),\n        [\n            (\"__add__\", Dual(4.0, [\"x\"], [2])),\n            (\"__radd__\", Dual(4.0, [\"x\"], [2])),\n            (\"__sub__\", Dual(1.0, [\"x\"], [0])),\n            (\"__rsub__\", Dual(-1.0, [\"x\"], [0])),\n            (\"__mul__\", Dual(1.5, [\"x\"], [1.0]) * Dual(2.5, [\"x\"], [1.0])),\n            (\"__rmul__\", Dual(1.5, [\"x\"], [1.0]) * Dual(2.5, [\"x\"], [1.0])),\n            (\"__truediv__\", Dual(2.5, [\"x\"], []) / Dual(1.5, [\"x\"], [])),\n        ],\n    )\n    def test_variable_variable_ad1(self, op, exp):\n        f = Variable(1.5, (\"x\",))\n        v = Variable(2.5, (\"x\",))\n        with default_context(\"_global_ad_order\", 1):\n            result = getattr(v, op)(f)\n            assert result == exp\n\n    @pytest.mark.parametrize(\n        (\"op\", \"exp\"),\n        [\n            (\"__add__\", Dual2(4.0, [\"x\"], [2], [])),\n            (\"__radd__\", Dual2(4.0, [\"x\"], [2], [])),\n            (\"__sub__\", Dual2(1.0, [\"x\"], [0], [])),\n            (\"__rsub__\", Dual2(-1.0, [\"x\"], [0], [])),\n            (\"__mul__\", Dual2(1.5, [\"x\"], [1.0], []) * Dual2(2.5, [\"x\"], [1.0], [])),\n            (\"__rmul__\", Dual2(1.5, [\"x\"], [1.0], []) * Dual2(2.5, [\"x\"], [1.0], [])),\n            (\"__truediv__\", Dual2(2.5, [\"x\"], [], []) / Dual2(1.5, [\"x\"], [], [])),\n        ],\n    )\n    def test_variable_variable_ad2(self, op, exp):\n        f = Variable(1.5, (\"x\",))\n        v = Variable(2.5, (\"x\",))\n        with default_context(\"_global_ad_order\", 2):\n            result = getattr(v, op)(f)\n            assert result == exp\n\n    @pytest.mark.parametrize(\n        (\"op\", \"ad\", \"exp\"),\n        [\n            (\"__exp__\", 1, Dual(0.5, [\"x\"], []).__exp__()),\n            (\"__exp__\", 2, Dual2(0.5, [\"x\"], [], []).__exp__()),\n            (\"__log__\", 1, Dual(0.5, [\"x\"], []).__log__()),\n            (\"__log__\", 2, Dual2(0.5, [\"x\"], [], []).__log__()),\n            (\"__norm_cdf__\", 1, Dual(0.5, [\"x\"], []).__norm_cdf__()),\n            (\"__norm_cdf__\", 2, Dual2(0.5, [\"x\"], [], []).__norm_cdf__()),\n            (\"__norm_inv_cdf__\", 1, Dual(0.5, [\"x\"], []).__norm_inv_cdf__()),\n            (\"__norm_inv_cdf__\", 2, Dual2(0.5, [\"x\"], [], []).__norm_inv_cdf__()),\n        ],\n    )\n    def test_variable_funcs(self, op, ad, exp):\n        with default_context(\"_global_ad_order\", ad):\n            var = Variable(0.5, [\"x\"])\n            result = getattr(var, op)()\n            assert result == exp\n\n    @pytest.mark.parametrize(\n        (\"op\", \"ad\", \"exp\"),\n        [\n            (\"__pow__\", 1, Dual(2.5, [\"x\"], []).__pow__(2)),\n            (\"__pow__\", 2, Dual2(2.5, [\"x\"], [], []).__pow__(2)),\n        ],\n    )\n    def test_variable_pow(self, op, ad, exp):\n        with default_context(\"_global_ad_order\", ad):\n            var = Variable(2.5, [\"x\"])\n            result = getattr(var, op)(2)\n            assert result == exp\n\n    @pytest.mark.parametrize((\"order\", \"exp\"), [(1, 2.0), (2, 0.0)])\n    def test_gradient(self, order, exp):\n        var = Variable(2.0, [\"x\"], [2.0])\n        result = gradient(var, [\"x\"], order=order)[0]\n        assert result == exp\n\n    def test_eq(self):\n        v1 = Variable(1.0, [\"x\", \"y\"])\n        v2 = Variable(1.0, [\"x\", \"y\"])\n        assert v1 == v2\n\n    @pytest.mark.parametrize(\n        (\"func\", \"exp\"),\n        [\n            (dual_exp, Dual(0.5, [\"x\"], []).__exp__()),\n            (dual_log, Dual(0.5, [\"x\"], []).__log__()),\n            (dual_norm_cdf, Dual(0.5, [\"x\"], []).__norm_cdf__()),\n            (dual_inv_norm_cdf, Dual(0.5, [\"x\"], []).__norm_inv_cdf__()),\n            (dual_norm_pdf, dual_norm_pdf(Dual(0.5, [\"x\"], []))),\n        ],\n    )\n    def test_standalone_funcs(self, func, exp):\n        var = Variable(0.5, [\"x\"])\n        result = func(var)\n        assert result == exp\n\n    def test_z_exogenous_example(self):\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"curve\")\n        solver = Solver(\n            curves=[curve], instruments=[IRS(dt(2000, 1, 1), \"6m\", \"S\", curves=curve)], s=[2.50]\n        )\n        irs = IRS(\n            effective=dt(2000, 1, 1),\n            termination=\"6m\",\n            frequency=\"S\",\n            leg2_frequency=\"M\",\n            fixed_rate=Variable(3.0, [\"R\"]),\n            notional=Variable(5e6, [\"N\"]),\n            leg2_float_spread=Variable(0.0, [\"z\"]),\n            curves=\"curve\",\n        )\n        result = irs.exo_delta(vars=[\"N\", \"R\", \"z\"], vars_scalar=[1.0, 0.01, 1.0], solver=solver)\n\n        exp0 = irs.npv(solver=solver) / 5e6\n        exp1 = irs.analytic_delta(curves=curve)\n        exp2 = irs.analytic_delta(curves=curve, leg=2)\n\n        assert abs(exp0 - result.iloc[0, 0]) < 1e-8\n        assert abs(exp1 + result.iloc[1, 0]) < 1e-8\n        assert abs(exp2 + result.iloc[2, 0]) < 1e-8\n\n\ndef test_set_multiple_objects_order():\n    a = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"a\")\n    b = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"b\")\n    c = a\n\n    result = _set_ad_order_objects([2, 2, 0], [a, b, c])\n    assert a._ad == 2\n    assert b._ad == 2\n    assert c._ad == 2  # c is a!\n    expected = {\n        id(a): 0,\n        id(b): 0,\n    }\n    assert result == expected\n\n    _set_ad_order_objects(result, [a, b, c])\n    assert a._ad == 0\n    assert b._ad == 0\n    assert c._ad == 0  # c is a!\n\n\ndef test_set_multiple_objects_order_raises():\n    a = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"a\")\n    with pytest.raises(ValueError):\n        _set_ad_order_objects([0], [a, a])\n"
  },
  {
    "path": "python/tests/test_dualpy.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport math\nfrom statistics import NormalDist\n\nimport numpy as np\nimport pytest\nfrom packaging import version\nfrom rateslib.dual import (\n    Dual,\n    Dual2,\n    Variable,\n    dual_exp,\n    dual_inv_norm_cdf,\n    dual_log,\n    dual_norm_cdf,\n    dual_solve,\n    gradient,\n    set_order,\n)\nfrom rateslib.dual.utils import _abs_float, _dual_round\n\n\n@pytest.fixture\ndef x_1():\n    return Dual(1, vars=[\"v0\", \"v1\"], dual=[1, 2])\n\n\n@pytest.fixture\ndef x_2():\n    return Dual(2, vars=[\"v0\", \"v2\"], dual=[0, 3])\n\n\n@pytest.fixture\ndef y_1():\n    return Dual2(1, vars=[\"v0\", \"v1\"], dual=[1, 2], dual2=[])\n\n\n@pytest.fixture\ndef y_2():\n    return Dual2(1, vars=[\"v0\", \"v1\"], dual=[1, 2], dual2=[1.0, 1.0, 1.0, 1.0])\n\n\n@pytest.fixture\ndef y_3():\n    return Dual2(2, vars=[\"v0\", \"v2\"], dual=[0, 3], dual2=[1.0, 1.0, 1.0, 1.0])\n\n\n@pytest.fixture\ndef A():\n    return np.random.randn(25).reshape(5, 5)\n\n\n@pytest.fixture\ndef A_sparse():\n    return np.array(\n        [\n            [24, -36, 12, 0, 0, 0, 0, 0, 0],\n            [1, 0, 0, 0, 0, 0, 0, 0, 0],\n            [0, 0.25, 0.583333333333, 0.1666666666, 0, 0, 0, 0, 0],\n            [0, 0, 0.1666666666, 0.6666666666, 0.1666666666, 0, 0, 0, 0],\n            [0, 0, 0, 0.1666666666, 0.6666666666, 0.1666666666, 0, 0, 0],\n            [0, 0, 0, 0, 0.1666666666, 0.6666666666, 0.1666666666, 0, 0],\n            [0, 0, 0, 0, 0, 0.1666666666, 0.583333333333, 0.25, 0],\n            [0, 0, 0, 0, 0, 0, 0, 0, 1],\n            [0, 0, 0, 0, 0, 0, 12, -36, 24],\n        ],\n    )\n\n\n@pytest.fixture\ndef b():\n    return np.random.randn(5).reshape(5, 1)\n\n\ndef test_zero_init() -> None:\n    x = Dual(1, [\"x\"], [])\n    assert np.all(x.dual == np.ones(1))\n\n    y = Dual2(1, [\"x\"], [], [])\n    assert np.all(y.dual == np.ones(1))\n    assert np.all(y.dual2 == np.zeros((1, 1)))\n\n\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        \"__add__\",\n        \"__sub__\",\n        \"__mul__\",\n        \"__truediv__\",\n        \"__eq__\",\n    ],\n)\ndef test_no_type_crossing_on_ops(x_1, y_1, op) -> None:\n    # getattr(x_1, op)(y_1)\n    with pytest.raises(TypeError):\n        getattr(x_1, op)(y_1)\n\n    with pytest.raises(TypeError):\n        getattr(y_1, op)(x_1)\n\n\ndef test_dual_repr(x_1, y_2) -> None:\n    result = x_1.__repr__()\n    assert result == \"<Dual: 1.000000, (v0, v1), [1.0, 2.0]>\"\n\n    result = y_2.__repr__()\n    assert result == \"<Dual2: 1.000000, (v0, v1), [1.0, 2.0], [[...]]>\"\n\n\ndef test_dual_str(x_1, y_2) -> None:\n    result = x_1.__str__()\n    assert result == \"<Dual: 1.000000, (v0, v1), [1.0, 2.0]>\"\n\n    result = y_2.__str__()\n    assert result == \"<Dual2: 1.000000, (v0, v1), [1.0, 2.0], [[...]]>\"\n\n\ndef test_rdiv_raises(x_1, y_1) -> None:\n    with pytest.raises(TypeError):\n        _ = \"string\" / x_1\n\n    with pytest.raises(TypeError):\n        _ = \"string\" / y_1\n\n\n@pytest.mark.parametrize(\n    (\"input_\", \"expected\"),\n    [\n        (Variable(2.354, [\"x\"], [1.1011]), Variable(2.35, [\"x\"], [1.1011])),\n        (Dual(2.354, [\"x\"], [1.1011]), Dual(2.35, [\"x\"], [1.1011])),\n        (Dual2(2.354, [\"x\"], [1.1011], [2.1111]), Dual2(2.35, [\"x\"], [1.1011], [2.1111])),\n    ],\n)\ndef test_dual_round(input_, expected):\n    result = _dual_round(input_, 2)\n    assert result == expected\n\n\ndef test_neg(x_1, y_2) -> None:\n    assert -x_1 == Dual(-1, [\"v0\", \"v1\"], [-1.0, -2.0])\n    assert -y_2 == Dual2(-1, [\"v0\", \"v1\"], [-1.0, -2.0], [-1.0, -1.0, -1.0, -1.0])\n\n\ndef test_eq_ne(x_1, y_1, y_2) -> None:\n    # non-matching types\n    assert Dual(0, [\"single_var\"], []) != 0\n    assert Dual2(0, [\"single_var\"], [], []) != 0\n    # ints\n    assert Dual(2, [], []) == 2\n    assert Dual2(2, [], [], []) == 2\n    # floats\n    assert Dual(3.3, [], []) == 3.3\n    assert Dual2(3.3, [], [], []) == 3.3\n    # no type crossing\n    with pytest.raises(TypeError):\n        assert x_1 != y_1\n    # equality\n    assert x_1 == Dual(1, [\"v0\", \"v1\"], [1, 2])\n    assert y_1 == Dual2(1, [\"v0\", \"v1\"], [1, 2], [])\n    assert y_2 == Dual2(1, [\"v0\", \"v1\"], [1, 2], [1.0, 1.0, 1.0, 1.0])\n    # non-matching elements\n    assert x_1 != Dual(2, [\"v0\", \"v1\"], [1, 2])\n    assert x_1 != Dual(1, [\"v0\", \"v1\"], [2, 2])\n    assert x_1 != Dual(1, [\"v2\", \"v1\"], [1, 2])\n    # non-matching elements\n    assert y_1 != Dual2(2, [\"v0\", \"v1\"], [1, 2], [])\n    assert y_1 != Dual2(1, [\"v0\", \"v1\"], [2, 2], [])\n    assert y_1 != Dual2(1, [\"v2\", \"v1\"], [1, 2], [])\n    # non-matching dual2\n    assert y_2 != Dual2(1, [\"v0\", \"v1\"], [1, 2], [2.0, 2.0, 2.0, 2.0])\n\n\ndef test_lt() -> None:\n    assert Dual(1, [\"x\"], []) < Dual(2, [\"y\"], [])\n    assert Dual2(1, [\"z\"], [], []) < Dual2(2, [\"x\"], [], [])\n    assert Dual(1, [\"x\"], []) < 10\n    assert not Dual(1, [\"x\"], []) < 0\n\n\ndef test_lt_raises() -> None:\n    with pytest.raises(TypeError, match=\"Cannot compare\"):\n        assert Dual(1, [\"x\"], []) < Dual2(2, [\"y\"], [], [])\n\n\ndef test_gt() -> None:\n    assert Dual(2, [\"x\"], []) > Dual(1, [\"y\"], [])\n    assert Dual2(2, [\"z\"], [], []) > Dual2(1, [\"x\"], [], [])\n    assert Dual(1, [\"x\"], []) > 0\n    assert not Dual(1, [\"x\"], []) > 10\n\n\ndef test_gt_raises() -> None:\n    with pytest.raises(TypeError, match=\"Cannot compare\"):\n        assert Dual(2, [\"x\"], []) > Dual2(1, [\"y\"], [], [])\n\n\ndef test_dual2_abs_float(x_1, y_1, y_2) -> None:\n    assert _abs_float(x_1) == 1\n    assert _abs_float(y_1) == 1\n    assert _abs_float(y_2) == 1\n    assert float(x_1) == float(1)\n    assert float(y_1) == float(1)\n    assert float(y_2) == float(1)\n\n\n@pytest.mark.parametrize(\"op\", [\"__add__\", \"__sub__\", \"__mul__\", \"__truediv__\"])\ndef test_dual2_immutable(y_1, y_2, op) -> None:\n    _ = getattr(y_1, op)(y_2)\n    assert y_1 == Dual2(1, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]), dual2=[])\n    assert y_2 == Dual2(1, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]), dual2=[1.0, 1.0, 1.0, 1.0])\n\n\n@pytest.mark.parametrize(\"op\", [\"__add__\", \"__sub__\", \"__mul__\", \"__truediv__\"])\ndef test_dual_immutable(x_1, op) -> None:\n    _ = getattr(x_1, op)(Dual(2, vars=[\"new\"], dual=np.array([4])))\n    assert x_1 == Dual(1, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))\n\n\ndef test_dual_raises(x_1) -> None:\n    with pytest.raises(ValueError, match=\"`Dual` variable cannot possess `dual2`\"):\n        x_1.dual2\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual(3, vars=[\"v0\", \"v1\", \"v2\"], dual=np.array([1, 2, 3]))),\n        (\"__sub__\", Dual(-1, vars=[\"v0\", \"v1\", \"v2\"], dual=np.array([1, 2, -3]))),\n        (\"__mul__\", Dual(2, vars=[\"v0\", \"v1\", \"v2\"], dual=np.array([2, 4, 3]))),\n        (\"__truediv__\", Dual(0.5, vars=[\"v0\", \"v1\", \"v2\"], dual=np.array([0.5, 1, -0.75]))),\n    ],\n)\ndef test_ops(x_1, x_2, op, expected) -> None:\n    result = getattr(x_1, op)(x_2)\n    assert result == expected\n\n\ndef test_op_inversions(x_1, x_2) -> None:\n    assert (x_1 + x_2) - (x_2 + x_1) == 0\n    assert (x_1 / x_2) * (x_2 / x_1) == 1\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual2(3, [\"v0\", \"v1\", \"v2\"], [1, 2, 3], [2, 1, 1, 1, 1, 0, 1, 0, 1])),\n        (\"__sub__\", Dual2(-1, [\"v0\", \"v1\", \"v2\"], [1, 2, -3], [0, 1, -1, 1, 1, 0, -1, 0, -1])),\n        (\"__mul__\", Dual2(2, [\"v0\", \"v1\", \"v2\"], [2, 4, 3], [3, 2, 2.5, 2, 2, 3, 2.5, 3, 1])),\n        (\n            \"__truediv__\",\n            Dual2(\n                0.5,\n                [\"v0\", \"v1\", \"v2\"],\n                [0.5, 1.0, -0.75],\n                [0.25, 0.5, -0.625, 0.5, 0.5, -0.75, -0.625, -0.75, 0.875],\n            ),\n        ),\n    ],\n)\ndef test_ops2(y_2, y_3, op, expected) -> None:\n    result = getattr(y_2, op)(y_3)\n    assert result == expected\n\n\ndef test_op_inversions2(y_2, y_3) -> None:\n    assert (y_2 + y_3) - (y_3 + y_2) == 0\n    assert (y_2 / y_3) * (y_3 / y_2) == 1\n\n\ndef test_inverse(x_1, y_2) -> None:\n    assert x_1 * x_1**-1 == 1\n    assert y_2 * y_2**-1 == 1\n\n\ndef test_power_identity(x_1, y_2) -> None:\n    result = x_1**1\n    assert result == x_1\n\n    result = y_2**1\n    assert result == y_2\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual(1 + 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))),\n        (\"__sub__\", Dual(1 - 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))),\n        (\"__mul__\", Dual(1 * 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]) * 2.5)),\n        (\"__truediv__\", Dual(1 / 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]) / 2.5)),\n    ],\n)\ndef test_left_op_with_float(x_1, op, expected) -> None:\n    result = getattr(x_1, op)(2.5)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual2(1 + 2.5, [\"v0\", \"v1\"], [1, 2], [1.0, 1.0, 1.0, 1.0])),\n        (\n            \"__sub__\",\n            Dual2(1 - 2.5, [\"v0\", \"v1\"], [1, 2], [1.0, 1.0, 1.0, 1.0]),\n        ),\n        (\"__mul__\", Dual2(1 * 2.5, [\"v0\", \"v1\"], [2.5, 5.0], [2.5, 2.5, 2.5, 2.5])),\n        (\n            \"__truediv__\",\n            Dual2(1 / 2.5, [\"v0\", \"v1\"], [1 / 2.5, 2 / 2.5], [1 / 2.5, 1 / 2.5, 1 / 2.5, 1 / 2.5]),\n        ),\n    ],\n)\ndef test_left_op_with_float2(y_2, op, expected) -> None:\n    result = getattr(y_2, op)(2.5)\n    assert result == expected\n\n\ndef test_right_op_with_float(x_1) -> None:\n    assert 2.5 + x_1 == Dual(1 + 2.5, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))\n    assert 2.5 - x_1 == Dual(2.5 - 1, vars=[\"v0\", \"v1\"], dual=-np.array([1, 2]))\n    assert 2.5 * x_1 == x_1 * 2.5\n    assert 2.5 / x_1 == (x_1 / 2.5) ** -1\n\n\ndef test_right_op_with_float2(y_2) -> None:\n    assert 2.5 + y_2 == Dual2(\n        1 + 2.5,\n        vars=[\"v0\", \"v1\"],\n        dual=[1.0, 2.0],\n        dual2=[1.0, 1.0, 1.0, 1.0],\n    )\n    assert 2.5 - y_2 == Dual2(\n        2.5 - 1,\n        vars=[\"v0\", \"v1\"],\n        dual=[-1.0, -2.0],\n        dual2=[-1.0, -1.0, -1.0, -1.0],\n    )\n    assert 2.5 * y_2 == y_2 * 2.5\n    assert 2.5 / y_2 == (y_2 / 2.5) ** -1\n\n\ndef test_dual2_second_derivatives() -> None:\n    \"test power, multiplication, addition\"\n\n    def f(x, y, z):\n        \"\"\"\n        f_x = 4x^3 y^2, f_y = 2y x^4 + z, f_z = 3z^2 +y\n        f_xx = 12x^2 y^2, f_xy = 8 x^3 y, f_xz = 0,\n        f_yx = 8x^3 y, f_yy = 2 x^4, f_yz = 1,\n        f_zx = 0, f_zy = 1, f_zz = 6z\n        \"\"\"\n        return x**4 * y**2 + z**3 + y * z\n\n    x_, y_, z_ = 3, 2, 1\n\n    x = Dual2(x_, vars=[\"x\"], dual=[1], dual2=[])\n    y = Dual2(y_, vars=[\"y\"], dual=[1], dual2=[])\n    z = Dual2(z_, vars=[\"z\"], dual=[1], dual2=[])\n\n    result = f(x, y, z)\n    assert result.dual[0] == 4 * x_**3 * y_**2  # 432\n    assert result.dual[1] == 2 * y_ * x_**4 + z_  # 325\n    assert result.dual[2] == 3 * z_**2 + y_  # 5\n\n    assert result.dual2[0, 0] * 2 == 12 * x_**2 * y_**2\n    assert result.dual2[0, 1] * 2 == 8 * x_**3 * y_\n    assert result.dual2[0, 2] * 2 == 0\n    assert result.dual2[1, 0] * 2 == 8 * x_**3 * y_\n    assert result.dual2[1, 1] * 2 == 2 * x_**4\n    assert result.dual2[1, 2] * 2 == 1\n    assert result.dual2[2, 0] * 2 == 0\n    assert result.dual2[2, 1] * 2 == 1\n    assert result.dual2[2, 2] * 2 == 6 * z_\n\n\ndef test_dual2_second_derivatives2() -> None:\n    \"test dual_exp, multiplication, division, dual_log\"\n\n    def f(x, y, z):\n        return (x / z).__exp__() + (x * y).__log__()\n\n    x_, y_, z_ = 3, 2, 1\n\n    x = Dual2(x_, vars=[\"x\"], dual=[1], dual2=[])\n    y = Dual2(y_, vars=[\"y\"], dual=[1], dual2=[])\n    z = Dual2(z_, vars=[\"z\"], dual=[1], dual2=[])\n\n    result = f(x, y, z)\n    xi = result.vars.index(\"x\")\n    yi = result.vars.index(\"y\")\n    zi = result.vars.index(\"z\")\n    assert result.dual[xi] == math.exp(x_ / z_) / z_ + 1 / x_\n    assert result.dual[yi] == 1 / y_\n    assert result.dual[zi] == -x_ * math.exp(x_ / z_) / z_**2\n\n    assert result.dual2[xi, xi] * 2 == math.exp(x_ / z_) / z_**2 - 1 / x_**2\n    assert result.dual2[xi, yi] * 2 == 0\n    assert result.dual2[xi, zi] * 2 == math.exp(x_ / z_) * (-1 / z_**2 - x_ / z_**3)\n    assert result.dual2[yi, xi] * 2 == 0\n    assert result.dual2[yi, yi] * 2 == -1 / y_**2\n    assert result.dual2[yi, zi] * 2 == 0\n    assert result.dual2[zi, xi] * 2 == math.exp(x_ / z_) * (-1 / z_**2 - x_ / z_**3)\n    assert result.dual2[zi, yi] * 2 == 0\n    assert result.dual2[zi, zi] * 2 == math.exp(x_ / z_) * (x_**2 / z_**4 + 2 * x_ / z_**3)\n\n\ndef test_dual2_second_derivatives3() -> None:\n    \"\"\"\n    h, f = dual_log(f), x^3y+y\n    f_x = 1/f 3x^2y, f_y = 1/f (x^3+1),\n    f_xx = -1/f^2 (3x^2y)^2 + 1/f 6xy, f_xy = -1/f^2 (3x^2y)(x^3+1),\n    f_yy = -1/f^2 (x^3+1)^2 +1/f (0)\n    \"\"\"\n    x_, y_ = 2, 1\n    x = Dual2(x_, vars=[\"x\"], dual=[1], dual2=[])\n    y = Dual2(y_, vars=[\"y\"], dual=[1], dual2=[])\n\n    f = y * x**3 + y\n    f_, fx_, fy_ = f.real, 3 * y_ * x_**2, x_**3 + 1\n    fxx_, fxy_, fyy_ = 6 * x_ * y_, 3 * x_**2, 0\n\n    xi = f.vars.index(\"x\")\n    yi = f.vars.index(\"y\")\n\n    assert f.dual[xi] == fx_\n    assert f.dual[yi] == fy_\n    assert f.dual2[xi, xi] * 2 == fxx_\n    assert f.dual2[xi, yi] * 2 == fxy_\n    assert f.dual2[yi, yi] * 2 == 0\n\n    h = f.__log__()\n    assert h.real == math.log(y_ * x_**3 + y_)\n    assert h.dual[xi] == 1 / f_ * fx_\n    assert h.dual[yi] == 1 / f_ * fy_\n    assert h.dual2[xi, xi] * 2 == -1 / f_**2 * fx_**2 + 1 / f_ * fxx_\n    assert h.dual2[xi, yi] * 2 == -1 / f_**2 * fx_ * fy_ + 1 / f_ * fxy_\n    assert h.dual2[yi, xi] * 2 == -1 / f_**2 * fx_ * fy_ + 1 / f_ * fxy_\n    assert h.dual2[yi, yi] * 2 == -1 / f_**2 * fy_**2 + 1 / f_ * fyy_\n\n\n@pytest.mark.parametrize(\n    (\"power\", \"expected\"),\n    [\n        (1, (2, 1, 0)),\n        (2, (4, 4, 2)),\n        (3, (8, 12, 12)),\n        (4, (16, 32, 48)),\n        (5, (32, 80, 160)),\n        (6, (64, 192, 480)),\n    ],\n)\ndef test_dual_power_1d(power, expected) -> None:\n    x = Dual(2, vars=[\"x\"], dual=[1])\n    y = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    f, g = x**power, y**power\n    assert f.real == expected[0]\n    assert f.dual[0] == expected[1]\n\n    assert g.real == expected[0]\n    assert g.dual[0] == expected[1]\n    assert g.dual2[0, 0] * 2 == expected[2]\n\n\ndef test_dual2_power2_1d() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    assert (x**2) * (x ** (-2)) == 1\n    assert (x**5) * (x ** (-5)) == 1\n    z = (x**7.35) * (x ** (-7.35))\n    assert abs(z - 1.0) < 1e-12\n\n\ndef test_dual2_power_2d() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    y = Dual2(3, vars=[\"y\"], dual=[1], dual2=[])\n    f = (x**4 * y**3) ** 2\n    assert f.dual2[0, 1] * 2 == 1492992\n    assert f.dual2[1, 0] * 2 == 1492992\n\n\ndef test_dual2_inv_specific() -> None:\n    z = Dual2(2, vars=[\"x\", \"y\"], dual=[2, 3], dual2=[])\n    result = z**-1\n    expected = Dual2(\n        0.5,\n        vars=[\"x\", \"y\"],\n        dual=[-0.5, -0.75],\n        dual2=[0.5, 0.75, 0.75, 9 / 8],\n    )\n    assert result == expected\n\n\ndef test_dual_truediv(x_1) -> None:\n    expected = Dual(1, [], [])\n    result = x_1 / x_1\n    assert result == expected\n\n\ndef test_dual2_exp_1d() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    f = x.__exp__()\n    assert f.real == math.exp(2)\n    assert f.dual[0] == math.exp(2)\n    assert f.dual2[0, 0] * 2 == math.exp(2)\n\n\ndef test_dual2_log_1d() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    f = x.__log__()\n    assert f.real == math.log(2)\n    assert f.dual[0] == 0.5\n    assert f.dual2[0] * 2 == -0.25\n\n\ndef test_dual2_log_exp() -> None:\n    x = Dual2(2, vars=[\"x\"], dual=[1], dual2=[])\n    y = x.__log__()\n    z = y.__exp__()\n    assert x == z\n\n\ndef test_combined_vars_sorted(y_3) -> None:\n    x = Dual2(2, vars=[\"a\", \"v0\", \"z\"], dual=[1, 1, 1], dual2=[])\n    result = x * y_3\n    assert set(result.vars) == {\"a\", \"v0\", \"v2\", \"z\"}\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        2,\n        Dual(2, [], []),\n        Dual2(2, [], [], []),\n    ],\n)\ndef test_log(x) -> None:\n    result = dual_log(x)\n    expected = math.log(2)\n    assert result == expected\n\n\ndef test_dual_log_base() -> None:\n    result = dual_log(16, 2)\n    assert result == 4\n\n    result = dual_log(Dual(16, [], []), 2)\n    assert result == Dual(4, [], [])\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        2,\n        Dual(2, [], []),\n        Dual2(2, [], [], []),\n    ],\n)\ndef test_exp(x) -> None:\n    result = dual_exp(x)\n    expected = math.exp(2)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        Dual(1.25, [\"x\"], []),\n        Dual2(1.25, [\"x\"], [], []),\n    ],\n)\ndef test_norm_cdf(x) -> None:\n    result = x.__norm_cdf__()\n    expected = NormalDist().cdf(1.250)\n    assert abs(result - expected) < 1e-10\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        Dual(0.75, [\"x\"], []),\n        Dual2(0.75, [\"x\"], [], []),\n    ],\n)\ndef test_inv_norm_cdf(x) -> None:\n    result = x.__norm_inv_cdf__()\n    expected = NormalDist().inv_cdf(0.75)\n    assert abs(result - expected) < 1e-10\n\n\ndef test_norm_cdf_value() -> None:\n    result = dual_norm_cdf(1.0)\n    expected = 0.8413\n    assert abs(result - expected) < 1e-4\n\n\ndef test_inv_norm_cdf_value() -> None:\n    result = dual_inv_norm_cdf(0.50)\n    expected = 0.0\n    assert abs(result - expected) < 1e-4\n\n\n@pytest.mark.skip(reason=\"downcast vars is not used within the library, kept only for compat.\")\ndef test_downcast_vars() -> None:\n    w = Dual(2, [\"x\", \"y\", \"z\"], [0, 1, 1])\n    assert w.__downcast_vars__().vars == (\"y\", \"z\")\n\n    x = Dual2(2, [\"x\", \"y\", \"z\"], [0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1])\n    assert x.__downcast_vars__().vars == (\"y\", \"z\")\n\n    y = Dual2(2, [\"x\", \"y\", \"z\"], [0, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 1])\n    assert y.__downcast_vars__().vars == (\"z\",)\n\n    z = Dual2(2, [\"x\", \"y\", \"z\"], [0, 0, 1], [0, 0, 0, 0, 0, 1, 0, 1, 1])\n    assert z.__downcast_vars__().vars == (\"y\", \"z\")\n\n\n# Linalg dual_solve tests\n\n\ndef test_solve(A, b) -> None:\n    x = dual_solve(A, b)\n    x_np = np.linalg.solve(A, b)\n    diff = x - x_np\n    assertions = [abs(diff[i, 0]) < 1e-10 for i in range(A.shape[0])]\n    assert all(assertions)\n\n\ndef test_solve_lsqrs() -> None:\n    A = np.array([[0, 1], [1, 1], [2, 1], [3, 1]])\n    b = np.array([[-1, 0.2, 0.9, 2.1]]).T\n    result = dual_solve(A, b, allow_lsq=True, types=(float, float))\n    assert abs(result[0, 0] - 1.0) < 1e-9\n    assert abs(result[1, 0] + 0.95) < 1e-9\n\n\ndef test_sparse_solve(A_sparse) -> None:\n    b = np.array(\n        [0, 0.90929743, 0.14112001, -0.7568025, -0.95892427, -0.2794155, 0.6569866, 0.98935825, 0],\n    )\n    b = b[:, np.newaxis]\n    x = dual_solve(A_sparse, b)\n    x_np = np.linalg.solve(A_sparse, b)\n    diff = x - x_np\n    assertions = [abs(diff[i, 0]) < 1e-10 for i in range(A_sparse.shape[0])]\n    assert all(assertions)\n\n\n# Test numpy compat\n\n\ndef test_numpy_isclose(y_2) -> None:\n    # np.isclose not supported for non-numeric dtypes\n    a = np.array([y_2, y_2])\n    b = np.array([y_2, y_2])\n    with pytest.raises(TypeError):\n        assert np.isclose(a, b)\n\n\ndef test_numpy_equality(y_2) -> None:\n    # instead of isclose use == (which uses math.isclose elementwise) and then np.all\n    a = np.array([y_2, y_2])\n    b = np.array([y_2, y_2])\n    result = a == b\n    assert np.all(result)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        Dual2(3.0, [\"x\"], [1], [2]),\n    ],\n)\n@pytest.mark.parametrize(\n    \"arg\",\n    [\n        2.2,\n        Dual(3, [\"x\"], []),\n        Dual2(3, [\"x\"], [2], [3]),\n    ],\n)\n@pytest.mark.parametrize(\n    \"op_str\",\n    [\n        \"add\",\n        \"sub\",\n        \"mul\",\n        \"truediv\",\n    ],\n)\ndef test_numpy_broadcast_ops_types(z, arg, op_str) -> None:\n    op = \"__\" + op_str + \"__\"\n    if type(z) in [Dual, Dual2] and type(arg) in [Dual, Dual2] and type(arg) is not type(z):\n        pytest.skip(\"Cannot operate Dual and Dual2 together.\")\n    result = getattr(np.array([z, z]), op)(arg)\n    expected = np.array([getattr(z, op)(arg), getattr(z, op)(arg)])\n    assert np.all(result == expected)\n\n    result = getattr(arg, op)(np.array([z, z]))\n    if result is NotImplemented:\n        opr = \"__r\" + op_str + \"__\"\n        result = getattr(np.array([z, z]), opr)(arg)\n        expected = np.array([getattr(z, opr)(arg), getattr(z, opr)(arg)])\n    else:\n        expected = np.array([getattr(arg, op)(z), getattr(arg, op)(z)])\n    assert np.all(result == expected)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        Dual2(3.0, [\"x\"], [1], [2]),\n    ],\n)\ndef test_numpy_broadcast_pow_types(z) -> None:\n    result = np.array([z, z]) ** 3\n    expected = np.array([z**3, z**3])\n    assert np.all(result == expected)\n\n    result = z ** np.array([3, 4])\n    expected = np.array([z**3, z**4])\n    assert np.all(result == expected)\n\n\ndef test_numpy_matmul(y_2, y_1) -> None:\n    a = np.array([y_2, y_1])\n    result = np.matmul(a[:, np.newaxis], a[np.newaxis, :])\n    expected = np.array([[y_2 * y_2, y_2 * y_1], [y_2 * y_1, y_1 * y_1]])\n    assert np.all(result == expected)\n\n\n@pytest.mark.skipif(\n    version.parse(np.__version__) >= version.parse(\"1.25.0\"),\n    reason=\"Object dtypes accepted by NumPy in 1.25.0+\",\n)\ndef test_numpy_einsum(y_2, y_1) -> None:\n    # einsum does not work with object dtypes\n    a = np.array([y_2, y_1])\n    with pytest.raises(TypeError):\n        _ = np.einsum(\"i,j\", a, a, optimize=True)\n\n\n@pytest.mark.skipif(\n    version.parse(np.__version__) < version.parse(\"1.25.0\"),\n    reason=\"Object dtypes not accepted by NumPy in <1.25.0\",\n)\ndef test_numpy_einsum_works(y_2, y_1) -> None:\n    a = np.array([y_2, y_1])\n    result = np.einsum(\"i,j\", a, a, optimize=True)\n    expected = np.array([[y_2 * y_2, y_2 * y_1], [y_2 * y_1, y_1 * y_1]])\n    assert np.all(result == expected)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        Dual2(3.0, [\"x\"], [1], [2]),\n    ],\n)\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        np.int8,\n        np.int16,\n        np.int32,\n        np.int64,\n        np.float16,\n        np.float32,\n        np.float64,\n        np.longdouble,\n    ],\n)\ndef test_numpy_dtypes(z, dtype) -> None:\n    np.array([1, 2], dtype=dtype) + z\n    z + np.array([1, 2], dtype=dtype)\n\n    z + dtype(2)\n    dtype(2) + z\n"
  },
  {
    "path": "python/tests/test_dualrs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport math\n\nimport numpy as np\nimport pytest\nfrom packaging import version\nfrom rateslib.dual import Dual, Dual2, dual_exp, dual_log, dual_solve, gradient\nfrom rateslib.rs import ADOrder\n\nDUAL_CORE_PY = False\n\n\n@pytest.fixture\ndef x_1():\n    return Dual(1, vars=[\"v0\", \"v1\"], dual=[1, 2])\n\n\n@pytest.fixture\ndef x_2():\n    return Dual(2, vars=[\"v0\", \"v2\"], dual=[0, 3])\n\n\ndef test_zero_init() -> None:\n    x = Dual(1, vars=[\"x\"], dual=[])\n    assert np.all(x.dual == np.ones(1))\n\n\ndef test_dual_repr(x_1) -> None:\n    result = x_1.__repr__()\n    assert result == \"<Dual: 1.000000, (v0, v1), [1.0, 2.0]>\"\n\n\ndef test_dual_repr_4vars() -> None:\n    x = Dual(1.23456789, [\"a\", \"b\", \"c\", \"d\"], [1.01, 2, 3.50001, 4])\n    result = x.__repr__()\n    assert result == \"<Dual: 1.234568, (a, b, c, ...), [1.0, 2.0, 3.5, ...]>\"\n\n\ndef test_dual_str(x_1) -> None:\n    result = x_1.__str__()\n    assert result == \"<Dual: 1.000000, (v0, v1), [1.0, 2.0]>\"\n\n\n@pytest.mark.skipif(DUAL_CORE_PY, reason=\"Gradient comparison cannot compare Py and Rs Duals.\")\n@pytest.mark.parametrize(\n    (\"vars_\", \"expected\"),\n    [\n        ([\"v1\"], 2.00),\n        ([\"v1\", \"v0\"], np.array([2.0, 1.0])),\n    ],\n)\ndef test_gradient_method(vars_, expected, x_1) -> None:\n    result = gradient(x_1, vars_)\n    assert np.all(result == expected)\n\n\ndef test_neg(x_1) -> None:\n    result = -x_1\n    expected = Dual(-1, vars=[\"v0\", \"v1\"], dual=[-1, -2])\n    assert result == expected\n\n\ndef test_eq_ne(x_1) -> None:\n    # non-matching types\n    assert Dual(0, [\"single_var\"], []) != 0\n    # floats\n    assert Dual(2, [], []) == 2.0\n    assert Dual(2, [], []) == 2.0\n    # equality\n    assert x_1 == Dual(1, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))\n    # non-matching elements\n    assert x_1 != Dual(2, vars=[\"v0\", \"v1\"], dual=np.array([1, 2]))\n    assert x_1 != Dual(1, vars=[\"v0\", \"v1\"], dual=np.array([2, 2]))\n    assert x_1 != Dual(1, vars=[\"v2\", \"v1\"], dual=np.array([1, 2]))\n\n\ndef test_lt() -> None:\n    assert Dual(1, [\"x\"], []) < Dual(2, [\"y\"], [])\n    assert Dual(1, [\"x\"], []) < 10\n    assert Dual(1, [\"x\"], []) > 0.5\n\n\ndef test_le() -> None:\n    assert Dual(1.0, [\"x\"], []) <= Dual(1.0, [\"y\"], [])\n    assert Dual(1, [\"x\"], []) <= 1.0\n    assert Dual(1.0, [\"x\"], []) >= 1.0\n\n\ndef test_gt() -> None:\n    assert Dual(3, [\"x\"], []) > Dual(2, [\"y\"], [])\n    assert Dual(1, [\"x\"], []) > 0.5\n    assert Dual(0.3, [\"x\"], []) < 0.5\n\n\ndef test_ge() -> None:\n    assert Dual(1.0, [\"x\"], []) >= Dual(1.0, [\"y\"], [])\n    assert Dual(1, [\"x\"], []) >= 1.0\n    assert Dual(1.0, [\"x\"], []) <= 1.0\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual(3, vars=[\"v0\", \"v1\", \"v2\"], dual=[1, 2, 3])),\n        (\"__sub__\", Dual(-1, vars=[\"v0\", \"v1\", \"v2\"], dual=[1, 2, -3])),\n        (\"__mul__\", Dual(2, vars=[\"v0\", \"v1\", \"v2\"], dual=[2, 4, 3])),\n        (\"__truediv__\", Dual(0.5, vars=[\"v0\", \"v1\", \"v2\"], dual=[0.5, 1, -0.75])),\n    ],\n)\ndef test_ops(x_1, x_2, op, expected) -> None:\n    result = getattr(x_1, op)(x_2)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    (\"op\", \"expected\"),\n    [\n        (\"__add__\", Dual(1 + 2.5, vars=[\"v0\", \"v1\"], dual=[1, 2])),\n        (\"__sub__\", Dual(1 - 2.5, vars=[\"v0\", \"v1\"], dual=[1, 2])),\n        (\"__mul__\", Dual(1 * 2.5, vars=[\"v0\", \"v1\"], dual=[2.5, 5.0])),\n        (\"__truediv__\", Dual(1 / 2.5, vars=[\"v0\", \"v1\"], dual=[1 / 2.5, 2 / 2.5])),\n    ],\n)\ndef test_left_op_with_float(x_1, op, expected) -> None:\n    result = getattr(x_1, op)(2.5)\n    assert result == expected\n\n\ndef test_right_op_with_float(x_1) -> None:\n    assert 2.5 + x_1 == Dual(1 + 2.5, vars=[\"v0\", \"v1\"], dual=[1, 2])\n    assert 2.5 - x_1 == Dual(2.5 - 1, vars=[\"v0\", \"v1\"], dual=[-1, -2])\n    assert 2.5 * x_1 == x_1 * 2.5\n    assert 2.5 / x_1 == (x_1 / 2.5) ** -1.0\n\n\ndef test_op_inversions(x_1, x_2) -> None:\n    assert (x_1 + x_2) - (x_2 + x_1) == 0\n    assert (x_1 / x_2) * (x_2 / x_1) == 1\n\n\ndef test_inverse(x_1) -> None:\n    assert x_1 * x_1**-1 == 1\n\n\ndef test_power_identity(x_1) -> None:\n    result = x_1**1\n    assert result == x_1\n\n\n@pytest.mark.parametrize(\n    (\"power\", \"expected\"),\n    [\n        (1, (2, 1)),\n        (2, (4, 4)),\n        (3, (8, 12)),\n        (4, (16, 32)),\n        (5, (32, 80)),\n        (6, (64, 192)),\n    ],\n)\ndef test_dual_power_1d(power, expected) -> None:\n    x = Dual(2, vars=[\"x\"], dual=[1])\n    f = x**power\n    assert f.real == expected[0]\n    assert f.dual[0] == expected[1]\n\n\ndef test_dual_truediv(x_1) -> None:\n    expected = Dual(1, [], [])\n    result = x_1 / x_1\n    assert result == expected\n\n\ndef test_combined_vars_sorted(x_1) -> None:\n    x = Dual(2, vars=[\"a\", \"v0\", \"z\"], dual=[])\n    result = x_1 * x\n    expected = [\"v0\", \"v1\", \"a\", \"z\"]\n    assert result.vars == expected\n    # x vars are stored first\n    result = x * x_1\n    expected = [\"a\", \"v0\", \"z\", \"v1\"]\n    assert result.vars == expected\n\n\ndef test_exp(x_1) -> None:\n    result = x_1.__exp__()\n    expected = Dual(math.e, [\"v0\", \"v1\"], [math.e, 2 * math.e])\n    assert result == expected\n\n\ndef test_log(x_1) -> None:\n    result = x_1.__log__()\n    expected = Dual(0.0, [\"v0\", \"v1\"], [1.0, 2.0])\n    assert result == expected\n\n\n# Test NumPy compat\n\n\ndef test_numpy_isclose(x_1) -> None:\n    # np.isclose not supported for non-numeric dtypes\n    a = np.array([x_1, x_1])\n    b = np.array([x_1, x_1])\n    with pytest.raises(TypeError):\n        assert np.isclose(a, b)\n\n\ndef test_numpy_equality(x_1) -> None:\n    # instead of isclose use == (which uses math.isclose elementwise) and then np.all\n    a = np.array([x_1, x_1])\n    b = np.array([x_1, x_1])\n    result = a == b\n    assert np.all(result)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        # Dual2(3.0, \"x\", np.array([1]), np.array([[2]])),\n    ],\n)\n@pytest.mark.parametrize(\n    \"arg\",\n    [\n        2.2,\n        Dual(3, [\"x\"], []),\n        # Dual2(3, \"x\", np.array([2]), np.array([[3]])),\n    ],\n)\n@pytest.mark.parametrize(\n    \"op_str\",\n    [\n        \"add\",\n        \"sub\",\n        \"mul\",\n        \"truediv\",\n    ],\n)\ndef test_numpy_broadcast_ops_types(z, arg, op_str) -> None:\n    op = \"__\" + op_str + \"__\"\n    types = [Dual]  # ,Dual2]\n    if type(z) in types and type(arg) in types and type(arg) is not type(z):\n        pytest.skip(\"Cannot operate Dual and Dual2 together.\")\n    result = getattr(np.array([z, z]), op)(arg)\n    expected = np.array([getattr(z, op)(arg), getattr(z, op)(arg)])\n    assert np.all(result == expected)\n\n    result = getattr(arg, op)(np.array([z, z]))\n    if result is NotImplemented:\n        opr = \"__r\" + op_str + \"__\"\n        result = getattr(np.array([z, z]), opr)(arg)\n        expected = np.array([getattr(z, opr)(arg), getattr(z, opr)(arg)])\n    else:\n        expected = np.array([getattr(arg, op)(z), getattr(arg, op)(z)])\n    assert np.all(result == expected)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        # Dual2(3.0, \"x\", np.array([1]), np.array([[2]])),\n    ],\n)\ndef test_numpy_broadcast_pow_types(z) -> None:\n    result = np.array([z, z]) ** 3\n    expected = np.array([z**3, z**3])\n    assert np.all(result == expected)\n\n    result = z ** np.array([3, 4])\n    expected = np.array([z**3, z**4])\n    assert np.all(result == expected)\n\n\ndef test_numpy_matmul(x_1) -> None:\n    x_2 = Dual(2.5, [\"x\", \"y\"], [3.0, -2.0])\n    a = np.array([x_1, x_2])\n    result = np.matmul(a[:, np.newaxis], a[np.newaxis, :])\n    expected = np.array([[x_1 * x_1, x_1 * x_2], [x_2 * x_1, x_2 * x_2]])\n    assert np.all(result == expected)\n\n\n@pytest.mark.skipif(\n    version.parse(np.__version__) < version.parse(\"1.25.0\"),\n    reason=\"Object dtypes not accepted by NumPy in <1.25.0\",\n)\ndef test_numpy_einsum_works(x_1) -> None:\n    x_2 = Dual(2.5, [\"x\", \"y\"], [3.0, -2.0])\n    a = np.array([x_1, x_2])\n    result = np.einsum(\"i,j\", a, a, optimize=True)\n    expected = np.array([[x_1 * x_1, x_1 * x_2], [x_2 * x_1, x_2 * x_2]])\n    assert np.all(result == expected)\n\n\n@pytest.mark.parametrize(\n    \"z\",\n    [\n        Dual(2.0, [\"y\"], []),\n        # Dual2(3.0, \"x\", np.array([1]), np.array([[2]])),\n    ],\n)\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        np.int8,\n        np.int16,\n        np.int32,\n        np.int64,\n        np.float16,\n        np.float32,\n        np.float64,\n        np.longdouble,\n    ],\n)\ndef test_numpy_dtypes(z, dtype) -> None:\n    np.array([1, 2], dtype=dtype) + z\n    z + np.array([1, 2], dtype=dtype)\n\n    z + dtype(2)\n    dtype(2) + z\n\n\ndef test_dual_solve() -> None:\n    a = np.array([[Dual(1.0, [], []), Dual(0.0, [], [])], [Dual(0.0, [], []), Dual(1.0, [], [])]])\n    b = np.array([Dual(2.0, [\"x\"], [1.0]), Dual(5.0, [\"x\", \"y\"], [1.0, 1.0])])\n    result = dual_solve(a, b[:, None], types=(Dual, Dual))[:, 0]\n    expected = np.array([Dual(2.0, [\"x\", \"y\"], [1.0, 0.0]), Dual(5.0, [\"x\", \"y\"], [1.0, 1.0])])\n    assert np.all(result == expected)\n\n\n@pytest.mark.parametrize(\n    \"obj\",\n    [\n        Dual(1.0, [\"x\", \"y\"], [1.0, 2.0]),\n        Dual2(2.0, [\"x\", \"y\"], [1.0, 2.0], [1.0, 2.0, 2.0, 3.0]),\n    ],\n)\ndef test_pickle(obj) -> None:\n    import pickle\n\n    pickled_obj = pickle.dumps(obj)\n    reloaded = pickle.loads(pickled_obj)\n    assert obj == reloaded\n\n\n@pytest.mark.parametrize(\"z\", [2.0, Dual(2.0, [\"z\"], [])])\n@pytest.mark.parametrize(\"p\", [2.0, Dual(2.0, [\"p\"], [])])\ndef test_dual_powers_finite_diff(z, p):\n    if isinstance(z, float) and isinstance(p, float):\n        return None  # float power not in scope\n\n    result = z**p\n\n    if isinstance(z, Dual):\n        # Finite diff test\n        z_diff = ((z + 0.00001) ** p - result) / 0.00001\n        assert abs(gradient(result, [\"z\"])[0] - z_diff) < 1e-4\n\n    if isinstance(p, Dual):\n        # Finite diff test\n        p_diff = (z ** (p + 0.00001) - result) / 0.00001\n        assert abs(gradient(result, [\"p\"])[0] - p_diff) < 1e-4\n\n\ndef test_dual_powers_operators() -> None:\n    z = Dual(2.3, [\"x\", \"y\", \"z\"], [1.0, 2.0, 3.0])\n    p = Dual(4.4, [\"x\", \"y\", \"p\"], [2.0, 3.0, 4.0])\n    result = z**p\n    expected = dual_exp(p * dual_log(z))\n    assert abs(result - expected) < 1e-12\n    assert np.all(\n        np.isclose(gradient(result, [\"x\", \"y\", \"z\", \"p\"]), gradient(expected, [\"x\", \"y\", \"z\", \"p\"]))\n    )\n\n\n@pytest.mark.parametrize(\"z\", [2.0, Dual2(2.0, [\"z\"], [], [])])\n@pytest.mark.parametrize(\"p\", [2.0, Dual2(2.0, [\"p\"], [], [])])\ndef test_dual2_powers_finite_diff_first_order(z, p):\n    if isinstance(z, float) and isinstance(p, float):\n        return None  # float power not in scope\n\n    result = z**p\n\n    if isinstance(z, Dual2):\n        # Finite diff test\n        z_diff = ((z + 0.00001) ** p - result) / 0.00001\n        assert abs(gradient(result, [\"z\"])[0] - z_diff) < 1e-4\n\n    if isinstance(p, Dual2):\n        # Finite diff test\n        p_diff = (z ** (p + 0.00001) - result) / 0.00001\n        assert abs(gradient(result, [\"p\"])[0] - p_diff) < 1e-4\n\n\n@pytest.mark.parametrize(\"z\", [2.0, Dual2(2.0, [\"z\"], [], [])])\n@pytest.mark.parametrize(\"p\", [2.0, Dual2(2.0, [\"p\"], [], [])])\ndef test_dual2_powers_finite_diff_second_order(z, p):\n    if isinstance(z, float) and isinstance(p, float):\n        return None  # float power not in scope\n\n    result = z**p\n\n    vars_ = (isinstance(z, Dual2), isinstance(p, Dual2))\n    if vars_[0]:\n        z_up = (z + 0.00001) ** p\n        z_dw = (z - 0.00001) ** p\n        diff = (z_up + z_dw - 2 * result) / 1e-10\n        assert abs(gradient(result, [\"z\"], order=2)[0][0] - diff) < 1e-4\n\n    if vars_[1]:\n        p_up = z ** (p + 0.00001)\n        p_dw = z ** (p - 0.00001)\n        diff = (p_up + p_dw - 2 * result) / 1e-10\n        assert abs(gradient(result, [\"p\"], order=2)[0][0] - diff) < 1e-4\n\n    if vars_[1] and vars_[0]:\n        upup = (z + 0.00001) ** (p + 0.00001)\n        dwdw = (z - 0.00001) ** (p - 0.00001)\n        updw = (z + 0.00001) ** (p - 0.00001)\n        dwup = (z - 0.00001) ** (p + 0.00001)\n        diff = (upup + dwdw - updw - dwup) / 4e-10\n        assert abs(gradient(result, [\"z\", \"p\"], order=2)[0, 1] - diff) < 1e-4\n\n\ndef test_dual2_powers_operators() -> None:\n    z = Dual2(2.3, [\"x\", \"y\", \"z\"], [1.0, 2.0, 3.0], [1, 2, 3, 4, 5, 6, 7, 8, 9])\n    p = Dual2(4.4, [\"x\", \"y\", \"p\"], [2.0, 3.0, 4.0], [2, 3, 4, 5, 2, 3, 4, 3, 4])\n    result = z**p\n    expected = dual_exp(p * dual_log(z))\n    assert abs(result - expected) < 1e-12\n    assert np.all(\n        np.isclose(gradient(result, [\"x\", \"y\", \"z\", \"p\"]), gradient(expected, [\"x\", \"y\", \"z\", \"p\"]))\n    )\n    assert np.all(\n        np.isclose(\n            gradient(result, [\"x\", \"y\", \"z\", \"p\"], order=2),\n            gradient(expected, [\"x\", \"y\", \"z\", \"p\"], order=2),\n        )\n    )\n"
  },
  {
    "path": "python/tests/test_enums.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom rateslib.enums import FloatFixingMethod\n\n\ndef test_method_param():\n    a1 = FloatFixingMethod.RFRPaymentDelay()\n    a2 = FloatFixingMethod.RFRPaymentDelayAverage()\n\n    for obj in [a1, a2]:\n        assert obj.method_param() == 0\n\n    b1 = FloatFixingMethod.IBOR(6)\n    b2 = FloatFixingMethod.RFRLookback(6)\n\n    for obj in [b1, b2]:\n        assert obj.method_param() == 6\n"
  },
  {
    "path": "python/tests/test_fixings.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport os\n\nimport pytest\nfrom pandas import Series\nfrom rateslib import dt, fixings\nfrom rateslib.curves import Curve\nfrom rateslib.data.fixings import (\n    FloatRateIndex,\n    FloatRateSeries,\n    FXFixing,\n    FXIndex,\n    IRSFixing,\n    RFRFixing,\n    _FXFixingMajor,\n    _UnitFixing,\n)\nfrom rateslib.enums.generics import NoInput\nfrom rateslib.enums.parameters import FloatFixingMethod, SwaptionSettlementMethod\nfrom rateslib.instruments import IRS\nfrom rateslib.scheduling import Adjuster, get_calendar\n\n\n@pytest.mark.parametrize(\"name\", [\"estr\", \"sonia\", \"sofr\", \"swestr\", \"nowa\"])\ndef test_fixings(name) -> None:\n    result = fixings[name]\n    assert isinstance(result[1], Series)\n\n\ndef test_calendar_matches_fixings_corra() -> None:\n    # this should run without warnings or errors if the \"tro\" calendar matches the fixings.\n    swap = IRS(\n        effective=dt(2017, 1, 1),\n        termination=dt(2023, 7, 1),\n        frequency=\"A\",\n        leg2_rate_fixings=fixings[\"corra\"][1],\n        calendar=\"tro\",\n        fixed_rate=1.0,\n    )\n    curve = Curve({dt(2017, 1, 1): 1.0, dt(2027, 1, 1): 1.0}, calendar=\"tro\")\n    swap.npv(curves=curve)\n\n\ndef test_fixings_raises_file_error() -> None:\n    with pytest.raises(ValueError, match=\"Fixing data for the index \"):\n        fixings[\"nofile\"]\n\n\ndef test_add_fixings_directly() -> None:\n    s = Series(\n        index=[dt(2000, 2, 1), dt(2000, 3, 1), dt(2000, 1, 1)],\n        data=[200.0, 300.0, 100.0],\n    )\n    fixings.add(\"my_values\", s)\n    assert fixings[\"my_values\"][1].is_monotonic_increasing\n    assert fixings[\"my_values\"][1].name == \"rate\"\n    assert fixings[\"my_values\"][1].index.name == \"reference_date\"\n    fixings.pop(\"my_values\")\n\n\ndef test_add_fixings_directly_with_specific_state() -> None:\n    s = Series(\n        index=[dt(2000, 2, 1), dt(2000, 3, 1), dt(2000, 1, 1)],\n        data=[200.0, 300.0, 100.0],\n    )\n    fixings.add(\"my_values\", s, 10103)\n    assert fixings[\"my_values\"][0] == 10103\n    fixings.pop(\"my_values\")\n\n\ndef test_get_stub_ibor_fixings() -> None:\n    s = Series(\n        index=[dt(2000, 2, 1), dt(2000, 3, 1), dt(2000, 1, 1)],\n        data=[200.0, 300.0, 100.0],\n    )\n    fixings.add(\"usd_IBOR_3w\", s)\n    fixings.add(\"usd_IBOR_1m\", s)\n    fixings.add(\"usd_IBOR_2m\", s)\n    fixings.add(\"USD_ibor_3M\", s)\n    s, _, _ = fixings.get_stub_ibor_fixings(\n        value_start_date=dt(2000, 1, 1),\n        value_end_date=dt(2000, 2, 15),\n        fixing_calendar=get_calendar(\"nyc\"),\n        fixing_modifier=Adjuster.Following(),\n        fixing_identifier=\"USD_IBOR\",\n        fixing_date=dt(1999, 12, 30),\n    )\n    fixings.pop(\"usd_IBOR_3w\")\n    fixings.pop(\"usd_IBOR_1m\")\n    fixings.pop(\"usd_IBOR_2m\")\n    fixings.pop(\"USD_ibor_3M\")\n    assert s == [\"1M\", \"2M\"]\n\n\n@pytest.mark.parametrize((\"fixing\"), [True, False])\ndef test_get_stub_ibor_fixings_no_left(fixing) -> None:\n    s = Series(\n        index=[dt(2000, 2, 1), dt(2000, 3, 1), dt(2000, 1, 1)],\n        data=[200.0, 300.0, 100.0],\n    )\n    if fixing:\n        s[dt(1999, 12, 30)] = 12345.0\n    fixings.add(\"usd_IBOR_2w\", s)\n    fixings.add(\"usd_IBOR_3w\", s)\n    s, _, f = fixings.get_stub_ibor_fixings(\n        value_start_date=dt(2000, 1, 1),\n        value_end_date=dt(2000, 1, 8),\n        fixing_calendar=get_calendar(\"nyc\"),\n        fixing_modifier=Adjuster.Following(),\n        fixing_identifier=\"USD_IBOR\",\n        fixing_date=dt(1999, 12, 30),\n    )\n    fixings.pop(\"usd_IBOR_2w\")\n    fixings.pop(\"usd_IBOR_3w\")\n    assert s == [\"2W\"]\n    assert f == [12345.0 if fixing else None]\n\n\n@pytest.mark.parametrize((\"fixing\"), [True, False])\ndef test_get_stub_ibor_fixings_no_right(fixing) -> None:\n    s = Series(\n        index=[dt(2000, 2, 1), dt(2000, 3, 1), dt(2000, 1, 1)],\n        data=[200.0, 300.0, 100.0],\n    )\n    if fixing:\n        s[dt(1999, 12, 30)] = 12345.0\n    fixings.add(\"usd_IBOR_2m\", s)\n    fixings.add(\"USD_ibor_3M\", s)\n    s, _, f = fixings.get_stub_ibor_fixings(\n        value_start_date=dt(2000, 1, 1),\n        value_end_date=dt(2000, 7, 8),\n        fixing_calendar=get_calendar(\"nyc\"),\n        fixing_modifier=Adjuster.Following(),\n        fixing_identifier=\"USD_IBOR\",\n        fixing_date=dt(1999, 12, 30),\n    )\n    fixings.pop(\"usd_IBOR_2m\")\n    fixings.pop(\"USD_ibor_3M\")\n    assert s == [\"3M\"]\n    assert f == [12345.0 if fixing else None]\n\n\ndef test_get_stub_ibor_fixings_no_left_no_right() -> None:\n    s, _, _ = fixings.get_stub_ibor_fixings(\n        value_start_date=dt(2000, 1, 1),\n        value_end_date=dt(2000, 7, 8),\n        fixing_calendar=get_calendar(\"nyc\"),\n        fixing_modifier=Adjuster.Following(),\n        fixing_identifier=\"USD_NONE\",\n        fixing_date=dt(1999, 12, 30),\n    )\n    assert s == []\n\n\ndef test_state_id():\n    s = Series(\n        index=[dt(2000, 2, 1), dt(2000, 3, 1), dt(2000, 1, 1)],\n        data=[200.0, 300.0, 100.0],\n    )\n    fixings.add(\"usd_IBOR_3w\", s)\n    before = fixings[\"usd_IBOR_3w\"][0]\n    fixings.pop(\"usd_IBOR_3w\")\n    fixings.add(\"usd_IBOR_3w\", s)\n    assert before != fixings[\"usd_IBOR_3w\"][0]\n\n\ndef test_series_combine():\n    from rateslib.periods.protocols.fixings import _s2_before_s1\n\n    s1 = Series(index=[2, 3], data=[100.0, 200.0])\n    s2 = Series(index=[1, 2], data=[300.0, 400.0])\n    result = s1.combine(s2, _s2_before_s1)\n    assert all(result == Series(index=[1, 2, 3], data=[300.0, 400.0, 200.0]))\n\n\ndef test_reset_doc():\n    fx_fixing1 = FXFixing(delivery=dt(2021, 1, 1), fx_index=\"eurusd\", identifier=\"A\")\n    fx_fixing2 = FXFixing(delivery=dt(2021, 1, 1), fx_index=\"gbpusd\", identifier=\"B\")\n    fixings.add(\"A_eurusd\", Series(index=[dt(2020, 12, 30)], data=[1.1]), state=100)\n    fixings.add(\"B_gbpusd\", Series(index=[dt(2020, 12, 30)], data=[1.4]), state=200)\n\n    # data is populated from the available Series\n    assert fx_fixing1.value == 1.1\n    assert fx_fixing2.value == 1.4\n\n    # fixings are reset according to the data state\n    fx_fixing1.reset(state=100)\n    fx_fixing2.reset(state=100)\n\n    # only the private data for fixing1 is removed because of its link to the data state\n    assert fx_fixing1._value == NoInput.blank\n    assert fx_fixing2._value == 1.4\n    fixings.pop(\"A_eurusd\")\n    fixings.pop(\"B_gbpusd\")\n\n\nclass TestRFRFixing:\n    def test_rfr_lockout(self) -> None:\n        name = str(hash(os.urandom(8))) + \"_1B\"\n        estr_1b = Series(\n            index=[dt(2025, 9, 12), dt(2025, 9, 15), dt(2025, 9, 16)], data=[1.91, 1.92, 1.93]\n        )\n        fixings.add(name, estr_1b)\n        rfr_fixing = RFRFixing(\n            accrual_start=dt(2025, 9, 12),\n            accrual_end=dt(2025, 9, 19),\n            identifier=name,\n            spread_compound_method=\"NoneSimple\",\n            fixing_method=FloatFixingMethod.RFRLockout(2),\n            float_spread=100.0,\n            rate_index=FloatRateIndex(frequency=\"1B\", series=\"eur_rfr\"),\n        )\n        result = rfr_fixing.value\n        assert abs(result - 2.9202637862854033) < 1e-10\n        assert len(rfr_fixing.populated) == 5\n\n\nclass TestFXFixing:\n    def test_direct(self) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n\n        fx_fixing = FXFixing(\n            publication=dt(2000, 1, 1),\n            fx_index=FXIndex(\"usdrub\", \"fed\", 2),\n            identifier=name,\n        )\n        assert fx_fixing.value == 2.0\n        fixings.pop(name + \"_USDRUB\")\n\n    def test_inverted(self) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n\n        fx_fixing = FXFixing(\n            publication=dt(2000, 1, 1),\n            fx_index=FXIndex(\"rubusd\", \"fed\", 2),\n            identifier=name,\n        )\n        assert fx_fixing.value == 0.5\n        fixings.pop(name + \"_USDRUB\")\n\n    def test_cross1(self) -> None:\n        name = str(hash(os.urandom(8)))\n\n        fixings.add(name + \"_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n        fixings.add(name + \"_USDINR\", Series(index=[dt(2000, 1, 1)], data=[4.0]))\n\n        fx_fixing = FXFixing(\n            publication=dt(2000, 1, 1),\n            fx_index=FXIndex(\"rubinr\", \"fed\", 2),\n            identifier=name,\n        )\n        assert fx_fixing.value == 1 / 2.0 * 4.0\n        fixings.pop(name + \"_USDRUB\")\n        fixings.pop(name + \"_USDINR\")\n\n    def test_cross2(self) -> None:\n        name = str(hash(os.urandom(8)))\n\n        fixings.add(name + \"_RUBUSD\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n        fixings.add(name + \"_INRUSD\", Series(index=[dt(2000, 1, 1)], data=[4.0]))\n\n        fx_fixing = FXFixing(\n            publication=dt(2000, 1, 1),\n            fx_index=FXIndex(\"rubinr\", \"fed\", 2),\n            identifier=name,\n        )\n        assert fx_fixing.value == 2.0 * 1 / 4.0\n        fixings.pop(name + \"_RUBUSD\")\n        fixings.pop(name + \"_INRUSD\")\n\n    def test_reset(self):\n        fx_fixing = FXFixing(\n            publication=dt(2000, 1, 1), fx_index=FXIndex(\"rubusd\", \"fed\", 1), identifier=\"test\"\n        )\n        fixings.add(\"test_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n        assert fx_fixing.value == 0.5\n        fx_fixing.reset(state=1)\n        assert fx_fixing._value == 0.5\n        fx_fixing.reset(state=fixings[\"TEST_USDRUB\"][0])\n        assert fx_fixing._value == NoInput(0)\n        fixings.pop(\"test_USDRUB\")\n\n    def test_no_state_update(self):\n        # test that the fixing value and state is updated at the appropriate times.\n        fx_fixing = FXFixing(\n            delivery=dt(2000, 1, 1),\n            fx_index=FXIndex(\"rubusd\", \"fed\", 1, \"all\", 0),\n            identifier=\"test\",\n        )\n        fixings.add(\"test_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n        assert fx_fixing.value == 0.5\n        old_state = fx_fixing._state\n        fixings.pop(\"test_USDRUB\")\n        fixings.add(\"test_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[5.0]))\n        # value and state are unchanged\n        assert fx_fixing.value == 0.5\n        assert fx_fixing._state == old_state\n        fx_fixing.reset()\n        # value are state are now set after reset\n        assert fx_fixing.value == 0.20\n        assert fx_fixing._state == hash(fixings[\"TEST_USDRUB\"][0])\n        fixings.pop(\"test_USDRUB\")\n\n    # test all cross constructions\n\n    def test_construct_1_major_usd(self):\n        fx_fixing = FXFixing(fx_index=\"usdeur\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing2, _UnitFixing)\n\n        fx_fixing = FXFixing(fx_index=\"eurusd\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing2, _UnitFixing)\n\n        fx_fixing = FXFixing(fx_index=FXIndex(\"usdbrl\", \"fed\", 2), publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing2, _UnitFixing)\n\n        fx_fixing = FXFixing(fx_index=FXIndex(\"brlusd\", \"fed\", 2), publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing2, _UnitFixing)\n\n    def test_construct_1_major_eur(self):\n        fx_fixing = FXFixing(fx_index=\"eursek\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing2, _UnitFixing)\n\n        fx_fixing = FXFixing(fx_index=\"sekeur\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing2, _UnitFixing)\n\n    def test_construct_2_major_eur(self):\n        fx_fixing = FXFixing(fx_index=\"usdsek\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert fx_fixing.fx_fixing1.pair == \"usdeur\"\n        assert isinstance(fx_fixing.fx_fixing2, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing3, _UnitFixing)\n\n        fx_fixing = FXFixing(fx_index=\"sekusd\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert fx_fixing.fx_fixing1.pair == \"sekeur\"\n        assert isinstance(fx_fixing.fx_fixing2, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing3, _UnitFixing)\n\n        fx_fixing = FXFixing(fx_index=\"seknok\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert fx_fixing.fx_fixing1.pair == \"sekeur\"\n        assert isinstance(fx_fixing.fx_fixing2, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing3, _UnitFixing)\n\n    def test_construct_2_major_usd(self):\n        fx_fixing = FXFixing(fx_index=\"eurgbp\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert fx_fixing.fx_fixing1.pair == \"eurusd\"\n        assert isinstance(fx_fixing.fx_fixing2, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing3, _UnitFixing)\n\n        fx_fixing = FXFixing(fx_index=\"gbpeur\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert fx_fixing.fx_fixing1.pair == \"gbpusd\"\n        assert isinstance(fx_fixing.fx_fixing2, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing3, _UnitFixing)\n\n        fx_fixing = FXFixing(fx_index=\"gbpcad\", publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert fx_fixing.fx_fixing1.pair == \"gbpusd\"\n        assert isinstance(fx_fixing.fx_fixing2, _FXFixingMajor)\n        assert isinstance(fx_fixing.fx_fixing3, _UnitFixing)\n\n    def test_construct_3_major(self):\n        fx_fixing = FXFixing(fx_index=FXIndex(\"nokcad\", \"tro\", 2), publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert fx_fixing.fx_fixing1.pair == \"nokeur\"\n        assert isinstance(fx_fixing.fx_fixing2, _FXFixingMajor)\n        assert fx_fixing.fx_fixing2.pair == \"eurusd\"\n        assert isinstance(fx_fixing.fx_fixing3, _FXFixingMajor)\n        assert fx_fixing.fx_fixing3.pair == \"usdcad\"\n\n        fx_fixing = FXFixing(fx_index=FXIndex(\"cadnok\", \"tro\", 2), publication=dt(2000, 1, 1))\n        assert isinstance(fx_fixing.fx_fixing1, _FXFixingMajor)\n        assert fx_fixing.fx_fixing1.pair == \"cadusd\"\n        assert isinstance(fx_fixing.fx_fixing2, _FXFixingMajor)\n        assert fx_fixing.fx_fixing2.pair == \"usdeur\"\n        assert isinstance(fx_fixing.fx_fixing3, _FXFixingMajor)\n        assert fx_fixing.fx_fixing3.pair == \"eurnok\"\n\n\nclass TestFXFixingMajor:\n    def test_direct(self) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n\n        fx_fixing = _FXFixingMajor(\n            publication=dt(2000, 1, 1),\n            fx_index=FXIndex(\"usdrub\", \"fed\", 2),\n            identifier=name,\n        )\n        assert fx_fixing.value == 2.0\n        fixings.pop(name + \"_USDRUB\")\n\n    def test_inverted(self) -> None:\n        name = str(hash(os.urandom(8)))\n        fixings.add(name + \"_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n\n        fx_fixing = _FXFixingMajor(\n            publication=dt(2000, 1, 1),\n            fx_index=FXIndex(\"rubusd\", \"fed\", 2),\n            identifier=name,\n        )\n        assert fx_fixing.value == 0.5\n        fixings.pop(name + \"_USDRUB\")\n\n    def test_cross1(self) -> None:\n        name = str(hash(os.urandom(8)))\n\n        fixings.add(name + \"_INRRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n\n        fx_fixing = _FXFixingMajor(\n            publication=dt(2000, 1, 1),\n            fx_index=FXIndex(\"rubinr\", \"fed\", 2),\n            identifier=name,\n        )\n        assert fx_fixing.value == 1 / 2.0\n        fixings.pop(name + \"_INRRUB\")\n\n    def test_reset(self):\n        fx_fixing = _FXFixingMajor(\n            publication=dt(2000, 1, 1), fx_index=FXIndex(\"rubusd\", \"fed\", 1), identifier=\"test\"\n        )\n        fixings.add(\"test_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n        assert fx_fixing.value == 0.5\n        fx_fixing.reset(state=1)\n        assert fx_fixing._value == 0.5\n        fx_fixing.reset(state=fixings[\"TEST_USDRUB\"][0])\n        assert fx_fixing._value == NoInput(0)\n        fixings.pop(\"test_USDRUB\")\n\n    def test_no_state_update(self):\n        # test that the fixing value and state is updated at the appropriate times.\n        fx_fixing = _FXFixingMajor(\n            delivery=dt(2000, 1, 1),\n            fx_index=FXIndex(\"rubusd\", \"fed\", 1, \"all\", 0),\n            identifier=\"test\",\n        )\n        fixings.add(\"test_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[2.0]))\n        assert fx_fixing.value == 0.5\n        old_state = fx_fixing._state\n        fixings.pop(\"test_USDRUB\")\n        fixings.add(\"test_USDRUB\", Series(index=[dt(2000, 1, 1)], data=[5.0]))\n        # value and state are unchanged\n        assert fx_fixing.value == 0.5\n        assert fx_fixing._state == old_state\n        fx_fixing.reset()\n        # value are state are now set after reset\n        assert fx_fixing.value == 0.20\n        assert fx_fixing._state == fixings[\"TEST_USDRUB\"][0]\n        fixings.pop(\"test_USDRUB\")\n\n\nclass TestIRSFixing:\n    @pytest.mark.parametrize(\n        (\"method\", \"expected\"),\n        [\n            (SwaptionSettlementMethod.Physical, 192.8729663786536),\n            (SwaptionSettlementMethod.CashParTenor, 189.90825721068495),\n            (SwaptionSettlementMethod.CashCollateralized, 192.8729663786536),\n        ],\n    )\n    def test_annuity(self, method, expected) -> None:\n\n        fixing = IRSFixing(\n            irs_series=\"usd_irs\",\n            publication=dt(2026, 2, 18),\n            tenor=\"2Y\",\n        )\n        curve = Curve(nodes={dt(2026, 2, 18): 1.0, dt(2029, 2, 18): 0.9})\n        result = fixing.annuity(\n            settlement_method=method,\n            rate_curve=curve,\n            index_curve=curve,\n        )\n        assert abs(result - expected) < 1e-6\n"
  },
  {
    "path": "python/tests/test_fx.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\nfrom random import choice, shuffle\n\nimport numpy as np\nimport pytest\nfrom matplotlib import pyplot as plt\nfrom pandas import DataFrame, Series\nfrom pandas.testing import assert_frame_equal, assert_series_equal\nfrom rateslib.curves import CompositeCurve, Curve, LineCurve, MultiCsaCurve\nfrom rateslib.data.fixings import FXIndex\nfrom rateslib.default import NoInput\nfrom rateslib.dual import Dual, Dual2, gradient\nfrom rateslib.fx import (\n    FXForwards,\n    FXRates,\n    forward_fx,\n)\nfrom rateslib.fx.fx_forwards import _recursive_pair_population\nfrom rateslib.serialization import from_json\n\n\nclass TestStateAndCache:\n    def test_method_state_chg(self):\n        fxr = FXRates({\"eurusd\": 1.0, \"usdgbp\": 1.0})\n        original = fxr._state\n\n        fxr.update({\"eurusd\": 2.0})\n        new = fxr._state\n        assert new != original\n\n    def test_method_does_not_chg_state(self):\n        fxr = FXRates({\"eurusd\": 1.0, \"usdgbp\": 1.0})\n        original = fxr._state\n\n        fxr._set_ad_order(2)\n        new = fxr._state\n        assert new == original\n\n    def test_cached_property_fxarray(self):\n        fxr = FXRates({\"eurusd\": 1.0, \"usdgbp\": 1.0})\n        original = fxr.rate(\"eurgbp\")\n        fxr.update({\"eurusd\": 2.0})  # clear the FXarray cached property\n        new = fxr.rate(\"eurgbp\")\n        assert new != original\n\n    @pytest.mark.parametrize(\n        (\"meth\", \"args\"), [(\"update\", ([{\"eurusd\": 1.0}],)), (\"_set_ad_order\", (2,))]\n    )\n    def test_fxforwards_cache_clearing(self, meth, args):\n        fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fx_rates=[fxr1],  # FXRates as list\n            fx_curves={\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            },\n        )\n        fxf._cache[(dt(2000, 1, 1), \"eurusd\")] = 100.0\n        getattr(fxf, meth)(*args)\n        assert fxf._cache == {}\n\n\n@pytest.mark.parametrize(\n    \"fx_rates\",\n    [\n        {\"eurusd\": 1.0, \"seknok\": 1.0},\n        {\"eurusd\": 1.0, \"usdeur\": 1.0, \"usdgbp\": 1.0},\n        {\"eurusd\": 1.0, \"usdeur\": 1.0, \"seknok\": 1.0},\n    ],\n)\ndef test_ill_constrained(fx_rates) -> None:\n    with pytest.raises(ValueError, match=\"FX Array cannot be solved.\"):\n        FXRates(fx_rates)\n\n\ndef test_avoid_recursion_error() -> None:\n    pairs = [\"jpymxp\", \"usdnok\", \"usdgbp\", \"audmxp\", \"gbpsek\", \"eurnok\", \"eursek\"]\n    with pytest.raises(ValueError, match=\"FX Array cannot be solved. There are degenerate\"):\n        FXRates(dict.fromkeys(pairs, 1.2))\n\n\ndef test_rates() -> None:\n    fxr = FXRates({\"usdeur\": 2.0, \"usdgbp\": 2.5})\n    assert fxr.currencies == {\"usd\": 0, \"eur\": 1, \"gbp\": 2}\n    assert fxr.currencies_list == [\"usd\", \"eur\", \"gbp\"]\n    assert fxr.pairs == [\"usdeur\", \"usdgbp\"]\n    assert fxr.q == 3\n    assert fxr.fx_array[1, 2].real == 1.25\n    assert fxr.fx_array[1, 2] == Dual(1.25, [\"fx_usdeur\", \"fx_usdgbp\"], [-0.625, 0.50])\n    assert fxr.rate(\"eurgbp\") == Dual(1.25, [\"fx_usdeur\", \"fx_usdgbp\"], [-0.625, 0.50])\n    assert fxr.rate(FXIndex(\"eurgbp\", \"tgt\", 2)) == Dual(\n        1.25, [\"fx_usdeur\", \"fx_usdgbp\"], [-0.625, 0.50]\n    )\n\n\ndef test_fxrates_multi_single_currency() -> None:\n    fxr = FXRates({\"eurusd\": 0.5, \"usdgbp\": 1.25, \"usdjpy\": 100.0, \"usdnok\": 10.0, \"usdbrl\": 50.0})\n    fxr._set_ad_order(0)\n    expected = np.array(\n        [\n            [1.0, 2.0, 1.25, 100.0, 10.0, 50.0],\n            [0.5, 1.0, 0.625, 50.0, 5.0, 25.0],\n            [0.8, 1.6, 1.0, 80.0, 8.0, 40.0],\n            [0.01, 0.02, 0.0125, 1.0, 0.1, 0.5],\n            [0.1, 0.2, 0.125, 10.0, 1.0, 5.0],\n            [0.02, 0.04, 0.025, 2.0, 0.2, 1.0],\n        ]\n    )\n    for i in range(6):\n        for j in range(6):\n            assert abs(fxr.fx_array[i, j] - expected[i, j]) < 1e-8\n\n\ndef test_fxrates_multi_chain() -> None:\n    fxr = FXRates({\"eurusd\": 0.5, \"usdgbp\": 1.25, \"gbpjpy\": 100.0, \"nokjpy\": 10.0, \"nokbrl\": 5.0})\n    fxr._set_ad_order(0)\n    expected = np.array(\n        [\n            [1.0, 2.0, 1.25, 125.0, 12.5, 62.5],\n            [0.5, 1.0, 0.625, 62.5, 6.25, 31.25],\n            [0.8, 1.6, 1.0, 100.0, 10.0, 50.0],\n            [0.008, 0.016, 0.01, 1.0, 0.1, 0.5],\n            [0.08, 0.16, 0.10, 10.0, 1.0, 5.0],\n            [0.016, 0.032, 0.02, 2.0, 0.2, 1.0],\n        ]\n    )\n    for i in range(6):\n        for j in range(6):\n            assert abs(fxr.fx_array[i, j] - expected[i, j]) < 1e-8\n\n\ndef test_fxrates_pickle():\n    fxr = FXRates({\"usdeur\": 2.0, \"usdgbp\": 2.5}, settlement=dt(2002, 1, 1))\n    import pickle\n\n    pickled = pickle.dumps(fxr)\n    result = pickle.loads(pickled)\n    assert result == fxr\n\n\ndef test_rates_repr():\n    fxr = FXRates({\"usdeur\": 2.0, \"usdgbp\": 2.5})\n    result = fxr.__repr__()\n    expected = f\"<rl.FXRates:[usd,eur,gbp] at {hex(id(fxr))}>\"\n    assert result == expected\n\n    fxr = FXRates({\"usdeur\": 2.0, \"usdgbp\": 2.5, \"audcad\": 2.6, \"usdaud\": 1.2, \"cadjpy\": 100})\n    result = fxr.__repr__()\n    expected = f\"<rl.FXRates:[usd,eur,+4 others] at {hex(id(fxr))}>\"\n    assert result == expected\n\n\ndef test_fx_update_blank() -> None:\n    fxr = FXRates({\"usdeur\": 2.0, \"usdgbp\": 2.5})\n    result = fxr.update()\n    assert result is None\n\n\ndef test_convert_and_base() -> None:\n    fxr = FXRates({\"usdnok\": 8.0})\n    expected = Dual(125000, [\"fx_usdnok\"], [-15625])\n    result = fxr.convert(1e6, \"nok\", \"usd\")\n    result2 = fxr.convert_positions([0, 1e6], \"usd\")\n    assert result == expected\n    assert result2 == expected\n    result3 = fxr.positions(expected, \"usd\")\n    assert np.all(result3 == np.array([0, 1e6]))\n\n\ndef test_convert_none() -> None:\n    fxr = FXRates({\"usdnok\": 8.0})\n    assert fxr.convert(1, \"usd\", \"gbp\") is None\n\n\ndef test_convert_warn() -> None:\n    fxr = FXRates({\"usdnok\": 8.0})\n    with pytest.warns(UserWarning):\n        fxr.convert(1, \"usd\", \"gbp\", on_error=\"warn\")\n\n\ndef test_convert_error() -> None:\n    fxr = FXRates({\"usdnok\": 8.0})\n    with pytest.raises(ValueError):\n        fxr.convert(1, \"usd\", \"gbp\", on_error=\"raise\")\n\n\ndef test_positions_value() -> None:\n    fxr = FXRates({\"usdnok\": 8.0})\n    result = fxr.positions(80, \"nok\")\n    assert all(result == np.array([0, 80.0]))\n\n\ndef test_fxrates_set_order() -> None:\n    fxr = FXRates({\"usdnok\": 8.0})\n    fxr._set_ad_order(order=2)\n    expected = np.array(\n        [Dual2(1.0, [\"fx_usdnok\"], [0.0], []), Dual2(8.0, [\"fx_usdnok\"], [1.0], [])],\n    )\n    assert all(fxr.fx_vector == expected)\n\n\ndef test_update_raises() -> None:\n    fxr = FXRates({\"usdnok\": 8.0})\n    with pytest.raises(\n        ValueError,\n        match=\"The given `fx_rates` pairs are not contained in the `FXRates` object.\",\n    ):\n        fxr.update({\"usdnok\": 9.0, \"gbpnok\": 10.0})\n\n\ndef test_restate() -> None:\n    fxr = FXRates({\"usdnok\": 8.0, \"gbpnok\": 10})\n    fxr2 = fxr.restate([\"gbpusd\", \"usdnok\"])\n    assert fxr2.pairs == [\"gbpusd\", \"usdnok\"]\n    assert fxr2.rate(\"gbpusd\") == Dual(1.25, [\"fx_gbpusd\"], [1.0])\n    assert fxr2.rate(\"usdnok\") == Dual(8.0, [\"fx_usdnok\"], [1.0])\n\n\ndef test_restate_return_self() -> None:\n    # test a new object is always returned even if nothing is restated\n    fxr = FXRates({\"usdnok\": 8.0, \"gbpnok\": 10})\n    assert id(fxr) != id(fxr.restate([\"gbpnok\", \"usdnok\"], True))\n\n\ndef test_rates_table() -> None:\n    fxr = FXRates({\"EURNOK\": 10.0})\n    result = fxr.rates_table()\n    expected = DataFrame([[1.0, 10.0], [0.1, 1.0]], index=[\"eur\", \"nok\"], columns=[\"eur\", \"nok\"])\n    assert_frame_equal(result, expected)\n\n\ndef test_fxrates_to_json() -> None:\n    fxr = FXRates({\"usdnok\": 8.0, \"eurusd\": 1.05})\n    result = fxr.to_json()\n    expected = (\n        '{\"PyWrapped\":{\"FXRates\":{\"fx_rates\":['\n        '{\"pair\":[{\"name\":\"usd\"},{\"name\":\"nok\"}],\"rate\":{\"F64\":8.0},\"settlement\":null},'\n        '{\"pair\":[{\"name\":\"eur\"},{\"name\":\"usd\"}],\"rate\":{\"F64\":1.05},\"settlement\":null}'\n        '],\"currencies\":[{\"name\":\"usd\"},{\"name\":\"nok\"},{\"name\":\"eur\"}]}}}'\n    )\n    assert result == expected\n\n    fxr = FXRates({\"usdnok\": 8.0, \"eurusd\": 1.05}, dt(2022, 1, 3))\n    result = fxr.to_json()\n    expected = (\n        '{\"PyWrapped\":{\"FXRates\":{\"fx_rates\":['\n        '{\"pair\":[{\"name\":\"usd\"},{\"name\":\"nok\"}],\"rate\":{\"F64\":8.0},\"settlement\":\"2022-01-03T00:00:00\"},'\n        '{\"pair\":[{\"name\":\"eur\"},{\"name\":\"usd\"}],\"rate\":{\"F64\":1.05},\"settlement\":\"2022-01-03T00:00:00\"}'\n        '],\"currencies\":[{\"name\":\"usd\"},{\"name\":\"nok\"},{\"name\":\"eur\"}]}}}'\n    )\n    assert result == expected\n\n\ndef test_from_json_and_equality() -> None:\n    fxr1 = FXRates({\"usdnok\": 8.0, \"eurusd\": 1.05})\n    fxr2 = FXRates({\"usdnok\": 2.0, \"eurusd\": 4.0})\n    assert fxr1 != fxr2\n\n    fxr3 = from_json(\n        '{\"PyWrapped\":{\"FXRates\":{\"fx_rates\":[{\"pair\":[{\"name\":\"usd\"},{\"name\":\"nok\"}],\"rate\":{\"F64\":2.0},\"settlement\":null},{\"pair\":[{\"name\":\"eur\"},{\"name\":\"usd\"}],\"rate\":{\"F64\":4.0},\"settlement\":null}],\"currencies\":[{\"name\":\"usd\"},{\"name\":\"nok\"},{\"name\":\"eur\"}],\"fx_array\":{\"Dual\":{\"v\":1,\"dim\":[3,3],\"data\":[{\"real\":1.0,\"vars\":[],\"dual\":{\"v\":1,\"dim\":[0],\"data\":[]}},{\"real\":2.0,\"vars\":[\"fx_usdnok\"],\"dual\":{\"v\":1,\"dim\":[1],\"data\":[1.0]}},{\"real\":0.25,\"vars\":[\"fx_eurusd\"],\"dual\":{\"v\":1,\"dim\":[1],\"data\":[-0.0625]}},{\"real\":0.5,\"vars\":[\"fx_usdnok\"],\"dual\":{\"v\":1,\"dim\":[1],\"data\":[-0.25]}},{\"real\":1.0,\"vars\":[],\"dual\":{\"v\":1,\"dim\":[0],\"data\":[]}},{\"real\":0.125,\"vars\":[\"fx_usdnok\",\"fx_eurusd\"],\"dual\":{\"v\":1,\"dim\":[2],\"data\":[-0.0625,-0.03125]}},{\"real\":4.0,\"vars\":[\"fx_eurusd\"],\"dual\":{\"v\":1,\"dim\":[1],\"data\":[1.0]}},{\"real\":8.0,\"vars\":[\"fx_usdnok\",\"fx_eurusd\"],\"dual\":{\"v\":1,\"dim\":[2],\"data\":[4.0,2.0]}},{\"real\":1.0,\"vars\":[],\"dual\":{\"v\":1,\"dim\":[0],\"data\":[]}}]}}}}}',\n    )\n    assert fxr2 == fxr3\n\n    fxr4 = FXRates({\"usdnok\": 2.0, \"eurusd\": 4.0}, base=\"NOK\")\n    assert fxr3 != fxr4  # base is different\n\n\ndef test_copy() -> None:\n    fxr1 = FXRates({\"usdnok\": 8.0, \"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxr2 = fxr1.__copy__()\n    assert fxr1 == fxr2\n    assert id(fxr1) != id(fxr2)\n\n\ndef test_set_ad_order() -> None:\n    fxr = FXRates({\"usdnok\": 10.0})\n    fxr._set_ad_order(1)\n\n    fxr._set_ad_order(2)\n    assert fxr._ad == 2\n    assert type(fxr.fx_vector[0]) is Dual2\n    assert type(fxr.fx_vector[1]) is Dual2\n\n    fxr._set_ad_order(0)\n    assert fxr._ad == 0\n    assert fxr.fx_vector[0] == 1.0\n    assert fxr.fx_vector[1] == 10.0\n\n    with pytest.raises(ValueError, match=\"Order for AD can only be in {0,1,2}\"):\n        fxr._set_ad_order(\"bad arg\")\n\n\ndef test_set_ad_order_second_order_gradients() -> None:\n    # test ensures that FX Array is consecutively constructed passing correct 2nd order gradients.\n    # Versions <1.3.0 failed to correctly handle this becuase they simply upcast FX rates vector.\n    fxr = FXRates({\"usdnok\": 10.0, \"eurnok\": 8.0})\n\n    un = Dual2(10, [\"fx_usdnok\"], [], [])\n    en = Dual2(8.0, [\"fx_eurnok\"], [], [])\n    expected = un / en\n    row, col = fxr.currencies[\"usd\"], fxr.currencies[\"eur\"]\n\n    fxr._set_ad_order(2)\n    assert fxr._ad == 2\n    assert type(fxr.fx_vector[0]) is Dual2\n    assert type(fxr.fx_vector[1]) is Dual2\n    assert np.all(\n        np.isclose(\n            gradient(fxr.fx_array[row, col], [\"fx_usdnok\", \"fx_eurnok\"]),\n            gradient(expected, [\"fx_usdnok\", \"fx_eurnok\"]),\n        ),\n    )\n    assert np.all(\n        np.isclose(\n            gradient(fxr.fx_array[row, col], [\"fx_usdnok\", \"fx_eurnok\"], order=2),\n            gradient(expected, [\"fx_usdnok\", \"fx_eurnok\"], order=2),\n        ),\n    )\n\n\n@pytest.fixture\ndef usdusd():\n    nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.99}\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef eureur():\n    nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.997}\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef usdeur():\n    nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.996}\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef cadcad():\n    nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.987}\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\n@pytest.fixture\ndef cadcol():\n    nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.984}\n    return Curve(nodes=nodes, interpolation=\"log_linear\")\n\n\ndef test_fxforwards_repr(usdusd, eureur, usdeur) -> None:\n    fxf = FXForwards(\n        FXRates({\"usdeur\": 2.0}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    result = fxf.__repr__()\n    expected = f\"<rl.FXForwards:[usd,eur] at {hex(id(fxf))}>\"\n    assert result == expected\n\n    fxf = FXForwards(\n        FXRates(\n            {\n                \"usdeur\": 2.0,\n                \"usdgbp\": 3.0,\n                \"usdaud\": 4.0,\n                \"usdnok\": 5.0,\n                \"usdsek\": 6.0,\n            },\n            settlement=dt(2022, 1, 3),\n        ),\n        {\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"gbpgbp\": usdusd,\n            \"audaud\": eureur,\n            \"noknok\": usdusd,\n            \"seksek\": eureur,\n            \"usdeur\": usdeur,\n            \"usdaud\": usdusd,\n            \"eurnok\": eureur,\n            \"eursek\": usdeur,\n            \"eurgbp\": usdusd,\n        },\n    )\n    result = fxf.__repr__()\n    expected = f\"<rl.FXForwards:[usd,eur,+4 others] at {hex(id(fxf))}>\"\n    assert result == expected\n\n\ndef test_fxforwards_rates_unequal(usdusd, eureur, usdeur) -> None:\n    fxf = FXForwards(\n        FXRates({\"usdeur\": 2.0}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    fxr = FXRates({\"usdeur\": 2.0}, settlement=dt(2022, 1, 3))\n    assert fxf != fxr\n    assert fxr != fxf\n\n    fxf_other = FXForwards(\n        FXRates({\"usdeur\": 3.0}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    assert fxf != fxf_other\n\n    fxf2 = fxf.copy()\n    assert fxf2 == fxf\n    fxf2.base = \"eur\"\n    assert fxf2 != fxf\n\n\ndef test_fxforwards_without_settlement_raise() -> None:\n    fxr = FXRates({\"usdeur\": 1.0})\n    crv = Curve({dt(2022, 1, 1): 1.0})\n    with pytest.raises(ValueError, match=\"`fx_rates` as FXRates supplied to FXForwards must cont\"):\n        FXForwards(fx_rates=fxr, fx_curves={\"usdusd\": crv, \"usdeur\": crv, \"eureur\": crv})\n\n\ndef test_fxforwards_set_order(usdusd, eureur, usdeur) -> None:\n    fxf = FXForwards(\n        FXRates({\"usdeur\": 2.0}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    fxf._set_ad_order(order=2)\n    expected = np.array(\n        [Dual2(1.0, [\"fx_usdeur\"], [0.0], []), Dual2(2.0, [\"fx_usdeur\"], [1.0], [])],\n    )\n    assert all(fxf.fx_rates.fx_vector == expected)\n    assert usdusd.ad == 2\n    assert eureur.ad == 2\n    assert usdeur.ad == 2\n\n\ndef test_fxforwards_set_order_list(usdusd, eureur, usdeur) -> None:\n    fxf = FXForwards(\n        [\n            FXRates({\"usdeur\": 2.0}, settlement=dt(2022, 1, 3)),\n            FXRates({\"usdgbp\": 3.0}, settlement=dt(2022, 1, 4)),\n        ],\n        {\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"usdeur\": usdeur,\n            \"usdgbp\": usdeur.copy(),\n            \"gbpgbp\": eureur.copy(),\n        },\n    )\n    fxf._set_ad_order(order=2)\n    # expected = np.array(\n    #     [\n    #         Dual2(1.0, \"fx_usdeur\", [0.0]),\n    #         Dual2(2.0, \"fx_usdeur\", [1.0]),\n    #     ]\n    # )\n    assert type(fxf.fx_rates_immediate.fx_vector[0]) is Dual2\n    assert usdusd.ad == 2\n    assert eureur.ad == 2\n    assert usdeur.ad == 2\n    assert fxf.curve(\"usd\", \"gbp\").ad == 2\n\n\ndef test_fxforwards_and_swap(usdusd, eureur, usdeur) -> None:\n    fxf = FXForwards(\n        FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    result = fxf.rate(FXIndex(\"usdeur\", \"tgt\", 2), dt(2022, 3, 25))\n    expected = Dual(0.8991875219289739, [\"fx_usdeur\"], [0.99909725])\n    assert abs(result - expected) < 1e-10\n    assert np.isclose(result.dual, expected.dual)\n\n    # test fx_swap price\n    result = fxf.swap(\"usdeur\", [dt(2022, 1, 3), dt(2022, 3, 25)])\n    expected = (expected - fxf.rate(\"usdeur\", dt(2022, 1, 3))) * 10000\n    assert abs(result - expected) < 1e-10\n    assert np.isclose(result.dual, expected.dual)\n\n    result2 = fxf.swap(FXIndex(\"usdeur\", \"fed\", 2), [dt(2022, 1, 3), dt(2022, 3, 25)])\n    assert abs(result2 - result) < 1e-12\n\n    result = fxf.rate(\"eurusd\", dt(2022, 3, 25))\n    expected = Dual(1.1121150767915007, [\"fx_usdeur\"], [-1.23568342])\n    assert abs(result - expected) < 1e-10\n    assert np.isclose(result.dual, expected.dual)\n\n\ndef test_fxforwards2() -> None:\n    fx_rates = FXRates({\"usdeur\": 0.9, \"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fx_curves = {\n        \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.96}),\n        \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}),\n        \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.991}),\n        \"noknok\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}),\n        \"nokeur\": Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.978}),\n    }\n    fxf = FXForwards(fx_rates, fx_curves)\n\n    # First check the Immediate rates are correct:\n    d = dt(2022, 1, 3)\n    v, w = fxf.curve(\"usd\", \"usd\"), fxf.curve(\"eur\", \"usd\")\n    F_usdeur_exp = Dual(0.9, [\"fx_usdeur\"], []) * w[d] / v[d]\n    F_usdeur_res = fxf.rate(\"usdeur\", dt(2022, 1, 1))\n    assert abs(F_usdeur_exp - F_usdeur_res) < 1e-14\n\n    # And the other\n    v2, w2 = fxf.curve(\"eur\", \"eur\"), fxf.curve(\"nok\", \"eur\")\n    F_eurnok_exp = Dual(8.888889, [\"fx_eurnok\"], []) * w2[d] / v2[d]\n    F_eurnok_res = fxf.rate(\"eurnok\", dt(2022, 1, 1))\n    assert abs(F_eurnok_exp - F_eurnok_res) < 1e-14\n\n    # Now we will look to evaluate a cross forward rate\n    d = dt(2022, 8, 16)\n    f_usdnok_res = fxf.rate(\"usdnok\", dt(2022, 8, 16))\n    f_usdnok_exp = F_usdeur_exp * F_eurnok_exp * v[d] * v2[d] / (w[d] * w2[d])\n\n    # expected = Dual(7.9039924628096845, [\"fx_eurnok\", \"fx_usdeur\"], [0.88919914, 8.78221385])\n    assert abs(f_usdnok_res - f_usdnok_exp) < 1e-14\n    assert all(\n        np.isclose(\n            gradient(f_usdnok_res, [\"fx_eurnok\", \"fx_usdeur\"]),\n            gradient(f_usdnok_exp, [\"fx_eurnok\", \"fx_usdeur\"]),\n        ),\n    )\n\n\ndef test_fxforwards_immediate() -> None:\n    fx_rates = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n    fx_curves = {\n        \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 0.95}),\n        \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 1.0}),\n        \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 1.0}),\n    }\n    fxf = FXForwards(fx_rates, fx_curves)\n    F0_usdeur = 0.95 * 1.0 / 0.95  # f_usdeur * w_eurusd / v_usdusd\n    assert abs(fxf.fx_rates_immediate.fx_array[0, 1].real - F0_usdeur) < 1e-15\n    assert abs(fxf.rate(\"usdeur\").real - F0_usdeur) < 1e-15\n\n    result = fxf.rate(\"usdeur\", dt(2022, 1, 1))\n    expected = Dual(1, [\"fx_usdeur\"], [1 / 0.95])\n    assert abs(result - expected) < 1e-10\n    assert np.isclose(result.dual, expected.dual)\n\n    result = fxf.rate(\"usdeur\", dt(2022, 1, 3))\n    expected = Dual(0.95, [\"fx_usdeur\"], [1.0])\n    assert abs(result - expected) < 1e-10\n    assert np.isclose(result.dual, expected.dual)\n\n\ndef test_fxforwards_immediate2() -> None:\n    fx_rates = FXRates({\"usdeur\": 0.9, \"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fx_curves = {\n        \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 0.999}),\n        \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 0.998}),\n        \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 0.997}),\n        \"noknok\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 0.996}),\n        \"nokeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 0.995}),\n    }\n    fxf = FXForwards(fx_rates, fx_curves)\n    F0_usdeur = 0.9 * 0.997 / 0.999  # f_usdeur * v_eurusd / w_usdusd\n    F0_eurnok = 8.888889 * 0.995 / 0.998  # f_eurnok * w_nokeur / v_eureur\n    assert abs(fxf.fx_rates_immediate.fx_array[0, 1].real - F0_usdeur) < 1e-14\n    assert abs(fxf.fx_rates_immediate.fx_array[1, 2].real - F0_eurnok) < 1e-14\n\n\ndef test_fxforwards_bad_curves_raises(usdusd, eureur, usdeur) -> None:\n    bad_curve = Curve({dt(2000, 1, 1): 1.00, dt(2023, 1, 1): 0.99})\n    with pytest.raises(ValueError, match=\"`fx_curves` do not have the same initial\"):\n        FXForwards(\n            FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": bad_curve},\n        )\n\n    bad_curve = LineCurve({dt(2022, 1, 1): 1.00, dt(2023, 1, 1): 0.99})\n    with pytest.raises(TypeError, match=\"`fx_curves` must be DF based, not type Line\"):\n        FXForwards(\n            FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n            {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": bad_curve},\n        )\n\n    # SHOULD NOT NECESSARILY FAIL\n    # with pytest.raises(ValueError):\n    #     FXForwards(\n    #         FXRates({\"usdeur\": 0.9, \"eurgbp\": 0.9}, fx_settlement=dt(2022, 1, 3)),\n    #         {\"usdusd\": usdusd,\n    #          \"eureur\": eureur,\n    #          \"usdeur\": usdeur,\n    #          \"usdgbp\": usdeur,\n    #          \"gbpgbp\": eureur\n    #          }\n    #     )\n\n\ndef test_fxforwards_convert(usdusd, eureur, usdeur) -> None:\n    fxf = FXForwards(\n        FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    result = fxf.convert(\n        100,\n        domestic=\"usd\",\n        foreign=\"eur\",\n        settlement=dt(2022, 1, 15),\n        value_date=dt(2022, 1, 30),\n    )\n    expected = Dual(90.12374519723947, [\"fx_usdeur\"], [100.13749466359941])\n    assert abs(result - expected) < 1e-13\n    assert np.isclose(expected.dual, result.dual)\n\n    result = fxf.convert(\n        100,\n        domestic=\"usd\",\n        foreign=\"eur\",\n        settlement=NoInput(0),  # should imply immediate settlement\n        value_date=NoInput(0),  # should imply same as settlement\n    )\n    expected = Dual(90.00200704713323, [\"fx_usdeur\"], [100.00223005237025])\n    assert abs(result - expected) < 1e-13\n    assert np.isclose(expected.dual, result.dual)\n\n\ndef test_fxforwards_convert_not_in_ccys(usdusd, eureur, usdeur) -> None:\n    fxf = FXForwards(\n        FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    ccy = \"gbp\"\n    with pytest.raises(ValueError, match=f\"'{ccy}' not in FXForwards.currencies\"):\n        fxf.convert(\n            100,\n            domestic=ccy,\n            foreign=\"eur\",\n            settlement=dt(2022, 1, 15),\n            value_date=dt(2022, 1, 30),\n            on_error=\"raise\",\n        )\n\n    result = fxf.convert(\n        100,\n        domestic=ccy,\n        foreign=\"eur\",\n        settlement=dt(2022, 1, 15),\n        value_date=dt(2022, 1, 30),\n        on_error=\"ignore\",\n    )\n    assert result is None\n\n    with pytest.warns(UserWarning):\n        result = fxf.convert(\n            100,\n            domestic=ccy,\n            foreign=\"eur\",\n            settlement=dt(2022, 1, 15),\n            value_date=dt(2022, 1, 30),\n            on_error=\"warn\",\n        )\n        assert result is None\n\n\ndef test_fxforwards_position_not_dual(usdusd, eureur, usdeur) -> None:\n    fxf = FXForwards(\n        FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    result = fxf.positions(100)\n    expected = DataFrame(\n        {dt(2022, 1, 1): [100.0, 0.0], dt(2022, 1, 3): [0.0, 0.0]},\n        index=[\"usd\", \"eur\"],\n    )\n    assert_frame_equal(result, expected)\n\n    result = fxf.positions(100, aggregate=True)\n    expected = Series(\n        [100.0, 0.0],\n        index=[\"usd\", \"eur\"],\n        name=dt(2022, 1, 1),\n    )\n    assert_series_equal(result, expected)\n\n\ndef test_fx_curves_locals_raises():\n    with pytest.raises(ValueError, match=\"`fx_curves` must contain local cash-collateral curves\"):\n        FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.0, \"usdnok\": 1.0}, settlement=dt(2000, 1, 1)),\n            fx_curves={\n                \"eurusd\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99}),\n                \"usdnok\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99}),\n                \"usdeur\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99}),\n                \"eureur\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99}),\n                \"noknok\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99}),\n            },\n        )\n\n\ndef test_multiple_currencies_number_raises(usdusd) -> None:\n    fxr1 = FXRates({\"eurusd\": 0.95}, settlement=dt(2022, 1, 3))\n    fxr2 = FXRates({\"gbpcad\": 1.1}, settlement=dt(2022, 1, 2))\n    with pytest.raises(ValueError, match=\"`fx_curves` is underspecified.\"):\n        FXForwards([fxr1, fxr2], {})\n\n    with pytest.raises(ValueError, match=\"`fx_curves` is overspecified.\"):\n        FXForwards(\n            fxr1,\n            {\n                \"eureur\": usdusd,\n                \"usdusd\": usdusd,\n                \"usdeur\": usdusd,\n                \"eurusd\": usdusd,\n            },\n        )\n\n\ndef test_forwards_unexpected_curve_raise(usdusd) -> None:\n    fxr = FXRates({\"eurusd\": 0.95}, settlement=dt(2022, 1, 3))\n    with pytest.raises(ValueError, match=\"`fx_curves` contains an unexpected currency\"):\n        FXForwards(\n            fxr,\n            {\n                \"eureur\": usdusd,\n                \"usdusd\": usdusd,\n                \"usdeur\": usdusd,\n                \"usdcad\": usdusd,\n            },\n        )\n\n\ndef test_forwards_codependent_curve_raise(usdusd) -> None:\n    fxr = FXRates({\"eurusd\": 0.95, \"usdnok\": 10.0}, settlement=dt(2022, 1, 3))\n    with pytest.raises(ValueError, match=\"`fx_curves` contains co-dependent rates\"):\n        FXForwards(\n            fxr,\n            {\n                \"eureur\": usdusd,\n                \"usdusd\": usdusd,\n                \"usdeur\": usdusd,\n                \"eurusd\": usdusd,\n                \"noknok\": usdusd,\n            },\n        )\n\n\nclass TestFXForwardsBase:\n    # these tests will validate the base argument supplied to the FXForwards object\n    # in different framework type constructions\n\n    def test_single_system(self, usdusd, eureur):\n        # test that creating 2 currencies setting base as either yields the same FX rates.\n        fxr = FXRates({\"eurusd\": 200.0}, settlement=dt(2022, 1, 3))\n        fxf1 = FXForwards(fxr, {\"eureur\": eureur, \"eurusd\": eureur, \"usdusd\": usdusd}, base=\"usd\")\n        fxf2 = FXForwards(fxr, {\"eureur\": eureur, \"eurusd\": eureur, \"usdusd\": usdusd}, base=\"eur\")\n        res1 = fxf1.rate(\"eurusd\", dt(2022, 3, 1))\n        res2 = fxf2.rate(\"eurusd\", dt(2022, 3, 1))\n        assert res1 == res2\n\n    @pytest.mark.parametrize(\"base\", [\"usd\", \"eur\", \"cad\", NoInput(0)])\n    @pytest.mark.parametrize(\"idx\", [0, 1])\n    def test_multi_currency_system(self, base, idx, usdusd, eureur, cadcad, cadcol, usdeur):\n        ccys = [\"usd\", \"eur\", \"cad\"]\n        shuffle(ccys)\n        pairs = [f\"{ccys[0]}{ccys[1]}\", f\"{ccys[idx]}{ccys[2]}\"]\n        fxr = FXRates(dict(zip(pairs, [5.0, 15.0])), base=base, settlement=dt(2022, 1, 3))\n\n        shuffle(ccys)\n        curv_pairs = [f\"{ccys[0]}{ccys[1]}\", f\"{ccys[idx]}{ccys[2]}\"]\n        fxc = {\n            \"eureur\": eureur,\n            \"cadcad\": cadcad,\n            \"usdusd\": usdusd,\n            **dict(zip(curv_pairs, [cadcol, usdeur])),\n        }\n        fxf1 = FXForwards(fxr, fxc, base=\"usd\")\n        fxf2 = FXForwards(fxr, fxc, base=\"eur\")\n        fxf3 = FXForwards(fxr, fxc, base=\"cad\")\n        fxf4 = FXForwards(fxr, fxc, base=NoInput(0))\n\n        shuffle(ccys)\n        r1 = fxf1.rate(f\"{ccys[0]}{ccys[1]}\", dt(2022, 2, 27))\n        r2 = fxf2.rate(f\"{ccys[0]}{ccys[1]}\", dt(2022, 2, 27))\n        r3 = fxf3.rate(f\"{ccys[0]}{ccys[1]}\", dt(2022, 2, 27))\n        r4 = fxf4.rate(f\"{ccys[0]}{ccys[1]}\", dt(2022, 2, 27))\n\n        assert r1 == r2\n        assert r1 == r3\n        assert r1 == r4\n\n    @pytest.mark.parametrize(\"base1\", [NoInput(0), \"usd\", \"cad\"])\n    @pytest.mark.parametrize(\"base2\", [NoInput(0), \"eur\", \"usd\"])\n    @pytest.mark.parametrize(\"pair1\", [\"cadusd\", \"usdcad\"])\n    @pytest.mark.parametrize(\"pair2\", [\"usdeur\", \"eurusd\"])\n    def test_separable_system(\n        self, usdusd, eureur, usdeur, cadcad, cadcol, base1, base2, pair1, pair2\n    ):\n        fxr1 = FXRates({pair1: 1.25}, settlement=dt(2022, 1, 3), base=base1)\n        fxr2 = FXRates({pair2: 2.0}, settlement=dt(2022, 1, 2), base=base2)\n\n        curves = {\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"cadcad\": cadcad,\n            \"cadusd\": cadcol,\n            \"usdeur\": usdeur,\n        }\n        fxf1 = FXForwards([fxr2, fxr1], curves, base=\"usd\")\n        fxf2 = FXForwards([fxr2, fxr1], curves, base=\"eur\")\n        fxf3 = FXForwards([fxr2, fxr1], curves, base=\"cad\")\n\n        for pair in [\"usdcad\", \"cadeur\", \"eurusd\"]:\n            assert fxf1.rate(pair, dt(2022, 3, 20)) == fxf2.rate(pair, dt(2022, 3, 20))\n            assert fxf1.rate(pair, dt(2022, 3, 20)) == fxf3.rate(pair, dt(2022, 3, 20))\n\n    def test_dependent_acyclic_system(self, usdusd, eureur, usdeur, cadcad, cadcol):\n        pair = choice([\"usdcad\", \"cadusd\"])\n        pair2 = choice([\"eurusd\", \"usdeur\"])\n\n        fxr1 = FXRates({pair2: 1.25}, settlement=dt(2022, 1, 3))\n        fxr2 = FXRates({pair: 2.0}, settlement=dt(2022, 1, 2))\n\n        curves = {\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"cadcad\": cadcad,\n            \"cadeur\": cadcol,\n            \"usdeur\": usdeur,\n        }\n        fxf1 = FXForwards([fxr1, fxr2], curves, base=\"usd\")\n        fxf2 = FXForwards([fxr1, fxr2], curves, base=\"eur\")\n        fxf3 = FXForwards([fxr1, fxr2], curves, base=\"cad\")\n\n        for pair in [\"usdcad\", \"cadeur\", \"eurusd\"]:\n            assert fxf1.rate(pair, dt(2022, 3, 20)) == fxf2.rate(pair, dt(2022, 3, 20))\n            assert fxf1.rate(pair, dt(2022, 3, 20)) == fxf3.rate(pair, dt(2022, 3, 20))\n\n\ndef test_multiple_settlement_forwards() -> None:\n    fxr1 = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, dt(2022, 1, 2))\n\n    fxf = FXForwards(\n        [fxr1, fxr2],\n        {\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 0.95}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 1.0}),\n            \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 3): 1.0}),\n            \"cadusd\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.97}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.969}),\n        },\n    )\n    F0_usdeur = 0.95 * 1.0 / 0.95  # f_usdeur * w_eurusd / v_usdusd\n    F0_usdeur_result = fxf.rate(\"usdeur\", dt(2022, 1, 1))\n    assert abs(F0_usdeur_result.real - F0_usdeur) < 1e-13\n\n    expected = Dual(0.95, [\"fx_usdeur\"], [1.0])\n    result = fxf.rate(\"usdeur\", dt(2022, 1, 3))\n    assert abs(result - expected) < 1e-13\n    assert np.isclose(gradient(result, [\"fx_usdeur\"]), expected.dual)\n\n\ndef test_generate_proxy_curve() -> None:\n    fxr1 = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, dt(2022, 1, 2))\n\n    fxf = FXForwards(\n        [fxr1, fxr2],\n        {\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.95}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 1.0}),\n            \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.99}),\n            \"cadusd\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.97}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.969}),\n        },\n    )\n    c1 = fxf.curve(\"cad\", \"cad\")\n    assert c1[dt(2022, 10, 1)] == 0.969\n\n    c2 = fxf.curve(\"cad\", \"usd\")\n    assert c2[dt(2022, 10, 1)] == 0.97\n\n    c3 = fxf.curve(\"cad\", \"eur\")\n    assert type(c3) is not Curve  # should be ProxyCurve\n    expected = Dual(0.9797979797979798, [\"fx_usdcad\", \"fx_usdeur\"], [0, 0])\n    result = c3[dt(2022, 10, 1)]\n    assert abs(result - expected) < 1e-12\n    assert all(np.isclose(gradient(expected, result.vars), gradient(result)))\n\n\ndef test_generate_multi_csa_curve() -> None:\n    fxr1 = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, dt(2022, 1, 2))\n\n    fxf = FXForwards(\n        [fxr1, fxr2],\n        {\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.95}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 1.0}),\n            \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.99}),\n            \"cadusd\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.97}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.969}),\n        },\n    )\n    c1 = fxf.curve(\"cad\", [\"cad\", \"usd\", \"eur\"])\n    assert isinstance(c1, MultiCsaCurve)\n\n\ndef test_proxy_curves_update_with_underlying() -> None:\n    # Test ProxyCurves update after construction and underlying update\n    fxr1 = FXRates({\"usdeur\": 0.95}, dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, dt(2022, 1, 2))\n\n    fxf = FXForwards(\n        [fxr1, fxr2],\n        {\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.95}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 1.0}),\n            \"eurusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 10, 1): 0.99}),\n            \"cadusd\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.97}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.00, dt(2022, 10, 1): 0.969}),\n        },\n    )\n\n    proxy_curve = fxf.curve(\"cad\", \"eur\")\n    prev_value = proxy_curve[dt(2022, 10, 1)]\n    fxf.fx_curves[\"eureur\"].update_node(dt(2022, 10, 1), 0.90)\n    new_value = proxy_curve[dt(2022, 10, 1)]\n\n    assert prev_value != new_value\n\n\ndef test_full_curves(usdusd, eureur, usdeur) -> None:\n    usdusd = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.999})\n    eureur = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.998})\n    eurusd = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.9985})\n    noknok = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.997})\n    nokeur = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.9965})\n    fxr = FXRates({\"usdnok\": 8.0, \"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(\n        fxr,\n        {\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"eurusd\": eurusd,\n            \"noknok\": noknok,\n            \"nokeur\": nokeur,\n        },\n    )\n    curve = fxf._full_curve(\"usd\", \"nok\")\n    assert type(curve) is Curve\n    assert curve.nodes.n == 10  # constructed with DF on every date\n\n\ndef test_rate_dynamic_path_calculation() -> None:\n    # test that a path is dynamically determined for regular settle dates\n    usdusd = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.999})\n    eureur = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.998})\n    eurusd = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.9985})\n    noknok = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.997})\n    nokeur = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.9965})\n    fxr = FXRates({\"eurusd\": 1.05, \"usdnok\": 8.0}, settlement=dt(2022, 1, 3), base=\"usd\")\n    fxf = FXForwards(\n        fxr,\n        {\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"eurusd\": eurusd,\n            \"noknok\": noknok,\n            \"nokeur\": nokeur,\n        },\n    )\n    _ = fxf.rate(\"nokusd\", dt(2022, 1, 7))\n    assert fxf.currencies_list == [\"usd\", \"eur\", \"nok\"]\n    assert fxf._paths[(2, 0)] == 1\n\n\n@pytest.mark.parametrize(\"settlement\", [dt(2022, 1, 3), dt(2022, 1, 1)])\ndef test_no_rate_path_on_immediate(settlement) -> None:\n    # test that a path is not dynamically determined for an immediate calculation\n    usdusd = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.999})\n    eureur = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.998})\n    eurusd = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.9985})\n    noknok = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.997})\n    nokeur = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.9965})\n    fxr = FXRates({\"eurusd\": 1.05, \"usdnok\": 8.0}, settlement=dt(2022, 1, 3), base=\"usd\")\n    fxf = FXForwards(\n        fxr,\n        {\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"eurusd\": eurusd,\n            \"noknok\": noknok,\n            \"nokeur\": nokeur,\n        },\n    )\n    _ = fxf.rate(\"nokusd\", settlement)\n    assert fxf.currencies_list == [\"usd\", \"eur\", \"nok\"]\n    assert (2, 0) not in fxf._paths\n\n\n@pytest.mark.parametrize(\n    \"left\",\n    [\n        NoInput(0),\n        dt(2022, 1, 1),\n        \"0d\",\n    ],\n)\n@pytest.mark.parametrize(\n    \"right\",\n    [\n        NoInput(0),\n        dt(2022, 1, 10),\n        \"9d\",\n    ],\n)\ndef test_fx_plot(left, right) -> None:\n    usdusd = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.999})\n    eureur = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.998})\n    eurusd = Curve({dt(2022, 1, 1): 1.0, dt(2022, 1, 10): 0.9985})\n    fxr = FXRates({\"usdeur\": 1.05}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(\n        fxr,\n        {\n            \"usdusd\": usdusd,\n            \"eureur\": eureur,\n            \"eurusd\": eurusd,\n        },\n    )\n    result = fxf.plot(\"eurusd\", left=left, right=right)\n    assert len(result) == 3\n    y_data = result[2][0].get_data()[1]\n    assert abs(float(y_data[8]) - 0.9520631477714822) < 1e-10\n    plt.close(\"all\")\n\n\ndef test_delta_risk_equivalence() -> None:\n    start, end = dt(2022, 1, 1), dt(2023, 1, 1)\n    fx_curves = {\n        \"usdusd\": Curve({start: 1.0, end: 0.96}, id=\"uu\", ad=1),\n        \"eureur\": Curve({start: 1.0, end: 0.99}, id=\"ee\", ad=1),\n        \"eurusd\": Curve({start: 1.0, end: 0.991}, id=\"eu\", ad=1),\n        \"noknok\": Curve({start: 1.0, end: 0.98}, id=\"nn\", ad=1),\n        \"nokeur\": Curve({start: 1.0, end: 0.978}, id=\"ne\", ad=1),\n    }\n    fx_rates = FXRates({\"usdeur\": 0.9, \"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fxf = FXForwards(fx_rates, fx_curves)\n\n    discounted_nok = fx_curves[\"nokeur\"][dt(2022, 8, 15)] * 1000\n    result1 = discounted_nok * fxf.rate(\"nokusd\", dt(2022, 1, 1))\n\n    forward_eur = fxf.rate(\"nokeur\", dt(2022, 8, 15)) * 1000\n    discounted_eur = forward_eur * fx_curves[\"eureur\"][dt(2022, 8, 15)]\n    result2 = discounted_eur * fxf.rate(\"eurusd\", dt(2022, 1, 1))\n\n    forward_usd = fxf.rate(\"nokusd\", dt(2022, 8, 15)) * 1000\n    discounted_usd = forward_usd * fxf.curve(\"usd\", \"eur\")[dt(2022, 8, 15)]\n    result3 = discounted_usd\n\n    assert set(result1.vars) == {\n        \"ee0\",\n        \"ee1\",\n        \"eu0\",\n        \"eu1\",\n        \"fx_eurnok\",\n        \"fx_usdeur\",\n        \"ne0\",\n        \"ne1\",\n        \"uu0\",\n        \"uu1\",\n    }\n    v = result1.vars\n    assert abs(result1 - result2) < 1e-12\n    assert abs(result1 - result3) < 1e-12\n    assert all(np.isclose(gradient(result1, v), gradient(result3, v)))\n    assert all(np.isclose(gradient(result1, v), gradient(result2, v)))\n\n\ndef test_fx_immediate_rate_equivalence_to_forward() -> None:\n    # this test checks that the FX Immediate object created has the same dual values\n    # expected from manual calculation.\n    start, end = dt(2022, 1, 1), dt(2023, 1, 1)\n    fx_curves = {\n        \"usdusd\": Curve({start: 1.0, end: 0.96}, id=\"uu\", ad=1),\n        \"eureur\": Curve({start: 1.0, end: 0.99}, id=\"ee\", ad=1),\n        \"eurusd\": Curve({start: 1.0, end: 0.991}, id=\"eu\", ad=1),\n        \"noknok\": Curve({start: 1.0, end: 0.98}, id=\"nn\", ad=1),\n        \"nokeur\": Curve({start: 1.0, end: 0.978}, id=\"ne\", ad=1),\n    }\n    fx_rates = FXRates({\"usdeur\": 0.9, \"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fxf = FXForwards(fx_rates, fx_curves)\n\n    # nokeur\n    ne = fxf.curve(\"nok\", \"eur\")\n    ee = fxf.curve(\"eur\", \"eur\")\n    ne0, ne1 = ne[start], ne[dt(2022, 1, 3)]\n    ee0, ee1 = ee[start], ee[dt(2022, 1, 3)]\n    expected = 1 / Dual(8.888889, [\"fx_eurnok\"], []) * ne0 / ne1 * ee1 / ee0\n    result = fxf.fx_rates_immediate.rate(\"nokeur\")\n    assert abs(result - expected) < 1e-12\n    assert all(np.isclose(gradient(result, result.vars), gradient(expected, result.vars)))\n\n\ndef test_rates_update_empty_dict() -> None:\n    # test updating an FXRates with empty dict does nothing.\n    fxr = FXRates({\"usdeur\": 2.0, \"usdgbp\": 2.5})\n    fxr.update({})\n    assert float(fxr.rate(\"usdeur\")) == 2.0\n    assert float(fxr.rate(\"usdgbp\")) == 2.5\n\n\ndef test_oo_update_rates_and_id() -> None:\n    # Test the FXRates object can be updated with new FX Rates without creating new\n    fxr = FXRates({\"usdeur\": 2.0, \"usdgbp\": 2.5})\n    id_ = id(fxr)\n    assert fxr.rate(\"eurgbp\") == Dual(1.25, [\"fx_usdeur\", \"fx_usdgbp\"], [-0.625, 0.5])\n    fxr.update({\"usdGBP\": 3.0})\n    assert fxr.rate(\"eurgbp\") == Dual(1.5, [\"fx_usdeur\", \"fx_usdgbp\"], [-0.75, 0.5])\n    assert id(fxr) == id_\n\n\n@pytest.mark.parametrize(\n    (\"fx_rates\", \"err\"),\n    [\n        ({\"usdeur\": 1.2}, \"`fx_rates` must be a list of dicts\"),\n        ([{\"usdjpy\": 100.5}, {\"eursek\": 3.0}], \"The given `fx_rates` pairs are not contained\"),\n        ([{\"usdeur\": 100.5}], \"`fx_rates` must be a list of dicts with length\"),\n    ],\n)\ndef test_fx_forwards_update_list(fx_rates, err):\n    start, end = dt(2022, 1, 1), dt(2023, 1, 1)\n    fx_curves = {\n        \"usdusd\": Curve({start: 1.0, end: 0.96}, id=\"uu\", ad=1),\n        \"eureur\": Curve({start: 1.0, end: 0.99}, id=\"ee\", ad=1),\n        \"eurusd\": Curve({start: 1.0, end: 0.991}, id=\"eu\", ad=1),\n        \"noknok\": Curve({start: 1.0, end: 0.98}, id=\"nn\", ad=1),\n        \"nokeur\": Curve({start: 1.0, end: 0.978}, id=\"ne\", ad=1),\n    }\n    fxr1 = FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 2))\n    fxr2 = FXRates({\"eurnok\": 8.888889}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards([fxr1, fxr2], fx_curves)\n    with pytest.raises(ValueError, match=err):\n        fxf.update(fx_rates)\n\n\ndef test_oo_update_forwards_rates() -> None:\n    # Test the FXForwards object update method will react to an update of FXRates\n    start, end = dt(2022, 1, 1), dt(2023, 1, 1)\n    fx_curves = {\n        \"usdusd\": Curve({start: 1.0, end: 0.96}, id=\"uu\", ad=1),\n        \"eureur\": Curve({start: 1.0, end: 0.99}, id=\"ee\", ad=1),\n        \"eurusd\": Curve({start: 1.0, end: 0.991}, id=\"eu\", ad=1),\n        \"noknok\": Curve({start: 1.0, end: 0.98}, id=\"nn\", ad=1),\n        \"nokeur\": Curve({start: 1.0, end: 0.978}, id=\"ne\", ad=1),\n    }\n    fx_rates = FXRates({\"usdeur\": 0.9, \"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fxf = FXForwards(fx_rates, fx_curves)\n    original_fwd = fxf.rate(\"usdnok\", dt(2022, 7, 15))  # 7.917 = 0.9 * 8.888\n    fx_rates.update({\"usdeur\": 1.0})\n    fxf.update()\n    updated_fwd = fxf.rate(\"usdnok\", dt(2022, 7, 15))  # 8.797 = 1.0 * 8.888\n    assert original_fwd != updated_fwd\n\n\n@pytest.mark.parametrize(\"curve_up\", [True, False])\n@pytest.mark.parametrize(\"fxr_up\", [True, False])\ndef test_oo_update_forwards(curve_up, fxr_up) -> None:\n    # FXForwards.update() has dependencies to FXRates and Curve.\n    # If either is updated then the immediates FXRates should change\n    start, end = dt(2022, 1, 1), dt(2023, 1, 1)\n    curve = Curve({start: 1.0, end: 0.96}, id=\"uu\", ad=1)\n    fx_curves = {\n        \"usdusd\": curve,\n        \"eureur\": Curve({start: 1.0, end: 0.99}, id=\"ee\", ad=1),\n        \"eurusd\": Curve({start: 1.0, end: 0.991}, id=\"eu\", ad=1),\n        \"noknok\": Curve({start: 1.0, end: 0.98}, id=\"nn\", ad=1),\n        \"nokeur\": Curve({start: 1.0, end: 0.978}, id=\"ne\", ad=1),\n    }\n    fx_rates1 = FXRates({\"usdeur\": 0.9}, dt(2022, 1, 2))\n    fx_rates2 = FXRates({\"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fxf = FXForwards([fx_rates1, fx_rates2], fx_curves)\n    original_fwd = fxf.rate(\"usdnok\", dt(2022, 7, 15))\n\n    if curve_up:\n        curve._set_node_vector([0.94], 1)\n    if fxr_up:\n        fx_rates1.update({\"usdeur\": 0.8})\n    fxf.update()\n    new_fwd = fxf.rate(\"usdnok\", dt(2022, 7, 15))\n\n    assert (new_fwd != original_fwd) is (curve_up or fxr_up)\n\n\ndef test_oo_update_forwards_rates_list() -> None:\n    # Test the FXForwards object update method will react to an update of FXRates\n    start, end = dt(2022, 1, 1), dt(2023, 1, 1)\n    fx_curves = {\n        \"usdusd\": Curve({start: 1.0, end: 0.96}, id=\"uu\", ad=1),\n        \"eureur\": Curve({start: 1.0, end: 0.99}, id=\"ee\", ad=1),\n        \"eurusd\": Curve({start: 1.0, end: 0.991}, id=\"eu\", ad=1),\n        \"noknok\": Curve({start: 1.0, end: 0.98}, id=\"nn\", ad=1),\n        \"nokeur\": Curve({start: 1.0, end: 0.978}, id=\"ne\", ad=1),\n    }\n    fx_rates1 = FXRates({\"usdeur\": 0.9}, dt(2022, 1, 2))\n    fx_rates2 = FXRates({\"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fxf = FXForwards([fx_rates1, fx_rates2], fx_curves)\n    original_fwd = fxf.rate(\"usdnok\", dt(2022, 7, 15))  # 7.917 = 0.9 * 8.888\n    assert abs(original_fwd - 7.917) < 1e-3\n    fx_rates1.update({\"usdeur\": 1.0})\n    fxf.update()\n    updated_fwd = fxf.rate(\"usdnok\", dt(2022, 7, 15))  # 8.797 = 1.0 * 8.888\n    assert abs(updated_fwd - 8.797) < 1e-3\n    assert original_fwd != updated_fwd\n\n\ndef test_oo_update_forwards_rates_equivalence() -> None:\n    # Test the FXForwards object update method is equivalent to an FXRates update\n    start, end = dt(2022, 1, 1), dt(2023, 1, 1)\n    fx_curves = {\n        \"usdusd\": Curve({start: 1.0, end: 0.96}, id=\"uu\", ad=1),\n        \"eureur\": Curve({start: 1.0, end: 0.99}, id=\"ee\", ad=1),\n        \"eurusd\": Curve({start: 1.0, end: 0.991}, id=\"eu\", ad=1),\n        \"noknok\": Curve({start: 1.0, end: 0.98}, id=\"nn\", ad=1),\n        \"nokeur\": Curve({start: 1.0, end: 0.978}, id=\"ne\", ad=1),\n    }\n    fx_rates1 = FXRates({\"usdeur\": 0.9, \"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fx_rates2 = FXRates({\"usdeur\": 0.9, \"eurnok\": 8.888889}, dt(2022, 1, 3))\n    fxf1 = FXForwards(fx_rates1, fx_curves)\n    fxf2 = FXForwards(fx_rates2, fx_curves)\n\n    fx_rates1.update({\"usdeur\": 1.0})\n    fxf1.update()\n\n    fxf2.update([{\"usdeur\": 1.0}])\n    assert fxf1.rate(\"usdnok\", dt(2022, 7, 15)) == fxf2.rate(\"usdnok\", dt(2022, 7, 15))\n\n\n@pytest.mark.parametrize(\n    \"fxr\",\n    [\n        FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n        [\n            FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n        ],\n    ],\n)\ndef test_fxforwards_to_json_round_trip(fxr, usdusd, eureur, usdeur) -> None:\n    fxc = {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur}\n    fxf = FXForwards(fxr, fxc)\n\n    result = fxf.to_json()\n    fxf1 = FXForwards.from_json(result)\n    fxr1, fxc1 = fxf1.fx_rates, fxf1.fx_curves\n\n    assert fxc1 == fxc\n    assert fxr1 == fxr\n    assert fxf1 == fxf\n\n\ndef test_bad_settlement_date(usdusd, usdeur, eureur) -> None:\n    fxf = FXForwards(\n        FXRates({\"usdeur\": 0.9}, settlement=dt(2022, 1, 3)),\n        {\"usdusd\": usdusd, \"eureur\": eureur, \"usdeur\": usdeur},\n    )\n    with pytest.raises(ValueError, match=\"`settlement` cannot\"):\n        fxf.rate(\"usdeur\", dt(1999, 1, 1))  # < date before curves\n\n\ndef test_fxforwards_separable_system() -> None:\n    fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n    fxf = FXForwards(\n        fx_rates=[fxr1, fxr2],\n        fx_curves={\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n        },\n    )\n    result = fxf.rate(\"eurcad\", dt(2022, 2, 1))\n    expected = 1.05 * 1.10\n    assert abs(result - expected) < 1e-2\n\n\ndef test_fxforwards_acyclic_system() -> None:\n    fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n    fxf = FXForwards(\n        fx_rates=[fxr1, fxr2],\n        fx_curves={\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n        },\n    )\n    result = fxf.rate(\"eurcad\", dt(2022, 2, 1))\n    expected = 1.05 * 1.10\n    assert abs(result - expected) < 1e-2\n\n\ndef test_fxforwards_cyclic_system_fails() -> None:\n    fxr1 = FXRates({\"eurusd\": 1.05, \"gbpusd\": 1.2}, settlement=dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n    with pytest.raises(ValueError, match=\"`fx_curves` is underspecified.\"):\n        FXForwards(\n            fx_rates=[fxr1, fxr2],\n            fx_curves={\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"cadeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"gbpcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"gbpgbp\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            },\n        )\n\n\ndef test_fxforwards_cyclic_system_restructured() -> None:\n    # this system as reported in the book has two settlement dates but must be adjusted\n    # given the curve currency one-hot matrix\n    fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n    fxr3 = FXRates({\"gbpusd\": 1.2}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(\n        fx_rates=[fxr1, fxr2, fxr3],\n        fx_curves={\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"gbpcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"gbpgbp\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n        },\n    )\n    result = fxf.rate(\"eurcad\", dt(2022, 2, 1))\n    expected = 1.05 * 1.10\n    assert abs(result - expected) < 1e-2\n\n\ndef test_fxforwards_cyclic_system_restructured2() -> None:\n    fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3), base=\"eur\")\n    fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2), base=\"cad\")\n    fxr3 = FXRates({\"gbpusd\": 1.2}, settlement=dt(2022, 1, 3), base=\"gbp\")\n    fxf = FXForwards(\n        fx_rates=[fxr1, fxr2, fxr3],\n        fx_curves={\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"gbpcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"gbpgbp\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n        },\n    )\n    result = fxf.rate(\"eurcad\", dt(2022, 2, 1))\n    expected = 1.05 * 1.10\n    assert abs(result - expected) < 1e-2\n\n\ndef test_fxforwards_settlement_pairs() -> None:\n    fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n    fxr3 = FXRates({\"gbpusd\": 1.2}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(\n        fx_rates=[fxr1, fxr2, fxr3],  # FXRates as list\n        fx_curves={\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"gbpcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"gbpgbp\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n        },\n    )\n    assert fxf.pairs_settlement[\"eurusd\"] == dt(2022, 1, 3)\n    assert fxf.pairs_settlement[\"usdcad\"] == dt(2022, 1, 2)\n    assert fxf.pairs_settlement[\"gbpusd\"] == dt(2022, 1, 3)\n\n    fxf = FXForwards(\n        fx_rates=fxr1,  # FXRates as list\n        fx_curves={\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n        },\n    )\n    assert fxf.pairs_settlement[\"eurusd\"] == dt(2022, 1, 3)\n\n\ndef test_fxforwards_positions_when_immediate_aligns_with_settlement() -> None:\n    fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 1))\n    fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 1))\n    fxf = FXForwards(\n        fx_rates=[fxr1, fxr2],\n        fx_curves={\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n        },\n    )\n    pv = Dual(100000, [\"fx_eurusd\", \"fx_usdcad\"], [-100000, -150000])\n    result = fxf.positions(pv, base=\"usd\")\n    expected = DataFrame(\n        index=[\"cad\", \"eur\", \"usd\"],\n        columns=[dt(2022, 1, 1)],\n        data=[[181500.0], [-100000.0], [40000]],\n    )\n    assert_frame_equal(result, expected)\n\n\ndef test_fxforwards_positions_multiple_fx_rates() -> None:\n    fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n    fxf = FXForwards(\n        fx_rates=[fxr1, fxr2],\n        fx_curves={\n            \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            \"cadusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n        },\n    )\n    pv = Dual(100000, [\"fx_eurusd\", \"fx_usdcad\"], [-100000, -150000])\n    result = fxf.positions(pv, base=\"usd\")\n    expected = DataFrame(\n        index=[\"cad\", \"eur\", \"usd\"],\n        columns=[dt(2022, 1, 1), dt(2022, 1, 2), dt(2022, 1, 3)],\n        data=[[0.0, 181500.0, 0.0], [0.0, 0.0, -100000.0], [100000, -165000, 105000]],\n    )\n    assert_frame_equal(result, expected)\n\n\ndef test_forward_fx_immediate() -> None:\n    d_curve = Curve(nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, interpolation=\"log_linear\")\n    f_curve = Curve(nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.95})\n    result = forward_fx(dt(2022, 4, 1), d_curve, f_curve, 10.0)\n    assert abs(result - 10.102214) < 1e-6\n\n    result = forward_fx(dt(2022, 1, 1), d_curve, f_curve, 10.0, dt(2022, 1, 1))\n    assert abs(result - 10.0) < 1e-6\n\n    result = forward_fx(dt(2022, 1, 1), d_curve, f_curve, 10.0)\n    assert abs(result - 10.0) < 1e-6\n\n\ndef test_forward_fx_spot_equivalent() -> None:\n    d_curve = Curve(nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, interpolation=\"log_linear\")\n    f_curve = Curve(nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.95})\n    result = forward_fx(dt(2022, 7, 1), d_curve, f_curve, 10.102214, dt(2022, 4, 1))\n    assert abs(result - 10.206626) < 1e-6\n\n\nclass TestFXForwards:\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"rate\", (\"cadeur\", dt(2022, 1, 12))),\n            (\"convert\", (100, \"cad\")),\n            (\"positions\", (100, \"cad\")),\n            (\"convert_positions\", ([100, -100, 100, -100],)),\n            (\"swap\", (\"cadeur\", [dt(2022, 1, 10), dt(2022, 1, 16)])),\n            (\"to_json\", tuple()),\n        ],\n    )\n    def test_hash_update_on_fxr_update(self, method, args):\n        # test validate cache works correctly on various methods after FXRates update\n        fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n        fxr3 = FXRates({\"gbpusd\": 1.2}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fx_rates=[fxr1, fxr2, fxr3],  # FXRates as list\n            fx_curves={\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"cadeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"gbpcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"gbpgbp\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            },\n        )\n\n        before = fxf._state\n        getattr(fxf, method)(*args)\n        # no cache update is necessary\n        assert before == fxf._state\n\n        fxr1.update({\"eurusd\": 2.0})\n        getattr(fxf, method)(*args)\n        # cache update should have occurred\n        assert before != fxf._state\n\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"rate\", (\"cadeur\", dt(2022, 1, 12))),\n            (\"convert\", (100, \"cad\")),\n            (\"positions\", (100, \"cad\")),\n            (\"convert_positions\", ([100, -100, 100, -100],)),\n            (\"swap\", (\"cadeur\", [dt(2022, 1, 10), dt(2022, 1, 16)])),\n            (\"to_json\", tuple()),\n        ],\n    )\n    def test_hash_update_on_curve_update(self, method, args):\n        # test validate cache works correctly on various methods after Curve update\n        fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n        fxr3 = FXRates({\"gbpusd\": 1.2}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fx_rates=[fxr1, fxr2, fxr3],  # FXRates as list\n            fx_curves={\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"cadeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"gbpcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"gbpgbp\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            },\n        )\n\n        before = fxf._state\n        getattr(fxf, method)(*args)\n        # no cache update is necessary\n        assert before == fxf._state\n\n        fxf.curve(\"eur\", \"eur\")._set_node_vector([0.998], 1)\n        getattr(fxf, method)(*args)\n        # cache update should have occurred\n        assert before != fxf._state\n\n    def test_update_does_nothing_with_same_hashes(self):\n        fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fx_rates=[fxr1],  # FXRates as list\n            fx_curves={\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            },\n        )\n        before = fxf._state\n        fxf.update()\n        after = fxf._state\n        assert before == after\n\n        before = fxf._state\n        fxf.update([{\"eurusd\": 2.0}])\n        after = fxf._state\n        assert before != after\n\n    def test_cache_population(self):\n        fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxr2 = FXRates({\"usdcad\": 1.1}, settlement=dt(2022, 1, 2))\n        fxr3 = FXRates({\"gbpusd\": 1.2}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fx_rates=[fxr1, fxr2, fxr3],  # FXRates as list\n            fx_curves={\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"cadcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"cadeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"gbpcad\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"gbpgbp\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            },\n        )\n        fxf._set_ad_order(0)\n        assert fxf._cache == {}\n        fxf.rate(\"gbpeur\", dt(2022, 1, 11))\n        assert fxf._cache == {\n            (\"gbpcad\", dt(2022, 1, 11)): 1.3199999999999998,\n            (\"cadeur\", dt(2022, 1, 11)): 0.8658008658008657,\n            (\"gbpeur\", dt(2022, 1, 11)): 1.1428571428571426,\n        }\n\n    def test_proxy_curve_cache(self):\n        fxr1 = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n        fxf = FXForwards(\n            fx_rates=[fxr1],  # FXRates as list\n            fx_curves={\n                \"usdusd\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"eureur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n                \"usdeur\": Curve({dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999}),\n            },\n        )\n        c = fxf.curve(\"eur\", \"usd\")\n        assert \"eurusd\" in fxf.fx_proxy_curves\n        c2 = fxf.curve(\"eur\", \"usd\")\n        assert id(c) == id(c2)\n\n    def test_creation_composite_curve(self):\n        c1 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        c2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99})\n        cc = CompositeCurve([c1, c2])\n        mc = MultiCsaCurve([c1, c2])\n        fxr = FXRates({\"eurusd\": 1.5}, settlement=dt(2000, 1, 1))\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\"eureur\": mc, \"eurusd\": c2, \"usdusd\": cc},\n        )\n        pc = fxf.curve(\"usd\", \"eur\")\n        result = pc[dt(2000, 1, 15)]\n        assert abs(result - 0.998456) < 1e-6\n\n    def test_creation_proxy_curve(self):\n        c1 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98})\n        c2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99})\n        cc = CompositeCurve([c1, c2])\n        mc = MultiCsaCurve([c1, c2])\n        fxr = FXRates({\"eurusd\": 1.5}, settlement=dt(2000, 1, 1))\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\"eureur\": mc, \"eurusd\": c2, \"usdusd\": cc},\n        )\n        pc = fxf.curve(\"usd\", \"eur\")\n        fxf2 = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\"eureur\": pc, \"eurusd\": pc, \"usdusd\": cc},\n        )\n        pc = fxf2.curve(\"usd\", \"eur\")\n        result = pc[dt(2000, 1, 15)]\n        assert abs(result - 0.998843) < 1e-6\n\n    def test_creation_operations_curve(self):\n        c1 = Curve({dt(2000, 1, 2): 1.0, dt(2001, 1, 1): 0.98})\n        c2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.99})\n        rc = c1.roll(\"3d\")\n        sc = c1.shift(10.0)\n        tc = c2.translate(dt(2000, 1, 2))\n        fxr = FXRates({\"eurusd\": 1.5}, settlement=dt(2000, 1, 2))\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\"eureur\": rc, \"eurusd\": sc, \"usdusd\": tc},\n        )\n        pc = fxf.curve(\"usd\", \"eur\")\n        result = pc[dt(2000, 1, 15)]\n        assert abs(result - 0.999679) < 1e-6\n\n\ndef test_recursive_pair_population1():\n    arr = np.array(\n        [\n            [1, 0, 1],\n            [0, 1, 0],\n            [0, 1, 1],\n        ]\n    )\n    result = _recursive_pair_population(arr)\n    expected = {\n        (0, 1): 2,\n        (0, 2): -1,\n        (1, 0): 2,\n        (1, 2): -1,\n        (2, 0): -1,\n        (2, 1): -1,\n    }\n    assert result[1] == expected\n\n\ndef test_recursive_pair_population2():\n    # 5 currency example in 'Coding Interest Rates'\n    arr = np.array(\n        [\n            [1, 0, 0, 0, 0],\n            [1, 1, 0, 0, 0],\n            [0, 1, 1, 0, 0],\n            [1, 0, 0, 1, 0],\n            [0, 0, 0, 1, 1],\n        ]\n    )\n    result = _recursive_pair_population(arr)\n    expected = {\n        (0, 1): -1,\n        (0, 2): 1,\n        (0, 3): -1,\n        (0, 4): 3,\n        (1, 0): -1,\n        (1, 2): -1,\n        (1, 3): 0,\n        (1, 4): 3,\n        (2, 0): 1,\n        (2, 1): -1,\n        (2, 3): 1,\n        (2, 4): 3,\n        (3, 0): -1,\n        (3, 1): 0,\n        (3, 2): 1,\n        (3, 4): -1,\n        (4, 0): 3,\n        (4, 1): 3,\n        (4, 2): 3,\n        (4, 3): -1,\n    }\n    assert result[1] == expected\n"
  },
  {
    "path": "python/tests/test_fx_volatility.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\nfrom itertools import combinations\n\nimport numpy as np\nimport pytest\nfrom matplotlib import pyplot as plt\nfrom pandas import DataFrame, Series\nfrom pandas.testing import assert_frame_equal, assert_series_equal\nfrom rateslib import default_context\nfrom rateslib.curves import CompositeCurve, Curve, LineCurve\nfrom rateslib.default import NoInput\nfrom rateslib.dual import Dual, Dual2, Variable, gradient\nfrom rateslib.enums.parameters import _get_fx_delta_type\nfrom rateslib.fx import (\n    FXForwards,\n    FXRates,\n    forward_fx,\n)\nfrom rateslib.periods import FXCallPeriod\nfrom rateslib.scheduling import get_calendar\nfrom rateslib.volatility import (\n    FXDeltaVolSmile,\n    FXDeltaVolSurface,\n    FXSabrSmile,\n    FXSabrSurface,\n)\nfrom rateslib.volatility.utils import (\n    _SabrModel,\n    _SabrSmileNodes,\n)\n\n\n@pytest.fixture\ndef fxfo():\n    # FXForwards for FX Options tests\n    eureur = Curve(\n        {dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.9851909811629752},\n        calendar=\"tgt\",\n        id=\"eureur\",\n    )\n    usdusd = Curve(\n        {dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.976009366603271},\n        calendar=\"nyc\",\n        id=\"usdusd\",\n    )\n    eurusd = Curve({dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.987092591908283}, id=\"eurusd\")\n    fxr = FXRates({\"eurusd\": 1.0615}, settlement=dt(2023, 3, 20))\n    fxf = FXForwards(fx_curves={\"eureur\": eureur, \"eurusd\": eurusd, \"usdusd\": usdusd}, fx_rates=fxr)\n    # fxf.swap(\"eurusd\", [dt(2023, 3, 20), dt(2023, 6, 20)]) = 60.10\n    return fxf\n\n\nclass TestFXDeltaVolSmile:\n    @pytest.mark.parametrize(\"k\", [0.2, 0.8, 0.9, 1.0, 1.05, 1.10, 1.25, 1.5, 9.0])\n    def test_get_from_strike(self, fxfo, k) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={\n                0.25: 10.15,\n                0.5: 7.8,\n                0.75: 8.9,\n            },\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n        )\n        put_vol = fxvs.get_from_strike(\n            k=k,\n            f=fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n            z_w=fxfo.curve(\"eur\", \"usd\")[dt(2023, 6, 20)]\n            / fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)],\n        )\n        call_vol = fxvs.get_from_strike(\n            k=k,\n            f=fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n            z_w=fxfo.curve(\"eur\", \"usd\")[dt(2023, 6, 20)]\n            / fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)],\n        )\n        assert abs(put_vol[1] - call_vol[1]) < 1e-9\n\n    @pytest.mark.parametrize(\n        (\"var\", \"idx\", \"val\"),\n        [(\"vol0\", 0.25, 10.15), (\"vol1\", 0.5, 7.8), (\"vol2\", 0.75, 8.9)],\n    )\n    @pytest.mark.parametrize(\"k\", [0.9, 1.0, 1.05, 1.10, 1.4])\n    def test_get_from_strike_ad(self, fxfo, var, idx, val, k) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={\n                0.25: 10.15,\n                0.5: 7.8,\n                0.75: 8.9,\n            },\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n            ad=1,\n        )\n        kwargs = dict(\n            k=k,\n            f=fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n            z_w=fxfo.curve(\"eur\", \"usd\")[dt(2023, 6, 20)]\n            / fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)],\n        )\n        put_vol = fxvs.get_from_strike(**kwargs)\n\n        fxvs.update_node(idx, Dual(val + 0.0000001, [var], []))\n        put_vol_plus = fxvs.get_from_strike(**kwargs)\n\n        finite_diff = (put_vol_plus[1] - put_vol[1]) * 10000000.0\n        ad_grad = gradient(put_vol[1], [var])[0]\n\n        assert abs(finite_diff - ad_grad) < 1e-7\n\n    @pytest.mark.parametrize(\"k\", [0.9, 1.0, 1.05, 1.10, 1.4])\n    @pytest.mark.parametrize(\n        \"cross\",\n        [\n            ([\"vol0\", 10.15, 0.25], [\"vol1\", 7.8, 0.5]),\n            ([\"vol0\", 10.15, 0.25], [\"vol2\", 8.9, 0.75]),\n            ([\"vol1\", 7.8, 0.5], [\"vol2\", 8.9, 0.75]),\n        ],\n    )\n    def test_get_from_strike_ad_2(self, fxfo, k, cross) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={\n                0.25: 10.15,\n                0.5: 7.8,\n                0.75: 8.9,\n            },\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n            ad=2,\n        )\n        fxfo._set_ad_order(2)\n        kwargs = dict(\n            k=k,\n            f=fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n            z_w=fxfo.curve(\"eur\", \"usd\")[dt(2023, 6, 20)]\n            / fxfo.curve(\"eur\", \"usd\")[dt(2023, 3, 20)],\n        )\n        pv00 = fxvs.get_from_strike(**kwargs)\n\n        fxvs.update_node(cross[0][2], Dual2(cross[0][1] + 0.00001, [cross[0][0]], [], []))\n        fxvs.update_node(cross[1][2], Dual2(cross[1][1] + 0.00001, [cross[1][0]], [], []))\n        pv11 = fxvs.get_from_strike(**kwargs)\n\n        fxvs.update_node(cross[0][2], Dual2(cross[0][1] + 0.00001, [cross[0][0]], [], []))\n        fxvs.update_node(cross[1][2], Dual2(cross[1][1] - 0.00001, [cross[1][0]], [], []))\n        pv1_1 = fxvs.get_from_strike(**kwargs)\n\n        fxvs.update_node(cross[0][2], Dual2(cross[0][1] - 0.00001, [cross[0][0]], [], []))\n        fxvs.update_node(cross[1][2], Dual2(cross[1][1] - 0.00001, [cross[1][0]], [], []))\n        pv_1_1 = fxvs.get_from_strike(**kwargs)\n\n        fxvs.update_node(cross[0][2], Dual2(cross[0][1] - 0.00001, [cross[0][0]], [], []))\n        fxvs.update_node(cross[1][2], Dual2(cross[1][1] + 0.00001, [cross[1][0]], [], []))\n        pv_11 = fxvs.get_from_strike(**kwargs)\n\n        finite_diff = (pv11[1] + pv_1_1[1] - pv1_1[1] - pv_11[1]) * 1e10 / 4.0\n        ad_grad = gradient(pv00[1], [cross[0][0], cross[1][0]], 2)[0, 1]\n\n        assert abs(finite_diff - ad_grad) < 5e-5\n\n    def test_get_from_unsimilar_delta(self) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={0.25: 10.0, 0.5: 10.0, 0.75: 11.0},\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n        )\n        result = fxvs.get(0.65, \"spot_pa\", 1.0, 0.99 / 0.999)\n        expected = 10.0\n        assert (result - expected) < 0.01\n\n    @pytest.mark.parametrize((\"delta_type\", \"exp\"), [(\"spot\", 10.00000489), (\"forward\", 10.0)])\n    def test_get_from_similar_delta(self, delta_type, exp) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={0.25: 11.0, 0.5: 10.0, 0.75: 11.0},\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n        )\n        result = fxvs.get(0.5, delta_type, 1.0, 0.99 / 0.991)\n        assert abs(result - exp) < 1e-6\n\n    @pytest.mark.parametrize(\n        (\"delta_type\", \"exp\"), [(\"spot_pa\", 10.000085036853598), (\"forward_pa\", 10.0)]\n    )\n    def test_get_from_similar_delta_pa(self, delta_type, exp) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={0.25: 11.0, 0.5: 10.0, 0.75: 11.0},\n            delta_type=\"forward_pa\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n        )\n        result = fxvs.get(-0.5, delta_type, -1.0, 0.99 / 0.991)\n        assert abs(result - exp) < 1e-6\n\n    def test_get_from_unsimilar_delta2(self):\n        # GH 730\n        fdvs = FXDeltaVolSmile(\n            nodes={\n                0.1: 5,\n                0.25: 4,\n                0.5: 3,\n                0.75: 4,\n                0.9: 5,\n            },\n            expiry=dt(2025, 5, 10),\n            eval_date=dt(2025, 4, 10),\n            delta_type=\"forward\",\n        )\n        result = fdvs.get(delta=0.1, delta_type=\"forward_pa\", phi=1, z_w=1.0)\n        expected = 4.995304045589985\n        assert abs(result - expected) < 1e-9\n\n    def test_set_same_ad_order(self) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={0.25: 10.0, 0.5: 10.0, 0.75: 11.0},\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n            ad=1,\n        )\n        assert fxvs._set_ad_order(1) is None\n        assert fxvs.nodes.nodes[0.25] == Dual(10.0, [\"vol0\"], [])\n\n    def test_set_ad_order_raises(self) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={0.25: 10.0, 0.5: 10.0, 0.75: 11.0},\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n            ad=1,\n        )\n        with pytest.raises(ValueError, match=\"`order` can only be in\"):\n            fxvs._set_ad_order(10)\n\n    def test_iter_raises(self) -> None:\n        fxvs = FXDeltaVolSmile(\n            nodes={0.5: 1.0},\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n        )\n        with pytest.raises(TypeError, match=\"`Smile` types are not iterable.\"):\n            fxvs.__iter__()\n\n    def test_update_node(self):\n        fxvs = FXDeltaVolSmile(\n            nodes={0.5: 1.0},\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n        )\n        with pytest.raises(KeyError, match=r\"`key`: '0.4' is not in Curve ``nodes``\"):\n            fxvs.update_node(0.4, 10.0)\n\n        fxvs.update_node(0.5, 12.0)\n        assert fxvs[0.5] == 12.0\n\n    @pytest.mark.parametrize(\n        \"nodes\", [{0.5: 10.0}, {0.35: 10.0, 0.65: 9.0}, {0.25: 10.0, 0.5: 8.0, 0.75: 11.0}]\n    )\n    def test_delta_index_range_for_spot(self, nodes):\n        # spot delta type can lead to a delta index greater than 1.0\n        # test ensures extrapolation of a DeltaVolSmile is possible, but it is a flat function\n        fxv = FXDeltaVolSmile(\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2001, 1, 1),\n            nodes=nodes,\n            delta_type=\"spot\",\n        )\n        result = fxv[1.025]\n        assert result == fxv[1.0]\n\n    def test_update_csolve(self):\n        import rateslib\n\n        anchor = rateslib.dt(2025, 5, 22)\n        expiry = rateslib.dt(2025, 6, 24)\n\n        test_smile = rateslib.FXDeltaVolSmile(\n            nodes={\n                0.1: 5,\n                0.25: 4,\n                0.5: 3,\n                0.75: 4,\n                0.9: 5,\n            },\n            expiry=expiry,\n            eval_date=anchor,\n            delta_type=\"forward\",\n            id=\"test_vol\",\n        )\n\n        prior_c = test_smile.nodes.spline.spline.c\n        # update node\n        nodes_bump = {k: v + 0.5 for k, v in test_smile.nodes.nodes.items()}\n        test_smile.update(nodes_bump)\n        after_c = test_smile.nodes.spline.spline.c\n\n        assert after_c != prior_c\n\n    def test_flat_smile_with_zero_delta_index_input(self):\n        smile = FXDeltaVolSmile(\n            nodes={0.0: 10.0},\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            id=\"vol\",\n            expiry=dt(2023, 6, 16),\n        )\n        assert abs(smile[0.5] - 10.0) < 1e-14\n\n\nclass TestFXDeltaVolSurface:\n    def test_expiry_before_eval(self) -> None:\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[11, 10, 12], [8, 7, 9]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        with pytest.raises(ValueError, match=\"`expiry` before the `eval_date` of\"):\n            fxvs.get_smile(dt(2022, 1, 1))\n\n    def test_smile_0_no_interp(self) -> None:\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[11, 10, 12], [8, 7, 9]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        result = fxvs.get_smile(dt(2023, 2, 1))\n        expected = FXDeltaVolSmile(\n            nodes={0.25: 11, 0.5: 10, 0.75: 12},\n            eval_date=dt(2023, 1, 1),\n            expiry=dt(2023, 2, 1),\n            delta_type=\"forward\",\n        )\n        assert result.nodes == expected.nodes\n        assert result.meta.expiry == expected.meta.expiry\n        assert result.meta.delta_type == expected.meta.delta_type\n        assert result.meta.eval_date == expected.meta.eval_date\n\n    def test_smile_end_no_interp(self) -> None:\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[11, 10, 12], [8, 7, 9]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        result = fxvs.get_smile(dt(2029, 2, 1))\n        expected = FXDeltaVolSmile(\n            nodes={0.25: 8, 0.5: 7, 0.75: 9},\n            eval_date=dt(2023, 1, 1),\n            expiry=dt(2029, 2, 1),\n            delta_type=\"forward\",\n        )\n        assert result.nodes == expected.nodes\n        assert result.meta.expiry == expected.meta.expiry\n        assert result.meta.delta_type == expected.meta.delta_type\n        assert result.meta.eval_date == expected.meta.eval_date\n\n    def test_smile_tot_var_lin_interp(self) -> None:\n        # See Foreign Exchange Option Pricing: Iain Clarke Table 4.5\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        result = fxvs.get_smile(dt(2024, 7, 1))\n        expected = FXDeltaVolSmile(\n            nodes={0.25: 19.0693, 0.5: 17.8713, 0.75: 18.4864},\n            eval_date=dt(2023, 1, 1),\n            expiry=dt(2024, 7, 1),\n            delta_type=\"forward\",\n        )\n        for v1, v2 in zip(result.nodes.values, expected.nodes.values):\n            assert abs(v1 - v2) < 0.0001\n        assert result.meta.expiry == expected.meta.expiry\n        assert result.meta.delta_type == expected.meta.delta_type\n        assert result.meta.eval_date == expected.meta.eval_date\n\n    def test_smile_from_exact_expiry(self) -> None:\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n            id=\"surf\",\n        )\n        expected = FXDeltaVolSmile(\n            nodes={0.25: 19.590, 0.5: 18.25, 0.75: 18.967},\n            eval_date=dt(2023, 1, 1),\n            expiry=dt(2024, 1, 1),\n            delta_type=\"forward\",\n            id=\"surf_0_\",\n        )\n        result = fxvs.get_smile(dt(2024, 1, 1))\n        for v1, v2 in zip(result.nodes.values, expected.nodes.values):\n            assert abs(v1 - v2) < 0.0001\n        assert result.meta.expiry == expected.meta.expiry\n        assert result.meta.delta_type == expected.meta.delta_type\n        assert result.meta.eval_date == expected.meta.eval_date\n        assert result.id == expected.id\n\n    def test_get_vol_from_strike(self) -> None:\n        # from a surface creates a smile and then re-uses methods\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        result = fxvs.get_from_strike(k=1.05, f=1.03, z_w=0.99 / 0.999, expiry=dt(2024, 7, 1))[1]\n        # expected close to delta index of 0.5 i.e around 17.87% vol\n        expected = 17.882603173\n        assert abs(result - expected) < 1e-8\n\n    def test_get_vol_from_strike_raises(self) -> None:\n        # from a surface creates a smile and then re-uses methods\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        with pytest.raises(ValueError, match=\"`expiry` required to get cross-section\"):\n            fxvs.get_from_strike(k=1.05, f=1.03, z_w=0.99 / 0.999)\n\n    def test_set_node_vector(self) -> None:\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        vec = np.array([3, 2, 4, 5, 4, 6])\n        fxvs._set_node_vector(vec, 1)\n        for v1, v2 in zip(vec[:3], fxvs.smiles[0].nodes.values):\n            assert abs(v1 - v2) < 1e-10\n        for v1, v2 in zip(vec[3:], fxvs.smiles[1].nodes.values):\n            assert abs(v1 - v2) < 1e-10\n\n    def test_expiries_unsorted(self) -> None:\n        with pytest.raises(ValueError, match=\"Surface `expiries` are not sorted or\"):\n            FXDeltaVolSurface(\n                delta_indexes=[0.25, 0.5, 0.75],\n                expiries=[dt(2024, 1, 1), dt(2024, 1, 1)],\n                node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n                eval_date=dt(2023, 1, 1),\n                delta_type=\"forward\",\n            )\n\n    def test_set_weights(self) -> None:\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2024, 2, 1), dt(2024, 3, 1)],\n            node_values=[[11, 10, 12], [8, 7, 9], [9, 7.5, 10]],\n            eval_date=dt(2023, 12, 1),\n            delta_type=\"forward\",\n            weights=Series(2.0, index=[dt(2024, 1, 5), dt(2024, 1, 12), dt(2024, 2, 5)]),\n        )\n        assert fxvs.meta.weights.loc[dt(2023, 12, 15)] == 1.0\n        assert fxvs.meta.weights.loc[dt(2024, 1, 4)] == 0.9393939393939394\n        assert fxvs.meta.weights.loc[dt(2024, 1, 5)] == 1.878787878787879\n        assert fxvs.meta.weights.loc[dt(2024, 2, 2)] == 0.9666666666666667\n        assert fxvs.meta.weights.loc[dt(2024, 2, 5)] == 1.9333333333333333\n        assert fxvs.meta.weights.loc[dt(2027, 12, 15)] == 1.0\n\n        # test that the sum of weights to each expiry node is as expected.\n        for e in fxvs.meta.expiries:\n            assert (\n                abs(\n                    fxvs.meta.weights[fxvs.meta.eval_date : e].sum()\n                    - (e - fxvs.meta.eval_date).days\n                )\n                < 1e-13\n            )\n\n    @pytest.mark.parametrize(\"scalar\", [1.0, 0.5])\n    def test_weights_get_vol(self, scalar) -> None:\n        # from a surface creates a smile and then re-uses methods\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2023, 2, 1), dt(2023, 3, 1)],\n            node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        fxvs_weights = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2023, 2, 1), dt(2023, 3, 1)],\n            node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n            weights=Series(scalar, index=[dt(2023, 2, 2), dt(2023, 2, 3)]),\n        )\n        kwargs = dict(k=1.03, f=1.03, z_w=0.99 / 0.999, expiry=dt(2023, 2, 3))\n        result = fxvs.get_from_strike(**kwargs)\n        result2 = fxvs_weights.get_from_strike(**kwargs)\n        w = fxvs_weights.meta.weights\n\n        expected = result[1] * (w[: dt(2023, 2, 3)].sum() / 33.0) ** 0.5\n        # This result is not exact because the shape of the spline changes\n        assert abs(expected - result2[1]) < 5e-2\n\n    def test_weights_get_vol_clark(self) -> None:\n        cal = get_calendar(\"bus\")\n        weights = Series(0.0, index=cal.cal_date_range(dt(2024, 2, 9), dt(2024, 3, 9)))\n        weights.update(Series(1.0, index=cal.bus_date_range(dt(2024, 2, 9), dt(2024, 3, 8))))\n        fxvs_weights = FXDeltaVolSurface(\n            delta_indexes=[0.5],\n            expiries=[\n                dt(2024, 2, 12),\n                dt(2024, 2, 16),\n                dt(2024, 2, 23),\n                dt(2024, 3, 1),\n                dt(2024, 3, 8),\n            ],\n            node_values=[[8.15], [11.95], [11.97], [11.75], [11.80]],\n            eval_date=dt(2024, 2, 9),\n            delta_type=\"forward\",\n            weights=weights,\n        )\n\n        # Clark FX Option Pricing Table 4.7\n        expected = [\n            0.0,\n            0.0,\n            8.15,\n            9.99,\n            10.95,\n            11.54,\n            11.95,\n            11.18,\n            10.54,\n            10.96,\n            11.29,\n            11.56,\n            11.78,\n            11.97,\n            11.56,\n            11.20,\n            11.34,\n            11.46,\n            11.57,\n            11.66,\n            11.75,\n            11.48,\n            11.23,\n            11.36,\n            11.49,\n            11.60,\n            11.70,\n            11.80,\n            11.59,\n        ]\n\n        for i, date in enumerate(cal.cal_date_range(dt(2024, 2, 10), dt(2024, 3, 9))):\n            smile = fxvs_weights.get_smile(date)\n            assert abs(smile.nodes.nodes[0.5] - expected[i]) < 5e-3\n\n    def test_cache_clear_and_defaults(self):\n        fxvs = FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[19.590, 18.250, 18.967], [18.801, 17.677, 18.239]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n        )\n        fxvs.get_smile(dt(2024, 7, 1))\n        assert dt(2024, 7, 1) in fxvs._cache\n\n        fxvs._clear_cache()\n        assert dt(2024, 7, 1) not in fxvs._cache\n\n        with default_context(\"curve_caching\", False):\n            fxvs.get_smile(dt(2024, 7, 1))\n            # no clear cache required, but value will re-calc anyway\n            assert dt(2024, 7, 1) not in fxvs._cache\n\n    @pytest.mark.parametrize(\"smile_expiry\", [dt(2026, 5, 1), dt(2026, 6, 9), dt(2026, 7, 1)])\n    def test_flat_surface_and_get_smile_one_expiry(self, smile_expiry):\n        # gh 911\n        anchor = dt(2025, 6, 9)\n        expiry = dt(2026, 6, 9)\n\n        surf = FXDeltaVolSurface(\n            eval_date=anchor,\n            expiries=[expiry],\n            delta_indexes=[0.5],\n            node_values=[[10]],\n            delta_type=\"forward\",\n        )\n\n        smile = surf.get_smile(smile_expiry)\n        assert abs(smile[0.3] - 10.0) < 1e-13\n\n\nclass TestFXSabrSmile:\n    @pytest.mark.parametrize(\n        (\"strike\", \"vol\"),\n        [\n            (1.2034, 19.49),\n            (1.2050, 19.47),\n            (1.3395, 18.31),  # f == k\n            (1.3620, 18.25),\n            (1.5410, 18.89),\n            (1.5449, 18.93),\n        ],\n    )\n    def test_sabr_vol(self, strike, vol):\n        # test the SABR function using Clark 'FX Option Pricing' Table 3.7 as benchmark.\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n        )\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        result = fxss.get_from_strike(strike, 1.3395)[1]\n        assert abs(result - vol) < 1e-2\n\n    @pytest.mark.parametrize((\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33)])\n    def test_sabr_vol_finite_diff_first_order(self, k, f):\n        # Test all of the first order gradients using finite diff, for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n            ad=2,\n        )\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = fxss.get_from_strike(Dual2(k, [\"k\"], [], []), Dual2(f, [\"f\"], [], []))[1]\n\n        a = fxss.nodes.alpha\n        p = fxss.nodes.rho\n        v = fxss.nodes.nu\n\n        def inc_(key1, inc1):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n\n            fxss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = (\n                fxss._d_sabr_d_k_or_f(\n                    Dual2(in_[\"k\"], [\"k\"], [], []),\n                    Dual2(in_[\"f\"], [\"f\"], [], []),\n                    dt(2002, 1, 1),\n                    False,\n                    1,\n                )[0]\n                * 100.0\n            )\n\n            # reset\n            fxss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        for key in [\"k\", \"f\", \"alpha\", \"rho\", \"nu\"]:\n            map_ = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"vol0\", \"rho\": \"vol1\", \"nu\": \"vol2\"}\n            up_ = inc_(key, 1e-5)\n            dw_ = inc_(key, -1e-5)\n            assert abs((up_ - dw_) / 2e-5 - gradient(base, [map_[key]])[0]) < 1e-5\n\n    @pytest.mark.parametrize(\n        (\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33), (1.3399, 1.34), (1.34, 1.3401)]\n    )\n    @pytest.mark.parametrize(\"pair\", list(combinations([\"k\", \"f\", \"alpha\", \"rho\", \"nu\"], 2)))\n    def test_sabr_vol_cross_finite_diff_second_order(self, k, f, pair):\n        # Test all of the second order cross gradients using finite diff,\n        # for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"v\",\n            ad=2,\n        )\n\n        a = fxss.nodes.alpha\n        p = fxss.nodes.rho\n        v = fxss.nodes.nu\n\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = fxss.get_from_strike(Dual2(k, [\"k\"], [], []), Dual2(f, [\"f\"], [], []))[1]\n\n        def inc_(key1, key2, inc1, inc2):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n            in_[key2] += inc2\n\n            fxss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = (\n                fxss._d_sabr_d_k_or_f(\n                    Dual2(in_[\"k\"], [\"k\"], [], []),\n                    Dual2(in_[\"f\"], [\"f\"], [], []),\n                    dt(2002, 1, 1),\n                    False,\n                    1,\n                )[0]\n                * 100.0\n            )\n\n            # reset\n            fxss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        v_map = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"v0\", \"rho\": \"v1\", \"nu\": \"v2\"}\n\n        upup = inc_(pair[0], pair[1], 1e-3, 1e-3)\n        updown = inc_(pair[0], pair[1], 1e-3, -1e-3)\n        downup = inc_(pair[0], pair[1], -1e-3, 1e-3)\n        downdown = inc_(pair[0], pair[1], -1e-3, -1e-3)\n        expected = (upup + downdown - updown - downup) / 4e-6\n        result = gradient(base, [v_map[pair[0]], v_map[pair[1]]], order=2)[0][1]\n        assert abs(result - expected) < 1e-2\n\n    @pytest.mark.parametrize(\n        (\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33), (1.3399, 1.34), (1.34, 1.3401)]\n    )\n    @pytest.mark.parametrize(\"var\", [\"k\", \"f\", \"alpha\", \"rho\", \"nu\"])\n    def test_sabr_vol_same_finite_diff_second_order(self, k, f, var):\n        # Test all of the second order cross gradients using finite diff,\n        # for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"v\",\n            ad=2,\n        )\n\n        a = fxss.nodes.alpha\n        p = fxss.nodes.rho\n        v = fxss.nodes.nu\n\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = fxss.get_from_strike(Dual2(k, [\"k\"], [], []), Dual2(f, [\"f\"], [], []))[1]\n\n        def inc_(key1, inc1):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n\n            fxss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = (\n                fxss._d_sabr_d_k_or_f(\n                    Dual2(in_[\"k\"], [\"k\"], [], []),\n                    Dual2(in_[\"f\"], [\"f\"], [], []),\n                    dt(2002, 1, 1),\n                    False,\n                    1,\n                )[0]\n                * 100.0\n            )\n\n            # reset\n            fxss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        v_map = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"v0\", \"rho\": \"v1\", \"nu\": \"v2\"}\n\n        up = inc_(var, 1e-4)\n        down = inc_(var, -1e-4)\n        expected = (up + down - 2 * base) / 1e-8\n        result = gradient(base, [v_map[var]], order=2)[0][0]\n        assert abs(result - expected) < 5e-3\n\n    def test_sabr_vol_root_multi_duals_neighbourhood(self):\n        # test the SABR function when regular arithmetic operations produce an undefined 0/0\n        # value so AD has to be hard coded into the solution. This occurs when f == k.\n        # test by comparing derivatives with those captured at a nearby valid point\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n            ad=2,\n        )\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = fxss.get_from_strike(Dual2(1.34, [\"k\"], [], []), Dual2(1.34, [\"f\"], [], []))[1]\n        comparison1 = fxss.get_from_strike(Dual2(1.341, [\"k\"], [], []), Dual2(1.34, [\"f\"], [], []))[\n            1\n        ]\n\n        assert np.all(abs(base.dual - comparison1.dual) < 1e-1)\n        diff = base.dual2 - comparison1.dual2\n        dual2 = abs(diff) < 5e-1\n        assert np.all(dual2)\n\n    @pytest.mark.parametrize(\"param\", [\"alpha\", \"beta\", \"rho\", \"nu\"])\n    def test_missing_param_raises(self, param):\n        nodes = {\n            \"alpha\": 0.17431060,\n            \"beta\": 1.0,\n            \"rho\": -0.11268306,\n            \"nu\": 0.81694072,\n        }\n        nodes.pop(param)\n        with pytest.raises(ValueError):\n            FXSabrSmile(\n                nodes=nodes,\n                eval_date=dt(2001, 1, 1),\n                expiry=dt(2002, 1, 1),\n                id=\"vol\",\n            )\n\n    def test_non_iterable(self):\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n        )\n        with pytest.raises(TypeError):\n            _ = list(fxss)\n\n    def test_update_node_raises(self):\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n        )\n        with pytest.raises(KeyError, match=\"`key` is not in ``nodes``.\"):\n            fxss.update_node(\"bananas\", 12.0)\n\n    def test_set_ad_order_raises(self):\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n        )\n        with pytest.raises(ValueError, match=\"`order` can only be in {0, 1, 2} \"):\n            fxss._set_ad_order(12)\n\n    def test_get_node_vars_and_vector(self):\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"beta\": 1.0,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"myid\",\n        )\n        result = fxss._get_node_vars()\n        expected = (\"myid0\", \"myid1\", \"myid2\")\n        assert result == expected\n\n        result = fxss._get_node_vector()\n        expected = np.array([0.20, -0.1, 0.80])\n        assert np.all(result == expected)\n\n    def test_get_from_strike_expiry_raises(self):\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"beta\": 1.0,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n            ad=2,\n        )\n        with pytest.raises(ValueError, match=\"`expiry` of VolSmile and OptionPeriod do not match\"):\n            fxss.get_from_strike(k=1.0, f=1.0, z_w=1.0, expiry=(1999, 1, 1))\n\n    @pytest.mark.parametrize(\"k\", [1.2034, 1.2050, 1.3620, 1.5410, 1.5449])\n    def test_get_from_strike_ad_2(self, fxfo, k) -> None:\n        # Use finite diff to validate the 2nd order AD of the SABR function in alpha and rho.\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"beta\": 1.0,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n            ad=2,\n        )\n        fxfo._set_ad_order(2)\n        args = (\n            k,\n            fxfo.rate(\"eurusd\", dt(2023, 6, 20)),\n        )\n        pv00 = fxss.get_from_strike(*args)\n\n        fxss.update_node(\"alpha\", 0.20 + 0.00001)\n        fxss.update_node(\"rho\", -0.10 + 0.00001)\n        pv11 = fxss.get_from_strike(*args)\n\n        fxss.update_node(\"alpha\", 0.20 + 0.00001)\n        fxss.update_node(\"rho\", -0.10 - 0.00001)\n        pv1_1 = fxss.get_from_strike(*args)\n\n        fxss.update_node(\"alpha\", 0.20 - 0.00001)\n        fxss.update_node(\"rho\", -0.10 - 0.00001)\n        pv_1_1 = fxss.get_from_strike(*args)\n\n        fxss.update_node(\"alpha\", 0.20 - 0.00001)\n        fxss.update_node(\"rho\", -0.10 + 0.00001)\n        pv_11 = fxss.get_from_strike(*args)\n\n        finite_diff = (pv11[1] + pv_1_1[1] - pv1_1[1] - pv_11[1]) * 1e10 / 4.0\n        ad_grad = gradient(pv00[1], [\"vol0\", \"vol1\"], 2)[0, 1]\n\n        assert abs(finite_diff - ad_grad) < 1e-4\n\n    @pytest.mark.parametrize(\"p\", [-0.1, 0.15])\n    @pytest.mark.parametrize(\"a\", [0.05, 0.2])\n    @pytest.mark.parametrize(\"k_\", [1.15, 1.3620, 1.45, 1.3395])\n    def test_sabr_derivative(self, a, p, k_):\n        # test the analytic derivative of the SABR function with respect to k created by sympy\n        b = 1.0\n        v = 0.8\n        f = 1.3395\n        t = 1.0\n        k = Dual(k_, [\"k\"], [1.0])\n\n        sabr_vol, result = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p, v, 1)\n        expected = gradient(sabr_vol, [\"k\"])[0]\n\n        assert abs(result - expected) < 1e-13\n\n    @pytest.mark.parametrize(\"p\", [-0.1, 0.15])\n    @pytest.mark.parametrize(\"a\", [0.05, 0.2])\n    @pytest.mark.parametrize(\"f_\", [1.15, 1.3620, 1.45, 1.3395])\n    def test_sabr_derivative_f(self, a, p, f_):\n        # test the analytic derivative of the SABR function with respect to f created by sympy\n        # tests the regular case as well as the limit z->0 where a separate AD calculation o\n        # is branched.\n        b = 1.0\n        v = 0.8\n        k = 1.3395\n        t = 1.0\n        f = Dual(f_, [\"f\"], [1.0])\n\n        sabr_vol, result = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p, v, 2)\n        expected = gradient(sabr_vol, [\"f\"])[0]\n\n        assert abs(result - expected) < 1e-13\n\n    @pytest.mark.parametrize((\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33)])\n    def test_sabr_derivative_finite_diff_first_order(self, k, f):\n        # Test all of the first order gradients using finite diff, for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n            ad=2,\n        )\n        t = dt(2002, 1, 1)\n        base = fxss._d_sabr_d_k_or_f(\n            Dual2(k, [\"k\"], [1.0], []), Dual2(f, [\"f\"], [1.0], []), t, False, 1\n        )[1]\n\n        a = fxss.nodes.alpha\n        p = fxss.nodes.rho\n        v = fxss.nodes.nu\n\n        def inc_(key1, inc1):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n\n            fxss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = fxss._d_sabr_d_k_or_f(\n                Dual2(in_[\"k\"], [\"k\"], [], []),\n                Dual2(in_[\"f\"], [\"f\"], [], []),\n                dt(2002, 1, 1),\n                False,\n                1,\n            )[1]\n\n            # reset\n            fxss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        for key in [\"k\", \"f\", \"alpha\", \"rho\", \"nu\"]:\n            map_ = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"vol0\", \"rho\": \"vol1\", \"nu\": \"vol2\"}\n            up_ = inc_(key, 1e-5)\n            dw_ = inc_(key, -1e-5)\n            assert abs((up_ - dw_) / 2e-5 - gradient(base, [map_[key]])[0]) < 2e-3\n\n    @pytest.mark.parametrize(\n        (\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33), (1.3395, 1.34), (1.34, 1.3405)]\n    )\n    @pytest.mark.parametrize(\"pair\", list(combinations([\"k\", \"f\", \"alpha\", \"rho\", \"nu\"], 2)))\n    def test_sabr_derivative_cross_finite_diff_second_order(self, k, f, pair):\n        # Test all of the second order cross gradients using finite diff,\n        # for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"v\",\n            ad=2,\n        )\n\n        a = fxss.nodes.alpha\n        p = fxss.nodes.rho\n        v = fxss.nodes.nu\n\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = fxss._d_sabr_d_k_or_f(\n            Dual2(k, [\"k\"], [], []), Dual2(f, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n        )[1]\n\n        def inc_(key1, key2, inc1, inc2):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n            in_[key2] += inc2\n\n            fxss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = fxss._d_sabr_d_k_or_f(\n                Dual2(in_[\"k\"], [\"k\"], [], []),\n                Dual2(in_[\"f\"], [\"f\"], [], []),\n                dt(2002, 1, 1),\n                False,\n                1,\n            )[1]\n\n            # reset\n            fxss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        v_map = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"v0\", \"rho\": \"v1\", \"nu\": \"v2\"}\n\n        upup = inc_(pair[0], pair[1], 1e-3, 1e-3)\n        updown = inc_(pair[0], pair[1], 1e-3, -1e-3)\n        downup = inc_(pair[0], pair[1], -1e-3, 1e-3)\n        downdown = inc_(pair[0], pair[1], -1e-3, -1e-3)\n        expected = (upup + downdown - updown - downup) / 4e-6\n        result = gradient(base, [v_map[pair[0]], v_map[pair[1]]], order=2)[0][1]\n        assert abs(result - expected) < 5e-3\n\n    @pytest.mark.parametrize(\n        (\"k\", \"f\"),\n        [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33), (1.3395, 1.34), (1.34, 1.3405)],\n    )\n    @pytest.mark.parametrize(\"var\", [\"k\", \"f\", \"alpha\", \"rho\", \"nu\"])\n    def test_sabr_derivative_same_finite_diff_second_order(self, k, f, var):\n        # Test all of the second order cross gradients using finite diff,\n        # for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"v\",\n            ad=2,\n        )\n\n        a = fxss.nodes.alpha\n        p = fxss.nodes.rho\n        v = fxss.nodes.nu\n\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = fxss._d_sabr_d_k_or_f(\n            Dual2(k, [\"k\"], [], []), Dual2(f, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n        )[1]\n\n        def inc_(key1, inc1):\n            k_ = k\n            f_ = f\n            if key1 == \"k\":\n                k_ = k + inc1\n            elif key1 == \"f\":\n                f_ = f + inc1\n            else:\n                fxss.update_node(key1, getattr(fxss.nodes, key1) + inc1)\n                # fxss.nodes[key1] = fxss.nodes[key1] + inc1\n\n            _ = fxss._d_sabr_d_k_or_f(\n                Dual2(k_, [\"k\"], [], []), Dual2(f_, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n            )[1]\n\n            fxss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        v_map = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"v0\", \"rho\": \"v1\", \"nu\": \"v2\"}\n\n        up = inc_(var, 1e-3)\n        down = inc_(var, -1e-3)\n        expected = (up + down - 2 * base) / 1e-6\n        result = gradient(base, [v_map[var]], order=2)[0][0]\n        assert abs(result - expected) < 3e-3\n\n    def test_sabr_derivative_root_multi_duals_neighbourhood(self):\n        # test the SABR function when regular arithmetic operations produce an undefined 0/0\n        # value so AD has to be hard coded into the solution. This occurs when f == k.\n        # test by comparing derivatives with those captured at a nearby valid point\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n            ad=2,\n        )\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = fxss._d_sabr_d_k_or_f(\n            Dual2(1.34, [\"k\"], [], []), Dual2(1.34, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n        )[1]\n        comparison1 = fxss._d_sabr_d_k_or_f(\n            Dual2(1.341, [\"k\"], [], []), Dual2(1.34, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n        )[1]\n\n        assert np.all(abs(base.dual - comparison1.dual) < 5e-3)\n        diff = base.dual2 - comparison1.dual2\n        dual2 = abs(diff) < 3e-2\n        assert np.all(dual2)\n\n    def test_sabr_derivative_ad(self):\n        # Test is probably superceded by test_sabr_derivative_same/cross_finite_diff\n\n        # test the analytic derivative of the SABR function and its preservation of AD.\n        a = 0.10\n        b = 1.0\n        p = Dual2(-0.20, [\"p\"], [1.0], [0.0])\n        v = 0.8\n        f = 1.3395\n        t = 1.0\n        k = Dual2(1.45, [\"k\"], [1.0], [0.0])\n\n        _, result = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p, v, 1)\n        _, r1 = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p + 1e-4, v, 1)\n        _, r_1 = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p - 1e-4, v, 1)\n        expected = (r1 - r_1) / (2e-4)\n        result = gradient(result, [\"p\"])[0]\n        assert abs(result - expected) < 1e-9\n\n        _, result = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p, v, 1)\n        _, r1 = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p + 1e-4, v, 1)\n        _, r_1 = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p - 1e-4, v, 1)\n        expected = (r1 - 2 * result + r_1) / (1e-8)\n        result = gradient(result, [\"p\"], order=2)[0][0]\n        assert abs(result - expected) < 1e-8\n\n    def test_sabr_derivative_root(self):\n        # Test is probably superceded by test_sabr_derivative_same/cross_finite_diff\n\n        # test the analytic derivative of the SABR function when f == k\n        a = 0.10\n        b = 1.0\n        p = -0.20\n        v = 0.8\n        f = 1.3395\n        t = 1.0\n        k = Dual(1.3395, [\"k\"], [1.0])\n\n        sabr_vol, result = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p, v, 1)\n        expected = gradient(sabr_vol, [\"k\"])[0]\n\n        assert abs(result - expected) < 1e-13\n\n    def test_sabr_derivative_root_ad(self):\n        # Test is probably superceded by test_sabr_derivative_same/cross_finite_diff\n\n        # test the analytic derivative of the SABR function when f == k, and its preservation of AD.\n        a = 0.10\n        b = 1.0\n        p = Dual2(-0.20, [\"p\"], [1.0], [0.0])\n        v = 0.8\n        f = 1.3395\n        t = 1.0\n        k = Dual2(1.3395, [\"k\"], [1.0], [0.0])\n\n        _, result = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p, v, 1)\n        _, r1 = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p + 1e-4, v, 1)\n        _, r_1 = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p - 1e-4, v, 1)\n        expected = (r1 - r_1) / (2e-4)\n        result = gradient(result, [\"p\"])[0]\n        assert abs(result - expected) < 1e-9\n\n        _, result = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p, v, 1)\n        _, r1 = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p + 1e-4, v, 1)\n        _, r_1 = _SabrModel._d_sabr_d_k_or_f(k, f, t, a, b, p - 1e-4, v, 1)\n        expected = (r1 - 2 * result + r_1) / (1e-8)\n        result = gradient(result, [\"p\"], order=2)[0][0]\n        assert abs(result - expected) < 1e-8\n\n    def test_f_with_fxforwards(self, fxfo):\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 4, 16),\n            id=\"v\",\n            ad=1,\n            pair=\"eurusd\",\n            calendar=\"tgt|fed\",\n        )\n        result = fxss.get_from_strike(1.02, fxfo)[1]\n        expected = 17.803563\n        assert abs(result - expected) < 1e-6\n\n    def test_f_with_fxrates_raises(self, fxfo):\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 4, 16),\n            id=\"v\",\n            ad=1,\n            pair=\"eurusd\",\n            calendar=\"tgt|fed\",\n        )\n        with pytest.raises(ValueError):\n            fxss.get_from_strike(1.02, FXRates({\"eurusd\": 1.06}))\n\n    def test_plot_domain(self):\n        ss = FXSabrSmile(\n            eval_date=dt(2024, 5, 28),\n            expiry=dt(2054, 5, 28),\n            nodes={\"alpha\": 0.02, \"beta\": 1.0, \"rho\": 0.01, \"nu\": 0.05},\n        )\n        ax, fig, lines = ss.plot(f=1.60)\n        assert abs(lines[0]._x[0] - 1.3427) < 1e-4\n        assert abs(lines[0]._x[-1] - 1.9299) < 1e-4\n        assert abs(lines[0]._y[0] - 2.0698) < 1e-4\n        assert abs(lines[0]._y[-1] - 2.0865) < 1e-4\n\n    def test_get_from_strike_raises_fx(self, fxfo):\n        fxss = FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 4, 16),\n            id=\"v\",\n            ad=1,\n            calendar=\"tgt|fed\",\n        )\n        with pytest.raises(ValueError, match=\"`FXSabrSmile` must be specified with a `pair` arg\"):\n            fxss.get_from_strike(1.02, fxfo)\n\n    def test_solver_variable_numbers(self):\n        from rateslib import IRS, FXBrokerFly, FXCall, FXRiskReversal, FXStraddle, FXSwap, Solver\n\n        usdusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"nyc\", id=\"usdusd\")\n        eureur = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"tgt\", id=\"eureur\")\n        eurusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, id=\"eurusd\")\n\n        # Create an FX Forward market with spot FX rate data\n        fxr = FXRates({\"eurusd\": 1.0760}, settlement=dt(2024, 5, 9))\n        fxf = FXForwards(\n            fx_rates=fxr,\n            fx_curves={\"eureur\": eureur, \"usdusd\": usdusd, \"eurusd\": eurusd},\n        )\n\n        pre_solver = Solver(\n            curves=[eureur, eurusd, usdusd],\n            instruments=[\n                IRS(dt(2024, 5, 9), \"3W\", spec=\"eur_irs\", curves=\"eureur\"),\n                IRS(dt(2024, 5, 9), \"3W\", spec=\"usd_irs\", curves=\"usdusd\"),\n                FXSwap(\n                    dt(2024, 5, 9), \"3W\", pair=\"eurusd\", curves=[None, \"eurusd\", None, \"usdusd\"]\n                ),\n            ],\n            s=[3.90, 5.32, 8.85],\n            fx=fxf,\n            id=\"rates_sv\",\n        )\n\n        dv_smile = FXSabrSmile(\n            nodes={\"alpha\": 0.05, \"beta\": 1.0, \"rho\": 0.01, \"nu\": 0.03},\n            eval_date=dt(2024, 5, 7),\n            expiry=dt(2024, 5, 28),\n            id=\"eurusd_3w_smile\",\n            pair=\"eurusd\",\n        )\n        option_args = dict(\n            pair=\"eurusd\",\n            expiry=dt(2024, 5, 28),\n            calendar=\"tgt|fed\",\n            delta_type=\"spot\",\n            curves=[\"eurusd\", \"usdusd\"],\n            vol=\"eurusd_3w_smile\",\n        )\n\n        dv_solver = Solver(\n            pre_solvers=[pre_solver],\n            curves=[dv_smile],\n            instruments=[\n                FXStraddle(strike=\"atm_delta\", **option_args),\n                FXRiskReversal(strike=(\"-25d\", \"25d\"), **option_args),\n                FXRiskReversal(strike=(\"-10d\", \"10d\"), **option_args),\n                FXBrokerFly(strike=((\"-25d\", \"25d\"), \"atm_delta\"), **option_args),\n                FXBrokerFly(strike=((\"-10d\", \"10d\"), \"atm_delta\"), **option_args),\n            ],\n            s=[5.493, -0.157, -0.289, 0.071, 0.238],\n            fx=fxf,\n            id=\"dv_solver\",\n        )\n\n        fc = FXCall(\n            expiry=dt(2024, 5, 28),\n            pair=\"eurusd\",\n            strike=1.07,\n            notional=100e6,\n            curves=[\"eurusd\", \"usdusd\"],\n            vol=\"eurusd_3w_smile\",\n            premium=98.216647 * 1e8 / 1e4,\n            premium_ccy=\"usd\",\n            delta_type=\"spot\",\n        )\n        fc.delta(solver=dv_solver)\n\n    @pytest.mark.parametrize(\"a\", [0.02, 0.06])\n    @pytest.mark.parametrize(\"b\", [0.0, 0.4, 0.65, 1.0])\n    @pytest.mark.parametrize(\"p\", [-0.1, 0.1])\n    @pytest.mark.parametrize(\"v\", [0.05, 0.15])\n    @pytest.mark.parametrize(\"k\", [1.05, 1.25, 1.6])\n    def test_sabr_function_values(self, a, b, p, v, k):\n        fxs = FXSabrSmile(\n            nodes={\"alpha\": a, \"beta\": b, \"rho\": p, \"nu\": v},\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            ad=0,\n        )\n\n        # this code is taken from PySabr, another library implementing SABR.\n        # it is used as a benchmark\n        def _x(rho, z):\n            \"\"\"Return function x used in Hagan's 2002 SABR lognormal vol expansion.\"\"\"\n            a = (1 - 2 * rho * z + z**2) ** 0.5 + z - rho\n            b = 1 - rho\n            return np.log(a / b)\n\n        def lognormal_vol(k, f, t, alpha, beta, rho, volvol):\n            \"\"\"\n            Hagan's 2002 SABR lognormal vol expansion.\n\n            The strike k can be a scalar or an array, the function will return an array\n            of lognormal vols.\n            \"\"\"\n            # Negative strikes or forwards\n            if k <= 0 or f <= 0:\n                return 0.0\n            eps = 1e-07\n            logfk = np.log(f / k)\n            fkbeta = (f * k) ** (1 - beta)\n            a = (1 - beta) ** 2 * alpha**2 / (24 * fkbeta)\n            b = 0.25 * rho * beta * volvol * alpha / fkbeta**0.5\n            c = (2 - 3 * rho**2) * volvol**2 / 24\n            d = fkbeta**0.5\n            v = (1 - beta) ** 2 * logfk**2 / 24\n            w = (1 - beta) ** 4 * logfk**4 / 1920\n            z = volvol * fkbeta**0.5 * logfk / alpha\n            # if |z| > eps\n            if abs(z) > eps:\n                vz = alpha * z * (1 + (a + b + c) * t) / (d * (1 + v + w) * _x(rho, z))\n                return vz\n            # if |z| <= eps\n            else:\n                v0 = alpha * (1 + (a + b + c) * t) / (d * (1 + v + w))\n                return v0\n\n        expected = lognormal_vol(k, 1.25, 1.0, a, b, p, v)\n        result = fxs.get_from_strike(k, 1.25)[1] / 100.0\n\n        assert abs(result - expected) < 1e-4\n\n\nclass TestFXSabrSurface:\n    @pytest.mark.parametrize(\n        \"expiries\",\n        [\n            [dt(2024, 5, 29), dt(2024, 7, 29), dt(2024, 6, 29)],\n            [dt(2024, 5, 29), dt(2024, 6, 29), dt(2024, 6, 29)],\n        ],\n    )\n    def test_unsorted_expiries(self, expiries):\n        with pytest.raises(ValueError, match=\"Surface `expiries` are not sorted or contain dupl\"):\n            FXSabrSurface(\n                eval_date=dt(2024, 5, 28),\n                expiries=expiries,\n                node_values=[[0.05, 1.0, 0.01, 0.15]] * 3,\n                pair=\"eurusd\",\n                delivery_lag=2,\n                calendar=\"tgt|fed\",\n                id=\"eurusd_vol\",\n            )\n\n    def test_z_eurusd_surface_cookbook(self):\n        from rateslib import (\n            IRS,\n            XCS,\n            FXBrokerFly,\n            FXRiskReversal,\n            FXStraddle,\n            FXSwap,\n            Solver,\n            add_tenor,\n        )\n\n        fxr = FXRates({\"eurusd\": 1.0867}, settlement=dt(2024, 5, 30))\n        mkt_data = DataFrame(\n            data=[\n                [\n                    \"1w\",\n                    3.9035,\n                    5.3267,\n                    3.33,\n                ],\n                [\n                    \"2w\",\n                    3.9046,\n                    5.3257,\n                    6.37,\n                ],\n                [\n                    \"3w\",\n                    3.8271,\n                    5.3232,\n                    9.83,\n                ],\n                [\n                    \"1m\",\n                    3.7817,\n                    5.3191,\n                    13.78,\n                ],\n                [\n                    \"2m\",\n                    3.7204,\n                    5.3232,\n                    30.04,\n                ],\n                [\"3m\", 3.667, 5.3185, 45.85, -2.5],\n                [\n                    \"4m\",\n                    3.6252,\n                    5.3307,\n                    61.95,\n                ],\n                [\n                    \"5m\",\n                    3.587,\n                    5.3098,\n                    78.1,\n                ],\n                [\"6m\", 3.5803, 5.3109, 94.25, -3.125],\n                [\n                    \"7m\",\n                    3.5626,\n                    5.301,\n                    110.82,\n                ],\n                [\n                    \"8m\",\n                    3.531,\n                    5.2768,\n                    130.45,\n                ],\n                [\"9m\", 3.5089, 5.2614, 145.6, -7.25],\n                [\n                    \"10m\",\n                    3.4842,\n                    5.2412,\n                    162.05,\n                ],\n                [\n                    \"11m\",\n                    3.4563,\n                    5.2144,\n                    178,\n                ],\n                [\"1y\", 3.4336, 5.1936, None, -6.75],\n                [\"15m\", 3.3412, 5.0729, None, -6.75],\n                [\"18m\", 3.2606, 4.9694, None, -6.75],\n                [\"21m\", 3.1897, 4.8797, None, -7.75],\n                [\"2y\", 3.1283, 4.8022, None, -7.875],\n                [\"3y\", 2.9254, 4.535, None, -9],\n                [\"4y\", 2.81, 4.364, None, -10.125],\n                [\"5y\", 2.7252, 4.256, None, -11.125],\n                [\"6y\", 2.6773, 4.192, None, -12.125],\n                [\"7y\", 2.6541, 4.151, None, -13],\n                [\"8y\", 2.6431, 4.122, None, -13.625],\n                [\"9y\", 2.6466, 4.103, None, -14.25],\n                [\"10y\", 2.6562, 4.091, None, -14.875],\n                [\"12y\", 2.6835, 4.084, None, -16.125],\n                [\"15y\", 2.7197, 4.08, None, -17],\n                [\"20y\", 2.6849, 4.04, None, -16],\n                [\"25y\", 2.6032, 3.946, None, -12.75],\n                [\"30y\", 2.5217, 3.847, None, -9.5],\n            ],\n            columns=[\"tenor\", \"estr\", \"sofr\", \"fx_swap\", \"xccy\"],\n        )\n        eur = Curve(\n            nodes={\n                dt(2024, 5, 28): 1.0,\n                **{add_tenor(dt(2024, 5, 30), _, \"F\", \"tgt\"): 1.0 for _ in mkt_data[\"tenor\"]},\n            },\n            calendar=\"tgt\",\n            interpolation=\"log_linear\",\n            convention=\"act360\",\n            id=\"estr\",\n        )\n        usd = Curve(\n            nodes={\n                dt(2024, 5, 28): 1.0,\n                **{add_tenor(dt(2024, 5, 30), _, \"F\", \"nyc\"): 1.0 for _ in mkt_data[\"tenor\"]},\n            },\n            calendar=\"nyc\",\n            interpolation=\"log_linear\",\n            convention=\"act360\",\n            id=\"sofr\",\n        )\n        eurusd = Curve(\n            nodes={\n                dt(2024, 5, 28): 1.0,\n                **{add_tenor(dt(2024, 5, 30), _, \"F\", \"tgt\"): 1.0 for _ in mkt_data[\"tenor\"]},\n            },\n            interpolation=\"log_linear\",\n            convention=\"act360\",\n            id=\"eurusd\",\n        )\n        fxf = FXForwards(fx_rates=fxr, fx_curves={\"eureur\": eur, \"eurusd\": eurusd, \"usdusd\": usd})\n        estr_swaps = [\n            IRS(dt(2024, 5, 30), _, spec=\"eur_irs\", curves=\"estr\") for _ in mkt_data[\"tenor\"]\n        ]\n        estr_rates = mkt_data[\"estr\"].tolist()\n        labels = mkt_data[\"tenor\"].to_list()\n        sofr_swaps = [\n            IRS(dt(2024, 5, 30), _, spec=\"usd_irs\", curves=\"sofr\") for _ in mkt_data[\"tenor\"]\n        ]\n        sofr_rates = mkt_data[\"sofr\"].tolist()\n        eur_solver = Solver(\n            curves=[eur],\n            instruments=estr_swaps,\n            s=estr_rates,\n            fx=fxf,\n            instrument_labels=labels,\n            id=\"eur\",\n        )\n        usd_solver = Solver(\n            curves=[usd],\n            instruments=sofr_swaps,\n            s=sofr_rates,\n            fx=fxf,\n            instrument_labels=labels,\n            id=\"usd\",\n        )\n        fxswaps = [\n            FXSwap(dt(2024, 5, 30), _, pair=\"eurusd\", curves=[\"eurusd\", \"sofr\"])\n            for _ in mkt_data[\"tenor\"][0:14]\n        ]\n        fxswap_rates = mkt_data[\"fx_swap\"][0:14].tolist()\n        xcs = [\n            XCS(dt(2024, 5, 30), _, spec=\"eurusd_xcs\", curves=[\"estr\", \"eurusd\", \"sofr\", \"sofr\"])\n            for _ in mkt_data[\"tenor\"][14:]\n        ]\n        xcs_rates = mkt_data[\"xccy\"][14:].tolist()\n        fx_solver = Solver(\n            pre_solvers=[eur_solver, usd_solver],\n            curves=[eurusd],\n            instruments=fxswaps + xcs,\n            s=fxswap_rates + xcs_rates,\n            fx=fxf,\n            instrument_labels=labels,\n            id=\"eurusd_xccy\",\n        )\n        vol_data = DataFrame(\n            data=[\n                [\"1w\", 4.535, -0.047, 0.07, -0.097, 0.252],\n                [\"2w\", 5.168, -0.082, 0.077, -0.165, 0.24],\n                [\"3w\", 5.127, -0.175, 0.07, -0.26, 0.233],\n                [\"1m\", 5.195, -0.2, 0.07, -0.295, 0.235],\n                [\"2m\", 5.237, -0.28, 0.087, -0.535, 0.295],\n                [\"3m\", 5.257, -0.363, 0.1, -0.705, 0.35],\n                [\"4m\", 5.598, -0.47, 0.123, -0.915, 0.422],\n                [\"5m\", 5.776, -0.528, 0.133, -1.032, 0.463],\n                [\"6m\", 5.92, -0.565, 0.14, -1.11, 0.49],\n                [\"9m\", 6.01, -0.713, 0.182, -1.405, 0.645],\n                [\"1y\", 6.155, -0.808, 0.23, -1.585, 0.795],\n                [\"18m\", 6.408, -0.812, 0.248, -1.588, 0.868],\n                [\"2y\", 6.525, -0.808, 0.257, -1.58, 0.9],\n                [\"3y\", 6.718, -0.733, 0.265, -1.45, 0.89],\n                [\"4y\", 7.025, -0.665, 0.265, -1.31, 0.885],\n                [\"5y\", 7.26, -0.62, 0.26, -1.225, 0.89],\n                [\"6y\", 7.508, -0.516, 0.27, -0.989, 0.94],\n                [\"7y\", 7.68, -0.442, 0.278, -0.815, 0.975],\n                [\"10y\", 8.115, -0.267, 0.288, -0.51, 1.035],\n                [\"15y\", 8.652, -0.325, 0.362, -0.4, 1.195],\n                [\"20y\", 8.651, -0.078, 0.343, -0.303, 1.186],\n                [\"25y\", 8.65, -0.029, 0.342, -0.218, 1.178],\n                [\"30y\", 8.65, 0.014, 0.341, -0.142, 1.171],\n            ],\n            columns=[\"tenor\", \"atm\", \"25drr\", \"25dbf\", \"10drr\", \"10dbf\"],\n        )\n        vol_data[\"expiry\"] = [add_tenor(dt(2024, 5, 28), _, \"MF\", \"tgt\") for _ in vol_data[\"tenor\"]]\n        surface = FXSabrSurface(\n            eval_date=dt(2024, 5, 28),\n            expiries=list(vol_data[\"expiry\"]),\n            node_values=[[0.05, 1.0, 0.01, 0.15]] * 23,\n            pair=\"eurusd\",\n            delivery_lag=2,\n            calendar=\"tgt|fed\",\n            id=\"eurusd_vol\",\n        )\n        fx_args = dict(\n            pair=\"eurusd\",\n            curves=[\"eurusd\", \"sofr\"],\n            calendar=\"tgt\",\n            delivery_lag=2,\n            payment_lag=2,\n            eval_date=dt(2024, 5, 28),\n            modifier=\"MF\",\n            premium_ccy=\"usd\",\n            vol=\"eurusd_vol\",\n        )\n\n        instruments_le_1y, rates_le_1y, labels_le_1y = [], [], []\n        for row in range(11):\n            instruments_le_1y.extend(\n                [\n                    FXStraddle(\n                        strike=\"atm_delta\",\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"spot\",\n                        **fx_args,\n                    ),\n                    FXRiskReversal(\n                        strike=(\"-25d\", \"25d\"),\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"spot\",\n                        **fx_args,\n                    ),\n                    FXBrokerFly(\n                        strike=((\"-25d\", \"25d\"), \"atm_delta\"),\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"spot\",\n                        **fx_args,\n                    ),\n                    FXRiskReversal(\n                        strike=(\"-10d\", \"10d\"),\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"spot\",\n                        **fx_args,\n                    ),\n                    FXBrokerFly(\n                        strike=((\"-10d\", \"10d\"), \"atm_delta\"),\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"spot\",\n                        **fx_args,\n                    ),\n                ]\n            )\n            rates_le_1y.extend(\n                [\n                    vol_data[\"atm\"][row],\n                    vol_data[\"25drr\"][row],\n                    vol_data[\"25dbf\"][row],\n                    vol_data[\"10drr\"][row],\n                    vol_data[\"10dbf\"][row],\n                ]\n            )\n            labels_le_1y.extend(\n                [f\"atm_{row}\", f\"25drr_{row}\", f\"25dbf_{row}\", f\"10drr_{row}\", f\"10dbf_{row}\"]\n            )\n\n        instruments_gt_1y, rates_gt_1y, labels_gt_1y = [], [], []\n        for row in range(11, 23):\n            instruments_gt_1y.extend(\n                [\n                    FXStraddle(\n                        strike=\"atm_delta\",\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"forward\",\n                        **fx_args,\n                    ),\n                    FXRiskReversal(\n                        strike=(\"-25d\", \"25d\"),\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"forward\",\n                        **fx_args,\n                    ),\n                    FXBrokerFly(\n                        strike=((\"-25d\", \"25d\"), \"atm_delta\"),\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"forward\",\n                        **fx_args,\n                    ),\n                    FXRiskReversal(\n                        strike=(\"-10d\", \"10d\"),\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"forward\",\n                        **fx_args,\n                    ),\n                    FXBrokerFly(\n                        strike=((\"-10d\", \"10d\"), \"atm_delta\"),\n                        expiry=vol_data[\"expiry\"][row],\n                        delta_type=\"forward\",\n                        **fx_args,\n                    ),\n                ]\n            )\n            rates_gt_1y.extend(\n                [\n                    vol_data[\"atm\"][row],\n                    vol_data[\"25drr\"][row],\n                    vol_data[\"25dbf\"][row],\n                    vol_data[\"10drr\"][row],\n                    vol_data[\"10dbf\"][row],\n                ]\n            )\n            labels_gt_1y.extend(\n                [f\"atm_{row}\", f\"25drr_{row}\", f\"25dbf_{row}\", f\"10drr_{row}\", f\"10dbf_{row}\"]\n            )\n\n        Solver(\n            surfaces=[surface],\n            instruments=instruments_le_1y + instruments_gt_1y,\n            s=rates_le_1y + rates_gt_1y,\n            instrument_labels=labels_le_1y + labels_gt_1y,\n            fx=fxf,\n            pre_solvers=[fx_solver],\n            id=\"eurusd_vol\",\n        )\n\n    def test_k_derivative_interpolation(self, fxfo):\n        # test the derivative of the k-interpolated volatility of a SabrSurface against Fwd diff\n        # and AD.\n        surface = FXSabrSurface(\n            eval_date=dt(2023, 3, 16),\n            expiries=[dt(2025, 5, 28), dt(2026, 5, 28)],\n            node_values=[\n                [0.05, 1.0, 0.01, 0.15],\n                [0.06, 1.0, 0.02, 0.20],\n            ],\n            pair=\"eurusd\",\n            delivery_lag=2,\n            calendar=\"tgt|fed\",\n            id=\"eurusd_vol\",\n        )\n        k = Dual(1.10, [\"k\"], [1.0])\n        base = surface.get_from_strike(k, fxfo, dt(2025, 12, 12))[1]\n        expected_ad = gradient(base, vars=[\"k\"])[0]\n        expected_fwd_diff = (\n            surface.get_from_strike(k + 0.0001, fxfo, dt(2025, 12, 12))[1] - base\n        ) / 1e-4\n        result = surface._d_sabr_d_k_or_f(k, fxfo, dt(2025, 12, 12), False, 1)[1] * 100.0\n        assert abs(expected_fwd_diff - result) < 1e-3\n        assert abs(expected_ad - result) < 1e-3\n\n    @pytest.mark.parametrize(\n        (\"k\", \"expiry\", \"expected\"),\n        [\n            (1.10, dt(2023, 4, 15), 5.011351023668074),\n            (1.10, dt(2023, 6, 28), 5.011351023668074),\n            (1.10, dt(2023, 7, 15), 5.333915841859923),\n            (1.10, dt(2023, 9, 28), 6.021827601466909),\n            (1.10, dt(2023, 10, 28), 6.022252380963102),\n        ],\n    )\n    def test_get_from_strike(self, fxfo, k, expiry, expected):\n        # test different branches for expiry\n        surface = FXSabrSurface(\n            eval_date=dt(2023, 3, 16),\n            expiries=[dt(2023, 6, 28), dt(2023, 9, 28)],\n            node_values=[\n                [0.05, 1.0, 0.01, 0.15],\n                [0.06, 1.0, 0.02, 0.20],\n            ],\n            pair=\"eurusd\",\n            delivery_lag=2,\n            calendar=\"tgt|fed\",\n            id=\"eurusd_vol\",\n        )\n        result = surface.get_from_strike(k, fxfo, expiry)\n        assert result[0] == 0.0\n        assert abs(result[1] - expected) < 1e-14\n        assert result[2] == k\n\n    def test_variables_on_extrapolated_sabr_smiles_before(self, fxfo):\n        # assert that vars on extrapolated smiles reference the underlying smiles vars\n        fxss = FXSabrSurface(\n            eval_date=dt(2023, 3, 16),\n            expiries=[dt(2023, 7, 15), dt(2023, 9, 15)],\n            node_values=[[0.05, 1.0, 0.01, 0.15]] * 2,\n            pair=\"eurusd\",\n            delivery_lag=2,\n            calendar=\"tgt|fed\",\n            id=\"v\",\n            ad=1,\n        )\n        result = fxss.get_from_strike(1.10, fxfo, dt(2023, 4, 14))[1]\n        assert result.vars == [\"v_0_0\", \"v_0_1\", \"v_0_2\", \"fx_eurusd\"]\n\n    def test_variables_on_extrapolated_sabr_smiles_after(self, fxfo):\n        # assert that vars on extrapolated smiles reference the underlying smiles vars\n        fxss = FXSabrSurface(\n            eval_date=dt(2023, 3, 16),\n            expiries=[dt(2023, 7, 15), dt(2023, 9, 15)],\n            node_values=[[0.05, 1.0, 0.01, 0.15]] * 2,\n            pair=\"eurusd\",\n            delivery_lag=2,\n            calendar=\"tgt|fed\",\n            id=\"v\",\n            ad=1,\n        )\n        result = fxss.get_from_strike(1.10, fxfo, dt(2024, 4, 14))[1]\n        assert result.vars == [\"v_1_0\", \"v_1_1\", \"v_1_2\", \"fx_eurusd\"]\n\n    def test_update_state(self):\n        fxss = FXSabrSurface(\n            eval_date=dt(2023, 3, 16),\n            expiries=[dt(2023, 7, 15), dt(2023, 9, 15)],\n            node_values=[[0.05, 1.0, 0.01, 0.15]] * 2,\n            pair=\"eurusd\",\n            delivery_lag=2,\n            calendar=\"tgt|fed\",\n            id=\"v\",\n            ad=1,\n        )\n        state_ = fxss._state\n        fxss.smiles[1].update_node(\"alpha\", 0.06)\n        assert state_ != fxss._get_composited_state()\n\n        # calling get from strike will validate\n        fxss.get_from_strike(1.1, 1.1, dt(2023, 7, 15))\n        assert fxss._state == fxss._get_composited_state()\n\n    @pytest.mark.parametrize(\"smile_expiry\", [dt(2026, 5, 1), dt(2026, 6, 9), dt(2026, 7, 1)])\n    def test_flat_surface_and_get_smile_one_expiry(self, smile_expiry):\n        # gh 911\n        anchor = dt(2025, 6, 9)\n        expiry = dt(2026, 6, 9)\n\n        surf = FXSabrSurface(\n            eval_date=anchor,\n            expiries=[expiry],\n            node_values=[[0.10, 1.0, 0.0, 0.0]],\n        )\n\n        result = surf.get_from_strike(1.0, 1.10, smile_expiry)[1]\n        assert abs(result - 10.0) < 1e-13\n\n    @pytest.mark.parametrize(\"option_expiry\", [dt(2026, 5, 1), dt(2026, 6, 9), dt(2026, 7, 1)])\n    def test_flat_surface_option_strike_delta(self, option_expiry):\n        surf = FXSabrSurface(\n            eval_date=dt(2025, 6, 9),\n            expiries=[dt(2026, 6, 9)],\n            node_values=[[0.10, 1.0, 0.0, 0.0]],\n        )\n        fxo = FXCallPeriod(\n            pair=\"eurusd\",\n            expiry=option_expiry,\n            delivery=option_expiry,\n            strike=NoInput(0),\n            delta_type=\"forward\",\n        )\n        result = fxo._index_vol_and_strike_from_delta_sabr(0.25, \"forward\", surf, 1, 1.10)\n        assert abs(result[1] - 10.0) < 1e-13\n\n        result = fxo._index_vol_and_strike_from_atm_sabr(1.10, 0.50, surf)\n        assert abs(result[1] - 10.0) < 1e-13\n\n\nclass TestStateAndCache:\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            FXDeltaVolSmile(\n                nodes={0.25: 10.0, 0.5: 10.0, 0.75: 11.0},\n                delta_type=\"forward\",\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                id=\"vol\",\n            ),\n            FXSabrSmile(\n                nodes={\n                    \"alpha\": 0.17431060,\n                    \"beta\": 1.0,\n                    \"rho\": -0.11268306,\n                    \"nu\": 0.81694072,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                id=\"vol\",\n            ),\n        ],\n    )\n    @pytest.mark.parametrize((\"method\", \"args\"), [(\"_set_ad_order\", (1,))])\n    def test_method_does_not_change_state(self, curve, method, args):\n        before = curve._state\n        getattr(curve, method)(*args)\n        after = curve._state\n        assert before == after\n\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            FXDeltaVolSmile(\n                nodes={0.25: 10.0, 0.5: 10.0, 0.75: 11.0},\n                delta_type=\"forward\",\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                id=\"vol\",\n            ),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98, 0.99], 1)),\n            (\"update_node\", (0.25, 0.98)),\n            (\"update\", ({0.25: 10.0, 0.5: 10.0, 0.75: 10.1},)),\n        ],\n    )\n    def test_method_changes_state(self, curve, method, args):\n        before = curve._state\n        getattr(curve, method)(*args)\n        after = curve._state\n        assert before != after\n\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            FXSabrSmile(\n                nodes={\n                    \"alpha\": 0.17431060,\n                    \"beta\": 1.0,\n                    \"rho\": -0.11268306,\n                    \"nu\": 0.81694072,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                id=\"vol\",\n            )\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98, 0.99], 1)),\n            (\"update_node\", (\"alpha\", 0.98)),\n        ],\n    )\n    def test_method_changes_state_sabr(self, curve, method, args):\n        before = curve._state\n        getattr(curve, method)(*args)\n        after = curve._state\n        assert before != after\n\n    def test_populate_cache(self):\n        # objects have yet to implement cache handling\n        pass\n\n    def test_method_clears_cache(self):\n        # objects have yet to implement cache handling\n        pass\n\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98], 1)),\n            (\"_set_ad_order\", (2,)),\n        ],\n    )\n    def test_surface_clear_cache(self, method, args):\n        surf = FXDeltaVolSurface(\n            expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n            delta_indexes=[0.5],\n            node_values=[[10.0], [9.0]],\n            eval_date=dt(1999, 1, 1),\n            delta_type=\"forward\",\n        )\n        surf.get_smile(dt(2000, 3, 1))\n        assert dt(2000, 3, 1) in surf._cache\n\n        getattr(surf, method)(*args)\n        assert len(surf._cache) == 0\n\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"get_from_strike\", (1.0, 1.0, dt(2000, 5, 3), NoInput(0))),\n            (\"_get_index\", (0.9, dt(2000, 5, 3))),\n            (\"get_smile\", (dt(2000, 5, 3),)),\n        ],\n    )\n    def test_surface_populate_cache(self, method, args):\n        surf = FXDeltaVolSurface(\n            expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n            delta_indexes=[0.5],\n            node_values=[[10.0], [9.0]],\n            eval_date=dt(1999, 1, 1),\n            delta_type=\"forward\",\n        )\n        before = surf._cache_len\n        getattr(surf, method)(*args)\n        assert surf._cache_len == before + 1\n\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98, 0.99, 0.99, 0.98, 0.99], 1)),\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"surface\",\n        [\n            FXDeltaVolSurface(\n                expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n                delta_indexes=[0.25, 0.5, 0.75],\n                node_values=[[10.0, 9.0, 8.0], [9.0, 8.0, 7.0]],\n                eval_date=dt(1999, 1, 1),\n                delta_type=\"forward\",\n            ),\n            FXSabrSurface(\n                expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n                node_values=[[10.0, 1.0, 8.0, 9.0], [9.0, 1.0, 8.0, 7.0]],\n                eval_date=dt(1999, 1, 1),\n            ),\n        ],\n    )\n    def test_surface_change_state(self, method, args, surface):\n        pre_state = surface._state\n        getattr(surface, method)(*args)\n        assert surface._state != pre_state\n\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_ad_order\", (2,)),\n        ],\n    )\n    @pytest.mark.parametrize(\n        \"surface\",\n        [\n            FXDeltaVolSurface(\n                expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n                delta_indexes=[0.25, 0.5, 0.75],\n                node_values=[[10.0, 9.0, 8.0], [9.0, 8.0, 7.0]],\n                eval_date=dt(1999, 1, 1),\n                delta_type=\"forward\",\n            ),\n            FXSabrSurface(\n                expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n                node_values=[[10.0, 1.0, 8.0, 9.0], [9.0, 1.0, 8.0, 7.0]],\n                eval_date=dt(1999, 1, 1),\n            ),\n        ],\n    )\n    def test_surface_maintain_state(self, method, args, surface):\n        pre_state = surface._state\n        getattr(surface, method)(*args)\n        assert surface._state == pre_state\n\n    def test_surface_validate_states(self):\n        # test the get_smile method validates the states after a mutation\n        surf = FXDeltaVolSurface(\n            expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n            delta_indexes=[0.5],\n            node_values=[[10.0], [9.0]],\n            eval_date=dt(1999, 1, 1),\n            delta_type=\"forward\",\n        )\n        pre_state = surf._state\n        surf.smiles[0].update_node(0.5, 11.0)\n        surf.get_smile(dt(2000, 1, 9))\n        post_state = surf._state\n        assert pre_state != post_state  # validate states has been run and updated the state.\n\n    @pytest.mark.parametrize(\n        \"smile\",\n        [\n            FXDeltaVolSmile(\n                nodes={0.25: 10.0, 0.5: 10.0, 0.75: 11.0},\n                delta_type=\"forward\",\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                id=\"vol\",\n            ),\n            FXSabrSmile(\n                nodes={\n                    \"alpha\": 0.17431060,\n                    \"beta\": 1.0,\n                    \"rho\": -0.11268306,\n                    \"nu\": 0.81694072,\n                },\n                eval_date=dt(2023, 3, 16),\n                expiry=dt(2023, 6, 16),\n                id=\"vol\",\n            ),\n        ],\n    )\n    def test_initialisation_state_smile(self, smile):\n        assert smile._state != 0\n\n    def test_initialisation_state_surface(self):\n        surf = FXDeltaVolSurface(\n            expiries=[dt(2000, 1, 1), dt(2001, 1, 1)],\n            delta_indexes=[0.5],\n            node_values=[[10.0], [9.0]],\n            eval_date=dt(1999, 1, 1),\n            delta_type=\"forward\",\n        )\n        assert surf._state != 0\n\n\ndef test_validate_delta_type() -> None:\n    with pytest.raises(ValueError, match=\"`delta_type` as string: 'BAD_TYPE' i\"):\n        _get_fx_delta_type(\"BAD_TYPE\")\n"
  },
  {
    "path": "python/tests/test_fxrs.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.fx import FXRates\nfrom rateslib.rs import Ccy, Dual, Dual2, FXRate\nfrom rateslib.serialization import from_json\n\n\ndef test_ccy_creation() -> None:\n    c1 = Ccy(\"usd\")\n    c2 = Ccy(\"USD\")\n    assert c1 == c2\n\n\n@pytest.mark.parametrize(\"val\", [0.99, Dual(0.99, [\"x\"], []), Dual2(0.99, [\"x\"], [], [])])\ndef test_fx_rate_creation(val) -> None:\n    fxr = FXRate(\"usd\", \"eur\", val, dt(2001, 1, 1))\n    assert fxr.rate == val\n    assert fxr.pair == \"usdeur\"\n    assert fxr.settlement == dt(2001, 1, 1)\n\n\ndef test_json_round_trip() -> None:\n    fxr = FXRates({\"eurusd\": 1.08, \"usdjpy\": 110.0}, dt(2004, 1, 1))\n    json = fxr.to_json()\n    fxr2 = from_json(json)\n    assert fxr == fxr2\n\n\ndef test_equality() -> None:\n    fxr = FXRates({\"eurusd\": 1.08, \"usdjpy\": 110.0}, dt(2004, 1, 1))\n    fxr2 = FXRates({\"eurusd\": 1.08, \"usdjpy\": 110.0}, dt(2004, 1, 1))\n    assert fxr == fxr2\n"
  },
  {
    "path": "python/tests/test_ir_volatility.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport sys\nfrom datetime import datetime as dt\nfrom itertools import combinations, product\n\nimport numpy as np\nimport pytest\nfrom matplotlib import pyplot as plt\nfrom pandas import DataFrame, Index, IndexSlice, Series\nfrom pandas.testing import assert_frame_equal, assert_series_equal\nfrom rateslib import calendars, default_context\nfrom rateslib.curves import CompositeCurve, Curve, LineCurve\nfrom rateslib.data.fixings import IRSSeries\nfrom rateslib.default import NoInput\nfrom rateslib.dual import Dual, Dual2, Variable, gradient\nfrom rateslib.instruments import IRS, IRSCall, IRSPut, IRSStraddle, IRVolValue\nfrom rateslib.solver import Solver\nfrom rateslib.splines import PPSplineF64\nfrom rateslib.volatility import (\n    IRSabrCube,\n    IRSabrSmile,\n    IRSplineCube,\n    IRSplineSmile,\n)\nfrom rateslib.volatility.ir.utils import _bilinear_interp, _scale_weights\nfrom rateslib.volatility.utils import _OptionModelBachelier, _OptionModelBlack76, _SabrSmileNodes\n\n\n@pytest.mark.parametrize(\n    (\"h\", \"v\", \"expected\"),\n    [\n        ((1, 1), (1, 1), 10),\n        ((0.5, 0.5), (0.5, 0.5), 5.0),\n        ((0.0, 0.0), (0.0, 0.0), 0.0),\n        ((0.0, 0.5), (0.0, 0.0), 0.0),\n        ((0.0, 0.0), (0.8, 0.4), 4.80),\n        ((0.1, 0.2), (0.4, 0.5), 4.0 * 0.1 * 0.5 + 6.0 * 0.8 * 0.4 + 10.0 * 0.2 * 0.5),\n    ],\n)\ndef test_bilinear_interp(h, v, expected):\n    result = _bilinear_interp(0.0, 4.0, 6.0, 10.0, h, v)\n    assert abs(result - expected) < 1e-10\n\n\ndef test_numpy_ravel_for_dates_posix():\n    a = np.array([[1, 1, 2], [3, 4, 5]])\n    b = np.reshape(list(a.ravel()), (2, 3))\n    assert np.all(a == b)\n\n\n@pytest.fixture\ndef curve():\n    return Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2032, 3, 31): 0.50,\n        },\n        interpolation=\"log_linear\",\n        id=\"v\",\n        convention=\"Act360\",\n        ad=1,\n    )\n\n\nclass TestIRSabrSmile:\n    @pytest.mark.parametrize(\n        (\"strike\", \"vol\"),\n        [\n            (1.2034, 19.49),\n            (1.2050, 19.47),\n            (1.3395, 18.31),  # f == k\n            (1.3620, 18.25),\n            (1.5410, 18.89),\n            (1.5449, 18.93),\n        ],\n    )\n    def test_sabr_vol(self, strike, vol):\n        # repeat the same test developed for FXSabrSmile\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n        )\n        result = irss.get_from_strike(k=strike, f=1.3395).vol\n        assert abs(result - vol) < 1e-2\n\n    def test_sabr_vol_plot(self):\n        # repeat the same test developed for FXSabrSmile\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n        )\n        result = irss.plot(f=1.0)\n        _x = result[2][0]._x\n        _y = result[2][0]._y\n        assert (_x[0], _y[0]) == (0.7524348790033292, 23.108399874378378)\n        assert (_x[-1], _y[-1]) == (1.3743407823531082, 21.950871667495214)\n\n    def test_sabr_vol_plot_fail(self):\n        # repeat the same test developed for FXSabrSmile\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n        )\n        with pytest.raises(\n            ValueError,\n            match=r\"`f` \\(ATM-forward interest rate\\) is required by `_BaseIRSmile.plot`.\",\n        ):\n            irss.plot()\n\n    @pytest.mark.parametrize((\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33)])\n    def test_sabr_vol_finite_diff_first_order(self, k, f):\n        # Test all of the first order gradients using finite diff, for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n            ad=2,\n        )\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = irss.get_from_strike(k=Dual2(k, [\"k\"], [], []), f=Dual2(f, [\"f\"], [], [])).vol\n\n        a = irss.nodes.alpha\n        p = irss.nodes.rho\n        v = irss.nodes.nu\n\n        def inc_(key1, inc1):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n\n            irss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = (\n                irss._d_sabr_d_k_or_f(\n                    Dual2(in_[\"k\"], [\"k\"], [], []),\n                    Dual2(in_[\"f\"], [\"f\"], [], []),\n                    dt(2002, 1, 1),\n                    False,\n                    1,\n                )[0]\n                * 100.0\n            )\n\n            # reset\n            irss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        for key in [\"k\", \"f\", \"alpha\", \"rho\", \"nu\"]:\n            map_ = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"vol0\", \"rho\": \"vol1\", \"nu\": \"vol2\"}\n            up_ = inc_(key, 1e-5)\n            dw_ = inc_(key, -1e-5)\n            assert abs((up_ - dw_) / 2e-5 - gradient(base, [map_[key]])[0]) < 1e-5\n\n    @pytest.mark.parametrize(\n        (\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33), (1.3399, 1.34), (1.34, 1.3401)]\n    )\n    @pytest.mark.parametrize(\"pair\", list(combinations([\"k\", \"f\", \"alpha\", \"rho\", \"nu\"], 2)))\n    def test_sabr_vol_cross_finite_diff_second_order(self, k, f, pair):\n        # Test all of the second order cross gradients using finite diff,\n        # for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n\n        a = irss.nodes.alpha\n        p = irss.nodes.rho\n        v = irss.nodes.nu\n\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = irss.get_from_strike(k=Dual2(k, [\"k\"], [], []), f=Dual2(f, [\"f\"], [], [])).vol\n\n        def inc_(key1, key2, inc1, inc2):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n            in_[key2] += inc2\n\n            irss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = (\n                irss._d_sabr_d_k_or_f(\n                    Dual2(in_[\"k\"], [\"k\"], [], []),\n                    Dual2(in_[\"f\"], [\"f\"], [], []),\n                    dt(2002, 1, 1),\n                    False,\n                    1,\n                )[0]\n                * 100.0\n            )\n\n            # reset\n            irss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        v_map = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"v0\", \"rho\": \"v1\", \"nu\": \"v2\"}\n\n        upup = inc_(pair[0], pair[1], 1e-3, 1e-3)\n        updown = inc_(pair[0], pair[1], 1e-3, -1e-3)\n        downup = inc_(pair[0], pair[1], -1e-3, 1e-3)\n        downdown = inc_(pair[0], pair[1], -1e-3, -1e-3)\n        expected = (upup + downdown - updown - downup) / 4e-6\n        result = gradient(base, [v_map[pair[0]], v_map[pair[1]]], order=2)[0][1]\n        assert abs(result - expected) < 1e-2\n\n    @pytest.mark.parametrize(\n        (\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33), (1.3399, 1.34), (1.34, 1.3401)]\n    )\n    @pytest.mark.parametrize(\"var\", [\"k\", \"f\", \"alpha\", \"rho\", \"nu\"])\n    def test_sabr_vol_same_finite_diff_second_order(self, k, f, var):\n        # Test all of the second order cross gradients using finite diff,\n        # for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n\n        a = irss.nodes.alpha\n        p = irss.nodes.rho\n        v = irss.nodes.nu\n\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = irss.get_from_strike(k=Dual2(k, [\"k\"], [], []), f=Dual2(f, [\"f\"], [], [])).vol\n\n        def inc_(key1, inc1):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n\n            irss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = (\n                irss._d_sabr_d_k_or_f(\n                    Dual2(in_[\"k\"], [\"k\"], [], []),\n                    Dual2(in_[\"f\"], [\"f\"], [], []),\n                    dt(2002, 1, 1),\n                    False,\n                    1,\n                )[0]\n                * 100.0\n            )\n\n            # reset\n            irss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        v_map = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"v0\", \"rho\": \"v1\", \"nu\": \"v2\"}\n\n        up = inc_(var, 1e-4)\n        down = inc_(var, -1e-4)\n        expected = (up + down - 2 * base) / 1e-8\n        result = gradient(base, [v_map[var]], order=2)[0][0]\n        assert abs(result - expected) < 5e-3\n\n    def test_sabr_vol_root_multi_duals_neighbourhood(self):\n        # test the SABR function when regular arithmetic operations produce an undefined 0/0\n        # value so AD has to be hard coded into the solution. This occurs when f == k.\n        # test by comparing derivatives with those captured at a nearby valid point\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = irss.get_from_strike(k=Dual2(1.34, [\"k\"], [], []), f=Dual2(1.34, [\"f\"], [], [])).vol\n        comparison1 = irss.get_from_strike(\n            k=Dual2(1.341, [\"k\"], [], []), f=Dual2(1.34, [\"f\"], [], [])\n        ).vol\n\n        assert np.all(abs(base.dual - comparison1.dual) < 1e-1)\n        diff = base.dual2 - comparison1.dual2\n        dual2 = abs(diff) < 5e-1\n        assert np.all(dual2)\n\n    @pytest.mark.parametrize(\"param\", [\"alpha\", \"rho\", \"nu\"])\n    def test_missing_param_raises(self, param):\n        nodes = {\n            \"alpha\": 0.17431060,\n            \"rho\": -0.11268306,\n            \"nu\": 0.81694072,\n        }\n        nodes.pop(param)\n        with pytest.raises(ValueError):\n            IRSabrSmile(\n                nodes=nodes,\n                beta=1.0,\n                eval_date=dt(2001, 1, 1),\n                expiry=dt(2002, 1, 1),\n                irs_series=\"eur_irs6\",\n                tenor=\"2y\",\n                id=\"v\",\n                ad=2,\n            )\n\n    def test_non_iterable(self):\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n        with pytest.raises(TypeError):\n            list(irss)\n\n    def test_update_node_raises(self):\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n        with pytest.raises(KeyError, match=\"'bananas' is not in `nodes`.\"):\n            irss.update_node(\"bananas\", 12.0)\n\n    def test_set_ad_order_raises(self):\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n        with pytest.raises(ValueError, match=\"`order` can only be in {0, 1, 2} \"):\n            irss._set_ad_order(12)\n\n    def test_get_node_vars_and_vector(self):\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"myid\",\n        )\n        result = irss._get_node_vars()\n        expected = (\"myid0\", \"myid1\", \"myid2\")\n        assert result == expected\n\n        result = irss._get_node_vector()\n        expected = np.array([0.20, -0.1, 0.80])\n        assert np.all(result == expected)\n\n    def test_get_from_strike_expiry_raises(self):\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"myid\",\n        )\n        with pytest.raises(\n            ValueError, match=\"`expiry` of _BaseIRSmile and intended price do not match\"\n        ):\n            irss.get_from_strike(k=1.0, f=1.0, expiry=dt(1999, 1, 1))\n\n    @pytest.mark.parametrize(\"k\", [1.2034, 1.2050, 1.3620, 1.5410, 1.5449])\n    def test_get_from_strike_ad_2(self, k) -> None:\n        # Use finite diff to validate the 2nd order AD of the SABR function in alpha and rho.\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n            ad=2,\n        )\n\n        kwargs = dict(\n            k=k,\n            f=1.350,\n        )\n        pv00 = irss.get_from_strike(**kwargs)\n\n        irss.update_node(\"alpha\", 0.20 + 0.00001)\n        irss.update_node(\"rho\", -0.10 + 0.00001)\n        pv11 = irss.get_from_strike(**kwargs)\n\n        irss.update_node(\"alpha\", 0.20 + 0.00001)\n        irss.update_node(\"rho\", -0.10 - 0.00001)\n        pv1_1 = irss.get_from_strike(**kwargs)\n\n        irss.update_node(\"alpha\", 0.20 - 0.00001)\n        irss.update_node(\"rho\", -0.10 - 0.00001)\n        pv_1_1 = irss.get_from_strike(**kwargs)\n\n        irss.update_node(\"alpha\", 0.20 - 0.00001)\n        irss.update_node(\"rho\", -0.10 + 0.00001)\n        pv_11 = irss.get_from_strike(**kwargs)\n\n        finite_diff = (pv11.vol + pv_1_1.vol - pv1_1.vol - pv_11.vol) * 1e10 / 4.0\n        ad_grad = gradient(pv00.vol, [\"vol0\", \"vol1\"], 2)[0, 1]\n\n        assert abs(finite_diff - ad_grad) < 1e-4\n\n    @pytest.mark.parametrize((\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33)])\n    def test_sabr_derivative_finite_diff_first_order(self, k, f):\n        # Test all of the first order gradients using finite diff, for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n            ad=2,\n        )\n        t = dt(2002, 1, 1)\n        base = irss._d_sabr_d_k_or_f(\n            Dual2(k, [\"k\"], [1.0], []), Dual2(f, [\"f\"], [1.0], []), t, False, 1\n        )[1]\n\n        a = irss.nodes.alpha\n        p = irss.nodes.rho\n        v = irss.nodes.nu\n\n        def inc_(key1, inc1):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n\n            irss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = irss._d_sabr_d_k_or_f(\n                Dual2(in_[\"k\"], [\"k\"], [], []),\n                Dual2(in_[\"f\"], [\"f\"], [], []),\n                dt(2002, 1, 1),\n                False,\n                1,\n            )[1]\n\n            # reset\n            irss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        for key in [\"k\", \"f\", \"alpha\", \"rho\", \"nu\"]:\n            map_ = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"vol0\", \"rho\": \"vol1\", \"nu\": \"vol2\"}\n            up_ = inc_(key, 1e-5)\n            dw_ = inc_(key, -1e-5)\n            expected = (up_ - dw_) / 2e-5\n            result = gradient(base, [map_[key]])[0]\n            assert abs(expected - result) < 7e-3\n\n    @pytest.mark.parametrize(\n        (\"k\", \"f\"), [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33), (1.3395, 1.34), (1.34, 1.3405)]\n    )\n    @pytest.mark.parametrize(\"pair\", list(combinations([\"k\", \"f\", \"alpha\", \"rho\", \"nu\"], 2)))\n    def test_sabr_derivative_cross_finite_diff_second_order(self, k, f, pair):\n        # Test all of the second order cross gradients using finite diff,\n        # for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n\n        a = irss.nodes.alpha\n        p = irss.nodes.rho\n        v = irss.nodes.nu\n\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = irss._d_sabr_d_k_or_f(\n            Dual2(k, [\"k\"], [], []), Dual2(f, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n        )[1]\n\n        def inc_(key1, key2, inc1, inc2):\n            in_ = {\"k\": k, \"f\": f, \"alpha\": a, \"rho\": p, \"nu\": v}\n            in_[key1] += inc1\n            in_[key2] += inc2\n\n            irss._nodes = _SabrSmileNodes(\n                _alpha=in_[\"alpha\"], _beta=1.0, _rho=in_[\"rho\"], _nu=in_[\"nu\"]\n            )\n            _ = irss._d_sabr_d_k_or_f(\n                Dual2(in_[\"k\"], [\"k\"], [], []),\n                Dual2(in_[\"f\"], [\"f\"], [], []),\n                dt(2002, 1, 1),\n                False,\n                1,\n            )[1]\n\n            # reset\n            irss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        v_map = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"v0\", \"rho\": \"v1\", \"nu\": \"v2\"}\n\n        upup = inc_(pair[0], pair[1], 1e-3, 1e-3)\n        updown = inc_(pair[0], pair[1], 1e-3, -1e-3)\n        downup = inc_(pair[0], pair[1], -1e-3, 1e-3)\n        downdown = inc_(pair[0], pair[1], -1e-3, -1e-3)\n        expected = (upup + downdown - updown - downup) / 4e-6\n        result = gradient(base, [v_map[pair[0]], v_map[pair[1]]], order=2)[0][1]\n        assert abs(result - expected) < 5e-3\n\n    @pytest.mark.parametrize(\n        (\"k\", \"f\"),\n        [(1.34, 1.34), (1.33, 1.35), (1.35, 1.33), (1.3395, 1.34), (1.34, 1.3405)],\n    )\n    @pytest.mark.parametrize(\"var\", [\"k\", \"f\", \"alpha\", \"rho\", \"nu\"])\n    def test_sabr_derivative_same_finite_diff_second_order(self, k, f, var):\n        # Test all of the second order cross gradients using finite diff,\n        # for the case when f != k and\n        # when f == k, which is a branched calculation to handle a undefined point.\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n\n        a = irss.nodes.alpha\n        p = irss.nodes.rho\n        v = irss.nodes.nu\n\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = irss._d_sabr_d_k_or_f(\n            Dual2(k, [\"k\"], [], []), Dual2(f, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n        )[1]\n\n        def inc_(key1, inc1):\n            k_ = k\n            f_ = f\n            if key1 == \"k\":\n                k_ = k + inc1\n            elif key1 == \"f\":\n                f_ = f + inc1\n            else:\n                irss.update_node(key1, getattr(irss.nodes, key1) + inc1)\n                # irss.nodes[key1] = irss.nodes[key1] + inc1\n\n            _ = irss._d_sabr_d_k_or_f(\n                Dual2(k_, [\"k\"], [], []), Dual2(f_, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n            )[1]\n\n            irss._nodes = _SabrSmileNodes(_alpha=a, _beta=1.0, _rho=p, _nu=v)\n            return _\n\n        v_map = {\"k\": \"k\", \"f\": \"f\", \"alpha\": \"v0\", \"rho\": \"v1\", \"nu\": \"v2\"}\n\n        up = inc_(var, 1e-3)\n        down = inc_(var, -1e-3)\n        expected = (up + down - 2 * base) / 1e-6\n        result = gradient(base, [v_map[var]], order=2)[0][0]\n        assert abs(result - expected) < 3e-3\n\n    def test_sabr_derivative_root_multi_duals_neighbourhood(self):\n        # test the SABR function when regular arithmetic operations produce an undefined 0/0\n        # value so AD has to be hard coded into the solution. This occurs when f == k.\n        # test by comparing derivatives with those captured at a nearby valid point\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.10,\n                \"nu\": 0.80,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n        # F_0,T is stated in section 3.5.4 as 1.3395\n        base = irss._d_sabr_d_k_or_f(\n            Dual2(1.34, [\"k\"], [], []), Dual2(1.34, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n        )[1]\n        comparison1 = irss._d_sabr_d_k_or_f(\n            Dual2(1.341, [\"k\"], [], []), Dual2(1.34, [\"f\"], [], []), dt(2002, 1, 1), False, 1\n        )[1]\n\n        assert np.all(abs(base.dual - comparison1.dual) < 5e-3)\n        diff = base.dual2 - comparison1.dual2\n        dual2 = abs(diff) < 3e-2\n        assert np.all(dual2)\n\n    #\n    # def test_plot_domain(self):\n    #     ss = FXSabrSmile(\n    #         eval_date=dt(2024, 5, 28),\n    #         expiry=dt(2054, 5, 28),\n    #         nodes={\"alpha\": 0.02, \"beta\": 1.0, \"rho\": 0.01, \"nu\": 0.05},\n    #     )\n    #     ax, fig, lines = ss.plot(f=1.60)\n    #     assert abs(lines[0]._x[0] - 1.3427) < 1e-4\n    #     assert abs(lines[0]._x[-1] - 1.9299) < 1e-4\n    #     assert abs(lines[0]._y[0] - 2.0698) < 1e-4\n    #     assert abs(lines[0]._y[-1] - 2.0865) < 1e-4\n    #\n\n    #\n    # def test_solver_variable_numbers(self):\n    #     from rateslib import IRS, FXBrokerFly, FXCall, FXRiskReversal, FXStraddle, FXSwap, Solver\n    #\n    #     usdusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"nyc\", id=\"usdusd\")\n    #     eureur = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"tgt\", id=\"eureur\")\n    #     eurusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, id=\"eurusd\")\n    #\n    #     # Create an FX Forward market with spot FX rate data\n    #     fxr = FXRates({\"eurusd\": 1.0760}, settlement=dt(2024, 5, 9))\n    #     fxf = FXForwards(\n    #         fx_rates=fxr,\n    #         fx_curves={\"eureur\": eureur, \"usdusd\": usdusd, \"eurusd\": eurusd},\n    #     )\n    #\n    #     pre_solver = Solver(\n    #         curves=[eureur, eurusd, usdusd],\n    #         instruments=[\n    #             IRS(dt(2024, 5, 9), \"3W\", spec=\"eur_irs\", curves=\"eureur\"),\n    #             IRS(dt(2024, 5, 9), \"3W\", spec=\"usd_irs\", curves=\"usdusd\"),\n    #             FXSwap(\n    #                 dt(2024, 5, 9), \"3W\", pair=\"eurusd\", curves=[None, \"eurusd\", None, \"usdusd\"]\n    #             ),\n    #         ],\n    #         s=[3.90, 5.32, 8.85],\n    #         fx=fxf,\n    #         id=\"rates_sv\",\n    #     )\n    #\n    #     dv_smile = FXSabrSmile(\n    #         nodes={\"alpha\": 0.05, \"beta\": 1.0, \"rho\": 0.01, \"nu\": 0.03},\n    #         eval_date=dt(2024, 5, 7),\n    #         expiry=dt(2024, 5, 28),\n    #         id=\"eurusd_3w_smile\",\n    #         pair=\"eurusd\",\n    #     )\n    #     option_args = dict(\n    #         pair=\"eurusd\",\n    #         expiry=dt(2024, 5, 28),\n    #         calendar=\"tgt|fed\",\n    #         delta_type=\"spot\",\n    #         curves=[\"eurusd\", \"usdusd\"],\n    #         vol=\"eurusd_3w_smile\",\n    #     )\n    #\n    #     dv_solver = Solver(\n    #         pre_solvers=[pre_solver],\n    #         curves=[dv_smile],\n    #         instruments=[\n    #             FXStraddle(strike=\"atm_delta\", **option_args),\n    #             FXRiskReversal(strike=(\"-25d\", \"25d\"), **option_args),\n    #             FXRiskReversal(strike=(\"-10d\", \"10d\"), **option_args),\n    #             FXBrokerFly(strike=((\"-25d\", \"25d\"), \"atm_delta\"), **option_args),\n    #             FXBrokerFly(strike=((\"-10d\", \"10d\"), \"atm_delta\"), **option_args),\n    #         ],\n    #         s=[5.493, -0.157, -0.289, 0.071, 0.238],\n    #         fx=fxf,\n    #         id=\"dv_solver\",\n    #     )\n    #\n    #     fc = FXCall(\n    #         expiry=dt(2024, 5, 28),\n    #         pair=\"eurusd\",\n    #         strike=1.07,\n    #         notional=100e6,\n    #         curves=[\"eurusd\", \"usdusd\"],\n    #         vol=\"eurusd_3w_smile\",\n    #         premium=98.216647 * 1e8 / 1e4,\n    #         premium_ccy=\"usd\",\n    #         delta_type=\"spot\",\n    #     )\n    #     fc.delta(solver=dv_solver)\n    #\n    @pytest.mark.parametrize(\"a\", [0.02, 0.06])\n    @pytest.mark.parametrize(\"b\", [0.0, 0.4, 0.65, 1.0])\n    @pytest.mark.parametrize(\"p\", [-0.1, 0.1])\n    @pytest.mark.parametrize(\"v\", [0.05, 0.15])\n    @pytest.mark.parametrize(\"k\", [1.05, 1.25, 1.6])\n    def test_sabr_function_values(self, a, b, p, v, k):\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": a,\n                \"rho\": p,\n                \"nu\": v,\n            },\n            beta=b,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n\n        # this code is taken from PySabr, another library implementing SABR.\n        # it is used as a benchmark\n        def _x(rho, z):\n            \"\"\"Return function x used in Hagan's 2002 SABR lognormal vol expansion.\"\"\"\n            a = (1 - 2 * rho * z + z**2) ** 0.5 + z - rho\n            b = 1 - rho\n            return np.log(a / b)\n\n        def lognormal_vol(k, f, t, alpha, beta, rho, volvol):\n            \"\"\"\n            Hagan's 2002 SABR lognormal vol expansion.\n\n            The strike k can be a scalar or an array, the function will return an array\n            of lognormal vols.\n            \"\"\"\n            # Negative strikes or forwards\n            if k <= 0 or f <= 0:\n                return 0.0\n            eps = 1e-07\n            logfk = np.log(f / k)\n            fkbeta = (f * k) ** (1 - beta)\n            a = (1 - beta) ** 2 * alpha**2 / (24 * fkbeta)\n            b = 0.25 * rho * beta * volvol * alpha / fkbeta**0.5\n            c = (2 - 3 * rho**2) * volvol**2 / 24\n            d = fkbeta**0.5\n            v = (1 - beta) ** 2 * logfk**2 / 24\n            w = (1 - beta) ** 4 * logfk**4 / 1920\n            z = volvol * fkbeta**0.5 * logfk / alpha\n            # if |z| > eps\n            if abs(z) > eps:\n                vz = alpha * z * (1 + (a + b + c) * t) / (d * (1 + v + w) * _x(rho, z))\n                return vz\n            # if |z| <= eps\n            else:\n                v0 = alpha * (1 + (a + b + c) * t) / (d * (1 + v + w))\n                return v0\n\n        expected = lognormal_vol(k, 1.25, 1.0, a, b, p, v)\n        result = irss.get_from_strike(k=k, f=1.25).vol / 100.0\n\n        assert abs(result - expected) < 1e-4\n\n    def test_init_raises_key(self):\n        with pytest.raises(\n            ValueError, match=r\"'nu' is a required SABR parameter that must be inclu\"\n        ):\n            IRSabrSmile(\n                nodes={\n                    \"alpha\": 0.05,\n                    \"rho\": 0.1,\n                    \"bad\": 0.1,\n                },\n                beta=-0.03,\n                eval_date=dt(2001, 1, 1),\n                expiry=dt(2002, 1, 1),\n                irs_series=\"eur_irs6\",\n                tenor=\"2y\",\n                id=\"v\",\n                ad=2,\n            )\n\n    def test_attributes(self):\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.05,\n                \"rho\": 0.1,\n                \"nu\": 0.1,\n            },\n            beta=1.0,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n            ad=2,\n        )\n        assert irss._n == 4\n\n    def test_get_from_strike_with_curves(self):\n        curve = Curve({dt(2001, 1, 1): 1.0, dt(2003, 1, 1): 0.94})\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.05,\n                \"rho\": 0.1,\n                \"nu\": 0.1,\n            },\n            beta=-0.03,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"v\",\n        )\n        result = irss.get_from_strike(k=3.0, curves=[curve])\n        assert abs(result.f - 3.142139380) < 1e-6\n        assert abs(result.vol - 1.575277) < 1e-4\n\n    def test_set_node_vector(self):\n        irss = IRSabrSmile(\n            nodes={\n                \"alpha\": 0.05,\n                \"rho\": 0.1,\n                \"nu\": 0.1,\n            },\n            beta=-0.03,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            ad=2,\n            id=\"v\",\n        )\n        irss._set_node_vector(np.array([1.0, 2.0, 3.0]), ad=1)\n        assert irss.nodes.alpha == Dual(1.0, [\"v0\"], [])\n        assert irss.nodes.rho == Dual(2.0, [\"v1\"], [])\n        assert irss.nodes.nu == Dual(3.0, [\"v2\"], [])\n\n    @pytest.mark.skip(reason=\"SABR Smile cannot solve to parameters matching the target\")\n    def test_plot_normal_from_black_shift(self):\n        # test that smiles with shift equate to the same normal vol graph\n        smile1 = IRSabrSmile(\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2000, 7, 1),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.05,\n                \"nu\": 1.5,\n            },\n            beta=0.5,\n            id=\"sofr_vol\",\n            shift=0.0,\n        )\n        smile2 = IRSabrSmile(\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2000, 7, 1),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            nodes={\n                \"alpha\": 0.20,\n                \"rho\": -0.06,\n                \"nu\": 1.5,\n            },\n            beta=0.5,\n            id=\"sofr_vol\",\n            shift=10.0,\n        )\n\n        from rateslib import IRS, IRSCall, Solver\n\n        curve = Curve(nodes={dt(2000, 1, 1): 1.0, dt(2003, 1, 1): 0.90}, id=\"sofr\")\n        curve_solver = Solver(\n            curves=[curve],\n            instruments=[IRS(dt(2000, 1, 1), \"1y\", spec=\"usd_irs\", curves=\"sofr\")],\n            s=[3.0],\n            instrument_labels=[\"1Y IRS\"],\n        )\n        option_args = dict(\n            expiry=dt(2000, 7, 1),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            metric=\"NormalVol\",\n            curves=\"sofr\",\n            vol=\"sofr_vol\",\n        )\n\n        instruments = [\n            IRSCall(strike=\"-20bps\", **option_args),\n            IRSCall(strike=\"atm\", **option_args),\n            IRSCall(strike=\"+20bps\", **option_args),\n        ]\n\n        def solver_factory(smile):\n            solver = Solver(\n                pre_solvers=[curve_solver],\n                curves=[smile],\n                instruments=instruments,\n                s=[50.0, 47.0, 49.0],\n                instrument_labels=[\"-20bps Vol\", \"ATM Vol\", \"+20bps Vol\"],\n                ini_lambda=(20000, 0.4, 2),\n                conv_tol=1e-6,\n            )\n            return solver\n\n        s2 = solver_factory(smile2)\n        s1 = solver_factory(smile1)\n\n        _res1_nvol = [_.rate(solver=s1) for _ in instruments]\n        _res2_nvol = [_.rate(solver=s2) for _ in instruments]\n        _res1_lnvol = [_.rate(solver=s1, metric=\"black_vol_shift_0\") for _ in instruments]\n        _res2_lnvol = [_.rate(solver=s2, metric=\"black_vol_shift_10\") for _ in instruments]\n\n        fig, ax, lines = smile1.plot(curves=curve, y_axis=\"normal_vol\", comparators=[smile2])\n\n        pp1 = PPSplineF64(k=2, t=[lines[0]._x[0]] + lines[0]._x.tolist() + [lines[0]._x[-1]])\n        pp1.csolve(tau=lines[0]._x, y=lines[0]._y, left_n=0, right_n=0, allow_lsq=False)\n        pp2 = PPSplineF64(k=2, t=[lines[1]._x[0]] + lines[1]._x.tolist() + [lines[1]._x[-1]])\n        pp2.csolve(tau=lines[1]._x, y=lines[1]._y, left_n=0, right_n=0, allow_lsq=False)\n\n        x = np.linspace(2.54, 2.83, 101)\n        eps = [abs(pp1.ppev_single(_) - pp2.ppev_single(_)) for _ in x]\n\n        assert all(_ < 0.001 for _ in eps)\n\n    @pytest.mark.parametrize(\n        \"klass\",\n        [\n            (IRSStraddle, IRSPut, IRSCall),\n            (IRVolValue, IRVolValue, IRVolValue),\n        ],\n    )\n    def test_plot_normal_from_black_shift2_with_IROption_Solving(self, klass):\n        # klass denotes the instruments used in the solving process\n        from rateslib import IRS, IRSCall, IRSPut, IRSStraddle, Solver\n\n        # test that smiles with shift equate to the same normal vol graph\n        smile_args = dict(\n            eval_date=dt(2026, 3, 2),\n            expiry=\"6m\",\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            id=\"sofr_vol\",\n        )\n\n        curve = Curve(\n            nodes={dt(2026, 3, 2): 1.0, dt(2029, 3, 2): 0.90},\n            calendar=\"nyc\",\n            convention=\"act360\",\n            id=\"sofr\",\n        )\n\n        curve_solver = Solver(\n            curves=[curve],\n            instruments=[IRS(dt(2026, 3, 4), \"2y\", spec=\"usd_irs\", curves=[\"sofr\"])],\n            s=[3.90],\n            instrument_labels=[\"US_2y\"],\n        )\n\n        def smile_solver_factory(smile):\n            _solver = Solver(\n                pre_solvers=[curve_solver],  # <- contains the US SOFR Curve\n                curves=[smile],  # <- mutates only the smile\n                instruments=[\n                    klass[0](\n                        dt(2026, 9, 2),\n                        \"1y\",\n                        \"atm\",\n                        \"usd_irs\",\n                        curves=\"sofr\",\n                        vol=\"sofr_vol\",\n                        metric=\"normal_vol\",\n                    ),\n                    klass[1](\n                        dt(2026, 9, 2),\n                        \"1y\",\n                        \"-20bps\",\n                        \"usd_irs\",\n                        curves=\"sofr\",\n                        vol=\"sofr_vol\",\n                        metric=\"normal_vol\",\n                    ),\n                    klass[2](\n                        dt(2026, 9, 2),\n                        \"1y\",\n                        \"+20bps\",\n                        \"usd_irs\",\n                        curves=\"sofr\",\n                        vol=\"sofr_vol\",\n                        metric=\"normal_vol\",\n                    ),\n                ],\n                s=[50, 62, 60],\n                instrument_labels=[\"ATM\", \"-20bps\", \"20bps\"],\n                id=\"sofr_sv\",\n            )\n\n        smile1 = IRSabrSmile(\n            shift=0, beta=0.5, nodes={\"alpha\": 0.2, \"rho\": -0.05, \"nu\": 0.5}, **smile_args\n        )\n        smile2 = IRSabrSmile(\n            shift=0, beta=0.75, nodes={\"alpha\": 0.2, \"rho\": -0.05, \"nu\": 0.5}, **smile_args\n        )\n        smile3 = IRSabrSmile(\n            shift=0, beta=0.25, nodes={\"alpha\": 0.2, \"rho\": -0.05, \"nu\": 0.5}, **smile_args\n        )\n        smile4 = IRSabrSmile(\n            shift=100, beta=0.5, nodes={\"alpha\": 0.2, \"rho\": -0.05, \"nu\": 0.5}, **smile_args\n        )\n        smile5 = IRSabrSmile(\n            shift=200, beta=0.5, nodes={\"alpha\": 0.2, \"rho\": -0.05, \"nu\": 0.5}, **smile_args\n        )\n\n        # calibrate each smile similarly\n        smile_solver_factory(smile1)\n        smile_solver_factory(smile2)\n        smile_solver_factory(smile3)\n        smile_solver_factory(smile4)\n        smile_solver_factory(smile5)\n\n        fig, ax, lines = smile1.plot(\n            curves=curve, y_axis=\"normal_vol\", comparators=[smile2, smile3, smile4, smile5]\n        )\n\n        pp1 = PPSplineF64(k=2, t=[lines[0]._x[0]] + lines[0]._x.tolist() + [lines[0]._x[-1]])\n        pp1.csolve(tau=lines[0]._x, y=lines[0]._y, left_n=0, right_n=0, allow_lsq=False)\n        pp2 = PPSplineF64(k=2, t=[lines[1]._x[0]] + lines[1]._x.tolist() + [lines[1]._x[-1]])\n        pp2.csolve(tau=lines[1]._x, y=lines[1]._y, left_n=0, right_n=0, allow_lsq=False)\n        pp3 = PPSplineF64(k=2, t=[lines[2]._x[0]] + lines[2]._x.tolist() + [lines[2]._x[-1]])\n        pp3.csolve(tau=lines[2]._x, y=lines[2]._y, left_n=0, right_n=0, allow_lsq=False)\n        pp4 = PPSplineF64(k=2, t=[lines[3]._x[0]] + lines[3]._x.tolist() + [lines[3]._x[-1]])\n        pp4.csolve(tau=lines[3]._x, y=lines[3]._y, left_n=0, right_n=0, allow_lsq=False)\n        pp5 = PPSplineF64(k=2, t=[lines[4]._x[0]] + lines[4]._x.tolist() + [lines[4]._x[-1]])\n        pp5.csolve(tau=lines[4]._x, y=lines[4]._y, left_n=0, right_n=0, allow_lsq=False)\n\n        x = np.linspace(3.50, 4.40, 101)\n        comparators = [pp2, pp3, pp4, pp5]\n        for pp in comparators:\n            eps = np.array([abs(pp1.ppev_single(_) - pp.ppev_single(_)) for _ in x])\n            assert eps.max() < 0.3\n            assert eps.mean() < 0.08\n\n    def test_d_sigma_d_f(self):\n        irss = IRSabrSmile(\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2000, 7, 1),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            beta=0.5,\n            nodes=dict(alpha=0.2, rho=-0.05, nu=0.65),\n            shift=0.0,\n        )\n        result = irss._d_sigma_d_f(k=0.8, f=1.0)\n        manual = irss.get_from_strike(k=0.8, f=Dual(1.0, [\"f\"], []))\n        manual_gradient = gradient(manual.vol, [\"f\"])[0] / 100.0\n        assert abs(result - manual_gradient) < 2e-3\n\n    def test_time_scalar(self):\n        irss = IRSabrSmile(\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2000, 7, 1),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            beta=0.5,\n            nodes=dict(alpha=0.2, rho=-0.05, nu=0.65),\n            shift=0.0,\n            time_scalar=0.9,\n        )\n        assert irss.meta.t_expiry == 0.9 * (31 + 29 + 31 + 30 + 31 + 30) / 365\n\n\nclass TestIRSabrCube:\n    def test_init(self):\n        IRSabrCube(\n            eval_date=dt(2026, 2, 16),\n            expiries=[\"1m\", \"3m\"],\n            tenors=[\"1Y\", \"2y\", \"3y\"],\n            irs_series=\"usd_irs\",\n            id=\"usd_ir_vol\",\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2, 0.3], [0.11, 0.12, 0.13]]),\n            rho=np.array([[0.1, 0.2, 0.3], [0.11, 0.12, 0.13]]),\n            nu=np.array([[0.1, 0.2, 0.3], [0.11, 0.12, 0.13]]),\n        )\n        pass\n\n    @pytest.mark.parametrize((\"ad\", \"klass\"), [(1, Dual), (2, Dual2)])\n    def test_constructed_sabr_smile_vars(self, ad, klass):\n        irsc = IRSabrCube(\n            eval_date=dt(2026, 2, 20),\n            expiries=[\"1m\", \"3m\"],\n            tenors=[\"2y\", \"5y\"],\n            irs_series=\"usd_irs\",\n            beta=0.5,\n            alpha=0.05,\n            rho=-0.01,\n            nu=0.01,\n            ad=ad,\n            id=\"my-c\",\n        )\n        _ = irsc.get_from_strike(k=1.0, f=1.02, expiry=dt(2026, 3, 30), tenor=dt(2028, 8, 12))\n        smile = irsc._cache[(dt(2026, 3, 30), dt(2028, 8, 12))]\n        assert smile.nodes.alpha.vars == [\"my-c_a_0_0\", \"my-c_a_0_1\", \"my-c_a_1_0\", \"my-c_a_1_1\"]\n        assert smile.nodes.rho.vars == [\"my-c_p_0_0\", \"my-c_p_0_1\", \"my-c_p_1_0\", \"my-c_p_1_1\"]\n        assert smile.nodes.nu.vars == [\"my-c_v_0_0\", \"my-c_v_0_1\", \"my-c_v_1_0\", \"my-c_v_1_1\"]\n        assert isinstance(smile.nodes.alpha, klass)\n\n    @pytest.mark.parametrize(\n        (\"expiry\", \"tenor\", \"expected\"),\n        [\n            # tests on a node directly\n            (dt(2001, 1, 1), dt(2002, 1, 1), (0.1, 1.0, 10.0)),\n            (dt(2002, 1, 1), dt(2003, 1, 1), (0.3, 3.0, 30.0)),\n            (dt(2001, 1, 1), dt(2003, 1, 1), (0.2, 2.0, 20.0)),\n            (dt(2002, 1, 1), dt(2004, 1, 1), (0.4, 4.0, 40.0)),\n            # test within bounds\n            (\n                dt(2001, 4, 1),\n                dt(2002, 7, 1),\n                (0.17424657534246576, 1.7424657534246577, 17.424657534246577),\n            ),\n            (\n                dt(2001, 4, 1),\n                dt(2003, 1, 1),\n                (0.22465753424657536, 2.2465753424657535, 22.46575342465753),\n            ),\n            (\n                dt(2001, 10, 1),\n                dt(2003, 1, 1),\n                (0.27479452054794523, 2.747945205479452, 27.47945205479452),\n            ),\n            (\n                dt(2001, 10, 1),\n                dt(2003, 7, 1),\n                (0.32438356164383564, 3.243835616438356, 32.43835616438356),\n            ),\n            # test out of bounds\n            (dt(2000, 7, 1), dt(2001, 1, 1), (0.1, 1.0, 10.0)),  # 6m6m\n            (\n                dt(2000, 7, 1),\n                dt(2002, 1, 1),\n                (0.1504109589041096, 1.504109589041096, 15.04109589041096),\n            ),  # 6m18m\n            (dt(2000, 7, 1), dt(2003, 7, 1), (0.2, 2.0, 20.0)),  # 6m3y\n            (\n                dt(2001, 7, 1),\n                dt(2002, 1, 1),\n                (0.1991780821917808, 1.9917808219178081, 19.91780821917808),\n            ),  # 18m6m\n            (\n                dt(2001, 7, 1),\n                dt(2004, 7, 1),\n                (0.2991780821917808, 2.991780821917808, 29.91780821917808),\n            ),  # 18m3y\n            (dt(2003, 1, 1), dt(2003, 7, 1), (0.30, 3.0, 30.0)),  # 3y6m\n            (\n                dt(2003, 1, 1),\n                dt(2004, 7, 1),\n                (0.34986301369863015, 3.4986301369863018, 34.986301369863014),\n            ),  # 3y18m\n            (dt(2003, 1, 1), dt(2006, 1, 1), (0.4, 4.0, 40.0)),  # 3y3y\n        ],\n    )\n    def test_interpolation_boundaries(self, expiry, tenor, expected):\n        # test that the SabrCube will interpolate the parameters if the expiry and tenors are\n        # - exactly falling on node dates\n        # - some elements within the node-mesh\n        # - some elements outside the node-mesh which are mapped to nearest components.\n        irsc = IRSabrCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2], [0.3, 0.4]]),\n            rho=np.array([[1.0, 2.0], [3.0, 4.0]]),\n            nu=np.array([[10.0, 20.0], [30.0, 40.0]]),\n            id=\"my-c\",\n        )\n        result = tuple(irsc._bilinear_interpolation(expiry=expiry, tenor=tenor))\n        assert result == expected\n\n    @pytest.mark.parametrize(\n        (\"expiry\", \"tenor\", \"expected\"),\n        [\n            (dt(2000, 7, 1), dt(2001, 1, 1), (0.1, 1.0, 10.0)),\n            (dt(2000, 7, 1), dt(2001, 7, 1), (0.1, 1.0, 10.0)),\n            (\n                dt(2000, 7, 1),\n                dt(2002, 1, 1),\n                (0.1504109589041096, 1.504109589041096, 15.04109589041096),\n            ),\n            (dt(2000, 7, 1), dt(2003, 7, 1), (0.2, 2.0, 20.0)),\n            (dt(2001, 1, 1), dt(2001, 7, 1), (0.1, 1.0, 10.0)),\n            (dt(2001, 1, 1), dt(2002, 1, 1), (0.1, 1.0, 10.0)),\n            (\n                dt(2001, 1, 1),\n                dt(2002, 7, 1),\n                (0.1495890410958904, 1.495890410958904, 14.95890410958904),\n            ),\n            (dt(2001, 1, 1), dt(2003, 7, 1), (0.2, 2.0, 20.0)),\n            (dt(2002, 1, 1), dt(2002, 7, 1), (0.1, 1.0, 10.0)),\n            (dt(2002, 1, 1), dt(2003, 1, 1), (0.1, 1.0, 10.0)),\n            (\n                dt(2002, 1, 1),\n                dt(2003, 7, 1),\n                (0.1495890410958904, 1.495890410958904, 14.95890410958904),\n            ),\n            (dt(2002, 1, 1), dt(2004, 7, 1), (0.2, 2.0, 20.0)),\n        ],\n    )\n    def test_interpolation_single_expiry(self, expiry, tenor, expected):\n        # test that the SabrCube will interpolate the parameters if the expiry and tenors are\n        # - exactly falling on node dates\n        # - some elements within the node-mesh\n        # - some elements outside the node-mesh which are mapped to nearest components.\n        irsc = IRSabrCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\"],\n            tenors=[\"1y\", \"2y\"],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2]]),\n            rho=np.array([[1.0, 2.0]]),\n            nu=np.array([[10.0, 20.0]]),\n            id=\"my-c\",\n        )\n        result = tuple(irsc._bilinear_interpolation(expiry=expiry, tenor=tenor))\n        assert result == expected\n\n    @pytest.mark.parametrize(\n        (\"expiry\", \"tenor\", \"expected\"),\n        [\n            (dt(2000, 7, 1), dt(2001, 1, 1), (0.1, 1.0, 10.0)),\n            (dt(2000, 7, 1), dt(2001, 7, 1), (0.1, 1.0, 10.0)),\n            (dt(2000, 7, 1), dt(2002, 1, 1), (0.1, 1.0, 10.0)),\n            (dt(2001, 1, 1), dt(2001, 7, 1), (0.1, 1.0, 10.0)),\n            (dt(2001, 1, 1), dt(2002, 1, 1), (0.1, 1.0, 10.0)),\n            (dt(2001, 1, 1), dt(2002, 7, 1), (0.1, 1.0, 10.0)),\n            (\n                dt(2001, 7, 1),\n                dt(2002, 1, 1),\n                (0.1495890410958904, 1.495890410958904, 14.95890410958904),\n            ),\n            (\n                dt(2001, 7, 1),\n                dt(2002, 7, 1),\n                (0.1495890410958904, 1.495890410958904, 14.95890410958904),\n            ),\n            (\n                dt(2001, 7, 1),\n                dt(2003, 1, 1),\n                (0.1495890410958904, 1.495890410958904, 14.95890410958904),\n            ),\n            (dt(2002, 7, 1), dt(2003, 1, 1), (0.2, 2.0, 20.0)),\n            (dt(2002, 7, 1), dt(2003, 7, 1), (0.2, 2.0, 20.0)),\n            (dt(2002, 7, 1), dt(2004, 7, 1), (0.2, 2.0, 20.0)),\n        ],\n    )\n    def test_interpolation_single_tenor(self, expiry, tenor, expected):\n        # test that the SabrCube will interpolate the parameters if the expiry and tenors are\n        # - exactly falling on node dates\n        # - some elements within the node-mesh\n        # - some elements outside the node-mesh which are mapped to nearest components.\n        irsc = IRSabrCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\"],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            beta=0.5,\n            alpha=np.array([[0.1], [0.2]]),\n            rho=np.array([[1.0], [2.0]]),\n            nu=np.array([[10.0], [20.0]]),\n            id=\"my-c\",\n        )\n        result = tuple(irsc._bilinear_interpolation(expiry=expiry, tenor=tenor).tolist())\n        assert result == expected\n\n    def test_alpha(self):\n        irsc = IRSabrCube(\n            eval_date=dt(2026, 2, 16),\n            expiries=[\"1m\", \"3m\"],\n            tenors=[\"1Y\", \"2Y\"],\n            irs_series=\"usd_irs\",\n            id=\"usd_ir_vol\",\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2], [0.11, 0.12]]),\n            rho=np.array([[0.1, 0.3], [0.11, 0.12]]),\n            nu=np.array([[0.1, 0.4], [0.11, 0.12]]),\n        )\n        expected = DataFrame(\n            index=Index([\"1m\", \"3m\"], name=\"expiry\"),\n            columns=Index([\"1Y\", \"2Y\"], name=\"tenor\"),\n            data=[[0.1, 0.2], [0.11, 0.12]],\n            dtype=object,\n        )\n        assert_frame_equal(expected, irsc.alpha)\n        expected = DataFrame(\n            index=Index([\"1m\", \"3m\"], name=\"expiry\"),\n            columns=Index([\"1Y\", \"2Y\"], name=\"tenor\"),\n            data=[[0.1, 0.3], [0.11, 0.12]],\n            dtype=object,\n        )\n        assert_frame_equal(expected, irsc.rho)\n        expected = DataFrame(\n            index=Index([\"1m\", \"3m\"], name=\"expiry\"),\n            columns=Index([\"1Y\", \"2Y\"], name=\"tenor\"),\n            data=[[0.1, 0.4], [0.11, 0.12]],\n            dtype=object,\n        )\n        assert_frame_equal(expected, irsc.nu)\n        assert irsc._n == 12\n\n    def test_cache(self):\n        irsc = IRSabrCube(\n            eval_date=dt(2026, 2, 16),\n            expiries=[\"1m\", \"3m\"],\n            tenors=[\"1Y\", \"2Y\"],\n            irs_series=\"usd_irs\",\n            id=\"usd_ir_vol\",\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2], [0.11, 0.12]]),\n            rho=np.array([[0.1, 0.3], [0.11, 0.12]]),\n            nu=np.array([[0.1, 0.4], [0.11, 0.12]]),\n        )\n        irsc.get_from_strike(k=1.02, f=1.04, expiry=dt(2026, 3, 30), tenor=dt(2027, 8, 12))\n        assert (dt(2026, 3, 30), dt(2027, 8, 12)) in irsc._cache\n\n    def test_get_node_vector(self):\n        irsc = IRSabrCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2], [0.3, 0.4]]),\n            rho=np.array([[1.0, 2.0], [3.0, 4.0]]),\n            nu=np.array([[10.0, 20.0], [30.0, 40.0]]),\n            id=\"X\",\n        )\n        result = irsc._get_node_vector()\n        expected = np.array([0.1, 0.2, 0.3, 0.4, 1.0, 2.0, 3, 4, 10, 20, 30, 40])\n        assert np.all(result == expected)\n\n    def test_get_node_vector_ad1(self):\n        irsc = IRSabrCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2], [0.3, 0.4]]),\n            rho=np.array([[1.0, 2.0], [3.0, 4.0]]),\n            nu=np.array([[10.0, 20.0], [30.0, 40.0]]),\n            id=\"X\",\n            ad=1,\n        )\n        result = irsc._get_node_vector()\n        assert result[2] == Dual(0.30, [\"X_a_1_0\"], [])\n        assert result[9] == Dual(20.0, [\"X_v_0_1\"], [])\n\n    def test_set_node_vector(self):\n        irsc = IRSabrCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2], [0.3, 0.4]]),\n            rho=np.array([[1.0, 2.0], [3.0, 4.0]]),\n            nu=np.array([[10.0, 20.0], [30.0, 40.0]]),\n            id=\"X\",\n        )\n        irsc._set_node_vector(np.array([0.1, 0.2, 0.3, 0.4, 1.0, 2.0, 3, 4, 10, 20, 30, 40]), ad=1)\n        result = irsc._get_node_vector()\n        assert result[2] == Dual(0.30, [\"X_a_1_0\"], [])\n        assert result[9] == Dual(20.0, [\"X_v_0_1\"], [])\n\n    @pytest.mark.parametrize(\n        (\"weights\", \"expiries\"),\n        [\n            (\n                Series(index=[dt(2000, 1, 3), dt(2000, 1, 8), dt(2000, 1, 4)], data=0.0),\n                [dt(2000, 1, 5), dt(2000, 1, 10), dt(2000, 1, 15)],\n            ),\n            (\n                Series(index=[dt(2000, 1, 3), dt(2000, 1, 20), dt(2000, 1, 4)], data=0.0),\n                [dt(2000, 1, 5), dt(2000, 1, 10), dt(2000, 1, 15)],\n            ),\n        ],\n    )\n    def test_weights_implementation(self, weights, expiries):\n        result = _scale_weights(\n            eval_date=dt(2000, 1, 1),\n            weights=weights,\n            expiries=expiries,\n        )\n\n        c = result.cumsum()\n        for expiry in expiries:\n            if expiry > c.index[-1]:\n                assert c.iloc[-1] == (c.index[-1] - dt(2000, 1, 1)).days\n            else:\n                assert c[expiry] == (expiry - dt(2000, 1, 1)).days\n\n        assert c.iloc[-1] == (c.index[-1] - dt(2000, 1, 1)).days\n\n    def test_weights(self):\n        nyc = calendars.get(\"nyc\")\n        irsc = IRSabrCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            beta=0.5,\n            alpha=np.array([[0.1, 0.2], [0.3, 0.4]]),\n            rho=np.array([[1.0, 2.0], [3.0, 4.0]]),\n            nu=np.array([[10.0, 20.0], [30.0, 40.0]]),\n            id=\"X\",\n            weights=Series(\n                index=[\n                    _\n                    for _ in nyc.cal_date_range(dt(2000, 1, 1), dt(2001, 2, 3))\n                    if nyc.is_non_bus_day(_)\n                ],\n                data=0.0,\n            ),\n        )\n        result = irsc.meta.time_scalars\n        assert abs(result.iloc[-1] - 1.0) < 1e-14\n\n\nclass TestIRSplineSmile:\n    @pytest.mark.parametrize(\n        (\"strike\", \"vol\"),\n        [\n            (1.2034, 51.0888),\n            (1.2050, 51.07599999999999),\n            (1.3395, 50.0),  # f == k\n            (1.3620, 50.2475),\n            (1.5410, 52.216499999999996),\n            (1.5449, 52.2594),\n        ],\n    )\n    def test_spline_vol(self, strike, vol):\n        # repeat the same test developed for FXSabrSmile\n        irss = IRSplineSmile(\n            nodes={-200.0: 70.0, -100.0: 58, 0: 50.0, 100.0: 61, 200.0: 75.0},\n            k=2,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n        )\n        result = irss.get_from_strike(k=strike, f=1.3395).vol\n        assert abs(result - vol) < 1e-2\n\n    @pytest.mark.parametrize(\n        (\"strike\", \"vol\"),\n        [\n            (1.01, 50.0),\n            (1.85, 50.0),\n            (1.3395, 50.0),  # f == k\n        ],\n    )\n    @pytest.mark.parametrize(\"k\", [2, 4])\n    def test_spline_vol_flat(self, strike, vol, k):\n        # repeat the same test developed for FXSabrSmile\n        irss = IRSplineSmile(\n            nodes={0: 50.0},\n            k=k,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n        )\n        result = irss.get_from_strike(k=strike, f=1.3395).vol\n        assert abs(result - vol) < 1e-2\n\n    @pytest.mark.parametrize(\"k\", [2, 4])\n    @pytest.mark.parametrize(\n        (\"nodes\", \"expected_k\"),\n        [\n            ({0.0: 100.0}, 2),\n            ({-10.0: 49.0, 10.0: 53.0}, 2),\n            ({-25.0: 62, 0: 59, 25: 65}, None),\n            ({-25.0: 64, -10: 60, 10: 61, 25: 66}, None),\n        ],\n    )\n    def test_spline_construction(self, k, nodes, expected_k):\n        irss = IRSplineSmile(\n            nodes=nodes,\n            k=k,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n        )\n        expected_k = expected_k or k\n        for key, v in nodes.items():\n            result = irss.get_from_strike(k=key / 100.0, f=0.0).vol\n            assert abs(result - v) < 1e-6\n            assert irss.nodes.spline.k == expected_k\n\n    @pytest.mark.parametrize(\n        (\"model\", \"metric\"), [(\"black76\", \"black_vol_shift_0\"), (\"bachelier\", \"normal_vol\")]\n    )\n    def test_pricing_model(self, model, metric):\n        irss = IRSplineSmile(\n            nodes={0: 20.0},\n            k=2,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"usd_irs\",\n            tenor=\"3m\",\n            id=\"vol\",\n            pricing_model=model,\n        )\n        curve = Curve({dt(2001, 1, 1): 1.0, dt(2003, 1, 1): 0.94})\n        iro = IRSCall(\n            expiry=dt(2002, 1, 1),\n            tenor=\"3m\",\n            irs_series=\"usd_irs\",\n            strike=3.0,\n        )\n        result = iro.rate(vol=irss, curves=curve, metric=metric)\n        expected = 20.0\n        assert abs(result - expected) < 1e-6\n\n    @pytest.mark.parametrize(\"model\", [\"black76\", \"bachelier\"])\n    @pytest.mark.parametrize(\"k\", [2, 4])\n    def test_d_sigma_d_f(self, model, k):\n        irss = IRSplineSmile(\n            nodes={-200.0: 70.0, -100.0: 58, 0: 50.0, 100.0: 61, 200.0: 75.0},\n            k=k,\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n            pricing_model=model,\n        )\n        result = irss._d_sigma_d_f(k=0.8, f=1.0)\n        dual = irss.nodes.spline.evaluate(x=(0.8 - Dual(1.0, [\"f\"], [])) * 100.0, m=0)\n        manual_gradient = gradient(dual, [\"f\"])[0] / 100.0\n        assert abs(result - manual_gradient) < 1e-10\n\n    def test_time_scalar(self):\n        irss = IRSplineSmile(\n            nodes={-200.0: 70.0, -100.0: 58, 0: 50.0, 100.0: 61, 200.0: 75.0},\n            k=2,\n            eval_date=dt(2000, 1, 1),\n            expiry=dt(2000, 7, 1),\n            irs_series=\"eur_irs6\",\n            tenor=\"2y\",\n            id=\"vol\",\n            time_scalar=0.9,\n        )\n        assert irss.meta.t_expiry == 0.9 * (31 + 29 + 31 + 30 + 31 + 30) / 365\n\n\nclass TestIRSplineCube:\n    def test_init(self):\n        IRSplineCube(\n            eval_date=dt(2026, 2, 16),\n            expiries=[\"1m\", \"3m\"],\n            tenors=[\"1Y\", \"2y\", \"3y\"],\n            strikes=[-100.0, 0.0, 100.0],\n            irs_series=\"usd_irs\",\n            id=\"usd_ir_vol\",\n            parameters=20.0,\n        )\n        pass\n\n    @pytest.mark.parametrize((\"ad\", \"klass\"), [(1, Dual), (2, Dual2)])\n    def test_constructed_spline_smile_vars(self, ad, klass):\n        irsc = IRSplineCube(\n            eval_date=dt(2026, 2, 20),\n            expiries=[\"1m\", \"3m\"],\n            tenors=[\"2y\", \"5y\"],\n            strikes=[-10.0],\n            irs_series=\"usd_irs\",\n            parameters=10.0,\n            ad=ad,\n            id=\"my-c\",\n        )\n        _ = irsc.get_from_strike(k=1.0, f=1.02, expiry=dt(2026, 3, 30), tenor=dt(2028, 8, 12))\n        smile = irsc._cache[(dt(2026, 3, 30), dt(2028, 8, 12))]\n        vars_ = smile.pricing_params[0].vars\n        assert vars_ == [\"my-c0\", \"my-c1\", \"my-c2\", \"my-c3\"]\n        assert isinstance(smile.pricing_params[0], klass)\n\n    @pytest.mark.parametrize(\n        (\"expiry\", \"tenor\", \"expected\"),\n        [\n            # tests on a node directly\n            (dt(2001, 1, 1), dt(2002, 1, 1), (10.0,)),\n            (dt(2002, 1, 1), dt(2003, 1, 1), (30.0,)),\n            (dt(2001, 1, 1), dt(2003, 1, 1), (20.0,)),\n            (dt(2002, 1, 1), dt(2004, 1, 1), (40.0,)),\n            # test within bounds\n            (dt(2001, 4, 1), dt(2002, 7, 1), (17.424657534246577,)),\n            (\n                dt(2001, 4, 1),\n                dt(2003, 1, 1),\n                (22.46575342465753,),\n            ),\n            (\n                dt(2001, 10, 1),\n                dt(2003, 1, 1),\n                (27.47945205479452,),\n            ),\n            (\n                dt(2001, 10, 1),\n                dt(2003, 7, 1),\n                (32.43835616438356,),\n            ),\n            # test out of bounds\n            (dt(2000, 7, 1), dt(2001, 1, 1), (10.0,)),  # 6m6m\n            (\n                dt(2000, 7, 1),\n                dt(2002, 1, 1),\n                (15.04109589041096,),\n            ),  # 6m18m\n            (dt(2000, 7, 1), dt(2003, 7, 1), (20.0,)),  # 6m3y\n            (\n                dt(2001, 7, 1),\n                dt(2002, 1, 1),\n                (19.91780821917808,),\n            ),  # 18m6m\n            (\n                dt(2001, 7, 1),\n                dt(2004, 7, 1),\n                (29.91780821917808,),\n            ),  # 18m3y\n            (dt(2003, 1, 1), dt(2003, 7, 1), (30.0,)),  # 3y6m\n            (\n                dt(2003, 1, 1),\n                dt(2004, 7, 1),\n                (34.986301369863014,),\n            ),  # 3y18m\n            (dt(2003, 1, 1), dt(2006, 1, 1), (40.0,)),  # 3y3y\n        ],\n    )\n    def test_interpolation_boundaries(self, expiry, tenor, expected):\n        # test that the SplineCube will interpolate the parameters if the expiry and tenors are\n        # - exactly falling on node dates\n        # - some elements within the node-mesh\n        # - some elements outside the node-mesh which are mapped to nearest components.\n        irsc = IRSplineCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            strikes=[0.0],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            parameters=np.reshape(np.array([10.0, 20.0, 30.0, 40.0]), (2, 2, 1)),\n            id=\"my-c\",\n        )\n        result = tuple(irsc._bilinear_interpolation(expiry=expiry, tenor=tenor))\n        assert result == expected\n\n    @pytest.mark.parametrize(\n        (\"expiry\", \"tenor\", \"expected\"),\n        [\n            (dt(2000, 7, 1), dt(2001, 1, 1), (10.0,)),\n            (dt(2000, 7, 1), dt(2001, 7, 1), (10.0,)),\n            (\n                dt(2000, 7, 1),\n                dt(2002, 1, 1),\n                (15.04109589041096,),\n            ),\n            (dt(2000, 7, 1), dt(2003, 7, 1), (20.0,)),\n            (dt(2001, 1, 1), dt(2001, 7, 1), (10.0,)),\n            (dt(2001, 1, 1), dt(2002, 1, 1), (10.0,)),\n            (\n                dt(2001, 1, 1),\n                dt(2002, 7, 1),\n                (14.95890410958904,),\n            ),\n            (dt(2001, 1, 1), dt(2003, 7, 1), (20.0,)),\n            (dt(2002, 1, 1), dt(2002, 7, 1), (10.0,)),\n            (dt(2002, 1, 1), dt(2003, 1, 1), (10.0,)),\n            (\n                dt(2002, 1, 1),\n                dt(2003, 7, 1),\n                (14.95890410958904,),\n            ),\n            (dt(2002, 1, 1), dt(2004, 7, 1), (20.0,)),\n        ],\n    )\n    def test_interpolation_single_expiry(self, expiry, tenor, expected):\n        # test that the SplineCube will interpolate the parameters if the expiry and tenors are\n        # - exactly falling on node dates\n        # - some elements within the node-mesh\n        # - some elements outside the node-mesh which are mapped to nearest components.\n        irsc = IRSplineCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\"],\n            tenors=[\"1y\", \"2y\"],\n            strikes=[0.0],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            parameters=np.reshape(np.array([10.0, 20.0]), (1, 2, 1)),\n            id=\"my-c\",\n        )\n        result = tuple(irsc._bilinear_interpolation(expiry=expiry, tenor=tenor))\n        assert result == expected\n\n    @pytest.mark.parametrize(\n        (\"expiry\", \"tenor\", \"expected\"),\n        [\n            (dt(2000, 7, 1), dt(2001, 1, 1), (10.0,)),\n            (dt(2000, 7, 1), dt(2001, 7, 1), (10.0,)),\n            (dt(2000, 7, 1), dt(2002, 1, 1), (10.0,)),\n            (dt(2001, 1, 1), dt(2001, 7, 1), (10.0,)),\n            (dt(2001, 1, 1), dt(2002, 1, 1), (10.0,)),\n            (dt(2001, 1, 1), dt(2002, 7, 1), (10.0,)),\n            (\n                dt(2001, 7, 1),\n                dt(2002, 1, 1),\n                (14.95890410958904,),\n            ),\n            (\n                dt(2001, 7, 1),\n                dt(2002, 7, 1),\n                (14.95890410958904,),\n            ),\n            (\n                dt(2001, 7, 1),\n                dt(2003, 1, 1),\n                (14.95890410958904,),\n            ),\n            (dt(2002, 7, 1), dt(2003, 1, 1), (20.0,)),\n            (dt(2002, 7, 1), dt(2003, 7, 1), (20.0,)),\n            (dt(2002, 7, 1), dt(2004, 7, 1), (20.0,)),\n        ],\n    )\n    def test_interpolation_single_tenor(self, expiry, tenor, expected):\n        # test that the SplineCube will interpolate the parameters if the expiry and tenors are\n        # - exactly falling on node dates\n        # - some elements within the node-mesh\n        # - some elements outside the node-mesh which are mapped to nearest components.\n        irsc = IRSplineCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\"],\n            strikes=[0.0],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            parameters=np.reshape(np.array([10.0, 20.0]), (2, 1, 1)),\n            id=\"my-c\",\n        )\n        result = tuple(irsc._bilinear_interpolation(expiry=expiry, tenor=tenor).tolist())\n        assert result == expected\n\n    def test_cache(self):\n        irsc = IRSplineCube(\n            eval_date=dt(2026, 2, 16),\n            expiries=[\"1m\", \"3m\"],\n            tenors=[\"1Y\", \"2Y\"],\n            strikes=[-10.0, 0.0, 10.0],\n            irs_series=\"usd_irs\",\n            id=\"usd_ir_vol\",\n            parameters=10.0,\n        )\n        irsc.get_from_strike(k=1.02, f=1.04, expiry=dt(2026, 3, 30), tenor=dt(2027, 8, 12))\n        assert (dt(2026, 3, 30), dt(2027, 8, 12)) in irsc._cache\n\n    def test_get_node_vector(self):\n        irsc = IRSplineCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            strikes=[-10.0, 0.0],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            parameters=np.reshape(np.array([1, 2, 3, 4, 5, 6, 7, 8]), (2, 2, 2)),\n            id=\"X\",\n        )\n        result = irsc._get_node_vector()\n        expected = np.array([1, 2, 3, 4, 5, 6, 7, 8])\n        assert irsc.get_smile(\"1y\", \"1y\").pricing_params == [np.float64(1.0), np.float64(2.0)]\n        assert np.all(result == expected)\n\n    def test_get_node_vector_ad1(self):\n        irsc = IRSplineCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            strikes=[0.0, 10.0],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            parameters=10.0,\n            id=\"X\",\n            ad=1,\n        )\n        result = irsc._get_node_vector()\n        assert result[2] == Dual(10.0, [\"X2\"], [])\n        assert result[7] == Dual(10.0, [\"X7\"], [])\n\n    def test_set_node_vector(self):\n        irsc = IRSplineCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            strikes=[-10.0, 0.0],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            parameters=10.0,\n            id=\"X\",\n        )\n        irsc._set_node_vector(np.array([0.1, 0.2, 0.3, 0.4, 1.0, 2.0, 3, 4]), ad=1)\n        result = irsc._get_node_vector()\n        assert result[2] == Dual(0.30, [\"X2\"], [])\n        assert result[7] == Dual(4, [\"X7\"], [])\n\n    @pytest.mark.skip(reason=\"no decision on how to use _set_ad_order for manually updated nodes.\")\n    def test_update_single_key(self):\n        # TODO need to decide how _set_or_ad should work with update nodes.\n        irsc = IRSplineCube(\n            eval_date=dt(2000, 1, 1),\n            expiries=[\"1y\", \"2y\"],\n            tenors=[\"1y\", \"2y\"],\n            strikes=[-10.0, 0.0],\n            irs_series=IRSSeries(\n                currency=\"usd\",\n                settle=0,\n                frequency=\"A\",\n                convention=\"Act360\",\n                calendar=\"all\",\n                leg2_fixing_method=\"ibor(2)\",\n            ),\n            parameters=10.0,\n            id=\"X\",\n            ad=1,\n        )\n        irsc.update_node((\"1y\", \"1y\", -10.0), 20.0)\n        result = irsc._get_node_vector()\n        assert result[0] == Dual(20.0, [\"X0\"], [])\n\n    @pytest.mark.parametrize(\n        (\"model\", \"metric\"), [(\"black76\", \"black_vol_shift_0\"), (\"bachelier\", \"normal_vol\")]\n    )\n    def test_pricing_model(self, model, metric):\n        irss = IRSplineCube(\n            parameters=[[[20.0]]],\n            k=2,\n            eval_date=dt(2001, 1, 1),\n            irs_series=\"usd_irs\",\n            expiries=[\"1y\"],\n            tenors=[\"3m\"],\n            strikes=[0.0],\n            id=\"vol\",\n            pricing_model=model,\n        )\n        curve = Curve({dt(2001, 1, 1): 1.0, dt(2003, 1, 1): 0.94})\n        iro = IRSCall(\n            expiry=dt(2002, 1, 1),\n            tenor=\"3m\",\n            irs_series=\"usd_irs\",\n            strike=3.0,\n        )\n        result = iro.rate(vol=irss, curves=curve, metric=metric)\n        expected = 20.0\n        assert abs(result - expected) < 1e-6\n\n    def test_business_day_time_and_weights(self):\n        nyc = calendars.get(\"nyc\")\n        irsc = IRSplineCube(\n            eval_date=dt(2000, 1, 3),\n            expiries=[\"1m\", \"3m\", \"6m\"],\n            tenors=[\"1y\"],\n            strikes=[0],\n            parameters=[[[30.0]], [[35.0]], [[38.0]]],\n            irs_series=\"usd_irs\",\n        )\n        irsc2 = IRSplineCube(\n            eval_date=dt(2000, 1, 3),\n            expiries=[\"1m\", \"3m\", \"6m\"],\n            tenors=[\"1y\"],\n            strikes=[0],\n            parameters=[[[30.0]], [[35.0]], [[38.0]]],\n            irs_series=\"usd_irs\",\n            weights=Series(\n                index=[\n                    _\n                    for _ in nyc.cal_date_range(dt(2000, 1, 7), dt(2000, 7, 15))\n                    if nyc.is_non_bus_day(_)\n                ],\n                data=0.0,\n            ),\n        )\n        curve = Curve(\n            nodes={dt(2000, 1, 3): 1.0, dt(2002, 1, 3): 0.93},\n            convention=\"act360\",\n            calendar=\"nyc\",\n        )\n        for expiry in irsc.meta.expiry_dates:\n            # test at expiries time remapping does not exist because these are the natural pillars\n            iro = IRSCall(\n                expiry=expiry,\n                strike=\"atm\",\n                irs_series=\"usd_irs\",\n                tenor=\"1y\",\n            )\n            r1 = iro.rate(curves=curve, vol=irsc, metric=\"percentnotional\") * 100.0\n            r2 = iro.rate(curves=curve, vol=irsc2, metric=\"percentnotional\") * 100.0\n            assert abs(r1 - r2) < 1e-8\n\n        for expiry in [dt(2000, 1, 14), dt(2000, 2, 18), dt(2000, 5, 12)]:\n            # test at expiries inbetween the time remapping exists\n            iro = IRSCall(\n                expiry=expiry,\n                strike=\"atm\",\n                irs_series=\"usd_irs\",\n                tenor=\"1y\",\n            )\n            r1 = iro.rate(curves=curve, vol=irsc, metric=\"percentnotional\") * 100.0\n            r2 = iro.rate(curves=curve, vol=irsc2, metric=\"percentnotional\") * 100.0\n            assert abs(r1 - r2) > 1e-3\n\n        for expiry in [dt(2000, 7, 20), dt(2000, 7, 25)]:\n            # test after weights stop being defined\n            iro = IRSCall(\n                expiry=expiry,\n                strike=\"atm\",\n                irs_series=\"usd_irs\",\n                tenor=\"1y\",\n            )\n            r1 = iro.rate(curves=curve, vol=irsc, metric=\"percentnotional\") * 100.0\n            r2 = iro.rate(curves=curve, vol=irsc2, metric=\"percentnotional\") * 100.0\n            assert abs(r1 - r2) < 1e-8\n\n\nclass TestStateAndCache:\n    @pytest.mark.parametrize(\n        \"obj\",\n        [\n            IRSabrSmile(\n                nodes={\n                    \"alpha\": 0.1,\n                    \"rho\": -0.05,\n                    \"nu\": 0.1,\n                },\n                beta=0.5,\n                eval_date=dt(2001, 1, 1),\n                expiry=dt(2002, 1, 1),\n                irs_series=\"eur_irs6\",\n                tenor=\"2y\",\n                id=\"v\",\n                ad=2,\n            ),\n            IRSabrCube(\n                eval_date=dt(2026, 2, 16),\n                expiries=[\"1m\", \"3m\"],\n                tenors=[\"1Y\", \"2y\", \"3y\"],\n                irs_series=\"usd_irs\",\n                id=\"usd_ir_vol\",\n                beta=0.5,\n                alpha=np.array([[0.1, 0.2, 0.3], [0.11, 0.12, 0.13]]),\n                rho=np.array([[0.1, 0.2, 0.3], [0.11, 0.12, 0.13]]),\n                nu=np.array([[0.1, 0.2, 0.3], [0.11, 0.12, 0.13]]),\n            ),\n        ],\n    )\n    @pytest.mark.parametrize((\"method\", \"args\"), [(\"_set_ad_order\", (1,))])\n    def test_method_does_not_change_state(self, obj, method, args):\n        before = obj._state\n        getattr(obj, method)(*args)\n        after = obj._state\n        assert before == after\n\n    @pytest.mark.parametrize(\n        \"obj\",\n        [\n            IRSabrSmile(\n                nodes={\n                    \"alpha\": 0.1,\n                    \"rho\": -0.05,\n                    \"nu\": 0.1,\n                },\n                beta=0.5,\n                eval_date=dt(2001, 1, 1),\n                expiry=dt(2002, 1, 1),\n                irs_series=\"eur_irs6\",\n                tenor=\"2y\",\n                id=\"v\",\n                ad=2,\n            ),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98, 0.99], 1)),\n            (\"update_node\", (\"alpha\", 0.98)),\n        ],\n    )\n    def test_method_changes_state(self, obj, method, args):\n        before = obj._state\n        getattr(obj, method)(*args)\n        after = obj._state\n        assert before != after\n\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            IRSabrSmile(\n                nodes={\n                    \"alpha\": 0.1,\n                    \"rho\": -0.05,\n                    \"nu\": 0.1,\n                },\n                beta=0.5,\n                eval_date=dt(2001, 1, 1),\n                expiry=dt(2002, 1, 1),\n                irs_series=\"eur_irs6\",\n                tenor=\"2y\",\n                id=\"v\",\n                ad=2,\n            ),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98, 0.99], 1)),\n            (\"update_node\", (\"alpha\", 0.98)),\n        ],\n    )\n    def test_method_changes_state_sabr(self, curve, method, args):\n        before = curve._state\n        getattr(curve, method)(*args)\n        after = curve._state\n        assert before != after\n\n    @pytest.mark.parametrize(\n        \"curve\",\n        [\n            IRSabrCube(\n                eval_date=dt(2026, 2, 16),\n                expiries=[\"1m\"],\n                tenors=[\"1Y\"],\n                irs_series=\"usd_irs\",\n                id=\"usd_ir_vol\",\n                beta=0.5,\n                alpha=np.array([[0.1]]),\n                rho=np.array([[0.2]]),\n                nu=np.array([[0.3]]),\n            ),\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98, 0.99], 1)),\n            (\"update_node\", ((dt(2026, 3, 16), dt(2027, 3, 18), \"alpha\"), 0.98)),\n        ],\n    )\n    def test_method_changes_state_sabr_cube(self, curve, method, args):\n        before = curve._state\n        getattr(curve, method)(*args)\n        after = curve._state\n        assert before != after\n\n    #\n    #     def test_populate_cache(self):\n    #         # objects have yet to implement cache handling\n    #         pass\n    #\n    #     def test_method_clears_cache(self):\n    #         # objects have yet to implement cache handling\n    #         pass\n    #\n    @pytest.mark.parametrize(\n        (\"method\", \"args\"),\n        [\n            (\"_set_node_vector\", ([0.99, 0.98, 1.0], 1)),\n            (\"_set_ad_order\", (2,)),\n        ],\n    )\n    def test_surface_clear_cache(self, method, args):\n        surf = IRSabrCube(\n            eval_date=dt(2026, 2, 16),\n            expiries=[\"1m\"],\n            tenors=[\"1Y\"],\n            irs_series=\"usd_irs\",\n            id=\"usd_ir_vol\",\n            beta=0.5,\n            alpha=np.array([[0.1]]),\n            rho=np.array([[0.2]]),\n            nu=np.array([[0.3]]),\n        )\n        surf.get_from_strike(f=1.0, k=1.01, expiry=dt(2026, 3, 1), tenor=dt(2027, 3, 1))\n        assert (dt(2026, 3, 1), dt(2027, 3, 1)) in surf._cache\n\n        getattr(surf, method)(*args)\n        assert len(surf._cache) == 0\n\n\nclass TestPricingModelConversion:\n    class TestBachelier:\n        @pytest.mark.parametrize(\n            (\"vol\", \"k\", \"shift\", \"expected\"),\n            [\n                (25.0, 2.99, 0.0, 8.3496780104),\n                (25.0, 2.99, 50.0, 7.15460637959775),\n                (25.0, 2.99, 200.0, 5.005529190687043),\n                (25.0, 1.50, 0.0, 11.615241673583585),\n                (25.0, 1.50, 50.0, 9.312911744191437),\n                (25.0, 1.50, 200.0, 5.9394076088397645),\n                (25.0, 4.50, 0.0, 6.753315378082834),\n                (25.0, 4.50, 50.0, 5.9394076088397645),\n                (25.0, 4.50, 200.0, 4.368303987428187),\n            ],\n        )\n        def test_convert_to_black_no_shift(self, vol, k, shift, expected):\n            result = _OptionModelBachelier.convert_to_black76(\n                f=3.0, k=k, shift=shift, vol=vol, t_e=1.0\n            )\n            assert abs(result - expected) < 1e-6\n\n    class TestBlack76:\n        @pytest.mark.parametrize(\n            (\"vol\", \"k\", \"shift\", \"expected\"),\n            [\n                (25.0, 2.99, 0.0, 74.68039981110007),\n                (25.0, 2.99, 50.0, 87.14793380301037),\n                (25.0, 2.99, 200.0, 124.55052385921005),\n                (25.0, 1.50, 0.0, 53.96106256666565),\n                (25.0, 1.50, 50.0, 66.8366143175683),\n                (25.0, 1.50, 200.0, 104.86487953597288),\n                (25.0, 4.50, 0.0, 92.24642085914786),\n                (25.0, 4.50, 50.0, 104.86487953597292),\n                (25.0, 4.50, 200.0, 142.55991748648242),\n            ],\n        )\n        def test_convert_to_bachelier(self, vol, k, shift, expected):\n            result = _OptionModelBlack76.convert_to_bachelier(\n                f=3.0, k=k, shift=shift, vol=vol, t_e=1.0\n            )\n            assert abs(result - expected) < 1e-9\n\n        @pytest.mark.parametrize(\n            (\"vol\", \"k\", \"shift\", \"tgt\", \"expected\"),\n            [\n                (25.0, 2.99, 0.0, 50.0, 21.40861097419223),\n                (25.0, 2.99, 50.0, 100.0, 21.85769609359381),\n                (25.0, 2.99, 200.0, 100.0, 31.30396613960251),\n                (25.0, 1.50, 0.0, 50.0, 20.16566976523089),\n                (25.0, 1.50, 50.0, 100.0, 20.980647995758154),\n                (25.0, 1.50, 200.0, 100.0, 33.00686423510773),\n                (25.0, 4.50, 0.0, 50.0, 21.9787696869096),\n                (25.0, 4.50, 50.0, 100.0, 22.309213489533068),\n                (25.0, 4.50, 200.0, 100.0, 30.382178316599756),\n            ],\n        )\n        def test_convert_to_new_shift(self, vol, k, shift, tgt, expected):\n            result = _OptionModelBlack76.convert_to_new_shift(\n                f=3.0, k=k, old_shift=shift, target_shift=tgt, vol=vol, t_e=1.0\n            )\n            assert abs(result - expected) < 1e-9\n\n\n@pytest.mark.skipif(\n    sys.version_info[:2] == (3, 10), reason=\"This test is incompatible with Python 3.10\"\n)\nclass TestCookbokReplicators:\n    def test_z_ir_vol_risks(self):\n        curve = Curve(\n            nodes={\n                dt(2026, 3, 17): 1.0,\n                dt(2026, 9, 17): 1.0,\n                dt(2027, 3, 17): 1.0,\n                dt(2028, 3, 17): 1.0,\n                dt(2029, 3, 17): 1.0,\n                dt(2030, 3, 17): 1.0,\n                dt(2031, 4, 17): 1.0,\n            },\n            convention=\"act360\",\n            calendar=\"nyc\",\n            interpolation=\"log_linear\",\n            id=\"sofr\",\n        )\n        swap_tenors = [\"6m\", \"1y\", \"2y\", \"3y\", \"4y\", \"5y\"]\n        curve_solver = Solver(\n            curves=[curve],\n            instruments=[\n                IRS(dt(2026, 3, 17), _, spec=\"usd_irs\", curves=\"sofr\") for _ in swap_tenors\n            ],\n            s=[4.10, 4.02, 4.08, 4.12, 4.18, 4.22],\n            instrument_labels=swap_tenors,\n            id=\"us_rates\",\n        )\n\n        expiries = [\"6m\", \"1y\", \"2y\"]\n        tenors = [\"3m\", \"1y\", \"2y\"]\n        pricing_cube = IRSabrCube(\n            eval_date=dt(2026, 3, 17),\n            expiries=expiries,\n            tenors=tenors,\n            irs_series=\"usd_irs\",\n            beta=0.5,  # <- beta is a hyper-parameter and applies globally to this Cube\n            alpha=0.5,  # <- alpha as scalar applies the same value to each gridpoint automatically\n            rho=[  # <- rho is provided in array format with a value at each gridpoint\n                [0.4, 0.45, 0.29],\n                [0.4, 0.4, 0.26],\n                [0.3, 0.3, 0.25],\n            ],\n            nu=[  # <- nu is provided in array format with a value at each gridpoint\n                [1.0, 0.98, 0.87],\n                [0.9, 0.875, 0.7],\n                [0.63, 0.6, 0.56],\n            ],\n            id=\"usd_cube\",\n        )\n        pricing_solver = Solver(surfaces=[pricing_cube], pre_solvers=[curve_solver])\n        iro = IRSPut(\n            expiry=dt(2027, 3, 3),\n            tenor=\"1y\",\n            irs_series=\"usd_irs\",\n            notional=125e6,\n            strike=3.99,\n            premium=400000,\n            curves=\"sofr\",\n            vol=\"usd_cube\",\n            metric=\"normal_vol\",\n        )\n\n        result = iro.npv(solver=pricing_solver)\n        assert abs(result - 12988.135) < 1e-2\n\n        result = iro.rate(solver=pricing_solver)\n        assert abs(result - 103.889) < 1e-2\n\n        expiries = [\"3m\", \"1y\", \"2y\"]  # <- expiries are different to those above\n        tenors = [\"1y\", \"2y\"]  # <- tenors are also different\n        strikes = [-100.0, -50.0, -25.0, 0.0, 25.0, 50.0, 100.0]  # <- strikes are bps to ATM\n        risk_cube = IRSplineCube(\n            eval_date=dt(2026, 3, 17),\n            expiries=expiries,\n            tenors=tenors,\n            strikes=strikes,\n            irs_series=\"usd_irs\",\n            parameters=25.0,  # <-  all normal vol values are initialised at 25bps\n            id=\"usd_cube\",\n        )\n        strikes_str = [f\"{_}bps\" for _ in strikes]\n        args = dict(\n            irs_series=\"eur_irs3\",\n            eval_date=dt(2026, 3, 11),\n            metric=\"normal_vol\",\n            curves=\"sofr\",\n            vol=\"usd_cube\",\n        )\n        instruments = [\n            IRVolValue(e, t, k, **args) for e, t, k in product(expiries, tenors, strikes_str)\n        ]\n        instrument_labels = [f\"{e}{t}_{k}\" for e, t, k in product(expiries, tenors, strikes_str)]\n        risk_solver = Solver.from_other(\n            pricing_solver=pricing_solver,  # <- will determine our ``s`` rates directly\n            surfaces=[risk_cube],\n            instruments=instruments,\n            instrument_labels=instrument_labels,\n            id=\"us_vol\",\n            pre_solvers=[\n                curve_solver\n            ],  # <- the curve_solver is still needed to pass the SOFR Curve.\n            grad_tol=1e-5,\n            func_tol=1e-5,\n            step_tol=1e-5,\n        )\n        df = iro.delta(solver=risk_solver)\n        ix = IndexSlice\n        delta = df.loc[ix[:, \"us_rates\"], :].sum(axis=None)\n        vega = df.loc[ix[:, \"us_vol\"], :].sum(axis=None)\n        gf = iro.gamma(solver=risk_solver)\n        gamma = gf.loc[ix[:, :, :, \"us_rates\"], ix[:, \"us_rates\"]].sum(axis=None)\n        vomma = gf.loc[ix[:, :, :, \"us_vol\"], ix[:, \"us_vol\"]].sum(axis=None)\n        vanna = gf.loc[ix[:, :, :, \"us_rates\"], ix[:, \"us_vol\"]].sum(axis=None)\n\n        agks = iro.analytic_greeks(solver=risk_solver)\n\n        assert abs(agks[\"gamma_usd\"] - gamma) < 5.5\n        assert abs(agks[\"vega_usd\"] - vega) < 1e-3\n        assert abs(agks[\"vomma_usd\"] - vomma) < 1e-3\n        assert abs(agks[\"vanna_usd\"] - vanna) < 1.1\n        assert abs(agks[\"delta_sticky_usd\"] - delta) < 42.0\n"
  },
  {
    "path": "python/tests/test_serialization.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.curves import Curve, LineCurve\nfrom rateslib.curves.utils import (\n    _CurveInterpolator,\n    _CurveMeta,\n    _CurveNodes,\n    _CurveSpline,\n    _CurveType,\n)\nfrom rateslib.default import NoInput\nfrom rateslib.dual import Dual, Dual2, Variable\nfrom rateslib.scheduling import Convention, get_calendar\nfrom rateslib.serialization import from_json\nfrom rateslib.serialization.utils import _enum_to_json\n\n\n@pytest.mark.parametrize(\"calendar\", [get_calendar(\"tgt\"), get_calendar(NoInput(0))])\n@pytest.mark.parametrize(\n    \"index_base\",\n    [\n        100.0,\n        Dual(100.0, [\"v\"], []),\n        Dual2(100.0, [\"v\"], [], []),\n        NoInput(0),\n    ],\n)\n@pytest.mark.parametrize(\"collateral\", [None, \"usd\"])\ndef test_curvemeta_json_round_trip(calendar, index_base, collateral):\n    obj = _CurveMeta(\n        _calendar=calendar,\n        _convention=Convention.Act360,\n        _modifier=\"MF\",\n        _index_base=index_base,\n        _index_lag=1,\n        _collateral=collateral,\n        _credit_discretization=20,\n        _credit_recovery_rate=Variable(2.5, [\"x\"]),\n    )\n    json_text = obj.to_json()\n    round_trip = from_json(json_text)\n    assert round_trip == obj\n\n\n@pytest.mark.parametrize(\n    \"obj\",\n    [\n        _CurveSpline(t=[dt(2000, 1, 1), dt(2002, 1, 1)], endpoints=(\"natural\", \"natural\")),\n        _CurveNodes({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 0.98}),\n        # _CurveNodes({dt(2000,1,1): Dual(1.0, [\"x\"], []), dt(2001, 1, 1): Dual(0.98, [\"s\"], [])}),\n    ],\n)\ndef test_curvespline_json_round_trip(obj):\n    json_text = obj.to_json()\n    round_trip = from_json(json_text)\n    assert round_trip == obj\n\n\n@pytest.mark.parametrize(\"local\", [\"linear\", \"spline\"])\n@pytest.mark.parametrize(\"t\", [NoInput(0), [dt(2000, 1, 1), dt(2002, 1, 1)]])\ndef test_curveinterpolator_json_round_trip(local, t):\n    if not isinstance(t, NoInput) and local == \"spline\":\n        with pytest.raises(ValueError, match=\"When defining 'spline' interpola\"):\n            _CurveInterpolator(local, t, None, None, None, None)\n        return None\n\n    obj = _CurveInterpolator(\n        local=local,\n        t=t,\n        endpoints=(\"natural\", \"natural\"),\n        node_dates=[dt(2000, 1, 1), dt(2002, 1, 1)],\n        convention=\"act365f\",\n        curve_type=_CurveType.dfs,\n    )\n    json_text = obj.to_json()\n    round_trip = from_json(json_text)\n    assert round_trip == obj\n\n\n@pytest.mark.parametrize(\"value\", [-1, 0, 1])\ndef test_no_input_round_trip(value):\n    obj = NoInput(value)\n    json = _enum_to_json(obj)\n    result = from_json(json)\n    assert result == obj\n\n\n@pytest.fixture\ndef curve():\n    return Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.99,\n        },\n        interpolation=\"linear\",\n        index_lag=3,\n        id=\"v\",\n        convention=\"Act360\",\n        ad=1,\n    )\n\n\n@pytest.fixture\ndef line_curve():\n    return LineCurve(\n        nodes={\n            dt(2022, 3, 1): 2.00,\n            dt(2022, 3, 31): 2.01,\n        },\n        interpolation=\"linear\",\n        id=\"v\",\n        ad=1,\n    )\n\n\n@pytest.fixture\ndef index_curve():\n    return Curve(\n        nodes={\n            dt(2022, 3, 1): 1.00,\n            dt(2022, 3, 31): 0.999,\n        },\n        interpolation=\"linear_index\",\n        id=\"v\",\n        ad=1,\n        index_base=110.0,\n        index_lag=3,\n    )\n\n\nclass TestCurve:\n    def test_serialization(self, curve) -> None:\n        expected = (\n            r'{\"PyNative\": '\n            r'{\"Curve\": {\"meta\": \"{\\\"PyNative\\\": '\n            r\"{\\\"_CurveMeta\\\": {\\\"calendar\\\": \"\n            r\"\\\"{\\\\\\\"NamedCal\\\\\\\":{\\\\\\\"name\\\\\\\":\\\\\\\"all\\\\\\\"}}\\\", \"\n            r\"\\\"convention\\\": \\\"{\\\\\\\"Convention\\\\\\\":\\\\\\\"Act360\\\\\\\"}\\\", \"\n            r\"\\\"modifier\\\": \\\"MF\\\", \\\"index_base\\\": \\\"{\\\\\\\"PyNative\\\\\\\":\"\n            r\"{\\\\\\\"NoInput\\\\\\\":0}}\\\", \\\"index_lag\\\": 3, \\\"collateral\\\": \"\n            r\"null, \\\"credit_discretization\\\": 23, \\\"credit_recovery_rate\\\": \"\n            r'\\\"0.4\\\"}}}\", \"interpolator\": \"{\\\"PyNative\\\": {\\\"_CurveInterpolator\\\": '\n            r\"{\\\"local\\\": \\\"linear\\\", \\\"spline\\\": \\\"null\\\", \\\"convention\\\": \"\n            r'\\\"{\\\\\\\"Convention\\\\\\\":\\\\\\\"Act360\\\\\\\"}\\\"}}}\", \"id\": \"v\", '\n            r'\"ad\": 1, \"nodes\": \"{\\\"PyNative\\\": {\\\"_CurveNodes\\\": {\\\"_nodes\\\": '\n            r'{\\\"2022-03-01\\\": 1.0, \\\"2022-03-31\\\": 0.99}}}}\"}}}'\n        )\n        result = curve.to_json()\n        assert result == expected\n\n    @pytest.mark.parametrize(\"c\", [\"curve\", \"line_curve\", \"index_curve\"])\n    def test_serialization_round_trip(self, c, curve, line_curve, index_curve) -> None:\n        if c == \"curve\":\n            obj = curve\n        elif c == \"line_curve\":\n            obj = line_curve\n        elif c == \"index_curve\":\n            obj = index_curve\n        serial = obj.to_json()\n        constructed = from_json(serial)\n        assert constructed == obj\n\n    def test_serialization_round_trip_spline(self) -> None:\n        curve = Curve(\n            nodes={\n                dt(2022, 3, 1): 1.00,\n                dt(2022, 3, 31): 0.99,\n                dt(2022, 5, 1): 0.98,\n                dt(2022, 6, 4): 0.97,\n                dt(2022, 7, 4): 0.96,\n            },\n            interpolation=\"linear\",\n            id=\"v\",\n            convention=\"Act360\",\n            ad=1,\n            t=[\n                dt(2022, 5, 1),\n                dt(2022, 5, 1),\n                dt(2022, 5, 1),\n                dt(2022, 5, 1),\n                dt(2022, 6, 4),\n                dt(2022, 7, 4),\n                dt(2022, 7, 4),\n                dt(2022, 7, 4),\n                dt(2022, 7, 4),\n            ],\n        )\n\n        serial = curve.to_json()\n        constructed = from_json(serial)\n        assert constructed == curve\n\n    def test_serialization_curve_str_calendar(self) -> None:\n        curve = Curve(\n            nodes={\n                dt(2022, 3, 1): 1.00,\n                dt(2022, 3, 31): 0.99,\n            },\n            interpolation=\"linear\",\n            id=\"v\",\n            convention=\"Act360\",\n            modifier=\"F\",\n            calendar=\"LDN\",\n            ad=1,\n        )\n        serial = curve.to_json()\n        constructed = from_json(serial)\n        assert constructed == curve\n\n    def test_serialization_curve_custom_calendar(self) -> None:\n        calendar = get_calendar(\"ldn\")\n        curve = Curve(\n            nodes={\n                dt(2022, 3, 1): 1.00,\n                dt(2022, 3, 31): 0.99,\n            },\n            interpolation=\"linear\",\n            id=\"v\",\n            convention=\"Act360\",\n            modifier=\"F\",\n            calendar=calendar,\n            ad=1,\n        )\n        serial = curve.to_json()\n        constructed = from_json(serial)\n        assert constructed == curve\n"
  },
  {
    "path": "python/tests/test_solver.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport sys\nimport warnings\nfrom datetime import datetime as dt\nfrom math import cos, exp\n\nimport numpy as np\nimport pytest\nfrom numpy.testing import assert_allclose\nfrom pandas import DataFrame, MultiIndex, Series\nfrom pandas.errors import PerformanceWarning\nfrom pandas.testing import assert_frame_equal, assert_series_equal\nfrom rateslib import add_tenor, calendars, default_context\nfrom rateslib.curves import CompositeCurve, Curve, LineCurve, MultiCsaCurve\nfrom rateslib.dual import Dual, Dual2, Variable, gradient, ift_1dim, newton_1dim, newton_ndim\nfrom rateslib.fx import FXForwards, FXRates\nfrom rateslib.instruments import (\n    IRS,\n    XCS,\n    FloatRateNote,\n    FXBrokerFly,\n    FXCall,\n    FXRiskReversal,\n    FXStraddle,\n    FXStrangle,\n    FXSwap,\n    Portfolio,\n    Value,\n)\nfrom rateslib.solver import Gradients, Solver\nfrom rateslib.volatility import FXDeltaVolSmile, FXDeltaVolSurface, FXSabrSmile, FXSabrSurface\n\n\nclass TestIFTSolver:\n    @pytest.mark.parametrize(\"args\", [(2.0, 3.0), (-2.0, -1.0)])\n    def test_failed_state(self, args):\n        def s(x):\n            return x\n\n        result = ift_1dim(s, 1.0, \"bisection\", args, raise_on_fail=False)\n        assert result[\"state\"] == -2\n\n    def test_failed_state_raises(self):\n        def s(x):\n            return x\n\n        with pytest.raises(ValueError, match=\"The internal iterative function `h` has reported\"):\n            ift_1dim(s, 1.0, \"bisection\", (2.0, 3.0), raise_on_fail=True)\n\n    def test_solution_func_tol_state(self):\n        def s(x):\n            return x**2\n\n        result = ift_1dim(s, 9.0, \"bisection\", (1.0, 5.0), func_tol=1e-10)\n        # function should perform 2 iterations and arrive at 3.0\n        assert result[\"state\"] == 2\n        assert result[\"g\"] == 3.0\n\n    def test_solution_conv_tol_state(self):\n        def s(x):\n            return x**2\n\n        result = ift_1dim(s, 9.0, \"bisection\", (1.15, 5.0), conv_tol=1e-5)\n        # function should perform many bisections iterations and arrive close to 3.0 with conv_tol\n        assert result[\"state\"] == 1\n        assert result[\"iterations\"] > 16\n        assert abs(result[\"g\"] - 3.0) < 1e-5\n\n    def test_solution_max_iter_state(self):\n        def s(x):\n            return x**2\n\n        result = ift_1dim(\n            s, 9.0, \"bisection\", (1.15, 5.0), conv_tol=1e-5, max_iter=5, raise_on_fail=False\n        )\n        # function should perform many bisections iterations and arrive close to 3.0 with conv_tol\n        assert result[\"state\"] == -1\n\n    def test_dual_returns(self):\n        def s(x):\n            return 3.0 / (1 + x / 100.0) + (100.0 + 3.0) / (1 + x / 100.0) ** 2\n\n        result = ift_1dim(s, Dual(101.0, [\"s\"], []), \"bisection\", (2.0, 4.0), conv_tol=1e-5)\n\n        # ds_dx = -3 / (1+g)**2 - 2*(103) / (1+g)**3\n        g = result[\"g\"]\n        ds_dx = -3.0 / (1.0 + g / 100.0) ** 2 - 2.0 * (103.0) / (1.0 + g / 100.0) ** 3\n        dg_ds_analytic = 1 / ds_dx * 100.0\n        dg_ds_ad = gradient(g, [\"s\"])[0]\n\n        assert abs(dg_ds_ad - dg_ds_analytic) < 1e-10\n\n    def test_dual2_returns(self):\n        # second part of dual returns\n        def s(x):\n            return 3.0 / (1 + x / 100.0) + (100.0 + 3.0) / (1 + x / 100.0) ** 2\n\n        result = ift_1dim(s, Dual2(101.0, [\"s\"], [], []), \"bisection\", (2.0, 4.0), conv_tol=1e-5)\n\n        # d2s_dx2 = 2.3 / (1+g)**3 + 6*(103) / (1+g)**4\n        g = result[\"g\"]\n        ds_dx = -3.0 / (1.0 + g / 100.0) ** 2 - 2.0 * (103.0) / (1.0 + g / 100.0) ** 3\n        d2s_dx2 = 6.0 / (1.0 + g / 100.0) ** 3 + 6.0 * (103.0) / (1.0 + g / 100.0) ** 4\n\n        d2g_ds2_analytic = -100 * d2s_dx2 / ds_dx**3\n        d2g_ds2_ad = gradient(g, [\"s\"], order=2)[0][0]\n\n        assert abs(d2g_ds2_ad - d2g_ds2_analytic) < 1e-10\n\n    class TestDekker:\n        def test_simple_linear(self):\n            # test should converge in one secant iteration\n            def s(g):\n                return g\n\n            s_tgt = s(2.0)\n            result = ift_1dim(s, s_tgt, \"modified_dekker\", (0, 4), conv_tol=1e-12)\n            assert result[\"g\"] == 2.0\n            assert result[\"iterations\"] == 1\n\n        def test_cubic_with_bracketed_intervals(self):\n            # test converge to different roots withing the bracketed interval\n            def s(g):\n                return g**3 - 6 * g**2 + 11 * g - 6\n\n            s_tgt = 0.0\n            # roots at 1, 2, 3\n            result = ift_1dim(s, s_tgt, \"modified_dekker\", (0, 1.5), conv_tol=1e-12, func_tol=1e-12)\n            assert abs(result[\"g\"] - 1.0) < 1e-12\n            assert result[\"iterations\"] < 10\n\n            result = ift_1dim(\n                s, s_tgt, \"modified_dekker\", (1.1, 2.9), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 2.0) < 1e-12\n            assert result[\"iterations\"] < 10\n\n            result = ift_1dim(\n                s, s_tgt, \"modified_dekker\", (2.1, 25.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 3.0) < 1e-12\n            assert result[\"iterations\"] < 15\n\n        @pytest.mark.parametrize(\"bracket\", [(0.0, 1.0), (1.0, 10.0)])\n        def test_root_in_bracket(self, bracket):\n            # test converge to different roots withing the bracketed interval\n            def s(g):\n                return g**3 - 6 * g**2 + 11 * g - 6\n\n            s_tgt = 0.0\n            # roots at 1, 2, 3\n            result = ift_1dim(s, s_tgt, \"modified_dekker\", bracket, conv_tol=1e-12, func_tol=1e-12)\n            assert abs(result[\"g\"] - 1.0) < 1e-12\n            assert result[\"iterations\"] == 1\n\n        def test_both_roots_in_bracket_takes_left_side(self):\n            # test converge to different roots withing the bracketed interval\n            def s(g):\n                return g**3 - 6 * g**2 + 11 * g - 6\n\n            s_tgt = 0.0\n            # roots at 1, 2, 3\n            result = ift_1dim(\n                s, s_tgt, \"modified_dekker\", (1.0, 2.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 1.0) < 1e-12\n            assert result[\"iterations\"] == 1\n\n        def test_horizontal_secant(self):\n            # the first iterate the boundaries yield the same value and the secant is div by zero\n            def s(g):\n                return g**2 - 2\n\n            s_tgt = 0.0\n            # roots at 1, 2, 3\n            result = ift_1dim(\n                s, s_tgt, \"modified_dekker\", (-2.0, 2.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] + 2**0.5) < 1e-12\n            assert result[\"iterations\"] < 10\n\n        def test_asymptote(self):\n            def s(g):\n                return 1 / (g - 3) - 6\n\n            s_tgt = 0.0\n            # roots at 19 / 6\n            result = ift_1dim(\n                s, s_tgt, \"modified_dekker\", (3.02, 4.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 19 / 6) < 1e-12\n            assert result[\"iterations\"] < 12\n\n        def test_dekker(self):\n            def s(x):\n                return exp(x) + x**2\n\n            s_tgt = s(2.0)\n            result = ift_1dim(s, s_tgt, \"modified_dekker\", (1.15, 5.0), conv_tol=1e-12)\n            assert result[\"g\"] == 2.0\n            assert result[\"iterations\"] < 12\n\n            result2 = ift_1dim(s, s_tgt, \"bisection\", (1.15, 5.0), conv_tol=1e-12)\n            assert 30 < result2[\"iterations\"] < 50\n\n        def test_dekker_conv_tol(self):\n            def s(x):\n                return exp(x) + x**2\n\n            s_tgt = s(2.0)\n            result = ift_1dim(s, s_tgt, \"modified_dekker\", (1.15, 5.0), conv_tol=1e-3)\n            assert result[\"state\"] == 1\n\n    class TestBrent:\n        def test_simple_linear(self):\n            # test should converge in one secant iteration\n            def s(g):\n                return g\n\n            s_tgt = s(2.0)\n            result = ift_1dim(s, s_tgt, \"modified_brent\", (0, 4), conv_tol=1e-12)\n            assert result[\"g\"] == 2.0\n            assert result[\"iterations\"] == 1\n\n        def test_cubic_with_bracketed_intervals(self):\n            # test converge to different roots withing the bracketed interval\n            def s(g):\n                return g**3 - 6 * g**2 + 11 * g - 6\n\n            s_tgt = 0.0\n            # roots at 1, 2, 3\n            result = ift_1dim(s, s_tgt, \"modified_brent\", (0, 1.5), conv_tol=1e-12, func_tol=1e-12)\n            assert abs(result[\"g\"] - 1.0) < 1e-12\n            assert result[\"iterations\"] < 10\n\n            result = ift_1dim(\n                s, s_tgt, \"modified_brent\", (1.1, 2.9), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 2.0) < 1e-12\n            assert result[\"iterations\"] < 10\n\n            result = ift_1dim(\n                s, s_tgt, \"modified_brent\", (2.1, 25.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 3.0) < 1e-12\n            assert result[\"iterations\"] < 15\n\n        @pytest.mark.parametrize(\"bracket\", [(0.0, 1.0), (1.0, 10.0)])\n        def test_root_in_bracket(self, bracket):\n            # test converge to different roots withing the bracketed interval\n            def s(g):\n                return g**3 - 6 * g**2 + 11 * g - 6\n\n            s_tgt = 0.0\n            # roots at 1, 2, 3\n            result = ift_1dim(s, s_tgt, \"modified_brent\", bracket, conv_tol=1e-12, func_tol=1e-12)\n            assert abs(result[\"g\"] - 1.0) < 1e-12\n            assert result[\"iterations\"] == 1\n\n        def test_both_roots_in_bracket_takes_left_side(self):\n            # test converge to different roots withing the bracketed interval\n            def s(g):\n                return g**3 - 6 * g**2 + 11 * g - 6\n\n            s_tgt = 0.0\n            # roots at 1, 2, 3\n            result = ift_1dim(\n                s, s_tgt, \"modified_brent\", (1.0, 2.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 1.0) < 1e-12\n            assert result[\"iterations\"] == 1\n\n        def test_horizontal_secant(self):\n            # the first iterate the boundaries yield the same value and the secant is div by zero\n            def s(g):\n                return g**2 - 2\n\n            s_tgt = 0.0\n            # roots at 1, 2, 3\n            result = ift_1dim(\n                s, s_tgt, \"modified_brent\", (-2.0, 2.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] + 2**0.5) < 1e-12\n            assert result[\"iterations\"] < 10\n\n        def test_asymptote(self):\n            def s(g):\n                return 1 / (g - 3) - 6\n\n            s_tgt = 0.0\n            # roots at 19 / 6\n            result = ift_1dim(\n                s, s_tgt, \"modified_brent\", (3.02, 4.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 19 / 6) < 1e-12\n            assert result[\"iterations\"] < 12\n\n        def test_brent(self):\n            def s(x):\n                return exp(x) + x**2\n\n            s_tgt = s(2.0)\n            result = ift_1dim(s, s_tgt, \"modified_brent\", (1.15, 5.0), conv_tol=1e-12)\n            assert result[\"g\"] == 2.0\n            assert result[\"iterations\"] < 12\n\n            # result2 = ift_1dim(s, s_tgt, \"bisection\", (1.15, 5.0), conv_tol=1e-12)\n            # assert result[\"time\"] <= result2[\"time\"]\n\n        def test_brent_conv_tol(self):\n            def s(x):\n                return exp(x) + x**2\n\n            s_tgt = s(2.0)\n            result = ift_1dim(s, s_tgt, \"modified_brent\", (1.15, 5.0), conv_tol=1e-3)\n            assert result[\"state\"] == 1\n\n        def test_paper_replication(self):\n            def s(g):\n                return exp(g**2 / -4.0) - 2 * cos(g) + g / 2.0 - 2.5\n\n            s_tgt = 0.0\n            # roots at 2.1584, 4.6196 and 7.255\n            result = ift_1dim(\n                s, s_tgt, \"modified_brent\", (1.0, 3.0), conv_tol=1e-12, func_tol=1e-12\n            )\n            assert abs(result[\"g\"] - 2.1584212092981225) < 1e-12\n            assert result[\"iterations\"] < 8\n\n    def test_another_func(self):\n        def s(g):\n            from math import cos\n\n            return cos(g) + g**3 + 2 * g**2 - 1.2\n\n        s_tgt = s(-1.5)  # close to zero, 3 roots in [-4.0, 2.0]\n        r_bi = ift_1dim(s, s_tgt, \"bisection\", (-4.0, 2.0))\n        r_dk = ift_1dim(s, s_tgt, \"modified_dekker\", (-4.0, 2.0))\n        r_br = ift_1dim(s, s_tgt, \"modified_brent\", (-4.0, 2.0))\n\n        assert r_bi[\"status\"] == \"SUCCESS\"\n        assert r_dk[\"status\"] == \"SUCCESS\"\n        assert r_br[\"status\"] == \"SUCCESS\"\n\n\nclass TestGradients:\n    @classmethod\n    def setup_class(cls):\n        class Inst:\n            def __init__(self, rate):\n                self._rate = rate\n\n            def rate(self, *args, **kwargs):\n                return self._rate\n\n        class SolverProxy(Gradients):\n            variables = [\"v1\", \"v2\", \"v3\"]\n            r = [Dual(1.0, [\"v1\"], []), Dual(3.0, [\"v1\", \"v2\", \"v3\"], [2.0, 1.0, -2.0])]\n            _J = None\n            instruments = [\n                [Inst(Dual2(1.0, [\"v1\"], [1.0], [4.0])), {}],\n                [\n                    Inst(\n                        Dual2(\n                            3.0,\n                            [\"v1\", \"v2\", \"v3\"],\n                            [2.0, 1.0, -2.0],\n                            [-2.0, 1.0, 1.0, 1.0, -3.0, 2.0, 1.0, 2.0, -4.0],\n                        ),\n                    ),\n                    {},\n                ],\n            ]\n            _J2 = None\n            _ad = 2\n            _grad_s_vT = np.array(\n                [\n                    [1.0, 2.0, 3.0],\n                    [2.0, 3.0, 4.0],\n                ],\n            )\n\n        setattr(cls, \"solver\", SolverProxy())\n\n    def test_J(self) -> None:\n        expected = np.array(\n            [\n                [1.0, 2.0],\n                [0.0, 1.0],\n                [0.0, -2.0],\n            ],\n        )\n        result = self.solver.J\n        assert_allclose(result, expected)\n\n    def test_grad_v_rT(self) -> None:\n        assert_allclose(self.solver.J, self.solver.grad_v_rT)\n\n    def test_J2(self) -> None:\n        expected = np.array(\n            [\n                [\n                    [8.0, 0.0, 0.0],\n                    [0.0, 0.0, 0.0],\n                    [0.0, 0.0, 0.0],\n                ],\n                [\n                    [-4.0, 2.0, 2.0],\n                    [2.0, -6.0, 4.0],\n                    [2.0, 4.0, -8.0],\n                ],\n            ],\n        )\n        expected = np.transpose(expected, (1, 2, 0))\n        result = self.solver.J2\n        assert_allclose(expected, result)\n\n    def test_grad_v_v_rT(self) -> None:\n        assert_allclose(self.solver.J2, self.solver.grad_v_v_rT)\n\n    def test_grad_s_vT(self) -> None:\n        expected = np.array(\n            [\n                [1.0, 2.0, 3.0],\n                [2.0, 3.0, 4.0],\n            ],\n        )\n        result = self.solver.grad_s_vT\n        assert_allclose(expected, result)\n\n\nclass TestDocs:\n    @pytest.mark.skipif(\n        sys.version_info[:2] == (3, 10), reason=\"This test is incompatible with Python 3.10\"\n    )\n    def test_external_system_replicator(self):\n        TODAY = dt(2026, 3, 23)\n        SPOT = calendars.get(\"nyc\").lag_bus_days(TODAY, 2, False)\n        TENORS = [\n            \"1W\",\n            \"2W\",\n            \"3W\",\n            \"1M\",\n            \"2M\",\n            \"3M\",\n            \"4M\",\n            \"5M\",\n            \"6M\",\n            \"7M\",\n            \"8M\",\n            \"9M\",\n            \"10M\",\n            \"11M\",\n            \"12M\",\n            \"18M\",\n            \"2Y\",\n            \"3Y\",\n            \"4Y\",\n            \"5Y\",\n            \"6Y\",\n            \"7Y\",\n            \"8Y\",\n            \"9Y\",\n            \"10Y\",\n            \"12Y\",\n            \"15Y\",\n            \"20Y\",\n            \"25Y\",\n            \"30Y\",\n            \"40Y\",\n            \"50Y\",\n        ]\n        MATURITIES = [add_tenor(SPOT, _, \"MF\", \"nyc\") for _ in TENORS]\n        curve = Curve(\n            nodes={TODAY: 1.0, **dict.fromkeys(MATURITIES, 1.0)},\n            calendar=\"nyc\",\n            convention=\"act360\",\n            id=\"sofr\",\n        )\n        solver = Solver(\n            curves=[curve],\n            instruments=[IRS(SPOT, _, spec=\"usd_irs\", curves=[curve]) for _ in TENORS],\n            s=[\n                3.684,\n                3.6805,\n                3.677,\n                3.6786,\n                3.6941,\n                3.7059,\n                3.71675,\n                3.72315,\n                3.73,\n                3.74215,\n                3.7509,\n                3.75895,\n                3.7656,\n                3.77005,\n                3.7741,\n                3.7373,\n                3.6866,\n                3.6316,\n                3.63217,\n                3.6625,\n                3.706,\n                3.7515,\n                3.7968,\n                3.84117,\n                3.88475,\n                3.9714,\n                4.07703,\n                4.15708,\n                4.15165,\n                4.1093,\n                3.99425,\n                3.875,\n            ],\n        )\n\n        _df = DataFrame(\n            {\n                \"tenor\": TENORS,\n                \"maturity\": MATURITIES,\n                \"df\": [float(curve[_]) for _ in MATURITIES],\n                \"rate\": [float(IRS(SPOT, _, spec=\"usd_irs\", curves=curve).rate()) for _ in TENORS],\n            }\n        )\n        irs = IRS(\n            dt(2031, 3, 25),\n            dt(2036, 3, 25),\n            spec=\"usd_irs\",\n            curves=[\"sofr\"],\n            fixed_rate=4.0,\n            notional=-100e6,\n        )\n        pv = irs.npv(solver=solver)\n        _ct = irs.cashflows_table(solver=solver)\n        _cf = irs.cashflows(solver=solver)\n        dv01 = irs.delta(solver=solver).sum(axis=None)\n        pv01 = irs.analytic_delta(solver=solver, leg=1)\n        gamma = irs.gamma(solver=solver).sum(axis=None)\n\n        assert abs(pv + 579593.21) < 16.5  # <0.01% deviation\n        assert abs(dv01 + 37518.12) < 3  # <0.01% deviation\n        assert abs(pv01 + 37471.51) < 1.5  # <0.01% deviation\n        assert abs(gamma - 58.50) < 0.004  # <0.01% deviation\n\n\n@pytest.mark.parametrize(\"algo\", [\"gauss_newton\", \"levenberg_marquardt\", \"gradient_descent\"])\ndef test_basic_solver(algo) -> None:\n    curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n    ]\n    s = np.array([1.0, 1.6, 2.0])\n    solver = Solver(\n        curves=[curve],\n        instruments=instruments,\n        s=s,\n        algorithm=algo,\n    )\n    assert float(solver.g) < 1e-9\n    assert curve.nodes.nodes[dt(2022, 1, 1)] == Dual(1.0, [\"v0\"], [1])\n    expected = [1, 0.9899250357528555, 0.9680433953206192, 0.9407188354823821]\n    for i, key in enumerate(curve.nodes.nodes.keys()):\n        assert abs(float(curve.nodes.nodes[key]) - expected[i]) < 1e-6\n\n\ndef test_solver_repr():\n    curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n    ]\n    s = np.array([1.0, 1.6, 2.0])\n    solver = Solver(curves=[curve], instruments=instruments, s=s, id=\"S_ID\")\n    result = solver.__repr__()\n    expected = f\"<rl.Solver:S_ID at {hex(id(solver))}>\"\n    assert result == expected\n\n\n@pytest.mark.parametrize(\"algo\", [\"gauss_newton\", \"levenberg_marquardt\", \"gradient_descent\"])\ndef test_solver_reiterate(algo) -> None:\n    # test that curves are properly updated by a reiterate\n    curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=\"v\"),\n        IRS(dt(2022, 1, 1), \"2Y\", \"Q\", curves=\"v\"),\n        IRS(dt(2022, 1, 1), \"3Y\", \"Q\", curves=\"v\"),\n    ]\n    s = np.array([1.0, 1.5, 2.0])\n    solver = Solver(\n        curves=[curve],\n        instruments=instruments,\n        s=s,\n        algorithm=algo,\n    )\n    assert float(solver.g) < 1e-9\n\n    solver.s[1] = 1.6\n    solver.iterate()\n\n    # now check that a reiteration has resolved the curve\n    assert curve.nodes.nodes[dt(2022, 1, 1)] == Dual(1.0, [\"v0\"], [1])\n    expected = [1, 0.9899250357528555, 0.9680433953206192, 0.9407188354823821]\n    for i, key in enumerate(curve.nodes.nodes.keys()):\n        assert abs(float(curve.nodes.nodes[key]) - expected[i]) < 1e-6\n\n\n@pytest.mark.parametrize(\"algo\", [\"gauss_newton\", \"levenberg_marquardt\", \"gradient_descent\"])\ndef test_basic_solver_line_curve(algo) -> None:\n    curve = LineCurve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        (Value(dt(2022, 1, 1)), {\"curves\": curve}),\n        (Value(dt(2023, 1, 1)), {\"curves\": curve}),\n        (Value(dt(2024, 1, 1)), {\"curves\": curve}),\n    ]\n    s = np.array([3.0, 3.6, 4.0])\n    solver = Solver(\n        curves=[curve],\n        instruments=instruments,\n        s=s,\n        algorithm=algo,\n    )\n    assert float(solver.g) < 1e-9\n    for i, key in enumerate(curve.nodes.nodes.keys()):\n        assert abs(float(curve.nodes.nodes[key]) - s[i]) < 1e-5\n\n\ndef test_basic_spline_solver() -> None:\n    spline_curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.99,\n            dt(2024, 1, 1): 0.965,\n            dt(2025, 1, 1): 0.93,\n        },\n        interpolation=\"log_linear\",\n        t=[\n            dt(2023, 1, 1),\n            dt(2023, 1, 1),\n            dt(2023, 1, 1),\n            dt(2023, 1, 1),\n            dt(2024, 1, 1),\n            dt(2025, 1, 3),\n            dt(2025, 1, 3),\n            dt(2025, 1, 3),\n            dt(2025, 1, 3),\n        ],\n        id=\"v\",\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": spline_curve}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"Q\"), {\"curves\": spline_curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": spline_curve}),\n    ]\n    s = np.array([1.0, 1.6, 2.0])\n    solver = Solver(\n        curves=[spline_curve],\n        instruments=instruments,\n        s=s,\n        algorithm=\"gauss_newton\",\n    )\n    assert float(solver.g) < 1e-12\n    assert spline_curve.nodes.nodes[dt(2022, 1, 1)] == Dual(1.0, [\"v0\"], [1])\n    expected = [1, 0.98992503575307, 0.9680377261843034, 0.9407048036486593]\n    for i, key in enumerate(spline_curve.nodes.nodes.keys()):\n        assert abs(float(spline_curve.nodes.nodes[key]) - expected[i]) < 1e-11\n\n\ndef test_large_spline_solver() -> None:\n    dates = [\n        dt(2000, 1, 3),\n        dt(2001, 1, 3),\n        dt(2002, 1, 3),\n        dt(2003, 1, 3),\n        dt(2004, 1, 3),\n        dt(2005, 1, 3),\n        dt(2006, 1, 3),\n        dt(2007, 1, 3),\n        dt(2008, 1, 3),\n        dt(2009, 1, 3),\n        dt(2010, 1, 3),\n        dt(2012, 1, 3),\n        dt(2015, 1, 3),\n        dt(2020, 1, 3),\n        dt(2025, 1, 3),\n        dt(2030, 1, 3),\n        dt(2035, 1, 3),\n        dt(2040, 1, 3),\n        dt(2050, 1, 3),\n    ]\n    curve = Curve(\n        nodes=dict.fromkeys(dates, 1.0),\n        t=[dt(2000, 1, 3)] * 3 + dates[:-1] + [dt(2050, 1, 5)] * 4,\n        calendar=\"nyc\",\n    )\n    solver = Solver(\n        curves=[curve],\n        instruments=[IRS(dt(2000, 1, 3), _, spec=\"usd_irs\", curves=curve) for _ in dates[1:]],\n        s=[1.0 + _ / 25 for _ in range(18)],\n    )\n    assert solver.result[\"status\"] == \"SUCCESS\"\n\n\ndef test_solver_raises_len() -> None:\n    with pytest.raises(ValueError, match=r\"`s: 2` \\(rates\\)  must be same length as\"):\n        Solver(\n            instruments=[1],\n            s=[1, 2],\n        )\n\n    with pytest.raises(ValueError, match=r\"`instrument_labels: 2` must be same length as\"):\n        Solver(\n            instruments=[1],\n            s=[1],\n            instrument_labels=[1, 2],\n        )\n\n    with pytest.raises(ValueError, match=r\"`weights: 1` must be same length as\"):\n        Solver(\n            instruments=[1, 2],\n            s=[1, 2],\n            instrument_labels=[1, 2],\n            weights=[1],\n        )\n\n\ndef test_basic_solver_weights() -> None:\n    # This test replicates test_basic_solver with the 3Y rate at two different rates.\n    # We vary the weights argument to selectively decide which one to use.\n    curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n    ]\n    s = np.array([1.0, 1.6, 2.02, 1.98])  # average 3Y at approximately 2.0%\n    with default_context(\"algorithm\", \"gauss_newton\"):\n        solver = Solver(\n            curves=[curve],\n            instruments=instruments,\n            s=s,\n            func_tol=0.00085,\n        )\n    assert float(solver.g) < 0.00085\n    assert curve.nodes.nodes[dt(2022, 1, 1)] == Dual(1.0, [\"v0\"], [1])\n    expected = [1, 0.9899250357528555, 0.9680433953206192, 0.9407188354823821]\n    for i, key in enumerate(curve.nodes.nodes.keys()):\n        assert abs(float(curve.nodes.nodes[key]) - expected[i]) < 1e-6\n\n    solver = Solver(\n        curves=[curve],\n        instruments=instruments,\n        s=s,\n        weights=[1, 1, 1, 1e-6],\n        func_tol=1e-7,\n        algorithm=\"gauss_newton\",\n    )\n    assert abs(float(instruments[2][0].rate(curves=curve)) - 2.02) < 1e-4\n\n    solver = Solver(\n        curves=[curve],\n        instruments=instruments,\n        s=s,\n        weights=[1, 1, 1e-6, 1],\n        func_tol=1e-7,\n        algorithm=\"gauss_newton\",\n    )\n    assert abs(float(instruments[2][0].rate(curves=curve)) - 1.98) < 1e-4\n\n\ndef test_solver_independent_curve() -> None:\n    # Test that a solver can use an independent curve as a static object and solve\n    # without mutating that un-referenced object.\n    independent_curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.98,\n            dt(2024, 1, 1): 0.96,\n            dt(2025, 1, 1): 0.94,\n        },\n    )\n    expected = independent_curve.copy()\n    var_curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 0.99,\n            dt(2024, 1, 1): 0.98,\n            dt(2025, 1, 1): 0.97,\n        },\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": [var_curve, independent_curve]}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"Q\"), {\"curves\": [var_curve, independent_curve]}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": [var_curve, independent_curve]}),\n    ]\n    s = np.array([2.00, 2.00, 2.00])\n    with default_context(\"curve_not_in_solver\", \"ignore\"):\n        Solver(\n            curves=[var_curve],\n            instruments=instruments,\n            s=s,\n            func_tol=1e-13,\n            conv_tol=1e-13,\n        )\n    for i, instrument in enumerate(instruments):\n        assert abs(float(instrument[0].rate(**instrument[1]) - s[i])) < 1e-7\n    assert independent_curve == expected\n\n\nclass TestSolverCompositeCurve:\n    def test_solver_composite_curve(self) -> None:\n        # this test creates a solver with a composite curve\n        # for the purpose of adding a turn\n        c_base = Curve(\n            {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0, dt(2025, 1, 1): 1.0},\n            id=\"sek_base\",\n        )\n        c_turns = Curve(\n            {\n                dt(2022, 1, 1): 1.0,\n                dt(2022, 12, 30): 1.0,\n                dt(2023, 1, 1): 1.0,\n                dt(2025, 1, 1): 1.0,\n            },\n            id=\"sek_turns\",\n        )\n        composite_curve = CompositeCurve([c_base, c_turns], id=\"sek\")\n\n        instruments_turns = [\n            IRS(dt(2022, 1, 1), \"1d\", \"A\", curves=\"sek_turns\"),\n            IRS(dt(2022, 12, 30), \"1d\", \"A\", curves=\"sek_turns\"),\n            IRS(dt(2023, 1, 1), \"1d\", \"A\", curves=\"sek_turns\"),\n        ]\n        s_turns = [0.0, -0.50, 0.0]\n        labels_turns = [\"NA1\", \"Turn1\", \"NA2\"]\n\n        instruments_base = [\n            IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"sek\"),\n            IRS(dt(2022, 1, 1), \"2Y\", \"A\", curves=\"sek\"),\n            IRS(dt(2022, 1, 1), \"3Y\", \"A\", curves=\"sek\"),\n        ]\n        s_base = [2.0, 2.3, 2.4]\n        labels_base = [\"1Y\", \"2Y\", \"3Y\"]\n\n        solver = Solver(\n            curves=[c_base, c_turns, composite_curve],\n            instruments=instruments_turns + instruments_base,\n            s=s_turns + s_base,\n            instrument_labels=labels_turns + labels_base,\n            id=\"solv\",\n        )\n\n        test_irs = IRS(dt(2022, 6, 1), \"15M\", \"A\", notional=1e6, curves=\"sek\")\n\n        expected = 2.31735564\n        result = test_irs.rate(solver=solver)\n        assert (result - expected) < 1e-8\n\n        delta = test_irs.delta(solver=solver)\n        expected = DataFrame(\n            data=[\n                -0.22582768057036448,\n                0.22571855114358436,\n                0.00010912854804701055,\n                -9.15902876400274,\n                131.75543312,\n                0.0033383280,\n            ],\n            columns=MultiIndex.from_tuples([(\"usd\", \"usd\")], names=[\"local_ccy\", \"display_ccy\"]),\n            index=MultiIndex.from_tuples(\n                [\n                    (\"instruments\", \"solv\", \"NA1\"),\n                    (\"instruments\", \"solv\", \"Turn1\"),\n                    (\"instruments\", \"solv\", \"NA2\"),\n                    (\"instruments\", \"solv\", \"1Y\"),\n                    (\"instruments\", \"solv\", \"2Y\"),\n                    (\"instruments\", \"solv\", \"3Y\"),\n                ],\n                names=[\"type\", \"solver\", \"label\"],\n            ),\n        )\n        assert_frame_equal(delta, expected)\n\n\ndef test_non_unique_curves() -> None:\n    curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"A\")\n    curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"A\")\n    solver = Solver(\n        curves=[curve],\n        instruments=[(IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve})],\n        s=[1],\n    )\n\n    with pytest.raises(ValueError, match=\"`curves` must each have their own unique\"):\n        Solver(\n            curves=[curve2],\n            instruments=[(IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve})],\n            s=[2],\n            pre_solvers=[solver],\n        )\n\n    with pytest.raises(ValueError, match=\"`curves` must each have their own unique\"):\n        Solver(\n            curves=[curve, curve2],\n            instruments=[(IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve})],\n            s=[2],\n        )\n\n\ndef test_max_iterations() -> None:\n    # This test replicates has an oscillatory solution between the different 3y rates.\n    curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n    ]\n    s = np.array([1.0, 1.6, 2.02, 1.98])  # average 3Y at approximately 2.0%\n    with default_context(\"algorithm\", \"gauss_newton\"):\n        solver = Solver(\n            curves=[curve],\n            instruments=instruments,\n            s=s,\n            func_tol=1e-10,\n            max_iter=30,\n            step_tol=0.0,\n            grad_tol=0.0,\n        )\n    assert len(solver.g_list) == 31\n\n\ndef test_step_tol() -> None:\n    curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n    ]\n    s = np.array([1.0, 1.6, 2.02, 1.98])  # average 3Y at approximately 2.0%\n    solver = Solver(\n        curves=[curve],\n        instruments=instruments,\n        s=s,\n        max_iter=30,\n        func_tol=0.0,\n        step_tol=1.0,\n        grad_tol=0.0,\n        conv_tol=0.0,\n    )\n    assert solver.result[\"state\"] == 4\n\n\ndef test_grad_tol() -> None:\n    curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n        (IRS(dt(2022, 1, 1), \"3Y\", \"Q\"), {\"curves\": curve}),\n    ]\n    s = np.array([1.0, 1.6, 2.02, 1.98])  # average 3Y at approximately 2.0%\n    solver = Solver(\n        curves=[curve],\n        instruments=instruments,\n        s=s,\n        max_iter=30,\n        func_tol=0.0,\n        step_tol=0.0,\n        grad_tol=0.01,\n        conv_tol=0.0,\n    )\n    assert solver.result[\"state\"] == 5\n\n\ndef test_solver_pre_solver_dependency_generates_same_delta() -> None:\n    \"\"\"\n    Build an ESTR curve with solver1.\n    Build an IBOR curve with solver2 dependent upon solver1.\n\n    Build an ESTR and IBOR curve simultaneously inside the same solver3.\n\n    Test the delta and the instrument calibration error\n    \"\"\"\n    eur_disc_curve = Curve(\n        nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0},\n        id=\"eur\",\n    )\n    eur_instruments = [\n        (IRS(dt(2022, 1, 1), \"8M\", \"A\"), {\"curves\": eur_disc_curve}),\n        (IRS(dt(2022, 1, 1), \"16M\", \"A\"), {\"curves\": eur_disc_curve}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"A\"), {\"curves\": eur_disc_curve}),\n    ]\n    eur_disc_s = [2.01, 2.22, 2.55]\n    eur_disc_solver = Solver([eur_disc_curve], [], eur_instruments, eur_disc_s, id=\"estr\")\n\n    eur_ibor_curve = Curve(\n        nodes={dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0},\n        id=\"eur_ibor\",\n    )\n    eur_ibor_instruments = [\n        (IRS(dt(2022, 1, 1), \"1Y\", \"A\"), {\"curves\": [eur_ibor_curve, eur_disc_curve]}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"A\"), {\"curves\": [eur_ibor_curve, eur_disc_curve]}),\n    ]\n    eur_ibor_s = [2.25, 2.65]\n    eur_solver2 = Solver(\n        [eur_ibor_curve],\n        [],\n        eur_ibor_instruments,\n        eur_ibor_s,\n        pre_solvers=[eur_disc_solver],\n        id=\"ibor\",\n    )\n\n    eur_disc_curve2 = Curve(\n        {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0},\n        id=\"eur\",\n    )\n    eur_ibor_curve2 = Curve(\n        {dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0},\n        id=\"eur_ibor\",\n    )\n    eur_instruments2 = [\n        (IRS(dt(2022, 1, 1), \"8M\", \"A\"), {\"curves\": eur_disc_curve2}),\n        (IRS(dt(2022, 1, 1), \"16M\", \"A\"), {\"curves\": eur_disc_curve2}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"A\"), {\"curves\": eur_disc_curve2}),\n        (IRS(dt(2022, 1, 1), \"1Y\", \"A\"), {\"curves\": [eur_ibor_curve2, eur_disc_curve2]}),\n        (IRS(dt(2022, 1, 1), \"2Y\", \"A\"), {\"curves\": [eur_ibor_curve2, eur_disc_curve2]}),\n    ]\n    eur_disc_s2 = [2.01, 2.22, 2.55, 2.25, 2.65]\n    eur_solver_sim = Solver(\n        [eur_disc_curve2, eur_ibor_curve2],\n        [],\n        eur_instruments2,\n        eur_disc_s2,\n        id=\"eur_sol_sim\",\n        instrument_labels=[\"estr0\", \"estr1\", \"estr2\", \"ibor0\", \"ibor1\"],\n    )\n\n    eur_swap = IRS(\n        dt(2022, 3, 1),\n        \"16M\",\n        \"M\",\n        fixed_rate=3.0,\n    )\n\n    delta_sim = eur_swap.delta(curves=[eur_ibor_curve2, eur_disc_curve2], solver=eur_solver_sim)\n    delta_pre = eur_swap.delta(curves=[eur_ibor_curve, eur_disc_curve], solver=eur_solver2)\n    delta_pre.index = delta_sim.index\n    assert_frame_equal(delta_sim, delta_pre)\n\n    error_sim = eur_solver_sim.error\n    error_pre = eur_solver2.error\n    assert_series_equal(error_pre, error_sim, check_index=False, rtol=1e-5, atol=1e-3)\n\n\ndef test_delta_gamma_calculation() -> None:\n    estr_curve = Curve(\n        {dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0},\n        id=\"estr_curve\",\n    )\n    estr_instruments = [\n        (IRS(dt(2022, 1, 1), \"10Y\", \"A\"), {\"curves\": estr_curve}),\n        (IRS(dt(2022, 1, 1), \"20Y\", \"A\"), {\"curves\": estr_curve}),\n    ]\n    estr_solver = Solver(\n        [estr_curve],\n        [],\n        estr_instruments,\n        [2.0, 1.5],\n        id=\"estr\",\n        instrument_labels=[\"10Y\", \"20Y\"],\n    )\n\n    # Mechanism 1: dynamic\n    eur_swap = IRS(dt(2032, 1, 1), \"10Y\", \"A\", notional=100e6)\n    assert (\n        74430 < float(eur_swap.delta(curves=estr_curve, solver=estr_solver).sum().iloc[0]) < 74432\n    )\n    assert -229 < float(eur_swap.gamma(curves=estr_curve, solver=estr_solver).sum().sum()) < -228\n\n    # Mechanism 1: dynamic names\n    assert (\n        74430 < float(eur_swap.delta(curves=\"estr_curve\", solver=estr_solver).sum().iloc[0]) < 74432\n    )\n    assert -229 < float(eur_swap.gamma(curves=\"estr_curve\", solver=estr_solver).sum().sum()) < -228\n\n    # Mechanism 1: fails on None curve specification\n    with pytest.raises(TypeError, match=\"`curves` have not been supplied correctly\"):\n        assert eur_swap.delta(solver=estr_solver)\n    with pytest.raises(TypeError, match=\"`curves` have not been supplied correctly\"):\n        assert eur_swap.gamma(solver=estr_solver)\n\n    # Mechanism 2: static specific\n    eur_swap = IRS(dt(2032, 1, 1), \"10Y\", \"A\", notional=100e6, curves=estr_curve)\n    assert 74430 < float(eur_swap.delta(solver=estr_solver).sum().iloc[0]) < 74432\n    assert -229 < float(eur_swap.gamma(solver=estr_solver).sum().sum()) < -228\n\n    # Mechanism 2: static named\n    eur_swap = IRS(dt(2032, 1, 1), \"10Y\", \"A\", notional=100e6, curves=\"estr_curve\")\n    assert 74430 < float(eur_swap.delta(solver=estr_solver).sum().iloc[0]) < 74432\n    assert -229 < float(eur_swap.gamma(solver=estr_solver).sum().sum()) < -228\n\n\ndef test_solver_delta_fx_noinput() -> None:\n    estr_curve = Curve(\n        {dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0},\n        id=\"estr_curve\",\n    )\n    estr_instruments = [\n        (IRS(dt(2022, 1, 1), \"10Y\", \"A\"), {\"curves\": estr_curve}),\n        (IRS(dt(2022, 1, 1), \"20Y\", \"A\"), {\"curves\": estr_curve}),\n    ]\n    estr_solver = Solver(\n        [estr_curve],\n        [],\n        estr_instruments,\n        [2.0, 1.5],\n        id=\"estr\",\n        instrument_labels=[\"10Y\", \"20Y\"],\n    )\n    eur_swap = IRS(dt(2032, 1, 1), \"10Y\", \"A\", notional=100e6, fixed_rate=2)\n    npv = eur_swap.npv(curves=estr_curve, solver=estr_solver, local=True)\n    result = estr_solver.delta(npv)\n    assert type(result) is DataFrame\n\n\ndef test_solver_pre_solver_dependency_generates_same_gamma() -> None:\n    estr_curve = Curve({dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0})\n    estr_instruments = [\n        (IRS(dt(2022, 1, 1), \"7Y\", \"A\"), {\"curves\": estr_curve}),\n        (IRS(dt(2022, 1, 1), \"15Y\", \"A\"), {\"curves\": estr_curve}),\n        (IRS(dt(2022, 1, 1), \"20Y\", \"A\"), {\"curves\": estr_curve}),\n    ]\n    estr_s = [2.0, 1.75, 1.5]\n    estr_labels = [\"7ye\", \"15ye\", \"20ye\"]\n    estr_solver = Solver(\n        [estr_curve],\n        [],\n        estr_instruments,\n        estr_s,\n        id=\"estr\",\n        instrument_labels=estr_labels,\n        algorithm=\"gauss_newton\",\n    )\n\n    ibor_curve = Curve({dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0})\n    ibor_instruments = [\n        (IRS(dt(2022, 1, 1), \"10Y\", \"A\"), {\"curves\": [ibor_curve, estr_curve]}),\n        (IRS(dt(2022, 1, 1), \"20Y\", \"A\"), {\"curves\": [ibor_curve, estr_curve]}),\n    ]\n    ibor_s = [2.1, 1.65]\n    ibor_labels = [\"10Yi\", \"20Yi\"]\n    ibor_solver = Solver(\n        [ibor_curve],\n        [],\n        ibor_instruments,\n        ibor_s,\n        id=\"ibor\",\n        instrument_labels=ibor_labels,\n        pre_solvers=[estr_solver],\n        algorithm=\"gauss_newton\",\n    )\n\n    eur_swap = IRS(dt(2032, 1, 1), \"10Y\", \"A\", notional=100e6)\n    gamma_pre = eur_swap.gamma(curves=[ibor_curve, estr_curve], solver=ibor_solver)\n    delta_pre = eur_swap.delta(curves=[ibor_curve, estr_curve], solver=ibor_solver)\n\n    estr_curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0})\n    ibor_curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0})\n    sim_instruments = [\n        (IRS(dt(2022, 1, 1), \"7Y\", \"A\"), {\"curves\": estr_curve2}),\n        (IRS(dt(2022, 1, 1), \"15Y\", \"A\"), {\"curves\": estr_curve2}),\n        (IRS(dt(2022, 1, 1), \"20Y\", \"A\"), {\"curves\": estr_curve2}),\n        (IRS(dt(2022, 1, 1), \"10Y\", \"A\"), {\"curves\": [ibor_curve2, estr_curve2]}),\n        (IRS(dt(2022, 1, 1), \"20Y\", \"A\"), {\"curves\": [ibor_curve2, estr_curve2]}),\n    ]\n    simultaneous_solver = Solver(\n        [estr_curve2, ibor_curve2],\n        [],\n        sim_instruments,\n        estr_s + ibor_s,\n        id=\"simul\",\n        instrument_labels=estr_labels + ibor_labels,\n        algorithm=\"gauss_newton\",\n    )\n    gamma_sim = eur_swap.gamma(curves=[ibor_curve2, estr_curve2], solver=simultaneous_solver)\n    delta_sim = eur_swap.delta(curves=[ibor_curve2, estr_curve2], solver=simultaneous_solver)\n\n    # check arrays in construction of gamma\n    grad_s_vT_sim = simultaneous_solver.grad_s_vT_pre\n    grad_s_vT_pre = ibor_solver.grad_s_vT_pre\n    assert_allclose(grad_s_vT_pre, grad_s_vT_sim, atol=1e-14, rtol=1e-10)\n\n    simultaneous_solver._set_ad_order(2)\n    J2_sim = simultaneous_solver.J2_pre\n    ibor_solver._set_ad_order(2)\n    J2_pre = ibor_solver.J2_pre\n    assert_allclose(J2_pre, J2_sim, atol=1e-14, rtol=1e-10)\n\n    grad_s_s_vT_sim = simultaneous_solver.grad_s_s_vT_pre\n    grad_s_s_vT_pre = ibor_solver.grad_s_s_vT_pre\n    assert_allclose(grad_s_s_vT_pre, grad_s_s_vT_sim, atol=1e-14, rtol=1e-10)\n\n    gamma_pre.index = gamma_sim.index\n    gamma_pre.columns = gamma_sim.columns\n    delta_pre.index = delta_sim.index\n    assert_frame_equal(delta_sim, delta_pre)\n    assert_frame_equal(gamma_sim, gamma_pre)\n\n\ndef test_nonmutable_presolver_defaults() -> None:\n    estr_curve = Curve({dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0})\n    estr_instruments = [\n        (IRS(dt(2022, 1, 1), \"10Y\", \"A\"), {\"curves\": estr_curve}),\n    ]\n    estr_s = [2.0]\n    estr_labels = [\"10ye\"]\n    estr_solver = Solver(\n        [estr_curve],\n        [],\n        estr_instruments,\n        estr_s,\n        id=\"estr\",\n        instrument_labels=estr_labels,\n    )\n    with pytest.raises(AttributeError, match=\"'tuple' object has no attribute\"):\n        estr_solver.pre_solvers.extend([1, 2, 3])\n\n\ndef test_solver_grad_s_vT_methods_equivalent() -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n            dt(2026, 1, 1): 1.0,\n            dt(2027, 1, 1): 1.0,\n        },\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"2Y\", \"A\"), {\"curves\": curve}),\n        (IRS(dt(2023, 1, 1), \"1Y\", \"A\"), {\"curves\": curve}),\n        (IRS(dt(2023, 1, 1), \"2Y\", \"A\"), {\"curves\": curve}),\n        (IRS(dt(2022, 5, 1), \"4Y\", \"A\"), {\"curves\": curve}),\n        (IRS(dt(2023, 1, 1), \"4Y\", \"A\"), {\"curves\": curve}),\n    ]\n    s = [1.2, 1.4, 1.6, 1.7, 1.9]\n    solver = Solver([curve], [], instruments, s, algorithm=\"gauss_newton\")\n\n    solver._grad_s_vT_method = \"_grad_s_vT_final_iteration_analytical\"\n    grad_s_vT_final_iter_anal = solver.grad_s_vT\n\n    solver._grad_s_vT_method = \"_grad_s_vT_final_iteration_dual\"\n    solver._grad_s_vT_final_iteration_algo = \"gauss_newton_final\"\n    solver._reset_properties_()\n    grad_s_vT_final_iter_dual = solver.grad_s_vT\n\n    solver._grad_s_vT_method = \"_grad_s_vT_fixed_point_iteration\"\n    solver._reset_properties_()\n    grad_s_vT_fixed_point_iter = solver.grad_s_vT\n\n    assert_allclose(grad_s_vT_final_iter_dual, grad_s_vT_final_iter_anal, atol=1e-12)\n    assert_allclose(grad_s_vT_fixed_point_iter, grad_s_vT_final_iter_anal, atol=1e-12)\n    assert_allclose(grad_s_vT_final_iter_dual, grad_s_vT_fixed_point_iter, atol=1e-12)\n\n\ndef test_solver_grad_s_vT_methods_equivalent_overspecified_curve() -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n            # dt(2026, 1, 1): 1.0,\n            dt(2027, 1, 1): 1.0,\n        },\n    )\n    instruments = [\n        (IRS(dt(2022, 1, 1), \"2Y\", \"A\"), {\"curves\": curve}),\n        (IRS(dt(2023, 1, 1), \"1Y\", \"A\"), {\"curves\": curve}),\n        (IRS(dt(2023, 1, 1), \"2Y\", \"A\"), {\"curves\": curve}),\n        (IRS(dt(2022, 5, 1), \"4Y\", \"A\"), {\"curves\": curve}),\n        (IRS(dt(2023, 1, 1), \"4Y\", \"A\"), {\"curves\": curve}),\n    ]\n    s = [1.2, 1.4, 1.6, 1.7, 1.9]\n    solver = Solver([curve], [], instruments, s, algorithm=\"gauss_newton\")\n\n    solver._grad_s_vT_method = \"_grad_s_vT_final_iteration_analytical\"\n    grad_s_vT_final_iter_anal = solver.grad_s_vT\n\n    solver._grad_s_vT_method = \"_grad_s_vT_final_iteration_dual\"\n    solver._grad_s_vT_final_iteration_algo = \"gauss_newton_final\"\n    solver._reset_properties_()\n    grad_s_vT_final_iter_dual = solver.grad_s_vT\n\n    solver._grad_s_vT_method = \"_grad_s_vT_fixed_point_iteration\"\n    solver._reset_properties_()\n    grad_s_vT_fixed_point_iter = solver.grad_s_vT\n\n    assert_allclose(grad_s_vT_final_iter_dual, grad_s_vT_final_iter_anal, atol=1e-6)\n    assert_allclose(grad_s_vT_fixed_point_iter, grad_s_vT_final_iter_anal, atol=1e-6)\n    assert_allclose(grad_s_vT_final_iter_dual, grad_s_vT_fixed_point_iter, atol=1e-6)\n\n\ndef test_solver_second_order_vars_raise_on_first_order() -> None:\n    curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"A\")\n    solver = Solver(\n        curves=[curve],\n        instruments=[(IRS(dt(2022, 1, 1), \"1Y\", \"Q\"), {\"curves\": curve})],\n        s=[1],\n    )\n\n    with pytest.raises(ValueError, match=\"Cannot perform second derivative calc\"):\n        solver.J2\n\n    with pytest.raises(ValueError, match=\"Cannot perform second derivative calc\"):\n        solver.grad_s_s_vT\n\n\ndef test_solver_second_order_vars_raise_on_first_order_pre_solvers() -> None:\n    curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"A\")\n    solver = Solver(\n        curves=[curve],\n        instruments=[IRS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=curve)],\n        s=[1],\n    )\n    curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"B\")\n    solver2 = Solver(\n        curves=[curve2],\n        instruments=[IRS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=curve2)],\n        s=[1],\n        pre_solvers=[solver],\n    )\n\n    with pytest.raises(ValueError, match=\"Cannot perform second derivative calc\"):\n        solver2.J2_pre\n\n    with pytest.raises(ValueError, match=\"Cannot perform second derivative calc\"):\n        solver.grad_s_s_vT_pre\n\n\ndef test_bad_algo_raises() -> None:\n    curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"A\")\n    with pytest.raises(NotImplementedError, match=\"`algorithm`: bad_algo\"):\n        Solver(\n            curves=[curve],\n            instruments=[IRS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=curve)],\n            s=[1],\n            algorithm=\"bad_algo\",\n        )\n\n\ndef test_solver_float_rate_bond() -> None:\n    \"\"\"\n    This test checks the rate method of FloatRateNote when using complex rate spread\n    calculations (which artificially introduces Dual2 and then removes it)\n    \"\"\"\n    d_c = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2022, 7, 1): 0.94,\n            dt(2023, 1, 1): 0.92,\n            dt(2024, 1, 1): 0.9,\n        },\n        id=\"credit\",\n    )\n    f_c = d_c.copy()\n    f_c._id = \"rfr\"\n    instruments = [\n        (\n            FloatRateNote(\n                dt(2022, 1, 1),\n                \"6M\",\n                \"Q\",\n                spread_compound_method=\"isda_compounding\",\n                settle=2,\n            ),\n            {\"metric\": \"spread\", \"curves\": [f_c, d_c]},\n        ),\n        (\n            FloatRateNote(\n                dt(2022, 1, 1),\n                \"1y\",\n                \"Q\",\n                spread_compound_method=\"isda_compounding\",\n                settle=2,\n                curves=[f_c, d_c],\n            ),\n            {\"metric\": \"spread\"},\n        ),\n        (\n            FloatRateNote(\n                dt(2022, 1, 1),\n                \"18m\",\n                \"Q\",\n                spread_compound_method=\"isda_compounding\",\n                settle=2,\n                curves=[f_c, d_c],\n            ),\n            {\"metric\": \"spread\"},\n        ),\n    ]\n    Solver([d_c], [], instruments, [25, 25, 25])\n    result = d_c.rate(dt(2022, 7, 1), \"1D\")\n    expected = f_c.rate(dt(2022, 7, 1), \"1D\") + 0.25\n    assert abs(result - expected) < 3e-4\n\n\ndef test_solver_grad_s_s_vt_methods_equivalent() -> None:\n    curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n            dt(2026, 1, 1): 1.0,\n            dt(2027, 1, 1): 1.0,\n            dt(2028, 1, 1): 1.0,\n            dt(2029, 1, 1): 1.0,\n        },\n        id=\"curve\",\n    )\n    instruments = [\n        IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"2y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"3y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"4y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"5y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"6y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"7y\", \"A\", curves=\"curve\"),\n    ]\n    with default_context(\"algorithm\", \"gauss_newton\"):\n        solver = Solver(\n            curves=[curve],\n            instruments=instruments,\n            s=[1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7],\n        )\n    grad_s_s_vt_fwddiff = solver._grad_s_s_vT_fwd_difference_method()\n    solver._set_ad_order(order=2)\n    grad_s_s_vt_final = solver._grad_s_s_vT_final_iteration_analytical()\n    solver._set_ad_order(order=1)\n    assert_allclose(grad_s_s_vt_final, grad_s_s_vt_fwddiff, atol=5e-7)\n\n\ndef test_gamma_raises() -> None:\n    curve = Curve(\n        {\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2025, 1, 1): 1.0,\n        },\n        id=\"v\",\n    )\n    instruments = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=curve),\n        IRS(dt(2022, 1, 1), \"2Y\", \"Q\", curves=curve),\n        IRS(dt(2022, 1, 1), \"3Y\", \"Q\", curves=curve),\n    ]\n    s = np.array([1.0, 1.6, 2.0])\n    solver = Solver(\n        curves=[curve],\n        instruments=instruments,\n        s=s,\n    )\n    with pytest.raises(ValueError, match=\"`Solver` must be in ad order 2\"):\n        solver.gamma(100)\n\n\ndef test_delta_irs_guide() -> None:\n    # this mirrors the delta user guide page\n    usd_curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2022, 2, 1): 1.0,\n            dt(2022, 4, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n        },\n        id=\"sofr\",\n    )\n    instruments = [\n        IRS(dt(2022, 1, 1), \"1m\", \"A\", curves=\"sofr\"),\n        IRS(dt(2022, 1, 1), \"3m\", \"A\", curves=\"sofr\"),\n        IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"sofr\"),\n    ]\n    usd_solver = Solver(\n        curves=[usd_curve],\n        id=\"usd_sofr\",\n        instruments=instruments,\n        s=[2.5, 3.25, 4.0],\n        instrument_labels=[\"1m\", \"3m\", \"1y\"],\n    )\n    irs = IRS(\n        effective=dt(2022, 1, 1),\n        termination=\"6m\",\n        frequency=\"A\",\n        currency=\"usd\",\n        fixed_rate=6.0,\n        curves=\"sofr\",\n    )\n    result = irs.delta(solver=usd_solver)  # local overrides base to USD\n    # result = irs.delta(solver=usd_solver, base=\"eur\", local=True)  # local overrides base to USD\n    expected = DataFrame(\n        [[0], [16.77263], [32.60487]],\n        index=MultiIndex.from_product(\n            [[\"instruments\"], [\"usd_sofr\"], [\"1m\", \"3m\", \"1y\"]],\n            names=[\"type\", \"solver\", \"label\"],\n        ),\n        columns=MultiIndex.from_tuples([(\"usd\", \"usd\")], names=[\"local_ccy\", \"display_ccy\"]),\n    )\n    assert_frame_equal(result, expected)\n\n\ndef test_delta_irs_guide_fx_base() -> None:\n    # this mirrors the delta user guide page\n    usd_curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2022, 2, 1): 1.0,\n            dt(2022, 4, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n        },\n        id=\"sofr\",\n    )\n    instruments = [\n        IRS(dt(2022, 1, 1), \"1m\", \"A\", curves=\"sofr\"),\n        IRS(dt(2022, 1, 1), \"3m\", \"A\", curves=\"sofr\"),\n        IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"sofr\"),\n    ]\n    usd_solver = Solver(\n        curves=[usd_curve],\n        id=\"usd_sofr\",\n        instruments=instruments,\n        s=[2.5, 3.25, 4.0],\n        instrument_labels=[\"1m\", \"3m\", \"1y\"],\n    )\n    irs = IRS(\n        effective=dt(2022, 1, 1),\n        termination=\"6m\",\n        frequency=\"A\",\n        currency=\"usd\",\n        fixed_rate=6.0,\n        curves=\"sofr\",\n    )\n    fxr = FXRates({\"eurusd\": 1.1})\n    result = irs.delta(solver=usd_solver, base=\"eur\", fx=fxr)\n    expected = DataFrame(\n        [\n            [0, 0, 0],\n            [15.247847, 15.247847, 16.772632],\n            [29.640788, 29.640788, 32.60487],\n            [0.926514, 0.926514, 0.0],\n        ],\n        index=MultiIndex.from_tuples(\n            [\n                (\"instruments\", \"usd_sofr\", \"1m\"),\n                (\"instruments\", \"usd_sofr\", \"3m\"),\n                (\"instruments\", \"usd_sofr\", \"1y\"),\n                (\"fx\", \"fx\", \"eurusd\"),\n            ],\n            names=[\"type\", \"solver\", \"label\"],\n        ),\n        columns=MultiIndex.from_tuples(\n            [\n                (\"all\", \"eur\"),\n                (\"usd\", \"eur\"),\n                (\"usd\", \"usd\"),\n            ],\n            names=[\"local_ccy\", \"display_ccy\"],\n        ),\n    )\n    assert_frame_equal(result, expected)\n\n\n# def test_irs_delta_curves_undefined():\n#     # the IRS is not constructed under best practice.\n#     # The delta solver does not know how to price the irs\n#     curve = Curve({dt(2022, 1, 1): 1.0, dt(2027, 1, 1): 0.99, dt(2032, 1, 1): 0.98},\n#                   id=\"sonia\")\n#     instruments = [\n#         IRS(dt(2022, 1, 1), \"5y\", \"A\", curves=\"sonia\"),\n#         IRS(dt(2027, 1, 1), \"5y\", \"A\", curves=\"sonia\"),\n#     ]\n#     solver = Solver(\n#         curves=[curve],\n#         instruments=instruments,\n#         s=[2.0, 2.5],\n#     )\n#     irs = IRS(dt(2022, 1, 1), \"10y\", \"S\", fixed_rate=2.38)\n#     with pytest.raises(TypeError, match=\"`curves` have not been supplied\"):\n#         irs.delta(solver=solver)\n\n\ndef test_mechanisms_guide_gamma() -> None:\n    instruments = [\n        IRS(dt(2022, 1, 1), \"4m\", \"Q\", curves=\"sofr\"),\n        IRS(dt(2022, 1, 1), \"8m\", \"Q\", curves=\"sofr\"),\n    ]\n    s = [1.85, 2.10]\n    ll_curve = Curve(\n        nodes={dt(2022, 1, 1): 1.0, dt(2022, 5, 1): 1.0, dt(2022, 9, 1): 1.0},\n        interpolation=\"log_linear\",\n        id=\"sofr\",\n    )\n    ll_solver = Solver(\n        curves=[ll_curve],\n        instruments=instruments,\n        s=s,\n        instrument_labels=[\"4m\", \"8m\"],\n        id=\"sofr\",\n    )\n\n    instruments = [\n        IRS(dt(2022, 1, 1), \"3m\", \"Q\", curves=\"estr\"),\n        IRS(dt(2022, 1, 1), \"9m\", \"Q\", curves=\"estr\"),\n    ]\n    s = [0.75, 1.65]\n    ll_curve = Curve(\n        nodes={dt(2022, 1, 1): 1.0, dt(2022, 4, 1): 1.0, dt(2022, 10, 1): 1.0},\n        interpolation=\"log_linear\",\n        id=\"estr\",\n    )\n    combined_solver = Solver(\n        curves=[ll_curve],\n        instruments=instruments,\n        s=s,\n        instrument_labels=[\"3m\", \"9m\"],\n        pre_solvers=[ll_solver],\n        id=\"estr\",\n    )\n\n    irs = IRS(\n        effective=dt(2022, 1, 1),\n        termination=\"6m\",\n        frequency=\"Q\",\n        currency=\"usd\",\n        notional=500e6,\n        fixed_rate=2.0,\n        curves=\"sofr\",\n    )\n    irs2 = IRS(\n        effective=dt(2022, 1, 1),\n        termination=\"6m\",\n        frequency=\"Q\",\n        currency=\"eur\",\n        notional=-300e6,\n        fixed_rate=1.0,\n        curves=\"estr\",\n    )\n    pf = Portfolio([irs, irs2])\n    pf.npv(solver=combined_solver, local=True)\n    pf.delta(solver=combined_solver)\n    fxr = FXRates({\"eurusd\": 1.10})\n    fxr._set_ad_order(2)\n    result = pf.gamma(solver=combined_solver, fx=fxr, base=\"eur\")\n    expected = DataFrame(\n        data=[\n            [0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.13769, 0.28088, 0.0],\n            [0.0, 0.0, 0.28088, 0.44493, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0],\n            [-0.28930, -0.45081, 0.0, 0.0, -0.68937],\n            [-0.45081, -0.47449, 0.0, 0.0, -1.37372],\n            [0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0],\n            [-0.68937, -1.37372, 0.0, 0.0, 0.00064],\n            [-0.31823, -0.49590, 0.0, 0.0, 0.0],\n            [-0.49590, -0.52194, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0],\n            [-0.28930, -0.45081, 0.0, 0.0, -0.68937],\n            [-0.45081, -0.47449, 0.0, 0.0, -1.37372],\n            [0.0, 0.0, 0.13770, 0.28088, 0.0],\n            [0.0, 0.0, 0.28088, 0.44493, 0.0],\n            [-0.68937, -1.37372, 0.0, 0.0, 0.00064],\n        ],\n        index=MultiIndex.from_tuples(\n            [\n                (\"eur\", \"eur\", \"instruments\", \"sofr\", \"4m\"),\n                (\"eur\", \"eur\", \"instruments\", \"sofr\", \"8m\"),\n                (\"eur\", \"eur\", \"instruments\", \"estr\", \"3m\"),\n                (\"eur\", \"eur\", \"instruments\", \"estr\", \"9m\"),\n                (\"eur\", \"eur\", \"fx\", \"fx\", \"eurusd\"),\n                (\"usd\", \"eur\", \"instruments\", \"sofr\", \"4m\"),\n                (\"usd\", \"eur\", \"instruments\", \"sofr\", \"8m\"),\n                (\"usd\", \"eur\", \"instruments\", \"estr\", \"3m\"),\n                (\"usd\", \"eur\", \"instruments\", \"estr\", \"9m\"),\n                (\"usd\", \"eur\", \"fx\", \"fx\", \"eurusd\"),\n                (\"usd\", \"usd\", \"instruments\", \"sofr\", \"4m\"),\n                (\"usd\", \"usd\", \"instruments\", \"sofr\", \"8m\"),\n                (\"usd\", \"usd\", \"instruments\", \"estr\", \"3m\"),\n                (\"usd\", \"usd\", \"instruments\", \"estr\", \"9m\"),\n                (\"usd\", \"usd\", \"fx\", \"fx\", \"eurusd\"),\n                (\"all\", \"eur\", \"instruments\", \"sofr\", \"4m\"),\n                (\"all\", \"eur\", \"instruments\", \"sofr\", \"8m\"),\n                (\"all\", \"eur\", \"instruments\", \"estr\", \"3m\"),\n                (\"all\", \"eur\", \"instruments\", \"estr\", \"9m\"),\n                (\"all\", \"eur\", \"fx\", \"fx\", \"eurusd\"),\n            ],\n            names=[\"local_ccy\", \"display_ccy\", \"type\", \"solver\", \"label\"],\n        ),\n        columns=MultiIndex.from_tuples(\n            [\n                (\"instruments\", \"sofr\", \"4m\"),\n                (\"instruments\", \"sofr\", \"8m\"),\n                (\"instruments\", \"estr\", \"3m\"),\n                (\"instruments\", \"estr\", \"9m\"),\n                (\"fx\", \"fx\", \"eurusd\"),\n            ],\n            names=[\"type\", \"solver\", \"label\"],\n        ),\n    )\n    assert_frame_equal(result, expected, atol=1e-2, rtol=1e-4)\n\n\ndef test_solver_gamma_pnl_explain() -> None:\n    instruments = [\n        IRS(dt(2022, 1, 1), \"10y\", \"A\", currency=\"usd\", curves=\"sofr\"),\n        IRS(dt(2032, 1, 1), \"10y\", \"A\", currency=\"usd\", curves=\"sofr\"),\n        IRS(dt(2022, 1, 1), \"10y\", \"A\", currency=\"eur\", curves=\"estr\"),\n        IRS(dt(2032, 1, 1), \"10y\", \"A\", currency=\"eur\", curves=\"estr\"),\n        XCS(\n            dt(2022, 1, 1),\n            \"10y\",\n            \"A\",\n            currency=\"eur\",\n            pair=\"eurusd\",\n            curves=[\"estr\", \"eurusd\", \"sofr\", \"sofr\"],\n        ),\n        XCS(\n            dt(2032, 1, 1),\n            \"10y\",\n            \"A\",\n            currency=\"usd\",\n            pair=\"usdeur\",\n            curves=[\"estr\", \"eurusd\", \"sofr\", \"sofr\"],\n        ),\n    ]\n    # s_base = np.array([3.45, 2.85, 2.25, 0.9, -15, -10])\n    sofr = Curve(nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0}, id=\"sofr\")\n    estr = Curve(nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0}, id=\"estr\")\n    eurusd = Curve(\n        nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0},\n        id=\"eurusd\",\n    )\n    fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(fxr, {\"eureur\": estr, \"eurusd\": eurusd, \"usdusd\": sofr})\n    sofr_solver = Solver(\n        curves=[sofr],\n        instruments=instruments[:2],\n        s=[3.45, 2.85],\n        instrument_labels=[\"10y\", \"10y10y\"],\n        id=\"sofr\",\n        fx=fxf,\n    )\n    estr_solver = Solver(\n        curves=[estr],\n        instruments=instruments[2:4],\n        s=[2.25, 0.90],\n        instrument_labels=[\"10y\", \"10y10y\"],\n        id=\"estr\",\n        fx=fxf,\n    )\n    solver = Solver(\n        curves=[eurusd],\n        instruments=instruments[4:],\n        s=[-10, -15],\n        instrument_labels=[\"10y\", \"10y10y\"],\n        id=\"xccy\",\n        fx=fxf,\n        pre_solvers=[sofr_solver, estr_solver],\n    )\n\n    pf = Portfolio(\n        [\n            IRS(\n                dt(2022, 1, 1),\n                \"20Y\",\n                \"A\",\n                currency=\"eur\",\n                fixed_rate=2.0,\n                notional=1e8,\n                curves=\"estr\",\n            ),\n        ],\n    )\n\n    npv_base = pf.npv(solver=solver, base=\"eur\")\n    expected_npv = -6230451.035973\n    assert (npv_base - expected_npv) < 1e-5\n\n    delta_base = pf.delta(solver=solver, base=\"usd\")\n    # this expectation is directly input from reviewed output.\n    expected_delta = DataFrame(\n        data=[\n            [3.51021, 0.0, 3.51021],\n            [-0.00005, 0.0, -0.00005],\n            [101841.37433, 97001.98184, 101841.37433],\n            [85750.45235, 81672.83139, 85750.45235],\n            [-3.55593, 0.0, -3.55593],\n            [0.00004, 0.0, 0.00004],\n            [-623.00136, 0.0, -623.00136],\n        ],\n        index=MultiIndex.from_tuples(\n            [\n                (\"instruments\", \"sofr\", \"10y\"),\n                (\"instruments\", \"sofr\", \"10y10y\"),\n                (\"instruments\", \"estr\", \"10y\"),\n                (\"instruments\", \"estr\", \"10y10y\"),\n                (\"instruments\", \"xccy\", \"10y\"),\n                (\"instruments\", \"xccy\", \"10y10y\"),\n                (\"fx\", \"fx\", \"eurusd\"),\n            ],\n            names=[\"type\", \"solver\", \"label\"],\n        ),\n        columns=MultiIndex.from_tuples(\n            [(\"all\", \"usd\"), (\"eur\", \"eur\"), (\"eur\", \"usd\")],\n            names=[\"local_ccy\", \"display_ccy\"],\n        ),\n    )\n    assert_frame_equal(delta_base, expected_delta, atol=1e-2, rtol=1e-4)\n\n    gamma_base = pf.gamma(solver=solver, base=\"eur\")\n    expected_gamma = DataFrame(\n        data=[\n            [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, -102.972447, -81.00807888, 0.0, 0.0, 0.0],\n            [0.0, 0.0, -81.00807888, -87.84105303, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n            [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n        ],\n        index=MultiIndex.from_tuples(\n            [\n                (\"eur\", \"eur\", \"instruments\", \"sofr\", \"10y\"),\n                (\"eur\", \"eur\", \"instruments\", \"sofr\", \"10y10y\"),\n                (\"eur\", \"eur\", \"instruments\", \"estr\", \"10y\"),\n                (\"eur\", \"eur\", \"instruments\", \"estr\", \"10y10y\"),\n                (\"eur\", \"eur\", \"instruments\", \"xccy\", \"10y\"),\n                (\"eur\", \"eur\", \"instruments\", \"xccy\", \"10y10y\"),\n                (\"eur\", \"eur\", \"fx\", \"fx\", \"eurusd\"),\n            ],\n            names=[\"local_ccy\", \"display_ccy\", \"type\", \"solver\", \"label\"],\n        ),\n        columns=MultiIndex.from_tuples(\n            [\n                (\"instruments\", \"sofr\", \"10y\"),\n                (\"instruments\", \"sofr\", \"10y10y\"),\n                (\"instruments\", \"estr\", \"10y\"),\n                (\"instruments\", \"estr\", \"10y10y\"),\n                (\"instruments\", \"xccy\", \"10y\"),\n                (\"instruments\", \"xccy\", \"10y10y\"),\n                (\"fx\", \"fx\", \"eurusd\"),\n            ],\n            names=[\"type\", \"solver\", \"label\"],\n        ),\n    )\n    with warnings.catch_warnings():\n        # TODO: pandas 3.0.0 can optionally turn off these PerformanceWarnings\n        warnings.simplefilter(action=\"ignore\", category=PerformanceWarning)\n        assert_frame_equal(\n            gamma_base.loc[(\"all\", \"eur\")], expected_gamma.loc[(\"eur\", \"eur\")], atol=1e-2, rtol=1e-4\n        )\n\n\ndef test_gamma_with_fxrates_ad_order_1_raises() -> None:\n    # when calculating gamma, AD order 2 is needed, the fx rates object passed\n    # must also be converted. TODO\n    pass\n\n\ndef test_error_labels() -> None:\n    solver_with_error = Solver(\n        curves=[\n            Curve(\n                nodes={dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 1.0, dt(2023, 1, 1): 1.0},\n                id=\"curve1\",\n            ),\n        ],\n        instruments=[\n            IRS(dt(2022, 1, 1), \"1M\", \"A\", curves=\"curve1\"),\n            IRS(dt(2022, 1, 1), \"2M\", \"A\", curves=\"curve1\"),\n            IRS(dt(2022, 1, 1), \"3M\", \"A\", curves=\"curve1\"),\n            IRS(dt(2022, 1, 1), \"4M\", \"A\", curves=\"curve1\"),\n            IRS(dt(2022, 1, 1), \"8M\", \"A\", curves=\"curve1\"),\n            IRS(dt(2022, 1, 1), \"12M\", \"A\", curves=\"curve1\"),\n        ],\n        s=[2.0, 2.2, 2.3, 2.4, 2.45, 2.55],\n        id=\"rates\",\n    )\n    result = solver_with_error.error\n    assert abs(result.loc[(\"rates\", \"rates0\")] - 22.798) < 1e-2\n\n\ndef test_solver_non_unique_id_raises() -> None:\n    curve = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"A\")\n    solver = Solver(\n        curves=[curve],\n        instruments=[IRS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=curve)],\n        s=[1],\n        id=\"bad\",\n    )\n    curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.98}, id=\"B\")\n    with pytest.raises(ValueError, match=\"Solver `id`s must be unique\"):\n        Solver(\n            curves=[curve2],\n            instruments=[IRS(dt(2022, 1, 1), \"1Y\", \"Q\", curves=curve2)],\n            s=[1],\n            id=\"bad\",\n            pre_solvers=[solver],\n        )\n\n\ndef test_solving_indirect_parameters_from_proxy_composite() -> None:\n    eureur = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"eureur\")\n    eurspd = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.999}, id=\"eurspd\")\n    eur3m = CompositeCurve([eureur, eurspd], id=\"eur3m\")\n    usdusd = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"usdusd\")\n    eurusd = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 1.0}, id=\"eurusd\")\n    fxr = FXRates({\"eurusd\": 1.1}, settlement=dt(2022, 1, 3))\n    fxf = FXForwards(\n        fx_rates=fxr,\n        fx_curves={\n            \"eureur\": eureur,\n            \"usdusd\": usdusd,\n            \"eurusd\": eurusd,\n        },\n    )\n    usdeur = fxf.curve(\"usd\", \"eur\", id=\"usdeur\")\n    instruments = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"A\", currency=\"eur\", curves=[\"eur3m\", \"eureur\"]),\n        IRS(dt(2022, 1, 1), \"1Y\", \"A\", currency=\"usd\", curves=\"usdusd\"),\n        XCS(\n            dt(2022, 1, 1),\n            \"1Y\",\n            \"A\",\n            currency=\"eur\",\n            pair=\"eurusd\",\n            curves=[\"eureur\", \"eureur\", \"usdusd\", \"usdeur\"],\n        ),\n    ]\n    Solver(\n        curves=[eureur, eur3m, usdusd, eurusd, usdeur],\n        instruments=instruments,\n        s=[2.0, 2.7, -15],\n        fx=fxf,\n    )\n\n\ndef test_solver_dimensions_of_matmul() -> None:\n    swaps = [\n        IRS(dt(2023, 7, 21), \"9m\", \"A\", fixed_rate=2.0, curves=\"chf\", currency=\"chf\"),\n        IRS(dt(2023, 7, 21), \"9m\", \"A\", fixed_rate=2.0, curves=\"gbp\", currency=\"gbp\"),\n        IRS(dt(2023, 7, 21), \"9m\", \"A\", fixed_rate=2.0, curves=\"usd\", currency=\"usd\"),\n    ]\n    chf_inst = [\n        IRS(dt(2023, 7, 21), \"6m\", \"A\", curves=\"chf\", currency=\"chf\"),\n        IRS(dt(2023, 7, 21), \"1y\", \"A\", curves=\"chf\", currency=\"chf\"),\n    ]\n    gbp_inst = [\n        IRS(dt(2023, 7, 21), \"6m\", \"A\", curves=\"gbp\", currency=\"gbp\"),\n        IRS(dt(2023, 7, 21), \"1y\", \"A\", curves=\"gbp\", currency=\"gbp\"),\n    ]\n    usd_inst = [\n        IRS(dt(2023, 7, 21), \"6m\", \"A\", curves=\"usd\", currency=\"usd\"),\n        IRS(dt(2023, 7, 21), \"1y\", \"A\", curves=\"usd\", currency=\"usd\"),\n    ]\n    usd = Curve(\n        {dt(2023, 7, 21): 1.0, dt(2024, 1, 21): 1.0, dt(2024, 7, 21): 1.0},\n        id=\"usd\",\n    )\n    gbp = Curve(\n        {dt(2023, 7, 21): 1.0, dt(2024, 1, 21): 1.0, dt(2024, 7, 21): 1.0},\n        id=\"gbp\",\n    )\n    chf = Curve(\n        {dt(2023, 7, 21): 1.0, dt(2024, 1, 21): 1.0, dt(2024, 7, 21): 1.0},\n        id=\"chf\",\n    )\n    fxr = FXRates({\"gbpusd\": 1.25, \"chfgbp\": 1.1})\n    solver1 = Solver(curves=[chf], instruments=chf_inst, s=[1.5, 1.8], id=\"CHF\")\n    solver2 = Solver(\n        curves=[gbp],\n        instruments=gbp_inst,\n        s=[1.6, 1.7],\n        id=\"GBP\",\n        pre_solvers=[solver1],\n    )\n    solver3 = Solver(\n        curves=[usd],\n        instruments=usd_inst,\n        s=[1.7, 1.9],\n        id=\"USD\",\n        pre_solvers=[solver2],\n    )\n    pf = Portfolio(swaps)\n    pf.delta(solver=solver3, base=\"gbp\", fx=fxr)\n    pf.gamma(solver=solver3, base=\"gbp\", fx=fxr)\n\n\ndef test_pre_solver_single_fx_object() -> None:\n    # this test considers building up FXForwards using chined solvers.\n    uu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"uu\")\n    ee = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"ee\")\n    gg = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"gg\")\n    eu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"eu\")\n    gu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"gu\")\n\n    fxf1 = FXForwards(\n        fx_rates=FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n        fx_curves={\n            \"usdusd\": uu,\n            \"eureur\": ee,\n            \"eurusd\": eu,\n        },\n    )\n\n    fxf2 = FXForwards(\n        fx_rates=FXRates({\"eurusd\": 1.0, \"gbpusd\": 1.5}, settlement=dt(2022, 1, 1)),\n        fx_curves={\n            \"usdusd\": uu,\n            \"eureur\": ee,\n            \"gbpgbp\": gg,\n            \"eurusd\": eu,\n            \"gbpusd\": gu,\n        },\n    )\n\n    s1 = Solver(\n        curves=[uu, ee, gg],\n        instruments=[\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"uu\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"ee\"),\n            IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"gg\"),\n        ],\n        s=[1.5, 1.5, 1.0],\n        id=\"local\",\n    )\n    s2 = Solver(\n        curves=[eu],\n        instruments=[\n            XCS(\n                dt(2022, 1, 1),\n                \"1Y\",\n                \"Q\",\n                currency=\"eur\",\n                pair=\"eurusd\",\n                curves=[\"ee\", \"eu\", \"uu\", \"uu\"],\n            ),\n        ],\n        s=[10.0],\n        id=\"x1\",\n        fx=fxf1,\n        pre_solvers=[s1],\n    )\n    Solver(\n        curves=[gu],\n        instruments=[\n            XCS(\n                dt(2022, 1, 1),\n                \"1Y\",\n                \"Q\",\n                currency=\"gbp\",\n                pair=\"gbpusd\",\n                curves=[\"gg\", \"gu\", \"uu\", \"uu\"],\n            ),\n        ],\n        s=[20.0],\n        id=\"x2\",\n        fx=fxf2,\n        pre_solvers=[s2],\n    )\n    result = gu[dt(2023, 1, 1)]\n    expected = 0.988\n    assert (result - expected) < 1e-4\n\n\ndef test_pre_solver_set_ad_order() -> None:\n    curve1 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n    curve2 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n    curve3 = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99})\n    cc = CompositeCurve([curve2, curve3])\n    s1 = Solver(curves=[curve1], instruments=[Value(dt(2022, 5, 1), curves=curve1)], s=[0.99])\n    s2 = Solver(curves=[curve2], instruments=[Value(dt(2022, 5, 1), curves=curve1)], s=[0.99])\n    s3 = Solver(\n        pre_solvers=[s1, s2],\n        curves=[cc, curve3],\n        instruments=[Value(dt(2022, 5, 1), curves=curve1)],\n        s=[0.99],\n    )\n    s3._set_ad_order(2)\n    for c in [curve1, curve2, curve3, cc]:\n        assert c._ad == 2\n    assert s2._ad == 2\n    assert s1._ad == 2\n\n    s3._set_ad_order(1)\n    for c in [curve1, curve2, curve3, cc]:\n        assert c._ad == 1\n    assert s2._ad == 1\n    assert s1._ad == 1\n\n\ndef test_solver_jacobians_in_text() -> None:\n    par_curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2027, 1, 1): 1.0,\n            dt(2032, 1, 1): 1.0,\n        },\n        id=\"curve\",\n    )\n    par_instruments = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"2Y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"5Y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"10Y\", \"A\", curves=\"curve\"),\n    ]\n    par_solver = Solver(\n        curves=[par_curve],\n        instruments=par_instruments,\n        s=[1.21, 1.635, 1.885, 1.93],\n        id=\"par_solver\",\n        instrument_labels=[\"1Y\", \"2Y\", \"5Y\", \"10Y\"],\n    )\n    fwd_curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n            dt(2027, 1, 1): 1.0,\n            dt(2032, 1, 1): 1.0,\n        },\n        id=\"curve\",\n    )\n    fwd_instruments = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n        IRS(dt(2023, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n        IRS(dt(2024, 1, 1), \"3Y\", \"A\", curves=\"curve\"),\n        IRS(dt(2027, 1, 1), \"5Y\", \"A\", curves=\"curve\"),\n    ]\n    s_fwd = [float(_.rate(solver=par_solver)) for _ in fwd_instruments]\n    fwd_solver = Solver(\n        curves=[fwd_curve],\n        instruments=fwd_instruments,\n        s=s_fwd,\n        id=\"fwd_solver\",\n        instrument_labels=[\"1Y\", \"1Y1Y\", \"2Y3Y\", \"5Y5Y\"],\n    )\n    S_BA = par_solver.jacobian(fwd_solver).to_numpy()\n    S_AB = fwd_solver.jacobian(par_solver).to_numpy()\n    assert np.all(np.isclose(np.eye(4), np.matmul(S_AB, S_BA)))\n\n\ndef test_solver_jacobians_pre() -> None:\n    par_curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n        },\n        id=\"curve\",\n    )\n    par_instruments = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n        IRS(dt(2022, 1, 1), \"2Y\", \"A\", curves=\"curve\"),\n    ]\n    par_solver = Solver(\n        curves=[par_curve],\n        instruments=par_instruments,\n        s=[1.21, 1.635],\n        id=\"par_solver\",\n        instrument_labels=[\"1Y\", \"2Y\"],\n    )\n    par_curve2 = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n        },\n        id=\"curve2\",\n    )\n    par_instruments2 = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve2\"),\n        IRS(dt(2022, 1, 1), \"2Y\", \"A\", curves=\"curve2\"),\n    ]\n    par_solver2 = Solver(\n        curves=[par_curve2],\n        instruments=par_instruments2,\n        s=[1.21, 1.635],\n        id=\"par_solver2\",\n        instrument_labels=[\"1Y\", \"2Y\"],\n        pre_solvers=[par_solver],\n    )\n\n    fwd_curve = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n        },\n        id=\"curve\",\n    )\n    fwd_instruments = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n        IRS(dt(2023, 1, 1), \"1Y\", \"A\", curves=\"curve\"),\n    ]\n    s_fwd = [float(_.rate(solver=par_solver2)) for _ in fwd_instruments]\n    fwd_solver = Solver(\n        curves=[fwd_curve],\n        instruments=fwd_instruments,\n        s=s_fwd,\n        id=\"fwd_solver\",\n        instrument_labels=[\"1Y\", \"1Y1Y\"],\n    )\n    fwd_curve2 = Curve(\n        nodes={\n            dt(2022, 1, 1): 1.0,\n            dt(2023, 1, 1): 1.0,\n            dt(2024, 1, 1): 1.0,\n        },\n        id=\"curve2\",\n    )\n    fwd_instruments2 = [\n        IRS(dt(2022, 1, 1), \"1Y\", \"A\", curves=\"curve2\"),\n        IRS(dt(2023, 1, 1), \"1Y\", \"A\", curves=\"curve2\"),\n    ]\n    s_fwd2 = [float(_.rate(solver=par_solver2)) for _ in fwd_instruments2]\n    fwd_solver2 = Solver(\n        curves=[fwd_curve2],\n        instruments=fwd_instruments2,\n        s=s_fwd2,\n        id=\"fwd_solver2\",\n        instrument_labels=[\"1Y\", \"1Y1Y\"],\n        pre_solvers=[fwd_solver],\n    )\n\n    S_BA = par_solver2.jacobian(fwd_solver2)\n    S_AB = fwd_solver2.jacobian(par_solver2)\n    assert np.all(np.isclose(np.eye(4), np.matmul(S_AB.to_numpy(), S_BA.to_numpy())))\n\n\ndef test_newton_solver_1dim_dual() -> None:\n    def root(x, s):\n        return x**2 - s, 2 * x\n\n    x0 = Dual(1.0, [\"x\"], [])\n    s = Dual(2.0, [\"s\"], [])\n    result = newton_1dim(root, x0, args=(s,))\n\n    expected = 0.5 / 2.0**0.5\n    sensitivity = gradient(result[\"g\"], [\"s\"])[0]\n    assert abs(expected - sensitivity) < 1e-9\n\n\ndef test_newton_solver_1dim_dual2() -> None:\n    def root(x, s):\n        return x**2 - s, 2 * x\n\n    x0 = Dual2(1.0, [\"x\"], [], [])\n    s = Dual2(2.0, [\"s\"], [], [])\n    result = newton_1dim(root, x0, args=(s,))\n\n    expected = 0.5 / 2.0**0.5\n    sensitivity = gradient(result[\"g\"], [\"s\"])[0]\n    assert abs(expected - sensitivity) < 1e-9\n\n    expected = -0.25 * (1 / 2.0**1.5)\n    sensitivity = gradient(result[\"g\"], [\"s\"], order=2)[0, 0]\n    assert abs(expected - sensitivity) < 1e-9\n\n\ndef test_newton_solver_2dim_dual() -> None:\n    def root(g, s):\n        f0 = g[0] ** 2 + g[1] ** 2 + s\n        f1 = g[0] ** 2 - 2 * g[1] ** 2 - s\n\n        f00 = 2 * g[0]\n        f01 = 2 * g[1]\n        f10 = 2 * g[0]\n        f11 = -4 * g[1]\n\n        return [f0, f1], [[f00, f01], [f10, f11]]\n\n    g0 = [Dual(1.0, [\"x\"], []), Dual(2.0, [\"y\"], [])]\n    s = Dual(-2.0, [\"s\"], [])\n    result = newton_ndim(root, g0, args=(s,))\n\n    expected_x = (2 / 3) ** 0.5\n    assert abs(result[\"g\"][0] - expected_x) < 1e-9\n\n    expected_y = (4 / 3) ** 0.5\n    assert abs(result[\"g\"][1] - expected_y) < 1e-9\n\n    expected_y = -0.5 * (2 / 3) ** 0.5 * (2.0) ** -0.5\n    expected_x = -0.5 * (1 / 3.0) ** 0.5 * (2.0) ** -0.5\n\n    sensitivity_x = gradient(result[\"g\"][0], [\"s\"])[0]\n    sensitivity_y = gradient(result[\"g\"][1], [\"s\"])[0]\n    assert abs(expected_x - sensitivity_x) < 1e-9\n    assert abs(expected_y - sensitivity_y) < 1e-9\n\n\ndef test_newton_solver_2dim_dual2() -> None:\n    def root(g, s):\n        f0 = g[0] ** 2 + g[1] ** 2 + s\n        f1 = g[0] ** 2 - 2 * g[1] ** 2 - s\n\n        f00 = 2 * g[0]\n        f01 = 2 * g[1]\n        f10 = 2 * g[0]\n        f11 = -4 * g[1]\n\n        return [f0, f1], [[f00, f01], [f10, f11]]\n\n    g0 = [Dual2(1.0, [\"x\"], [], []), Dual2(2.0, [\"y\"], [], [])]\n    s = Dual2(-2.0, [\"s\"], [], [])\n    result = newton_ndim(root, g0, args=(s,))\n\n    expected_x = (2 / 3) ** 0.5\n    assert abs(result[\"g\"][0] - expected_x) < 1e-9\n    expected_y = (4 / 3) ** 0.5\n    assert abs(result[\"g\"][1] - expected_y) < 1e-9\n\n    expected_y = -0.5 * (2 / 3) ** 0.5 * (2.0) ** -0.5\n    expected_x = -0.5 * (1 / 3.0) ** 0.5 * (2.0) ** -0.5\n    sensitivity_x = gradient(result[\"g\"][0], [\"s\"])[0]\n    sensitivity_y = gradient(result[\"g\"][1], [\"s\"])[0]\n    assert abs(expected_x - sensitivity_x) < 1e-9\n    assert abs(expected_y - sensitivity_y) < 1e-9\n\n    expected_y2 = -0.25 * (2 / 3) ** 0.5 * (2.0) ** -1.5\n    expected_x2 = -0.25 * (1 / 3) ** 0.5 * (2.0) ** -1.5\n    sensitivity_x2 = gradient(result[\"g\"][0], [\"s\"], order=2)[0, 0]\n    sensitivity_y2 = gradient(result[\"g\"][1], [\"s\"], order=2)[0, 0]\n    assert abs(expected_x2 - sensitivity_x2) < 1e-9\n    assert abs(expected_y2 - sensitivity_y2) < 1e-9\n\n\ndef test_newton_1d_failed_state() -> None:\n    def root(g):\n        f0 = g**2 + 10.0\n        f1 = 2 * g\n        return f0, f1\n\n    result = newton_1dim(root, 1.5, max_iter=5, raise_on_fail=False)\n    assert result[\"state\"] == -1\n\n\ndef test_newton_ndim_raises() -> None:\n    def root(g):\n        f0_0 = g[0] ** 2 + 10.0\n        f0_1 = g[0] + g[1] ** 2 - 2.0\n        return [f0_0, f0_1], [[2 * g[0], 0.0], [1.0, 2 * g[1]]]\n\n    with pytest.raises(ValueError, match=\"`max_iter`: 5 exceeded in 'newton_ndim'\"):\n        newton_ndim(root, [0.5, 1.0], max_iter=5)\n\n\ndef test_newton_solver_object_args():\n    def root(x, s):\n        return x**2 - s[\"some_obj\"], 2 * x\n\n    x0 = Dual(1.0, [\"x\"], [])\n    s = {\"some_obj\": Dual(2.0, [\"s\"], [])}\n    result = newton_1dim(root, x0, args=(s,))\n\n    expected = 0.5 / 2.0**0.5\n    sensitivity = gradient(result[\"g\"], [\"s\"])[0]\n    assert abs(expected - sensitivity) < 1e-9\n\n\ndef test_solver_with_vol_smile() -> None:\n    eureur = Curve(\n        {dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.9851909811629752},\n        calendar=\"tgt\",\n        id=\"eureur\",\n    )\n    usdusd = Curve(\n        {dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.976009366603271},\n        calendar=\"nyc\",\n        id=\"usdusd\",\n    )\n    # eurusd = Curve({dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.987092591908283}, id=\"eurusd\")\n    fxr = FXRates({\"eurusd\": 1.3088}, settlement=dt(2023, 3, 20))\n    fxf = FXForwards(fx_curves={\"eureur\": eureur, \"eurusd\": eureur, \"usdusd\": usdusd}, fx_rates=fxr)\n    fxf._set_ad_order(1)\n    solver = Solver(\n        curves=[eureur, usdusd],\n        instruments=[\n            IRS(dt(2023, 3, 20), \"1m\", curves=[eureur], spec=\"eur_irs\"),\n            IRS(dt(2023, 3, 20), \"1m\", curves=[usdusd], spec=\"usd_irs\"),\n        ],\n        s=[2.0113, 0.3525],\n        fx=fxf,\n    )\n    eurusd_1m_smile = FXDeltaVolSmile(\n        nodes={\n            0.25: 10.0,\n            0.50: 10.0,\n            0.75: 10.0,\n        },\n        eval_date=dt(2023, 3, 16),\n        expiry=dt(2023, 4, 18),\n        delta_type=\"spot\",\n        id=\"smile\",\n    )\n    args = {\n        \"pair\": \"eurusd\",\n        \"expiry\": dt(2023, 4, 18),\n        \"curves\": [\"eureur\", \"usdusd\"],\n        \"delta_type\": \"spot\",\n        \"vol\": \"smile\",\n    }\n    Solver(\n        pre_solvers=[solver],\n        curves=[eurusd_1m_smile],\n        instruments=[\n            FXStraddle(strike=\"atm_delta\", **args),\n            FXRiskReversal(strike=[\"-25d\", \"25d\"], **args),\n            FXStrangle(strike=[\"-25d\", \"25d\"], **args),\n        ],\n        s=[21.6215, -0.5, 22.359],\n        fx=fxf,\n    )\n\n\ndef test_solver_with_surface() -> None:\n    eureur = Curve({dt(2024, 5, 7): 1.0, dt(2025, 5, 30): 1.0}, calendar=\"tgt\", id=\"eureur\")\n    eurusd = Curve({dt(2024, 5, 7): 1.0, dt(2025, 5, 30): 1.0}, id=\"eurusd\")\n    usdusd = Curve({dt(2024, 5, 7): 1.0, dt(2025, 5, 30): 1.0}, calendar=\"nyc\", id=\"usdusd\")\n    # Create an FX Forward market with spot FX rate data\n    fxf = FXForwards(\n        fx_rates=FXRates({\"eurusd\": 1.0760}, settlement=dt(2024, 5, 9)),\n        fx_curves={\"eureur\": eureur, \"usdusd\": usdusd, \"eurusd\": eurusd},\n    )\n    solver = Solver(\n        curves=[eureur, eurusd, usdusd],\n        instruments=[\n            IRS(dt(2024, 5, 9), \"3W\", spec=\"eur_irs\", curves=\"eureur\"),\n            IRS(dt(2024, 5, 9), \"3W\", spec=\"usd_irs\", curves=\"usdusd\"),\n            FXSwap(\n                dt(2024, 5, 9),\n                \"3W\",\n                pair=\"eurusd\",\n                curves=[\"eurusd\", \"usdusd\"],\n            ),\n        ],\n        s=[3.90, 5.32, 8.85],\n        instrument_labels=[\"3w EU\", \"3w US\", \"3w FXSw\"],\n        fx=fxf,\n    )\n    surface = FXDeltaVolSurface(\n        eval_date=dt(2024, 5, 7),\n        expiries=[dt(2024, 5, 28), dt(2024, 6, 7)],\n        delta_indexes=[0.1, 0.25, 0.5, 0.75, 0.9],\n        delta_type=\"forward\",\n        node_values=np.ones(shape=(2, 5)) * 5.0,\n        id=\"eurusd_vol\",\n    )\n    data = DataFrame(\n        data=[\n            [5.493, -0.157, 0.071, -0.289, 0.238],\n            [5.525, -0.213, 0.075, -0.400, 0.250],\n        ],\n        columns=[\"ATM\", \"25dRR\", \"25dBF\", \"10dRR\", \"25dBF\"],\n        index=[dt(2024, 5, 28), dt(2024, 6, 7)],\n    )\n    fx_args = dict(\n        pair=\"eurusd\",\n        delta_type=\"spot\",\n        calendar=\"tgt\",\n        curves=[\"eurusd\", \"usdusd\"],\n        vol=\"eurusd_vol\",\n    )\n    instruments, s, labels = [], [], []\n    for e, row in enumerate(data.itertuples()):\n        instruments.extend(\n            [\n                FXStraddle(strike=\"atm_delta\", expiry=row[0], **fx_args),\n                FXRiskReversal(strike=(\"-25d\", \"25d\"), expiry=row[0], **fx_args),\n                FXBrokerFly(strike=((\"-25d\", \"25d\"), \"atm_delta\"), expiry=row[0], **fx_args),\n                FXRiskReversal(strike=(\"-10d\", \"10d\"), expiry=row[0], **fx_args),\n                FXBrokerFly(strike=((\"-10d\", \"10d\"), \"atm_delta\"), expiry=row[0], **fx_args),\n            ],\n        )\n        s.extend([row[1], row[2], row[3], row[4], row[5]])\n        labels.extend([f\"atm{e}\", f\"25rr{e}\", f\"25bf{e}\", f\"10rr{e}\", f\"10bf{e}\"])\n    surf_solver = Solver(\n        surfaces=[surface],\n        instruments=instruments,\n        s=s,\n        pre_solvers=[solver],\n        instrument_labels=labels,\n        fx=fxf,\n    )\n    fxc = FXCall(expiry=dt(2024, 6, 7), strike=1.08, **fx_args)\n    fxc.analytic_greeks(solver=surf_solver)\n    fxc.delta(solver=surf_solver)\n    fxc.gamma(solver=surf_solver)\n\n\nclass TestStateManagement:\n    def test_solver_state_storage(self):\n        # test the solver stores hashes of its objects: FXForwards, Curves and presolvers\n        uu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"uu\")\n        ee = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"ee\")\n        eu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"eu\")\n\n        fxf1 = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            fx_curves={\n                \"usdusd\": uu,\n                \"eureur\": ee,\n                \"eurusd\": eu,\n            },\n        )\n\n        s1 = Solver(\n            curves=[uu, ee],\n            instruments=[\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"uu\"),\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"ee\"),\n            ],\n            s=[1.5, 1.5],\n            id=\"local\",\n        )\n        s2 = Solver(\n            curves=[eu],\n            instruments=[\n                XCS(\n                    dt(2022, 1, 1),\n                    \"1Y\",\n                    \"Q\",\n                    currency=\"eur\",\n                    pair=\"eurusd\",\n                    curves=[\"ee\", \"eu\", \"uu\", \"uu\"],\n                ),\n            ],\n            s=[10.0],\n            id=\"x1\",\n            fx=fxf1,\n            pre_solvers=[s1],\n        )\n        hashes = {\"fx\": s2.fx._state, **{k: curve._state for k, curve in s2.pre_curves.items()}}\n        assert s2._states == hashes\n\n    @pytest.mark.parametrize(\n        \"method\",\n        [\n            \"delta\",\n            \"gamma\",\n            \"npv\",\n            \"rate\",\n        ],\n    )\n    @pytest.mark.parametrize(\n        (\"obj\", \"args\"), [(\"fxr\", ({\"eurusd\": 1.0},)), (\"fxf\", ([{\"eurusd\": 1.10}],))]\n    )\n    def test_warning_on_fx_mutation(self, method, obj, args):\n        # test the solver stores hashes of its objects: FXForwards, Curves and presolvers\n        uu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"uu\")\n        ee = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"ee\")\n        eu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"eu\")\n\n        fxr = FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1))\n        fxf = FXForwards(fx_rates=fxr, fx_curves={\"usdusd\": uu, \"eureur\": ee, \"eurusd\": eu})\n\n        s1 = Solver(\n            curves=[uu, ee],\n            instruments=[\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"uu\"),\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"ee\"),\n            ],\n            s=[1.5, 1.5],\n            id=\"local\",\n        )\n        s2 = Solver(\n            curves=[eu],\n            instruments=[\n                XCS(\n                    dt(2022, 1, 1),\n                    \"1Y\",\n                    \"Q\",\n                    currency=\"eur\",\n                    pair=\"eurusd\",\n                    curves=[\"ee\", \"eu\", \"uu\", \"uu\"],\n                ),\n            ],\n            s=[10.0],\n            id=\"x1\",\n            fx=fxf,\n            pre_solvers=[s1],\n        )\n\n        vars()[obj].update(*args)\n        irs = IRS(dt(2022, 1, 1), \"3y\", \"A\", curves=\"uu\")\n        with pytest.warns(UserWarning, match=\"The `fx` object associated with `solver`\"):\n            getattr(irs, method)(solver=s2)\n\n    @pytest.mark.parametrize(\n        \"method\",\n        [\n            \"delta\",\n            \"gamma\",\n            \"npv\",\n            \"rate\",\n        ],\n    )\n    def test_raise_on_pre_curve_mutation(self, method):\n        # test the solver stores hashes of its objects: FXForwards, Curves and presolvers\n        uu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"uu\")\n        ee = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"ee\")\n        eu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"eu\")\n\n        fxf1 = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            fx_curves={\n                \"usdusd\": uu,\n                \"eureur\": ee,\n                \"eurusd\": eu,\n            },\n        )\n\n        s1 = Solver(\n            curves=[uu, ee],\n            instruments=[\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"uu\"),\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"ee\"),\n            ],\n            s=[1.5, 1.5],\n            id=\"local\",\n        )\n        s2 = Solver(\n            curves=[eu],\n            instruments=[\n                XCS(\n                    dt(2022, 1, 1),\n                    \"1Y\",\n                    \"Q\",\n                    currency=\"eur\",\n                    pair=\"eurusd\",\n                    curves=[\"ee\", \"eu\", \"uu\", \"uu\"],\n                ),\n            ],\n            s=[10.0],\n            id=\"x1\",\n            fx=fxf1,\n            pre_solvers=[s1],\n        )\n\n        uu._set_node_vector([0.995], 1)\n        irs = IRS(dt(2022, 1, 1), \"3y\", \"A\", curves=\"uu\")\n        with pytest.raises(ValueError, match=\"The `curves` associated with `solver` have been upd\"):\n            getattr(irs, method)(solver=s2)\n\n    @pytest.mark.parametrize(\n        \"method\",\n        [\n            \"delta\",\n            \"gamma\",\n            \"npv\",\n            \"rate\",\n        ],\n    )\n    def test_raise_on_curve_mutation(self, method):\n        # test the solver stores hashes of its objects: FXForwards, Curves and presolvers\n        uu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"uu\")\n        ee = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"ee\")\n        eu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"eu\")\n\n        fxf1 = FXForwards(\n            fx_rates=FXRates({\"eurusd\": 1.0}, settlement=dt(2022, 1, 1)),\n            fx_curves={\n                \"usdusd\": uu,\n                \"eureur\": ee,\n                \"eurusd\": eu,\n            },\n        )\n\n        s1 = Solver(\n            curves=[uu, ee],\n            instruments=[\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"uu\"),\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"ee\"),\n            ],\n            s=[1.5, 1.5],\n            id=\"local\",\n        )\n        s2 = Solver(\n            curves=[eu],\n            instruments=[\n                XCS(\n                    dt(2022, 1, 1),\n                    \"1Y\",\n                    \"Q\",\n                    currency=\"eur\",\n                    pair=\"eurusd\",\n                    curves=[\"ee\", \"eu\", \"uu\", \"uu\"],\n                ),\n            ],\n            s=[10.0],\n            id=\"x1\",\n            fx=fxf1,\n            pre_solvers=[s1],\n        )\n\n        eu._set_node_vector([0.995], 1)\n        irs = IRS(dt(2022, 1, 1), \"3y\", \"A\", curves=\"uu\")\n        with pytest.raises(ValueError, match=\"The `curves` associated with `solver` have been up\"):\n            getattr(irs, method)(solver=s2)\n\n    @pytest.mark.parametrize(\n        \"method\",\n        [\n            \"delta\",\n            \"gamma\",\n            \"npv\",\n            \"rate\",\n        ],\n    )\n    def test_raise_on_composite_curve_mutation(self, method):\n        # test the solver stores hashes of its objects: FXForwards, Curves and presolvers\n        uu = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"uu\")\n        ee = Curve({dt(2022, 1, 1): 1.0, dt(2023, 1, 1): 0.99}, id=\"ee\")\n        cc = CompositeCurve([uu, ee], id=\"cc\")\n\n        s1 = Solver(\n            curves=[ee, cc],\n            instruments=[\n                IRS(dt(2022, 1, 1), \"1y\", \"A\", curves=\"cc\"),\n            ],\n            s=[1.5],\n            id=\"local\",\n        )\n\n        uu.update_node(dt(2023, 1, 1), 0.98)\n        irs = IRS(dt(2022, 1, 1), \"3y\", \"A\", curves=\"cc\")\n        with pytest.raises(ValueError, match=\"The `curves` associated with `solver` have been up\"):\n            getattr(irs, method)(solver=s1)\n\n    def test_solver_auto_updates_fx_before_state_setting(self):\n        # added `self.fx._set_ad_order(1)` to Solver.__init__\n        with warnings.catch_warnings():\n            warnings.simplefilter(action=\"error\", category=UserWarning)\n            smile = FXDeltaVolSmile(\n                nodes={\n                    0.10: 10.0,\n                    0.25: 10.0,\n                    0.50: 10.0,\n                    0.75: 10.0,\n                    0.90: 10.0,\n                },\n                eval_date=dt(2024, 5, 7),\n                expiry=dt(2024, 5, 28),\n                delta_type=\"spot\",\n                id=\"eurusd_3w_smile\",\n            )\n            # Define the interest rate curves for EUR, USD and X-Ccy basis\n            eureur = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"tgt\", id=\"eureur\")\n            eurusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, id=\"eurusd\")\n            usdusd = Curve({dt(2024, 5, 7): 1.0, dt(2024, 5, 30): 1.0}, calendar=\"nyc\", id=\"usdusd\")\n            # Create an FX Forward market with spot FX rate data\n            fxf = FXForwards(\n                fx_rates=FXRates({\"eurusd\": 1.0760}, settlement=dt(2024, 5, 9)),\n                fx_curves={\"eureur\": eureur, \"usdusd\": usdusd, \"eurusd\": eurusd},\n            )\n            # Setup the Solver instrument calibration for rates Curves and vol Smiles\n            option_args = dict(\n                pair=\"eurusd\",\n                expiry=dt(2024, 5, 28),\n                calendar=\"tgt\",\n                delta_type=\"spot\",\n                curves=[\"eurusd\", \"usdusd\"],\n                vol=\"eurusd_3w_smile\",\n            )\n            Solver(\n                curves=[eureur, eurusd, usdusd, smile],\n                instruments=[\n                    IRS(dt(2024, 5, 9), \"3W\", spec=\"eur_irs\", curves=\"eureur\"),\n                    IRS(dt(2024, 5, 9), \"3W\", spec=\"usd_irs\", curves=\"usdusd\"),\n                    FXSwap(\n                        dt(2024, 5, 9), \"3W\", pair=\"eurusd\", curves=[None, \"eurusd\", None, \"usdusd\"]\n                    ),\n                    FXStraddle(strike=\"atm_delta\", **option_args),\n                    FXRiskReversal(strike=(\"-25d\", \"25d\"), **option_args),\n                    FXRiskReversal(strike=(\"-10d\", \"10d\"), **option_args),\n                    FXBrokerFly(strike=((\"-25d\", \"25d\"), \"atm_delta\"), **option_args),\n                    FXBrokerFly(strike=((\"-10d\", \"10d\"), \"atm_delta\"), **option_args),\n                ],\n                s=[3.90, 5.32, 8.85, 5.493, -0.157, -0.289, 0.071, 0.238],\n                fx=fxf,\n            )\n\n    def test_solver_dual2_auto_updates_fx_before_state_setting(self):\n        with warnings.catch_warnings():\n            warnings.simplefilter(action=\"error\", category=UserWarning)\n            # tests the doc page j_gamma.rst\n            sofr = Curve(\n                nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0}, id=\"sofr\"\n            )\n            estr = Curve(\n                nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0}, id=\"estr\"\n            )\n            eurusd = Curve(\n                nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0}, id=\"eurusd\"\n            )\n            fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n            fxf = FXForwards(fxr, {\"eureur\": estr, \"eurusd\": eurusd, \"usdusd\": sofr})\n            instruments = [\n                IRS(dt(2022, 1, 1), \"10y\", \"A\", currency=\"usd\", curves=\"sofr\"),\n                IRS(dt(2032, 1, 1), \"10y\", \"A\", currency=\"usd\", curves=\"sofr\"),\n                IRS(dt(2022, 1, 1), \"10y\", \"A\", currency=\"eur\", curves=\"estr\"),\n                IRS(dt(2032, 1, 1), \"10y\", \"A\", currency=\"eur\", curves=\"estr\"),\n                XCS(\n                    dt(2022, 1, 1),\n                    \"10y\",\n                    \"A\",\n                    currency=\"usd\",\n                    pair=\"eurusd\",\n                    curves=[\"estr\", \"eurusd\", \"sofr\", \"sofr\"],\n                ),\n                XCS(\n                    dt(2032, 1, 1),\n                    \"10y\",\n                    \"A\",\n                    currency=\"usd\",\n                    pair=\"eurusd\",\n                    curves=[\"estr\", \"eurusd\", \"sofr\", \"sofr\"],\n                ),\n            ]\n            sofr_solver = Solver(\n                curves=[sofr],\n                instruments=instruments[:2],\n                s=[3.45, 2.85],\n                instrument_labels=[\"10y\", \"10y10y\"],\n                id=\"sofr\",\n                fx=fxf,\n            )\n            estr_solver = Solver(\n                curves=[estr],\n                instruments=instruments[2:4],\n                s=[2.25, 0.90],\n                instrument_labels=[\"10y\", \"10y10y\"],\n                id=\"estr\",\n                fx=fxf,\n            )\n            solver = Solver(\n                curves=[eurusd],\n                instruments=instruments[4:],\n                s=[-10, -15],\n                instrument_labels=[\"10y\", \"10y10y\"],\n                id=\"eurusd\",\n                fx=fxf,\n                pre_solvers=[sofr_solver, estr_solver],\n            )\n            pf = Portfolio(\n                [\n                    IRS(\n                        dt(2022, 1, 1),\n                        \"20Y\",\n                        \"A\",\n                        currency=\"eur\",\n                        fixed_rate=2.0,\n                        notional=1e8,\n                        curves=\"estr\",\n                    ),\n                    IRS(\n                        dt(2022, 1, 1),\n                        \"20Y\",\n                        \"A\",\n                        currency=\"usd\",\n                        fixed_rate=1.5,\n                        notional=-1.1e8,\n                        curves=\"sofr\",\n                    ),\n                ]\n            )\n            pf.gamma(solver=solver, base=\"eur\")\n\n    def test_pre_solvers_fx_is_updated_and_does_not_cause_validation_issue(self):\n        with warnings.catch_warnings():\n            warnings.simplefilter(action=\"error\", category=UserWarning)\n            # tests the doc page j_gamma.rst\n            sofr = Curve(\n                nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0}, id=\"sofr\"\n            )\n            estr = Curve(\n                nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0}, id=\"estr\"\n            )\n            eurusd = Curve(\n                nodes={dt(2022, 1, 1): 1.0, dt(2032, 1, 1): 1.0, dt(2042, 1, 1): 1.0}, id=\"eurusd\"\n            )\n            fxr = FXRates({\"eurusd\": 1.05}, settlement=dt(2022, 1, 3))\n            fxf = FXForwards(fxr, {\"eureur\": estr, \"eurusd\": eurusd, \"usdusd\": sofr})\n            instruments = [\n                IRS(dt(2022, 1, 1), \"10y\", \"A\", currency=\"usd\", curves=\"sofr\"),\n                IRS(dt(2032, 1, 1), \"10y\", \"A\", currency=\"usd\", curves=\"sofr\"),\n                IRS(dt(2022, 1, 1), \"10y\", \"A\", currency=\"eur\", curves=\"estr\"),\n                IRS(dt(2032, 1, 1), \"10y\", \"A\", currency=\"eur\", curves=\"estr\"),\n                XCS(\n                    dt(2022, 1, 1),\n                    \"10y\",\n                    \"A\",\n                    currency=\"eur\",\n                    pair=\"eurusd\",\n                    curves=[\"estr\", \"eurusd\", \"sofr\", \"sofr\"],\n                ),\n                XCS(\n                    dt(2032, 1, 1),\n                    \"10y\",\n                    \"A\",\n                    currency=\"usd\",\n                    pair=\"eurusd\",\n                    curves=[\"estr\", \"eurusd\", \"sofr\", \"sofr\"],\n                ),\n            ]\n            solver1 = Solver(\n                curves=[sofr, estr],\n                instruments=instruments[:4],\n                s=[3.45, 2.85, 2.4, 1.7],\n                instrument_labels=[\"10y\", \"10y10y\", \"10ye\", \"10y10ye\"],\n                id=\"sofr/estr\",\n                fx=fxf,\n            )\n            # solver 2 will solve the FX basis and update the FXForwards object which is also\n            # associated with solver1. If solver1 is state validated it will then fail.\n            # except when the _update_fx method of solver2 also nests calls to pre_solvers\n            _solver2 = Solver(\n                curves=[eurusd],\n                instruments=instruments[4:],\n                s=[-10, -15],\n                instrument_labels=[\"10y\", \"10y10y\"],\n                id=\"eurusd\",\n                fx=fxf,\n                pre_solvers=[solver1],\n            )\n            irs = IRS(\n                dt(2022, 1, 1),\n                \"20Y\",\n                \"A\",\n                currency=\"eur\",\n                fixed_rate=2.0,\n                notional=1e8,\n                curves=\"estr\",\n            )\n            irs.gamma(solver=solver1, base=\"eur\")\n\n    @pytest.mark.parametrize(\n        \"obj\",\n        [\n            Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n            LineCurve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n            FXDeltaVolSmile(\n                nodes={0.5: 10.0},\n                expiry=dt(2000, 1, 1),\n                eval_date=dt(1999, 1, 1),\n                delta_type=\"forward\",\n            ),\n            FXRates({\"eurusd\": 1.0}),\n            FXForwards(\n                FXRates({\"eurusd\": 1.0}, settlement=dt(2000, 1, 3)),\n                {\n                    \"eurusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                    \"eureur\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                    \"usdusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                },\n            ),\n            CompositeCurve(\n                [\n                    Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                    Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                ]\n            ),\n            MultiCsaCurve(\n                [\n                    Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                    Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                ]\n            ),\n            FXDeltaVolSurface(\n                delta_type=\"forward\",\n                delta_indexes=[0.5],\n                expiries=[dt(2000, 1, 8), dt(2001, 1, 1)],\n                eval_date=dt(1999, 1, 1),\n                node_values=[[10], [11]],\n            ),\n            Solver(\n                curves=[Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}, id=\"abc\")],\n                instruments=[IRS(dt(2000, 1, 1), \"1m\", spec=\"usd_irs\", curves=\"abc\")],\n                s=[2.0],\n                fx=FXForwards(\n                    FXRates({\"eurusd\": 1.0}, settlement=dt(2000, 1, 3)),\n                    {\n                        \"eurusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                        \"eureur\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                        \"usdusd\": Curve({dt(2000, 1, 1): 1.0, dt(2000, 3, 2): 0.99}),\n                    },\n                ),\n            ),\n        ],\n    )\n    def test_set_ad_order_does_not_change_object_state(self, obj):\n        pre_state = obj._state\n        obj._set_ad_order(2)\n        post_state = obj._state\n        assert pre_state == post_state\n\n    def test_solver_validation_control(self):\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0})\n        solver = Solver(\n            curves=[curve],\n            instruments=[IRS(dt(2000, 1, 1), \"1m\", spec=\"usd_irs\", curves=curve)],\n            s=[2.0],\n        )\n        curve.update_node(dt(2001, 1, 1), 0.99)\n        irs = IRS(dt(2000, 1, 1), \"2m\", spec=\"usd_irs\", curves=curve)\n        with pytest.raises(ValueError, match=\"The `curves` associated with `solver` have\"):\n            irs.rate(solver=solver)\n\n        solver._do_not_validate = True\n        result = irs.rate(solver=solver)\n        assert abs(result - 0.989345) < 1e-5\n\n\n@pytest.mark.parametrize(\n    \"obj\",\n    [\n        Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}),\n        LineCurve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 2.0}),\n        FXRates({\"eurusd\": 1.0}),\n        FXForwards(\n            fx_curves={\n                \"eureur\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}),\n                \"eurusd\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}),\n                \"usdusd\": Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}),\n            },\n            fx_rates=FXRates({\"eurusd\": 1.0}, settlement=dt(2000, 1, 3)),\n        ),\n        FXSabrSmile(\n            nodes={\n                \"alpha\": 0.17431060,\n                \"beta\": 1.0,\n                \"rho\": -0.11268306,\n                \"nu\": 0.81694072,\n            },\n            eval_date=dt(2001, 1, 1),\n            expiry=dt(2002, 1, 1),\n            id=\"vol\",\n        ),\n        FXSabrSurface(\n            eval_date=dt(2024, 5, 28),\n            expiries=[dt(2025, 2, 2), dt(2025, 3, 3)],\n            node_values=[[0.05, 1.0, 0.01, 0.15]] * 2,\n            pair=\"eurusd\",\n            delivery_lag=2,\n            calendar=\"tgt|fed\",\n            id=\"eurusd_vol\",\n        ),\n        FXDeltaVolSurface(\n            delta_indexes=[0.25, 0.5, 0.75],\n            expiries=[dt(2024, 1, 1), dt(2025, 1, 1)],\n            node_values=[[11, 10, 12], [8, 7, 9]],\n            eval_date=dt(2023, 1, 1),\n            delta_type=\"forward\",\n            id=\"vol\",\n        ),\n        FXDeltaVolSmile(\n            nodes={\n                0.25: 10.15,\n                0.5: 7.8,\n                0.75: 8.9,\n            },\n            delta_type=\"forward\",\n            eval_date=dt(2023, 3, 16),\n            expiry=dt(2023, 6, 16),\n            id=\"vol\",\n            ad=1,\n        ),\n        MultiCsaCurve(\n            [\n                Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}),\n                Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}),\n            ]\n        ),\n        CompositeCurve(\n            [\n                Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}),\n                Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}),\n            ]\n        ),\n    ],\n)\ndef test_objects_ad_attribute(obj):\n    result = getattr(obj, \"_ad\", None)\n    assert result is not None\n\n\n@pytest.mark.parametrize(\"label\", [\"shift\", \"rolled\", \"translated\"])\ndef test_curves_without_their_own_params(label):\n    curve = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"curve\")\n    _map = {\n        \"shift\": curve.shift(5, id=\"shift\"),\n        \"rolled\": curve.roll(5, id=\"rolled\"),\n        \"translated\": curve.translate(dt(2000, 1, 1), id=\"translated\"),\n    }\n\n    sv = Solver(\n        curves=[curve, _map[label]],\n        instruments=[IRS(dt(2000, 2, 1), dt(2000, 3, 1), spec=\"usd_irs\", curves=[\"curve\", label])],\n        s=[2.0],\n    )\n    assert sv.result[\"status\"] == \"SUCCESS\"\n\n\ndef test_from_other() -> None:\n    pricing_curve = Curve(\n        nodes={dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0, dt(2002, 1, 10): 1.0},\n        interpolation=\"spline\",\n        id=\"sofr\",\n    )\n    pricing_solver = Solver(\n        curves=[pricing_curve],\n        instruments=[\n            IRS(dt(2000, 1, 1), \"1y\", spec=\"usd_irs\", curves=[\"sofr\"]),\n            IRS(dt(2000, 1, 1), \"2y\", spec=\"usd_irs\", curves=[\"sofr\"]),\n        ],\n        s=[4.10, 4.25],\n        instrument_labels=[\"1y\", \"2y\"],\n        id=\"price_sv\",\n    )\n\n    risk_curve = Curve(\n        nodes={\n            dt(2000, 1, 1): 1.0,\n            dt(2000, 4, 1): 1.0,\n            dt(2000, 7, 1): 1.0,\n            dt(2000, 10, 1): 1.0,\n            dt(2001, 1, 1): 1.0,\n            dt(2001, 4, 1): 1.0,\n            dt(2001, 7, 1): 1.0,\n            dt(2001, 10, 1): 1.0,\n            dt(2002, 1, 10): 1.0,\n        },\n        interpolation=\"log_linear\",\n        id=\"sofr\",\n    )\n    risk_solver = Solver.from_other(\n        pricing_solver=pricing_solver,\n        curves=[risk_curve],\n        instruments=[\n            IRS(dt(2000, 1, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n            IRS(dt(2000, 4, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n            IRS(dt(2000, 7, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n            IRS(dt(2000, 10, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n            IRS(dt(2001, 1, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n            IRS(dt(2001, 4, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n            IRS(dt(2001, 7, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n            IRS(dt(2001, 10, 1), \"3m\", spec=\"usd_irs\", curves=[\"sofr\"]),\n        ],\n        instrument_labels=[\"0m3m\", \"3m3m\", \"6m3m\", \"9m3m\", \"1y3m\", \"15m3m\", \"18m3m\", \"21m3m\"],\n        id=\"risk_sv\",\n    )\n\n    expected = [3.967, 3.995, 4.051, 4.134, 4.235, 4.318, 4.375, 4.406]\n    assert all(abs(r - e) < 1e-3 for r, e in zip(expected, risk_solver.s))\n\n\nclass TestContainerSolver:\n    # these tests involve a Solver that has no instruments of its own and is just a\n    # wrapper of 1 or multiple `pre_solvers`\n\n    def test_combine_separate_solvers_for_delta(self):\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"x\")\n        curve2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"y\")\n        solver = Solver(\n            curves=[curve],\n            instruments=[Value(dt(2000, 9, 12), curves=\"x\", metric=\"o/n_rate\")],\n            s=[2.0],\n            instrument_labels=[\"X\"],\n            id=\"A1\",\n        )\n        solver2 = Solver(\n            curves=[curve2],\n            instruments=[Value(dt(2000, 9, 12), curves=\"y\", metric=\"o/n_rate\")],\n            s=[3.0],\n            instrument_labels=[\"Y\"],\n            id=\"A2\",\n        )\n        solver3 = Solver(pre_solvers=[solver, solver2])\n\n        v = IRS(dt(2000, 9, 12), \"1d\", \"M\", curves=\"x\")\n        w = IRS(dt(2000, 9, 12), \"1d\", \"M\", curves=\"y\")\n        result = Portfolio([v, w]).delta(solver=solver3)\n\n        m_idx = MultiIndex.from_tuples(\n            [(\"instruments\", \"A1\", \"X\"), (\"instruments\", \"A2\", \"Y\")],\n            names=[\"type\", \"solver\", \"label\"],\n        )\n        c_idx = MultiIndex.from_tuples([(\"usd\", \"usd\")], names=[\"local_ccy\", \"display_ccy\"])\n        expected = DataFrame([0.273825, 0.271870], index=m_idx, columns=c_idx)\n        assert_frame_equal(result, expected)\n\n    def test_combine_separate_solvers_for_exo_delta(self):\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"x\")\n        curve2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"y\")\n        solver = Solver(\n            curves=[curve],\n            instruments=[Value(dt(2000, 9, 12), curves=\"x\", metric=\"o/n_rate\")],\n            s=[2.0],\n            instrument_labels=[\"X\"],\n            id=\"A1\",\n        )\n        solver2 = Solver(\n            curves=[curve2],\n            instruments=[Value(dt(2000, 9, 12), curves=\"y\", metric=\"o/n_rate\")],\n            s=[3.0],\n            instrument_labels=[\"Y\"],\n            id=\"A2\",\n        )\n        solver3 = Solver(pre_solvers=[solver, solver2])\n\n        v = IRS(\n            dt(2000, 9, 12), \"1d\", \"M\", curves=\"x\", notional=Variable(1e8, [\"exo\"]), fixed_rate=5\n        )\n        w = IRS(\n            dt(2000, 9, 12), \"1d\", \"M\", curves=\"y\", notional=Variable(1e8, [\"exo\"]), fixed_rate=4\n        )\n        result = (\n            Portfolio([v, w]).exo_delta(solver=solver3, vars=[\"exo\"], vars_scalar=[1e8]).to_numpy()\n        )\n        pv = Portfolio([v, w]).npv(solver=solver3)\n        assert abs(result[0, 0] - pv) < 1e-7\n\n    def test_combine_separate_solvers_for_gamma(self):\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"x\")\n        curve2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"y\")\n        solver = Solver(\n            curves=[curve],\n            instruments=[Value(dt(2000, 9, 12), curves=\"x\", metric=\"o/n_rate\")],\n            s=[2.0],\n            instrument_labels=[\"X\"],\n            id=\"A1\",\n        )\n        solver2 = Solver(\n            curves=[curve2],\n            instruments=[Value(dt(2000, 9, 12), curves=\"y\", metric=\"o/n_rate\")],\n            s=[3.0],\n            instrument_labels=[\"Y\"],\n            id=\"A2\",\n        )\n        solver3 = Solver(pre_solvers=[solver, solver2])\n\n        v = IRS(dt(2000, 9, 12), \"1d\", \"M\", curves=\"x\")\n        w = IRS(dt(2000, 9, 12), \"1d\", \"M\", curves=\"y\")\n        result = Portfolio([v, w]).gamma(solver=solver3).to_numpy()\n\n        partial_result1 = v.gamma(solver=solver).to_numpy()\n        partial_result2 = w.gamma(solver=solver2).to_numpy()\n\n        assert np.all(\n            result\n            == np.block(\n                [\n                    [partial_result1, np.zeros(shape=(1, 1))],\n                    [np.zeros(shape=(1, 1)), partial_result2],\n                ]\n            )\n        )\n\n    def test_combine_separate_solvers_error(self):\n        curve = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"x\")\n        curve2 = Curve({dt(2000, 1, 1): 1.0, dt(2001, 1, 1): 1.0}, id=\"y\")\n        solver = Solver(\n            curves=[curve],\n            instruments=[Value(dt(2000, 9, 12), curves=\"x\", metric=\"o/n_rate\")],\n            s=[2.0],\n            instrument_labels=[\"X\"],\n            id=\"A1\",\n        )\n        solver2 = Solver(\n            curves=[curve2],\n            instruments=[Value(dt(2000, 9, 12), curves=\"y\", metric=\"o/n_rate\")],\n            s=[3.0],\n            instrument_labels=[\"Y\"],\n            id=\"A2\",\n        )\n        solver3 = Solver(pre_solvers=[solver, solver2])\n        result = solver3.error\n        assert isinstance(result, Series)\n\n    def test_error_empty(self):\n        s1 = Solver()\n        s2 = Solver()\n        s3 = Solver(pre_solvers=[s1, s2])\n        assert s3.error.empty\n"
  },
  {
    "path": "python/tests/test_splines.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport copy\n\nimport numpy as np\nimport pytest\nfrom rateslib.dual import Dual, Dual2, Variable, gradient, set_order_convert\nfrom rateslib.serialization import from_json\nfrom rateslib.splines import PPSplineDual, PPSplineDual2, PPSplineF64, evaluate\n\n\n@pytest.fixture\ndef t():\n    return np.array([1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4])\n\n\n@pytest.fixture\ndef x():\n    return np.linspace(1, 4, 7)\n\n\n@pytest.mark.parametrize(\n    (\"i\", \"expected\"),\n    [\n        (0, np.array([1.0, 0.125, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (1, np.array([0.0, 0.375, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (2, np.array([0.0, 0.375, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (3, np.array([0.0, 0.125, 1.0, 0.125, 0.0, 0.0, 0.0])),\n        (4, np.array([0.0, 0.0, 0.0, 0.59375, 0.25, 0.03125, 0.0])),\n        (5, np.array([0.0, 0.0, 0.0, 0.25, 0.5, 0.25, 0.0])),\n        (6, np.array([0.0, 0.0, 0.0, 0.03125, 0.25, 0.59375, 0.0])),\n        (7, np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.125, 1.0])),\n    ],\n)\ndef test_individual_bsplines(t, x, i, expected) -> None:\n    bs = PPSplineF64(k=4, t=t)\n    result = bs.bsplev(x, i=i)\n    assert (result == expected).all()\n\n\n@pytest.mark.parametrize(\n    (\"i\", \"expected\"),\n    [\n        (0, np.array([-3.0, -0.75, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (1, np.array([3.0, -0.75, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (2, np.array([0.0, 0.75, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (3, np.array([0.0, 0.75, -3.0, -0.75, 0.0, 0.0, 0.0])),\n        (4, np.array([0.0, 0.0, 3.0, -0.1875, -0.75, -0.1875, 0.0])),\n        (5, np.array([0.0, 0.0, 0.0, 0.75, 0.0, -0.75, 0.0])),\n        (6, np.array([0.0, 0.0, 0.0, 0.1875, 0.75, 0.1875, -3.0])),\n        (7, np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.75, 3.0])),\n    ],\n)\ndef test_first_derivative_endpoint_support(t, x, i, expected) -> None:\n    bs = PPSplineF64(k=4, t=t)\n    result = bs.bspldnev(x, i=i, m=1)\n    assert (result == expected).all()\n\n\n@pytest.mark.parametrize(\n    (\"i\", \"expected\"),\n    [\n        (0, np.array([6.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (1, np.array([-12.0, -3.0, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (2, np.array([6.0, -3.0, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (3, np.array([0.0, 3.0, 6.0, 3.0, 0.0, 0.0, 0.0])),\n        (4, np.array([0.0, 0.0, -9.0, -3.75, 1.5, 0.75, 0.0])),\n        (5, np.array([0.0, 0.0, 3.0, 0.0, -3.0, 0.0, 3.0])),\n        (6, np.array([0.0, 0.0, 0.0, 0.75, 1.5, -3.75, -9.0])),\n        (7, np.array([0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 6.0])),\n    ],\n)\ndef test_second_derivative_endpoint_support(t, x, i, expected) -> None:\n    bs = PPSplineF64(k=4, t=t)\n    result = bs.bspldnev(x, i=i, m=2)\n    assert (result == expected).all()\n\n\n@pytest.mark.parametrize(\n    (\"i\", \"expected\"),\n    [\n        (0, np.array([-6.0, -6.0, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (1, np.array([18.0, 18.0, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (2, np.array([-18.0, -18.0, 0.0, 0.0, 0.0, 0.0, 0.0])),\n        (3, np.array([6.0, 6.0, -6.0, -6.0, 0.0, 0.0, 0.0])),\n        (4, np.array([0.0, 0.0, 10.5, 10.5, -1.5, -1.5, -1.5])),\n        (5, np.array([0.0, 0.0, -6.0, -6.0, 6.0, 6.0, 6.0])),\n        (6, np.array([0.0, 0.0, 1.5, 1.5, -10.5, -10.5, -10.5])),\n        (7, np.array([0.0, 0.0, 0.0, 0.0, 6.0, 6.0, 6.0])),\n    ],\n)\ndef test_third_derivative_endpoint_support(t, x, i, expected) -> None:\n    bs = PPSplineF64(k=4, t=t)\n    result = bs.bspldnev(x, i=i, m=3)\n    assert (result == expected).all()\n\n\ndef test_fourth_derivative_endpoint_support(t, x) -> None:\n    bs = PPSplineF64(k=4, t=t)\n    expected = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])\n    for i in range(8):\n        test = bs.bspldnev(x, i=i, m=4) == expected\n        assert test.all()\n\n\ndef test_ppdnev(t) -> None:\n    bs = PPSplineF64(k=4, t=t, c=[1, 2, -1, 2, 1, 1, 2, 2.0])\n    r1 = bs.ppdnev_single(1.1, 2)\n    r2 = bs.ppdnev_single(1.8, 2)\n    r3 = bs.ppdnev_single(2.8, 2)\n    result = bs.ppdnev(np.array([1.1, 1.8, 2.8]), 2)\n    assert (result == np.array([r1, r2, r3])).all()\n\n\ndef test_ppev(t) -> None:\n    bs = PPSplineF64(k=4, t=t, c=[1, 2, -1, 2, 1, 1, 2, 2.0])\n    r1 = bs.ppev_single(1.1)\n    r2 = bs.ppev_single(1.8)\n    r3 = bs.ppev_single(2.8)\n    result = bs.ppev(np.array([1.1, 1.8, 2.8]))\n    assert (result == np.array([r1, r2, r3])).all()\n\n\ndef test_csolve() -> None:\n    t = [0, 0, 0, 0, 4, 4, 4, 4]\n    tau = np.array([0, 1, 3, 4])\n    val = np.array([0, 0, 2, 2])\n    bs = PPSplineF64(k=4, t=t, c=None)\n    bs.csolve(tau, val, 0, 0, False)  # values solve spline\n    result = bs.c\n    expected = np.array([0.0, -1.11111111111111, 3.11111111111, 2.0], dtype=object)\n    for i, res in enumerate(result):\n        assert abs(expected[i] - res) < 1e-7\n\n\ndef test_csolve_lsq() -> None:\n    t = [0, 0, 0, 0, 4, 4, 4, 4]\n    tau = np.array([0, 1, 2, 3, 4])\n    val = np.array([0, 0, 1.5, 2, 2])\n    bs = PPSplineF64(k=4, t=t)\n    bs.csolve(tau, val, 0, 0, allow_lsq=True)  # values solve spline\n    result = bs.c\n    expected = np.array([-0.042857, -0.7730158, 3.44920634, 1.9571428], dtype=object)\n    for i, res in enumerate(result):\n        assert abs(expected[i] - res) < 1e-5\n\n\n@pytest.mark.parametrize(\n    (\"tau\", \"val\", \"allow\"),\n    [\n        ([0, 1, 2, 3], [0, 0, 2, 2, 5], False),\n        ([0, 1, 2, 3, 5], [0, 0, 2, 2], False),\n        ([0, 1, 2, 3], [0, 0, 2, 2, 5], True),\n    ],\n)\ndef test_csolve_raises(tau, val, allow) -> None:\n    t = [0, 0, 0, 0, 4, 4, 4, 4]\n    tau = np.array(tau)\n    val = np.array(val)\n    bs = PPSplineF64(k=4, t=t)\n    with pytest.raises(ValueError):\n        bs.csolve(tau, val, 0, 0, allow_lsq=allow)\n\n\ndef test_copy() -> None:\n    bs = PPSplineF64(k=2, t=[1, 1, 2, 3, 3], c=[1, 2, 3])\n    bsc = copy.copy(bs)\n    assert id(bs) != id(bsc)\n\n\ndef test_spline_equality_type() -> None:\n    spline = PPSplineF64(k=1, t=[1, 2])\n    assert spline != \"bad\"\n\n    spline2 = PPSplineF64(k=1, t=[1, 2, 3])\n    assert spline != spline2\n\n    spline3 = PPSplineF64(k=1, t=[1, 3, 5])\n    assert spline2 != spline3\n\n    spline4 = PPSplineF64(k=2, t=[1, 3, 5])\n    assert spline3 != spline4\n\n    spline5 = PPSplineF64(k=2, t=[1, 3, 5])\n    assert spline4 == spline5\n\n    spline6 = PPSplineF64(k=2, t=[1, 1, 3, 5, 5], c=[1, 2, 3])\n    spline7 = PPSplineF64(k=2, t=[1, 1, 3, 5, 5], c=[1, 2, 3])\n    assert spline6 == spline7\n\n\n@pytest.mark.parametrize(\n    (\"klass\", \"order\"),\n    [\n        (PPSplineF64, 0),\n        (PPSplineDual, 1),\n    ],\n)\ndef test_dual_AD(klass, order) -> None:\n    sp = klass(\n        t=[0, 0, 0, 0, 1, 3, 4, 4, 4, 4],\n        k=4,\n    )\n    y = [set_order_convert(_, order, []) for _ in [0, 0, 0, 2, 2, 0]]\n    sp.csolve([0, 0, 1, 3, 4, 4], y, 2, 2, False)\n    analytic_deriv = sp.ppdnev_single(3.5, 1)\n    dual_deriv = gradient(sp.ppev_single_dual(Dual(3.5, [\"x\"], [2.0])))[0]\n    assert abs(dual_deriv - 2.0 * analytic_deriv) < 1e-9\n\n\n@pytest.mark.parametrize(\n    (\"klass\", \"order\"),\n    [\n        (PPSplineF64, 0),\n        (PPSplineDual2, 2),\n    ],\n)\ndef test_dual2_AD(klass, order) -> None:\n    sp = klass(\n        t=[0, 0, 0, 0, 1, 3, 4, 4, 4, 4],\n        k=4,\n    )\n    y = [set_order_convert(_, order, []) for _ in [0, 0, 0, 2, 2, 0]]\n    sp.csolve([0, 0, 1, 3, 4, 4], y, 2, 2, False)\n    analytic_deriv = sp.ppdnev_single(3.5, 1)\n    dual_deriv = gradient(sp.ppev_single_dual2(Dual2(3.5, [\"x\"], [3.0], [])))[0]\n    assert abs(dual_deriv - 3.0 * analytic_deriv) < 1e-9\n\n    analytic_deriv2 = sp.ppdnev_single(3.5, 2)\n    dual_deriv2 = gradient(sp.ppev_single_dual2(Dual2(3.5, [\"x\"], [3.0], [])), order=2)[0, 0]\n    assert abs(dual_deriv2 - 9.0 * analytic_deriv2) < 1e-9\n\n    dual_deriv_x = gradient(\n        sp.ppev_single_dual2(Dual2(3.5, [\"x1\", \"x2\"], [3.0, 1.5], [1, 1, 1, 1])),\n        order=2,\n    )[0, 1]\n    analytic_deriv_x = analytic_deriv2 * 3.0 * 1.5 + analytic_deriv * 2.0\n    assert abs(dual_deriv_x - analytic_deriv_x) < 1e-9\n\n\ndef test_dual_AD_raises() -> None:\n    sp = PPSplineDual(\n        t=[0, 0, 0, 0, 1, 3, 4, 4, 4, 4],\n        k=4,\n    )\n    _0 = Dual(0.0, [], [])\n    y0, y1 = Dual(0.0, [\"y0\"], []), Dual(0.0, [\"y1\"], [])\n    y2, y3 = Dual(2.0, [\"y2\"], []), Dual(2.0, [\"y3\"], [])\n    sp.csolve([0, 0, 1, 3, 4, 4], [_0, y0, y1, y2, y3, _0], 2, 2, False)\n    with pytest.raises(TypeError, match=\"Cannot index with type `Dual2`\"):\n        sp.ppev_single_dual2(Dual2(3.5, [\"x\"], [], []))\n\n    with pytest.raises(TypeError, match=\"Cannot mix `Dual2` and `Dual` types\"):\n        sp.ppev_single_dual(Dual2(3.5, [\"x\"], [], []))\n\n\ndef test_dual2_AD_raises() -> None:\n    sp = PPSplineDual2(\n        t=[0, 0, 0, 0, 1, 3, 4, 4, 4, 4],\n        k=4,\n    )\n    _0 = Dual2(0.0, [], [], [])\n    y0, y1 = Dual2(0.0, [\"y0\"], [], []), Dual2(0.0, [\"y1\"], [], [])\n    y2, y3 = Dual2(2.0, [\"y2\"], [], []), Dual2(2.0, [\"y3\"], [], [])\n    sp.csolve([0, 0, 1, 3, 4, 4], [_0, y0, y1, y2, y3, _0], 2, 2, False)\n    with pytest.raises(TypeError, match=\"Cannot index with type `Dual`\"):\n        sp.ppev_single_dual(Dual(3.5, [\"x\"], []))\n\n    with pytest.raises(TypeError, match=\"Cannot mix `Dual2` and `Dual` types\"):\n        sp.ppev_single_dual2(Dual(3.5, [\"x\"], []))\n\n\ndef test_dual_float_raises() -> None:\n    sp = PPSplineDual(\n        t=[0, 0, 0, 0, 1, 3, 4, 4, 4, 4],\n        k=4,\n    )\n    _0 = Dual(0.0, [], [])\n    y0, y1 = Dual(0.0, [\"y0\"], []), Dual(0.0, [\"y1\"], [])\n    y2, y3 = Dual(2.0, [\"y2\"], []), Dual(2.0, [\"y3\"], [])\n    with pytest.raises(TypeError, match=\"argument 'y': 'float' object is not an instance of 'Dua\"):\n        sp.csolve([0, 0, 1, 3, 4, 4], [0.0, y0, y1, y2, y3, _0], 2, 2, False)\n\n\ndef test_bsplmatrix() -> None:\n    t = [1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4]\n    spline = PPSplineF64(k=4, t=t)\n    tau = np.array([1.1, 1.3, 1.9, 2.2, 2.5, 3.1, 3.5, 3.9])\n    matrix = spline.bsplmatrix(tau, 0, 0)\n    assert matrix.shape == (8, 8)\n\n\ndef test_json_round_trip() -> None:\n    t = [0, 0, 0, 0, 4, 4, 4, 4]\n    tau = np.array([0, 1, 3, 4])\n    val = np.array([0, 0, 2, 2])\n    bs = PPSplineF64(k=4, t=t, c=None)\n    bs.csolve(tau, val, 0, 0, False)  # values solve spline\n    result = bs.to_json()\n    obj = from_json(result)\n    assert bs == obj\n\n    # test unsolved\n    t = [0, 0, 0, 0, 4, 4, 4, 4]\n    bs = PPSplineF64(k=4, t=t, c=None)\n    result = bs.to_json()\n    obj = from_json(result)\n    assert bs == obj\n\n\n@pytest.mark.skip(reason=\"TODO: devise a post solve check for NaN.\")\ndef test_should_raise_bad_solve() -> None:\n    pps = PPSplineF64(k=4, t=[1, 1, 1, 1, 4, 4, 4, 4], c=None)\n    with pytest.raises(ValueError):\n        pps.csolve(np.array([0, 1, 3, 4]), np.array([0, 0, 2, 2]), 0, 0, False)\n\n\n@pytest.mark.parametrize(\n    (\"obj\", \"val\", \"exp\"),\n    [\n        (PPSplineF64, [0, 0, 2, 2], Dual),\n        (PPSplineDual, [Dual(0, [], []), Dual(0, [], []), Dual(2, [], []), Dual(2, [], [])], Dual),\n        (\n            PPSplineDual2,\n            [\n                Dual2(0, [], [], []),\n                Dual2(0, [], [], []),\n                Dual2(2, [], [], []),\n                Dual2(2, [], [], []),\n            ],\n            Dual2,\n        ),\n    ],\n)\ndef test_evaluate_with_Variable_x(obj, val, exp):\n    t = [0, 0, 0, 0, 4, 4, 4, 4]\n    tau = np.array([0, 1, 3, 4])\n    bs = obj(k=4, t=t, c=None)\n    bs.csolve(tau, val, 0, 0, False)  # values solve spline\n    x = Variable(1.5, [\"x\"])\n    result = evaluate(bs, x, 0)\n    assert abs(result - 0.437499999999999) < 1e-12\n    assert isinstance(result, exp)\n"
  },
  {
    "path": "python/tests/test_to_fix.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime as dt\n\nimport pytest\nfrom rateslib.dual import Dual\nfrom rateslib.volatility import FXDeltaVolSmile\n\n\ndef test_fxsmile_update_node():\n    # update node does not validate the AD order of the supplied value\n    # this should probably return a more helpful error message\n    fxs = FXDeltaVolSmile(\n        eval_date=dt(2000, 1, 1),\n        expiry=dt(2000, 12, 1),\n        nodes={0.1: 1, 0.2: 2},\n        delta_type=\"forward\",\n    )\n    fxs._set_ad_order(2)\n    with pytest.raises(TypeError):\n        fxs.update_node(0.1, Dual(2.0, [\"x\"], []))\n"
  },
  {
    "path": "robots.txt",
    "content": "User-agent: GPTBot\nDisallow: /\n\nUser-agent: CCBot\nDisallow: /\n\nUser-agent: ClaudeBot\nDisallow: /"
  },
  {
    "path": "rust/_README.txt",
    "content": "This 'src' directory contains the rust implementation of rateslib. The \"rateslibrs\" elements.\nSome configuration is available from the \"cargo.toml\" file.\n\nRust tests are contained in the \"tests\" subfolder and are executed with >$ cargo test.\n\nThis package has a library --lib and a binary called \"rateslibrs\" defined by toml.\nTo call otehr files in the bin use --bin scratch, for example,\n"
  },
  {
    "path": "rust/curves/curve.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::interpolation::utils::index_left;\nuse crate::curves::nodes::{Nodes, NodesTimestamp};\nuse crate::dual::{get_variable_tags, ADOrder, Dual, Dual2, Number};\nuse crate::scheduling::{Convention, DateRoll};\nuse chrono::NaiveDateTime;\nuse indexmap::IndexMap;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Default struct for storing datetime indexed discount factors (DFs).\n#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]\npub struct CurveDF<T: CurveInterpolation, U: DateRoll> {\n    pub(crate) nodes: NodesTimestamp,\n    pub(crate) interpolator: T,\n    pub(crate) id: String,\n    pub(crate) convention: Convention,\n    pub(crate) modifier: Modifier,\n    pub(crate) index_base: Option<f64>,\n    pub(crate) calendar: U,\n}\n\n/// A rule to adjust a non-business day to a business day.\n#[pyclass(module = \"rateslib.rs\", eq, eq_int, from_py_object)]\n#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]\npub enum Modifier {\n    /// Actual: date is unchanged, even if it is a non-business day.\n    Act,\n    /// Following: date is rolled to the next business day.\n    F,\n    /// Modified following: date is rolled to the next except if it changes month.\n    ModF,\n    /// Previous: date is rolled to the previous business day.\n    P,\n    /// Modified previous: date is rolled to the previous except if it changes month.\n    ModP,\n}\n\n/// Assigns methods for returning values from datetime indexed Curves.\npub trait CurveInterpolation {\n    /// Get a value from the curve's `Nodes` expressed in its input form, i.e. discount factor or value.\n    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number;\n\n    /// Get the left side node key index of the given datetime\n    fn node_index(&self, nodes: &NodesTimestamp, date_timestamp: i64) -> usize {\n        // let timestamp = date.and_utc().timestamp();\n        index_left(&nodes.keys(), &date_timestamp, None)\n    }\n}\n\nimpl<T: CurveInterpolation, U: DateRoll> CurveDF<T, U> {\n    pub fn try_new(\n        nodes: Nodes,\n        interpolator: T,\n        id: &str,\n        convention: Convention,\n        modifier: Modifier,\n        index_base: Option<f64>,\n        calendar: U,\n    ) -> Result<Self, PyErr> {\n        let mut nodes = NodesTimestamp::from(nodes);\n        nodes.sort_keys();\n        Ok(Self {\n            nodes,\n            interpolator,\n            id: id.to_string(),\n            convention,\n            modifier,\n            index_base,\n            calendar,\n        })\n    }\n\n    /// Get the `ADOrder` of the `Curve`.\n    pub fn ad(&self) -> ADOrder {\n        match self.nodes {\n            NodesTimestamp::F64(_) => ADOrder::Zero,\n            NodesTimestamp::Dual(_) => ADOrder::One,\n            NodesTimestamp::Dual2(_) => ADOrder::Two,\n        }\n    }\n\n    pub fn interpolated_value(&self, date: &NaiveDateTime) -> Number {\n        self.interpolator.interpolated_value(&self.nodes, date)\n    }\n\n    pub fn node_index(&self, date_timestamp: i64) -> usize {\n        self.interpolator.node_index(&self.nodes, date_timestamp)\n    }\n\n    pub fn set_ad_order(&mut self, ad: ADOrder) -> Result<(), PyErr> {\n        let vars: Vec<String> = get_variable_tags(&self.id, self.nodes.keys().len());\n        match (ad, &self.nodes) {\n            (ADOrder::Zero, NodesTimestamp::F64(_))\n            | (ADOrder::One, NodesTimestamp::Dual(_))\n            | (ADOrder::Two, NodesTimestamp::Dual2(_)) => {\n                // leave unchanged.\n                Ok(())\n            }\n            (ADOrder::One, NodesTimestamp::F64(i)) => {\n                // rebuild the derivatives\n                self.nodes = NodesTimestamp::Dual(IndexMap::from_iter(\n                    i.into_iter()\n                        .enumerate()\n                        .map(|(i, (k, v))| (*k, Dual::new(*v, vec![vars[i].clone()]))),\n                ));\n                Ok(())\n            }\n            (ADOrder::Two, NodesTimestamp::F64(i)) => {\n                // rebuild the derivatives\n                self.nodes = NodesTimestamp::Dual2(IndexMap::from_iter(\n                    i.into_iter()\n                        .enumerate()\n                        .map(|(i, (k, v))| (*k, Dual2::new(*v, vec![vars[i].clone()]))),\n                ));\n                Ok(())\n            }\n            (ADOrder::One, NodesTimestamp::Dual2(i)) => {\n                self.nodes = NodesTimestamp::Dual(IndexMap::from_iter(\n                    i.into_iter().map(|(k, v)| (*k, Dual::from(v))),\n                ));\n                Ok(())\n            }\n            (ADOrder::Zero, NodesTimestamp::Dual(i)) => {\n                // covert dual into f64\n                self.nodes = NodesTimestamp::F64(IndexMap::from_iter(\n                    i.into_iter().map(|(k, v)| (*k, f64::from(v))),\n                ));\n                Ok(())\n            }\n            (ADOrder::Zero, NodesTimestamp::Dual2(i)) => {\n                // covert dual into f64\n                self.nodes = NodesTimestamp::F64(IndexMap::from_iter(\n                    i.into_iter().map(|(k, v)| (*k, f64::from(v))),\n                ));\n                Ok(())\n            }\n            (ADOrder::Two, NodesTimestamp::Dual(i)) => {\n                // rebuild derivatives\n                self.nodes = NodesTimestamp::Dual2(IndexMap::from_iter(\n                    i.into_iter().map(|(k, v)| (*k, Dual2::from(v))),\n                ));\n                Ok(())\n            }\n        }\n    }\n\n    pub fn index_value(&self, date: &NaiveDateTime) -> Result<Number, PyErr> {\n        match self.index_base {\n            None => Err(PyValueError::new_err(\"Can only calculate `index_value` for a Curve which has been initialised with `index_base`.\")),\n            Some(ib) => {\n                if date.and_utc().timestamp() < self.nodes.first_key() {\n                    Ok(Number::F64(0.0))\n                } else {\n                    Ok(Number::F64(ib) / self.interpolated_value(date))\n                }\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::curves::LogLinearInterpolator;\n    use crate::scheduling::{ndt, NamedCal};\n    use indexmap::IndexMap;\n\n    fn curve_fixture() -> CurveDF<LogLinearInterpolator, NamedCal> {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        let interpolator = LogLinearInterpolator::new();\n        let convention = Convention::Act360;\n        let modifier = Modifier::ModF;\n        let cal = NamedCal::try_new(\"all\").unwrap();\n        CurveDF::try_new(nodes, interpolator, \"crv\", convention, modifier, None, cal).unwrap()\n    }\n\n    fn index_curve_fixture() -> CurveDF<LogLinearInterpolator, NamedCal> {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        let interpolator = LogLinearInterpolator::new();\n        let convention = Convention::Act360;\n        let modifier = Modifier::ModF;\n        let cal = NamedCal::try_new(\"all\").unwrap();\n        CurveDF::try_new(\n            nodes,\n            interpolator,\n            \"crv\",\n            convention,\n            modifier,\n            Some(100.0),\n            cal,\n        )\n        .unwrap()\n    }\n\n    fn curve_dual_fixture() -> CurveDF<LogLinearInterpolator, NamedCal> {\n        let nodes = Nodes::Dual(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), Dual::new(1.0, vec![\"x\".to_string()])),\n            (ndt(2001, 1, 1), Dual::new(0.99, vec![\"y\".to_string()])),\n            (ndt(2002, 1, 1), Dual::new(0.98, vec![\"z\".to_string()])),\n        ]));\n        let interpolator = LogLinearInterpolator::new();\n        let convention = Convention::Act360;\n        let modifier = Modifier::ModF;\n        let cal = NamedCal::try_new(\"all\").unwrap();\n        CurveDF::try_new(nodes, interpolator, \"crv\", convention, modifier, None, cal).unwrap()\n    }\n\n    #[test]\n    fn test_get_index() {\n        let c = curve_fixture();\n        let result = c.node_index(ndt(2001, 7, 30).and_utc().timestamp());\n        assert_eq!(result, 1_usize)\n    }\n\n    #[test]\n    fn test_get_value() {\n        let c = curve_fixture();\n        let result = c.interpolated_value(&ndt(2000, 7, 1));\n        assert_eq!(result, Number::F64(0.9950147597711371))\n    }\n\n    fn nodes_timestamp_fixture() -> NodesTimestamp {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        NodesTimestamp::from(nodes)\n    }\n\n    #[test]\n    fn test_log_linear() {\n        let nts = nodes_timestamp_fixture();\n        let ll = LogLinearInterpolator::new();\n        let result = ll.interpolated_value(&nts, &ndt(2000, 7, 1));\n        // expected = exp(0 + (182 / 366) * (ln(0.99) - ln(1.0)) = 0.995015\n        assert_eq!(result, Number::F64(0.9950147597711371));\n    }\n\n    #[test]\n    fn test_set_order() {\n        // converts the input f64 nodes to dual with ordered variables tagged by id\n        let mut curve = curve_fixture();\n        let _ = curve.set_ad_order(ADOrder::One);\n        let result = curve.interpolated_value(&ndt(2001, 1, 1));\n        assert_eq!(\n            result,\n            Number::Dual(Dual::new(0.99, vec![\"crv1\".to_string()]))\n        );\n    }\n\n    #[test]\n    fn test_set_order_no_change() {\n        // asserts no change in values when AD order remains same\n        let mut curve = curve_dual_fixture();\n        let _ = curve.set_ad_order(ADOrder::One);\n        let result = curve.interpolated_value(&ndt(2001, 1, 1));\n        assert_eq!(result, Number::Dual(Dual::new(0.99, vec![\"y\".to_string()])));\n    }\n\n    #[test]\n    fn test_set_order_vars_remain() {\n        // asserts no change in variables transitioning ADone to ADtwo\n        let mut curve = curve_dual_fixture();\n        let _ = curve.set_ad_order(ADOrder::Two);\n        let result = curve.interpolated_value(&ndt(2001, 1, 1));\n        assert_eq!(\n            result,\n            Number::Dual2(Dual2::new(0.99, vec![\"y\".to_string()]))\n        );\n    }\n\n    #[test]\n    fn test_index_value() {\n        let index_curve = index_curve_fixture();\n        let result = index_curve.index_value(&ndt(2001, 1, 1)).unwrap();\n        assert_eq!(result, Number::F64(100.0 / 0.99))\n    }\n\n    #[test]\n    fn test_index_value_prior_to_first() {\n        let index_curve = index_curve_fixture();\n        let result = index_curve.index_value(&ndt(1980, 1, 1)).unwrap();\n        assert_eq!(result, Number::F64(0.0))\n    }\n}\n"
  },
  {
    "path": "rust/curves/curve_py.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper module to export Rust curve data types to Python using pyo3 bindings.\n\nuse crate::curves::nodes::{Nodes, NodesTimestamp};\nuse crate::curves::{\n    CurveDF, CurveInterpolation, FlatBackwardInterpolator, FlatForwardInterpolator,\n    LinearInterpolator, LinearZeroRateInterpolator, LogLinearInterpolator, Modifier,\n    NullInterpolator,\n};\nuse crate::dual::{get_variable_tags, set_order, ADOrder, Dual, Dual2, Number};\nuse crate::json::json_py::DeserializedObj;\nuse crate::json::JSON;\nuse crate::scheduling::{Calendar, Convention};\nuse bincode::config::legacy;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse chrono::NaiveDateTime;\nuse indexmap::IndexMap;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::types::PyBytes;\nuse serde::{Deserialize, Serialize};\n\n/// Interpolation\n#[derive(Debug, Clone, PartialEq, FromPyObject, Deserialize, Serialize, IntoPyObject)]\npub(crate) enum CurveInterpolator {\n    LogLinear(LogLinearInterpolator),\n    Linear(LinearInterpolator),\n    LinearZeroRate(LinearZeroRateInterpolator),\n    FlatForward(FlatForwardInterpolator),\n    FlatBackward(FlatBackwardInterpolator),\n    Null(NullInterpolator),\n}\n\n// // removed upgrading to pyo3 0.23, see https://pyo3.rs/v0.23.0/migration#intopyobject-and-intopyobjectref-derive-macros\n// impl IntoPy<PyObject> for CurveInterpolator {\n//     fn into_py(self, py: Python<'_>) -> PyObject {\n//         macro_rules! into_py {\n//             ($obj: ident) => {\n//                 Py::new(py, $obj).unwrap().to_object(py)\n//             };\n//         }\n//\n//         match self {\n//             CurveInterpolator::LogLinear(i) => into_py!(i),\n//             CurveInterpolator::Linear(i) => into_py!(i),\n//             CurveInterpolator::LinearZeroRate(i) => into_py!(i),\n//             CurveInterpolator::FlatForward(i) => into_py!(i),\n//             CurveInterpolator::FlatBackward(i) => into_py!(i),\n//             CurveInterpolator::Null(i) => into_py!(i),\n//         }\n//     }\n// }\n\nimpl CurveInterpolation for CurveInterpolator {\n    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number {\n        match self {\n            CurveInterpolator::LogLinear(i) => i.interpolated_value(nodes, date),\n            CurveInterpolator::Linear(i) => i.interpolated_value(nodes, date),\n            CurveInterpolator::LinearZeroRate(i) => i.interpolated_value(nodes, date),\n            CurveInterpolator::FlatBackward(i) => i.interpolated_value(nodes, date),\n            CurveInterpolator::FlatForward(i) => i.interpolated_value(nodes, date),\n            CurveInterpolator::Null(i) => i.interpolated_value(nodes, date),\n        }\n    }\n}\n\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Deserialize, Serialize)]\npub(crate) struct Curve {\n    inner: CurveDF<CurveInterpolator, Calendar>,\n}\n\n#[pymethods]\nimpl Curve {\n    #[new]\n    #[pyo3(signature = (nodes, interpolator, ad, id, convention, modifier, calendar, index_base=None))]\n    fn new_py(\n        nodes: IndexMap<NaiveDateTime, Number>,\n        interpolator: CurveInterpolator,\n        ad: ADOrder,\n        id: String,\n        convention: Convention,\n        modifier: Modifier,\n        calendar: Calendar,\n        index_base: Option<f64>,\n    ) -> PyResult<Self> {\n        let nodes_ = nodes_into_order(nodes, ad, &id);\n        let inner = CurveDF::try_new(\n            nodes_,\n            interpolator,\n            &id,\n            convention,\n            modifier,\n            index_base,\n            calendar,\n        )?;\n        Ok(Self { inner })\n    }\n\n    #[getter]\n    fn id(&self) -> String {\n        self.inner.id.clone()\n    }\n\n    #[getter]\n    fn nodes(&self) -> IndexMap<NaiveDateTime, Number> {\n        let nodes = Nodes::from(self.inner.nodes.clone());\n        match nodes {\n            Nodes::F64(i) => IndexMap::from_iter(i.into_iter().map(|(k, v)| (k, Number::F64(v)))),\n            Nodes::Dual(i) => IndexMap::from_iter(i.into_iter().map(|(k, v)| (k, Number::Dual(v)))),\n            Nodes::Dual2(i) => {\n                IndexMap::from_iter(i.into_iter().map(|(k, v)| (k, Number::Dual2(v))))\n            }\n        }\n    }\n\n    #[getter]\n    fn ad(&self) -> ADOrder {\n        self.inner.ad()\n    }\n\n    #[getter]\n    fn interpolation(&self) -> String {\n        match self.inner.interpolator {\n            CurveInterpolator::Linear(_) => \"linear\".to_string(),\n            CurveInterpolator::LogLinear(_) => \"log_linear\".to_string(),\n            CurveInterpolator::LinearZeroRate(_) => \"linear_zero_rate\".to_string(),\n            CurveInterpolator::FlatForward(_) => \"flat_forward\".to_string(),\n            CurveInterpolator::FlatBackward(_) => \"flat_backward\".to_string(),\n            CurveInterpolator::Null(_) => \"null\".to_string(),\n        }\n    }\n\n    #[getter]\n    fn convention(&self) -> Convention {\n        self.inner.convention\n    }\n\n    #[getter]\n    fn modifier(&self) -> Modifier {\n        self.inner.modifier\n    }\n\n    #[pyo3(name = \"index_value\")]\n    fn index_value_py(&self, date: NaiveDateTime) -> PyResult<Number> {\n        self.inner.index_value(&date)\n    }\n\n    fn set_ad_order(&mut self, ad: ADOrder) -> PyResult<()> {\n        let _ = self.inner.set_ad_order(ad);\n        Ok(())\n    }\n\n    fn __getitem__(&self, date: NaiveDateTime) -> Number {\n        self.inner.interpolated_value(&date)\n    }\n\n    fn __eq__(&self, other: Curve) -> bool {\n        self.inner.eq(&other.inner)\n    }\n\n    // JSON\n    /// Create a JSON string representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::Curve(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `Curve` to JSON.\",\n            )),\n        }\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__(\n        &self,\n    ) -> PyResult<(\n        IndexMap<NaiveDateTime, Number>,\n        CurveInterpolator,\n        ADOrder,\n        String,\n        Convention,\n        Modifier,\n        Calendar,\n        Option<f64>,\n    )> {\n        Ok((\n            self.inner.nodes.index_map(),\n            self.inner.interpolator.clone(),\n            self.inner.ad(),\n            self.inner.id.clone(),\n            self.inner.convention,\n            self.inner.modifier,\n            self.inner.calendar.clone(),\n            self.inner.index_base,\n        ))\n    }\n}\n\n// /// Convert the `nodes`of a `Curve` from a `HashMap` input form into the local data model.\n// /// Will upcast f64 values to a new ADOrder adding curve variable tags by id.\n// fn hashmap_into_nodes_timestamp(\n//     h: HashMap<NaiveDateTime, Number>,\n//     ad: ADOrder,\n//     id: &str,\n// ) -> NodesTimestamp {\n//     let vars: Vec<String> = get_variable_tags(id, h.keys().len());\n//\n//     /// First convert to IndexMap and sort key order.\n//     // let mut im: IndexMap<NaiveDateTime, Number> = IndexMap::from_iter(h.into_iter());\n//     let mut im: IndexMap<i64, Number> = IndexMap::from_iter(h.into_iter().map(|(k,v)| (k.and_utc().timestamp(), v)));\n//     im.sort_keys();\n//\n//     match ad {\n//         ADOrder::Zero => { NodesTimestamp::F64(IndexMap::from_iter(im.into_iter().map(|(k,v)| (k, f64::from(v))))) }\n//         ADOrder::One => { NodesTimestamp::Dual(IndexMap::from_iter(im.into_iter().enumerate().map(|(i,(k,v))| (k, Dual::from(set_order_with_conversion(v, ad, vec![vars[i].clone()])))))) }\n//         ADOrder::Two => { NodesTimestamp::Dual2(IndexMap::from_iter(im.into_iter().enumerate().map(|(i,(k,v))| (k, Dual2::from(set_order_with_conversion(v, ad, vec![vars[i].clone()])))))) }\n//     }\n// }\n\nfn nodes_into_order(mut nodes: IndexMap<NaiveDateTime, Number>, ad: ADOrder, id: &str) -> Nodes {\n    let vars: Vec<String> = get_variable_tags(id, nodes.keys().len());\n    nodes.sort_keys();\n    match ad {\n        ADOrder::Zero => Nodes::F64(IndexMap::from_iter(\n            nodes.into_iter().map(|(k, v)| (k, f64::from(v))),\n        )),\n        ADOrder::One => {\n            Nodes::Dual(IndexMap::from_iter(nodes.into_iter().enumerate().map(\n                |(i, (k, v))| (k, Dual::from(set_order(v, ad, vec![vars[i].clone()]))),\n            )))\n        }\n        ADOrder::Two => {\n            Nodes::Dual2(IndexMap::from_iter(nodes.into_iter().enumerate().map(\n                |(i, (k, v))| (k, Dual2::from(set_order(v, ad, vec![vars[i].clone()]))),\n            )))\n        }\n    }\n}\n\n#[pymethods]\nimpl Modifier {\n    // Pickling\n    #[new]\n    fn new_py(ad: u8) -> PyResult<Modifier> {\n        match ad {\n            0_u8 => Ok(Modifier::Act),\n            1_u8 => Ok(Modifier::F),\n            2_u8 => Ok(Modifier::ModF),\n            3_u8 => Ok(Modifier::P),\n            4_u8 => Ok(Modifier::ModP),\n            _ => Err(PyValueError::new_err(\n                \"unreachable code on Convention pickle.\",\n            )),\n        }\n    }\n    pub fn __getnewargs__<'py>(&self) -> PyResult<(u8,)> {\n        match self {\n            Modifier::Act => Ok((0_u8,)),\n            Modifier::F => Ok((1_u8,)),\n            Modifier::ModF => Ok((2_u8,)),\n            Modifier::P => Ok((3_u8,)),\n            Modifier::ModP => Ok((4_u8,)),\n        }\n    }\n}\n\n#[pyfunction]\npub(crate) fn _get_modifier_str(modifier: Modifier) -> String {\n    match modifier {\n        Modifier::F => \"F\".to_string(),\n        Modifier::ModF => \"MF\".to_string(),\n        Modifier::P => \"P\".to_string(),\n        Modifier::ModP => \"MP\".to_string(),\n        Modifier::Act => \"NONE\".to_string(),\n    }\n}\n"
  },
  {
    "path": "rust/curves/interpolation/interpolation_py.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::interpolation::utils::index_left;\nuse pyo3::pyfunction;\n\nmacro_rules! create_interface {\n    ($name: ident, $type: ident) => {\n        #[pyfunction]\n        #[pyo3(signature = (list_input, value, left_count=None))]\n        pub fn $name(list_input: Vec<$type>, value: $type, left_count: Option<usize>) -> usize {\n            index_left(&list_input[..], &value, left_count)\n        }\n    };\n}\n\ncreate_interface!(index_left_f64, f64);\n"
  },
  {
    "path": "rust/curves/interpolation/intp_flat_backward.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::nodes::NodesTimestamp;\nuse crate::curves::CurveInterpolation;\nuse crate::dual::Number;\nuse bincode::config::legacy;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse chrono::NaiveDateTime;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyBytes, PyTuple};\nuse pyo3::{pyclass, pymethods, Bound, PyResult, Python};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Define flat backward interpolation of nodes.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]\npub struct FlatBackwardInterpolator {}\n\n#[pymethods]\nimpl FlatBackwardInterpolator {\n    #[new]\n    pub fn new() -> Self {\n        FlatBackwardInterpolator {}\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {\n        Ok(PyTuple::empty(py))\n    }\n}\n\nimpl CurveInterpolation for FlatBackwardInterpolator {\n    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number {\n        let x = date.and_utc().timestamp();\n        let index = self.node_index(nodes, x);\n        macro_rules! interp {\n            ($Variant: ident, $indexmap: expr) => {{\n                let (x1, y1) = $indexmap.get_index(index).unwrap();\n                let (_x2, y2) = $indexmap.get_index(index + 1_usize).unwrap();\n                if x <= *x1 {\n                    Number::$Variant(y1.clone())\n                } else {\n                    Number::$Variant(y2.clone())\n                }\n            }};\n        }\n        match nodes {\n            NodesTimestamp::F64(m) => interp!(F64, m),\n            NodesTimestamp::Dual(m) => interp!(Dual, m),\n            NodesTimestamp::Dual2(m) => interp!(Dual2, m),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::curves::nodes::Nodes;\n    use crate::scheduling::ndt;\n    use indexmap::IndexMap;\n\n    fn nodes_timestamp_fixture() -> NodesTimestamp {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        NodesTimestamp::from(nodes)\n    }\n\n    #[test]\n    fn test_flat_backward() {\n        let nts = nodes_timestamp_fixture();\n        let li = FlatBackwardInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(2000, 7, 1));\n        assert_eq!(result, Number::F64(0.99));\n    }\n\n    #[test]\n    fn test_flat_backward_left_out_of_bounds() {\n        let nts = nodes_timestamp_fixture();\n        let li = FlatBackwardInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(1999, 7, 1));\n        assert_eq!(result, Number::F64(1.0));\n    }\n\n    #[test]\n    fn test_flat_backward_right_out_of_bounds() {\n        let nts = nodes_timestamp_fixture();\n        let li = FlatBackwardInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(2005, 7, 1));\n        assert_eq!(result, Number::F64(0.98));\n    }\n\n    #[test]\n    fn test_flat_backward_equals_interval_value() {\n        let nts = nodes_timestamp_fixture();\n        let li = FlatBackwardInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(2001, 1, 1));\n        assert_eq!(result, Number::F64(0.99));\n    }\n}\n"
  },
  {
    "path": "rust/curves/interpolation/intp_flat_forward.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::nodes::NodesTimestamp;\nuse crate::curves::CurveInterpolation;\nuse crate::dual::Number;\nuse bincode::config::legacy;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse chrono::NaiveDateTime;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyBytes, PyTuple};\nuse pyo3::{pyclass, pymethods, Bound, PyResult, Python};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Define flat forward interpolation of nodes.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]\npub struct FlatForwardInterpolator {}\n\n#[pymethods]\nimpl FlatForwardInterpolator {\n    #[new]\n    pub fn new() -> Self {\n        FlatForwardInterpolator {}\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {\n        Ok(PyTuple::empty(py))\n    }\n}\n\nimpl CurveInterpolation for FlatForwardInterpolator {\n    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number {\n        let x = date.and_utc().timestamp();\n        let index = self.node_index(nodes, x);\n        macro_rules! interp {\n            ($Variant: ident, $indexmap: expr) => {{\n                let (_x1, y1) = $indexmap.get_index(index).unwrap();\n                let (x2, y2) = $indexmap.get_index(index + 1_usize).unwrap();\n                if x >= *x2 {\n                    Number::$Variant(y2.clone())\n                } else {\n                    Number::$Variant(y1.clone())\n                }\n            }};\n        }\n        match nodes {\n            NodesTimestamp::F64(m) => interp!(F64, m),\n            NodesTimestamp::Dual(m) => interp!(Dual, m),\n            NodesTimestamp::Dual2(m) => interp!(Dual2, m),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::curves::nodes::Nodes;\n    use crate::scheduling::ndt;\n    use indexmap::IndexMap;\n\n    fn nodes_timestamp_fixture() -> NodesTimestamp {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        NodesTimestamp::from(nodes)\n    }\n\n    #[test]\n    fn test_flat_forward() {\n        let nts = nodes_timestamp_fixture();\n        let li = FlatForwardInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(2000, 7, 1));\n        assert_eq!(result, Number::F64(1.0));\n    }\n\n    #[test]\n    fn test_flat_forward_left_out_of_bounds() {\n        let nts = nodes_timestamp_fixture();\n        let li = FlatForwardInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(1999, 7, 1));\n        assert_eq!(result, Number::F64(1.0));\n    }\n\n    #[test]\n    fn test_flat_forward_right_out_of_bounds() {\n        let nts = nodes_timestamp_fixture();\n        let li = FlatForwardInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(2005, 7, 1));\n        assert_eq!(result, Number::F64(0.98));\n    }\n\n    #[test]\n    fn test_flat_forward_equals_interval_value() {\n        let nts = nodes_timestamp_fixture();\n        let li = FlatForwardInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(2001, 1, 1));\n        assert_eq!(result, Number::F64(0.99));\n    }\n}\n"
  },
  {
    "path": "rust/curves/interpolation/intp_linear.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::interpolation::utils::linear_interp;\nuse crate::curves::nodes::NodesTimestamp;\nuse crate::curves::CurveInterpolation;\nuse crate::dual::Number;\nuse bincode::config::legacy;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse chrono::NaiveDateTime;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyBytes, PyTuple};\nuse pyo3::{pyclass, pymethods, Bound, PyResult, Python};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Define linear interpolation of nodes.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]\npub struct LinearInterpolator {}\n\n#[pymethods]\nimpl LinearInterpolator {\n    #[new]\n    pub fn new() -> Self {\n        LinearInterpolator {}\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {\n        Ok(PyTuple::empty(py))\n    }\n}\n\nimpl CurveInterpolation for LinearInterpolator {\n    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number {\n        let x = date.and_utc().timestamp();\n        let index = self.node_index(nodes, x);\n\n        macro_rules! interp {\n            ($Variant: ident, $indexmap: expr) => {{\n                let (x1, y1) = $indexmap.get_index(index).unwrap();\n                let (x2, y2) = $indexmap.get_index(index + 1_usize).unwrap();\n                Number::$Variant(linear_interp(*x1 as f64, y1, *x2 as f64, y2, x as f64))\n            }};\n        }\n        match nodes {\n            NodesTimestamp::F64(m) => interp!(F64, m),\n            NodesTimestamp::Dual(m) => interp!(Dual, m),\n            NodesTimestamp::Dual2(m) => interp!(Dual2, m),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::curves::nodes::Nodes;\n    use crate::scheduling::ndt;\n    use indexmap::IndexMap;\n\n    fn nodes_timestamp_fixture() -> NodesTimestamp {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        NodesTimestamp::from(nodes)\n    }\n\n    #[test]\n    fn test_linear() {\n        let nts = nodes_timestamp_fixture();\n        let li = LinearInterpolator::new();\n        let result = li.interpolated_value(&nts, &ndt(2000, 7, 1));\n        // expected = 1.0 + (182 / 366) * (0.99 - 1.0) = 0.995027\n        assert_eq!(result, Number::F64(0.9950273224043715));\n    }\n}\n"
  },
  {
    "path": "rust/curves/interpolation/intp_linear_zero_rate.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::interpolation::utils::linear_zero_interp;\nuse crate::curves::nodes::NodesTimestamp;\nuse crate::curves::CurveInterpolation;\nuse crate::dual::Number;\nuse bincode::config::legacy;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse chrono::NaiveDateTime;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyBytes, PyTuple};\nuse pyo3::{pyclass, pymethods, Bound, PyResult, Python};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Define linear zero rate interpolation of nodes.\n///\n/// This interpolation can only be used with discount factors node values.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct LinearZeroRateInterpolator {}\n\n#[pymethods]\n\nimpl LinearZeroRateInterpolator {\n    #[new]\n    pub fn new() -> Self {\n        LinearZeroRateInterpolator {}\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {\n        Ok(PyTuple::empty(py))\n    }\n}\n\nimpl CurveInterpolation for LinearZeroRateInterpolator {\n    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number {\n        let x = date.and_utc().timestamp();\n        let index = self.node_index(nodes, x);\n\n        macro_rules! interp {\n            ($Variant: ident, $indexmap: expr) => {{\n                let (x0, _) = $indexmap.get_index(0_usize).unwrap();\n                let (x2, y2) = $indexmap.get_index(index + 1_usize).unwrap();\n                let (x1, y1) = $indexmap.get_index(index).unwrap();\n                Number::$Variant(linear_zero_interp(\n                    *x0 as f64, *x1 as f64, y1, *x2 as f64, y2, x as f64,\n                ))\n            }};\n        }\n        match nodes {\n            NodesTimestamp::F64(m) => interp!(F64, m),\n            NodesTimestamp::Dual(m) => interp!(Dual, m),\n            NodesTimestamp::Dual2(m) => interp!(Dual2, m),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::curves::nodes::Nodes;\n    use crate::scheduling::ndt;\n    use indexmap::IndexMap;\n\n    fn nodes_timestamp_fixture() -> NodesTimestamp {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        NodesTimestamp::from(nodes)\n    }\n\n    #[test]\n    fn test_log_linear() {\n        let nts = nodes_timestamp_fixture();\n        let ll = LinearZeroRateInterpolator::new();\n        let result = ll.interpolated_value(&nts, &ndt(2001, 7, 1));\n        // r1 = -ln(0.99) / 366, r2 = -ln(0.98) / 731\n        // r = r1 + (181 / 365) * (r2 - r1)\n        // expected = exp(-r * 547) r1 = 0.985044328\n        assert_eq!(result, Number::F64(0.9850443279738612));\n    }\n\n    #[test]\n    fn test_log_linear_first_period() {\n        let nts = nodes_timestamp_fixture();\n        let ll = LinearZeroRateInterpolator::new();\n        let result = ll.interpolated_value(&nts, &ndt(2000, 7, 1));\n        // r1 = r2, r2 = -ln(0.99) / 366\n        // r = r1\n        // expected = exp(-r * 182) = 0.99501476\n        assert_eq!(result, Number::F64(0.9950147597711371));\n    }\n}\n"
  },
  {
    "path": "rust/curves/interpolation/intp_log_cubic.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::interpolation::utils::log_linear_interp;\nuse crate::curves::nodes::NodesTimestamp;\nuse crate::curves::CurveInterpolation;\nuse crate::dual::DualsOrF64;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse bincode::config::legacy;\nuse chrono::NaiveDateTime;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyBytes, PyTuple};\nuse pyo3::{pyclass, pymethods, Bound, PyResult, Python};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Define log-linear interpolation of nodes.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct LogCubicInterpolator<T: NumberMapping> {\n    spline: T\n}\n\n#[pymethods]\nimpl<T> LogCubicInterpolator\nwhere          T: PartialOrd + Signed + Clone + Sum + Zero,\n   for<'a> &'a T: Sub<&'a T, Output = T>,\n   for<'a> &'a f64: Mul<&'a T, Output = T>,\n{\n    #[new]\n    pub fn new(t: Vec<f64>, c: Option<Vec<T>>) -> Self {\n        let spline: PPSpline<T> = PPSpline.new(3_usize, t, c);\n        LogCubicInterpolator {\n            spline\n        }\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new_bound(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<(Vec<f64>, Option<Vec<T>>)> {\n        Ok((self.t.clone(), ))\n    }\n}\n\nimpl CurveInterpolation for LogLinearInterpolator {\n    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> DualsOrF64 {\n        let x = date.and_utc().timestamp();\n        let index = self.node_index(nodes, x);\n\n        macro_rules! interp {\n            ($Variant: ident, $indexmap: expr) => {{\n                let (x1, y1) = $indexmap.get_index(index).unwrap();\n                let (x2, y2) = $indexmap.get_index(index + 1_usize).unwrap();\n                DualsOrF64::$Variant(log_linear_interp(*x1 as f64, y1, *x2 as f64, y2, x as f64))\n            }};\n        }\n        match nodes {\n            NodesTimestamp::F64(m) => interp!(F64, m),\n            NodesTimestamp::Dual(m) => interp!(Dual, m),\n            NodesTimestamp::Dual2(m) => interp!(Dual2, m),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::ndt;\n    use crate::curves::nodes::Nodes;\n    use indexmap::IndexMap;\n\n    fn nodes_timestamp_fixture() -> NodesTimestamp {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        NodesTimestamp::from(nodes)\n    }\n\n    #[test]\n    fn test_log_linear() {\n        let nts = nodes_timestamp_fixture();\n        let ll = LogLinearInterpolator::new();\n        let result = ll.interpolated_value(&nts, &ndt(2000, 7, 1));\n        // expected = exp(0 + (182 / 366) * (ln(0.99) - ln(1.0)) = 0.995015\n        assert_eq!(result, DualsOrF64::F64(0.9950147597711371));\n    }\n}\n"
  },
  {
    "path": "rust/curves/interpolation/intp_log_linear.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::interpolation::utils::log_linear_interp;\nuse crate::curves::nodes::NodesTimestamp;\nuse crate::curves::CurveInterpolation;\nuse crate::dual::Number;\nuse bincode::config::legacy;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse chrono::NaiveDateTime;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyBytes, PyTuple};\nuse pyo3::{pyclass, pymethods, Bound, PyResult, Python};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Define log-linear interpolation of nodes.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct LogLinearInterpolator {}\n\n#[pymethods]\nimpl LogLinearInterpolator {\n    #[new]\n    pub fn new() -> Self {\n        LogLinearInterpolator {}\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {\n        Ok(PyTuple::empty(py))\n    }\n}\n\nimpl CurveInterpolation for LogLinearInterpolator {\n    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number {\n        let x = date.and_utc().timestamp();\n        let index = self.node_index(nodes, x);\n\n        macro_rules! interp {\n            ($Variant: ident, $indexmap: expr) => {{\n                let (x1, y1) = $indexmap.get_index(index).unwrap();\n                let (x2, y2) = $indexmap.get_index(index + 1_usize).unwrap();\n                Number::$Variant(log_linear_interp(*x1 as f64, y1, *x2 as f64, y2, x as f64))\n            }};\n        }\n        match nodes {\n            NodesTimestamp::F64(m) => interp!(F64, m),\n            NodesTimestamp::Dual(m) => interp!(Dual, m),\n            NodesTimestamp::Dual2(m) => interp!(Dual2, m),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::curves::nodes::Nodes;\n    use crate::scheduling::ndt;\n    use indexmap::IndexMap;\n\n    fn nodes_timestamp_fixture() -> NodesTimestamp {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        NodesTimestamp::from(nodes)\n    }\n\n    #[test]\n    fn test_log_linear() {\n        let nts = nodes_timestamp_fixture();\n        let ll = LogLinearInterpolator::new();\n        let result = ll.interpolated_value(&nts, &ndt(2000, 7, 1));\n        // expected = exp(0 + (182 / 366) * (ln(0.99) - ln(1.0)) = 0.995015\n        assert_eq!(result, Number::F64(0.9950147597711371));\n    }\n}\n"
  },
  {
    "path": "rust/curves/interpolation/intp_null.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::nodes::NodesTimestamp;\nuse crate::curves::CurveInterpolation;\nuse crate::dual::Number;\nuse bincode::config::legacy;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse chrono::NaiveDateTime;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyBytes, PyTuple};\nuse pyo3::{pyclass, pymethods, Bound, PyResult, Python};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Define a null interpolation object.\n///\n/// This is used by PyO3 binding to indicate interpolation occurs in Python.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]\npub struct NullInterpolator {}\n\n#[pymethods]\nimpl NullInterpolator {\n    #[new]\n    pub fn new() -> Self {\n        NullInterpolator {}\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {\n        Ok(PyTuple::empty(py))\n    }\n}\n\nimpl CurveInterpolation for NullInterpolator {\n    fn interpolated_value(&self, _nodes: &NodesTimestamp, _date: &NaiveDateTime) -> Number {\n        panic!(\"NullInterpolator cannot be used to obtain interpolated values.\");\n        #[allow(unreachable_code)]\n        Number::F64(0.0)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::curves::nodes::Nodes;\n    use crate::scheduling::ndt;\n    use indexmap::IndexMap;\n\n    fn nodes_timestamp_fixture() -> NodesTimestamp {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        NodesTimestamp::from(nodes)\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_null_interpolation() {\n        let nts = nodes_timestamp_fixture();\n        let li = NullInterpolator::new();\n        li.interpolated_value(&nts, &ndt(2000, 7, 1));\n    }\n}\n"
  },
  {
    "path": "rust/curves/interpolation/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\npub(crate) mod interpolation_py;\n\npub(crate) mod intp_flat_backward;\npub(crate) mod intp_flat_forward;\npub(crate) mod intp_linear;\npub(crate) mod intp_linear_zero_rate;\npub(crate) mod intp_log_linear;\npub(crate) mod intp_null;\n\npub(crate) mod utils;\n"
  },
  {
    "path": "rust/curves/interpolation/utils.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::{MathFuncs, NumberOps};\nuse std::{\n    cmp::{PartialEq, PartialOrd},\n    ops::{Mul, Sub},\n};\n\n// pub(crate) fn linear_interp<T, U>(x1: &T, y1: &U, x2: &T, y2: &U, x: &T) -> U\n// where\n//     for<'a> &'a T: NumberOps<T>,\n//     for<'a> &'a U: NumberOps<U>,\n//     U: Mul<T, Output = U>,\n// {\n//     y1 + &((y2 - y1) * (&(x - x1) / &(x2 - x1)))\n// }\n\n/// Calculate the linear interpolation between two coordinates.\npub(crate) fn linear_interp<T>(x1: f64, y1: &T, x2: f64, y2: &T, x: f64) -> T\nwhere\n    for<'a> &'a T: NumberOps<T>,\n    T: Mul<f64, Output = T>,\n{\n    y1 + &((y2 - y1) * ((x - x1) / (x2 - x1)))\n}\n\n/// Calculate the log-linear interpolation between two coordinates.\npub(crate) fn log_linear_interp<T>(x1: f64, y1: &T, x2: f64, y2: &T, x: f64) -> T\nwhere\n    for<'a> &'a T: NumberOps<T>,\n    T: Mul<f64, Output = T> + MathFuncs,\n{\n    let (y1, y2) = (y1.log(), y2.log());\n    let y = linear_interp(x1, &y1, x2, &y2, x);\n    y.exp()\n}\n\n/// Calculate the linear zero rate interpolation between two coordinates.\npub(crate) fn linear_zero_interp<T>(x0: f64, x1: f64, y1: &T, x2: f64, y2: &T, x: f64) -> T\nwhere\n    for<'a> &'a T: NumberOps<T>,\n    T: Mul<f64, Output = T> + MathFuncs + Sub + Clone,\n{\n    let t1: f64 = x1 - x0;\n    let t2: f64 = x2 - x0;\n    let t: f64 = x - x0;\n    let r2: T = y2.log() * (-1_f64 / t2);\n    let r: T = if t1 == 0.0_f64 {\n        r2.clone() // Flat forward zero rate in first interval\n    } else {\n        let r1: T = y1.log() * (-1_f64 / t1);\n        &r1 + &((&r2 - &r1) * ((t - t1) / (t2 - t1)))\n    };\n    (r * -t).exp()\n}\n\n/// Calculate the left sided index for a given value in a sorted list.\n/// `left_count` is used recursively; it should always be entered as None intially.\n/// Examples\n/// --------\n/// If `list_input` is [1.2, 1.7, 1.9, 2.8];\n///\n/// - 0.5: returns 0 (extrapolated out of range)\n/// - 1.5: returns 0 (within first interval)\n/// - 1.7: returns 0 (closed right side of first interval)\n/// - 1.71: returns 1 (within second interval)\n/// - 2.8: returns 2 (closed right side of third interval)\n/// - 3.5: returns 2 (extrapolated out of range)\npub(crate) fn index_left<T>(list_input: &[T], value: &T, left_count: Option<usize>) -> usize\nwhere\n    for<'a> &'a T: PartialOrd + PartialEq,\n{\n    let lc = left_count.unwrap_or(0_usize);\n    let n = list_input.len();\n    match n {\n        1 => panic!(\"`index_left` designed for intervals. Cannot index sequence of length 1.\"),\n        2 => lc,\n        _ => {\n            let split = (n - 1_usize) / 2_usize; // this will take the floor of result\n            if n == 3 && value == &list_input[split] {\n                lc\n            } else if value <= &list_input[split] {\n                index_left(&list_input[..=split], value, Some(lc))\n            } else {\n                index_left(&list_input[split..], value, Some(lc + split))\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::dual::Dual;\n\n    #[test]\n    fn index_left_() {\n        let a = [1.2, 1.7, 1.9, 2.8];\n        assert_eq!(index_left(&a, &0.5, None), 0_usize);\n        assert_eq!(index_left(&a, &1.5, None), 0_usize);\n        assert_eq!(index_left(&a, &1.7, None), 0_usize);\n        assert_eq!(index_left(&a, &1.71, None), 1_usize);\n        assert_eq!(index_left(&a, &2.8, None), 2_usize);\n        assert_eq!(index_left(&a, &3.5, None), 2_usize);\n    }\n\n    #[test]\n    fn test_linear_interp() {\n        // float linear_interp\n        let result = linear_interp(1.0, &10.0, 2.0, &30.0, 1.5);\n        assert_eq!(result, 20.0_f64);\n\n        // Dual linear_interp\n        let result = linear_interp(\n            1.0,\n            &Dual::new(10.0, vec![\"x\".to_string()]),\n            2.0,\n            &Dual::new(30.0, vec![\"y\".to_string()]),\n            1.5,\n        );\n        assert_eq!(\n            result,\n            Dual::try_new(20.0, vec![\"x\".to_string(), \"y\".to_string()], vec![0.5, 0.5]).unwrap()\n        );\n    }\n\n    #[test]\n    fn test_log_linear_interp() {\n        // float linear_interp\n        let result = log_linear_interp(1.0, &10.0, 2.0, &30.0, 1.5);\n        let expected = (0.5 * 10.0.log() + 0.5 * 30.0.log()).exp();\n        assert_eq!(result, expected);\n\n        // Dual linear_interp\n        let y1 = Dual::new(10.0, vec![\"x\".to_string()]);\n        let y2 = Dual::new(30.0, vec![\"y\".to_string()]);\n        let result = log_linear_interp(1.0, &y1, 2.0, &y2, 1.5);\n        let expected = (0.5 * y1.log() + 0.5 * y2.log()).exp();\n        assert_eq!(result, expected);\n    }\n}\n"
  },
  {
    "path": "rust/curves/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Create curves for calculating interest rates and discount factors.\n\npub(crate) mod nodes;\npub use crate::curves::nodes::Nodes;\n\npub(crate) mod interpolation;\npub use crate::curves::interpolation::intp_flat_backward::FlatBackwardInterpolator;\npub use crate::curves::interpolation::intp_flat_forward::FlatForwardInterpolator;\npub use crate::curves::interpolation::intp_linear::LinearInterpolator;\npub use crate::curves::interpolation::intp_linear_zero_rate::LinearZeroRateInterpolator;\npub use crate::curves::interpolation::intp_log_linear::LogLinearInterpolator;\npub use crate::curves::interpolation::intp_null::NullInterpolator;\n\npub(crate) mod curve;\npub use crate::curves::curve::{CurveDF, CurveInterpolation, Modifier};\n\npub(crate) mod curve_py;\npub(crate) use crate::curves::curve_py::_get_modifier_str;\n\nmod serde;\n"
  },
  {
    "path": "rust/curves/nodes.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::{Dual, Dual2, Number};\nuse chrono::{DateTime, NaiveDateTime};\nuse indexmap::IndexMap;\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\n/// Datetime indexed values of a specific [ADOrder](`crate::dual::ADOrder`).\n#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]\npub enum Nodes {\n    F64(IndexMap<NaiveDateTime, f64>),\n    Dual(IndexMap<NaiveDateTime, Dual>),\n    Dual2(IndexMap<NaiveDateTime, Dual2>),\n}\n\n#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]\npub enum NodesTimestamp {\n    F64(IndexMap<i64, f64>),\n    Dual(IndexMap<i64, Dual>),\n    Dual2(IndexMap<i64, Dual2>),\n}\n\nimpl NodesTimestamp {\n    pub fn first_key(&self) -> i64 {\n        match self {\n            NodesTimestamp::F64(m) => *m.first().unwrap().0,\n            NodesTimestamp::Dual(m) => *m.first().unwrap().0,\n            NodesTimestamp::Dual2(m) => *m.first().unwrap().0,\n        }\n    }\n}\n\nimpl From<Nodes> for NodesTimestamp {\n    fn from(value: Nodes) -> Self {\n        match value {\n            Nodes::F64(m) => NodesTimestamp::F64(IndexMap::from_iter(\n                m.into_iter().map(|(k, v)| (k.and_utc().timestamp(), v)),\n            )),\n            Nodes::Dual(m) => NodesTimestamp::Dual(IndexMap::from_iter(\n                m.into_iter().map(|(k, v)| (k.and_utc().timestamp(), v)),\n            )),\n            Nodes::Dual2(m) => NodesTimestamp::Dual2(IndexMap::from_iter(\n                m.into_iter().map(|(k, v)| (k.and_utc().timestamp(), v)),\n            )),\n        }\n    }\n}\n\nimpl From<NodesTimestamp> for Nodes {\n    fn from(value: NodesTimestamp) -> Self {\n        match value {\n            NodesTimestamp::F64(m) => {\n                Nodes::F64(IndexMap::from_iter(m.into_iter().map(|(k, v)| {\n                    (DateTime::from_timestamp(k, 0).unwrap().naive_utc(), v)\n                })))\n            }\n            NodesTimestamp::Dual(m) => {\n                Nodes::Dual(IndexMap::from_iter(m.into_iter().map(|(k, v)| {\n                    (DateTime::from_timestamp(k, 0).unwrap().naive_utc(), v)\n                })))\n            }\n            NodesTimestamp::Dual2(m) => {\n                Nodes::Dual2(IndexMap::from_iter(m.into_iter().map(|(k, v)| {\n                    (DateTime::from_timestamp(k, 0).unwrap().naive_utc(), v)\n                })))\n            }\n        }\n    }\n}\n\nimpl NodesTimestamp {\n    // fn keys_as_f64(&self) -> Vec<f64> {\n    //     match self {\n    //         NodesTimestamp::F64(m) => m.keys().cloned().map(|x| x as f64).collect(),\n    //         NodesTimestamp::Dual(m) => m.keys().cloned().map(|x| x as f64).collect(),\n    //         NodesTimestamp::Dual2(m) => m.keys().cloned().map(|x| x as f64).collect(),\n    //     }\n    // }\n\n    pub(crate) fn sort_keys(&mut self) {\n        match self {\n            NodesTimestamp::F64(m) => m.sort_keys(),\n            NodesTimestamp::Dual(m) => m.sort_keys(),\n            NodesTimestamp::Dual2(m) => m.sort_keys(),\n        }\n    }\n\n    pub(crate) fn keys(&self) -> Vec<i64> {\n        match self {\n            NodesTimestamp::F64(m) => m.keys().cloned().collect(),\n            NodesTimestamp::Dual(m) => m.keys().cloned().collect(),\n            NodesTimestamp::Dual2(m) => m.keys().cloned().collect(),\n        }\n    }\n\n    pub(crate) fn index_map(&self) -> IndexMap<NaiveDateTime, Number> {\n        macro_rules! create_map {\n            ($map:ident, $Variant:ident) => {\n                IndexMap::from_iter($map.clone().into_iter().map(|(k, v)| {\n                    (\n                        DateTime::from_timestamp(k, 0).unwrap().naive_utc(),\n                        Number::$Variant(v),\n                    )\n                }))\n            };\n        }\n\n        match self {\n            NodesTimestamp::F64(m) => create_map!(m, F64),\n            NodesTimestamp::Dual(m) => create_map!(m, Dual),\n            NodesTimestamp::Dual2(m) => create_map!(m, Dual2),\n        }\n    }\n}\n\n//     /// Refactors the `get_index` method of an IndexMap and type casts the return values.\n//     pub(crate) fn get_index_as_f64(&self, index: usize) -> (f64, Number) {\n//         match self {\n//             NodesTimestamp::F64(m) => {\n//                 let (k, v) = m.get_index(index).unwrap();\n//                 (*k as f64, Number::F64(*v))\n//             },\n//             NodesTimestamp::Dual(m) => {\n//                 let (k, v) = m.get_index(index).unwrap();\n//                 (*k as f64, Number::Dual(v.clone()))\n//             },\n//             NodesTimestamp::Dual2(m) => {\n//                 let (k, v) = m.get_index(index).unwrap();\n//                 (*k as f64, Number::Dual2(v.clone()))\n//             },\n//         }\n//     }\n"
  },
  {
    "path": "rust/curves/serde.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::curves::curve_py::Curve;\nuse crate::curves::{CurveDF, CurveInterpolation};\nuse crate::json::JSON;\nuse crate::scheduling::DateRoll;\nuse serde::{Deserialize, Serialize};\n\nimpl<T, U> JSON for CurveDF<T, U>\nwhere\n    T: CurveInterpolation + for<'a> Deserialize<'a> + Serialize,\n    U: DateRoll + for<'a> Deserialize<'a> + Serialize,\n{\n}\n\nimpl JSON for Curve {}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::curves::curve_py::CurveInterpolator;\n    use crate::curves::{\n        FlatBackwardInterpolator, FlatForwardInterpolator, LinearInterpolator,\n        LinearZeroRateInterpolator, LogLinearInterpolator, Modifier, Nodes,\n    };\n    use crate::scheduling::{ndt, Convention, NamedCal};\n    use indexmap::IndexMap;\n\n    fn curve_fixture<T: CurveInterpolation>(interpolator: T) -> CurveDF<T, NamedCal> {\n        let nodes = Nodes::F64(IndexMap::from_iter(vec![\n            (ndt(2000, 1, 1), 1.0_f64),\n            (ndt(2001, 1, 1), 0.99_f64),\n            (ndt(2002, 1, 1), 0.98_f64),\n        ]));\n        let convention = Convention::Act360;\n        let modifier = Modifier::ModF;\n        let cal = NamedCal::try_new(\"all\").unwrap();\n        CurveDF::try_new(nodes, interpolator, \"crv\", convention, modifier, None, cal).unwrap()\n    }\n\n    #[test]\n    fn test_curve_json_all_interpolators() {\n        macro_rules! test_interpolator {\n            ($Variant: ident) => {\n                let interpolator = $Variant::new();\n                let curve = curve_fixture(interpolator);\n                let js = curve.to_json().unwrap();\n                let curve2 = CurveDF::from_json(&js).unwrap();\n                assert_eq!(curve, curve2);\n            };\n        }\n\n        test_interpolator!(FlatBackwardInterpolator);\n        test_interpolator!(FlatForwardInterpolator);\n        test_interpolator!(LogLinearInterpolator);\n        test_interpolator!(LinearInterpolator);\n        test_interpolator!(LinearZeroRateInterpolator);\n    }\n\n    #[test]\n    fn test_curve_json_py_enum() {\n        let interpolator = CurveInterpolator::Linear(LinearInterpolator::new());\n        let curve = curve_fixture(interpolator);\n        let js = curve.to_json().unwrap();\n        println!(\"{}\", js);\n        let curve2 = CurveDF::from_json(&js).unwrap();\n        assert_eq!(curve, curve2);\n    }\n}\n"
  },
  {
    "path": "rust/dual/docs.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Standalone documentation pages.\n//!\n//! # Examples\n//!\n//! Some add\n"
  },
  {
    "path": "rust/dual/dual.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\npub use crate::dual::dual_ops::convert::{set_order, set_order_clone};\npub use crate::dual::dual_ops::math_funcs::MathFuncs;\npub use crate::dual::dual_ops::numeric_ops::NumberOps;\nuse indexmap::set::IndexSet;\nuse ndarray::{Array, Array1, Array2, Axis};\nuse pyo3::exceptions::PyValueError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\nuse std::sync::Arc;\n\n/// A dual number data type supporting first order derivatives.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Default, Debug, Deserialize, Serialize)]\npub struct Dual {\n    pub(crate) real: f64,\n    pub(crate) vars: Arc<IndexSet<String>>,\n    pub(crate) dual: Array1<f64>,\n}\n\n/// A dual number data type supporting second order derivatives.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Default, Debug, Serialize, Deserialize)]\npub struct Dual2 {\n    pub(crate) real: f64,\n    pub(crate) vars: Arc<IndexSet<String>>,\n    pub(crate) dual: Array1<f64>,\n    pub(crate) dual2: Array2<f64>,\n}\n\n/// The state of the `vars` measured between two dual number type structs; a LHS relative to a RHS.\n#[derive(Clone, Debug, PartialEq)]\npub enum VarsRelationship {\n    /// The two structs share the same Arc pointer for their `vars`.\n    ArcEquivalent,\n    /// The structs have the same `vars` in the same order but not a shared Arc pointer.\n    ValueEquivalent,\n    /// The `vars` of the compared RHS is contained within those of the LHS.\n    Superset,\n    /// The `vars` of the calling LHS are contained within those of the RHS.\n    Subset,\n    /// Both the LHS and RHS have different `vars`.\n    Difference,\n}\n\n/// Manages the `vars` of the manifold associated with a dual number.\npub trait Vars\nwhere\n    Self: Clone,\n{\n    /// Get a reference to the Arc pointer for the `IndexSet` containing the struct's variables.\n    fn vars(&self) -> &Arc<IndexSet<String>>;\n\n    /// Create a new dual number with `vars` aligned with given new Arc pointer.\n    ///\n    /// This method compares the existing `vars` with the new and reshuffles manifold gradient\n    /// values in memory. For large numbers of variables this is one of the least efficient\n    /// operations relating different dual numbers and should be avoided where possible.\n    fn to_new_vars(\n        &self,\n        arc_vars: &Arc<IndexSet<String>>,\n        state: Option<VarsRelationship>,\n    ) -> Self;\n\n    /// Compare the `vars` on a `Dual` with a given Arc pointer.\n    fn vars_cmp(&self, arc_vars: &Arc<IndexSet<String>>) -> VarsRelationship {\n        if Arc::ptr_eq(self.vars(), arc_vars) {\n            VarsRelationship::ArcEquivalent\n        } else if self.vars().len() == arc_vars.len()\n            && self.vars().iter().zip(arc_vars.iter()).all(|(a, b)| a == b)\n        {\n            VarsRelationship::ValueEquivalent\n        } else if self.vars().len() >= arc_vars.len()\n            && arc_vars.iter().all(|var| self.vars().contains(var))\n        {\n            VarsRelationship::Superset\n        } else if self.vars().len() < arc_vars.len()\n            && self.vars().iter().all(|var| arc_vars.contains(var))\n        {\n            VarsRelationship::Subset\n        } else {\n            VarsRelationship::Difference\n        }\n    }\n    // fn vars_cmp(&self, arc_vars: &Arc<IndexSet<String>>) -> VarsRelationship;\n\n    /// Construct a tuple of 2 `Self` types whose `vars` are linked by an Arc pointer.\n    ///\n    /// Gradient values contained in fields may be shuffled in memory if necessary\n    /// according to the calculated `VarsRelationship`. Do not use `state` directly unless you have\n    /// performed a pre-check.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual, Vars, VarsRelationship};\n    /// let x = Dual::new(1.0, vec![\"x\".to_string()]);\n    /// let y = Dual::new(1.5, vec![\"y\".to_string()]);\n    /// let (a, b) = x.to_union_vars(&y, Some(VarsRelationship::Difference));\n    /// // a: <Dual: 1.0, (x, y), [1.0, 0.0]>\n    /// // b: <Dual: 1.5, (x, y), [0.0, 1.0]>\n    /// ```\n    fn to_union_vars(&self, other: &Self, state: Option<VarsRelationship>) -> (Self, Self)\n    where\n        Self: Sized,\n    {\n        let state_ = state.unwrap_or_else(|| self.vars_cmp(other.vars()));\n        match state_ {\n            VarsRelationship::ArcEquivalent => (self.clone(), other.clone()),\n            VarsRelationship::ValueEquivalent => {\n                (self.clone(), other.to_new_vars(self.vars(), Some(state_)))\n            }\n            VarsRelationship::Superset => (\n                self.clone(),\n                other.to_new_vars(self.vars(), Some(VarsRelationship::Subset)),\n            ),\n            VarsRelationship::Subset => {\n                (self.to_new_vars(other.vars(), Some(state_)), other.clone())\n            }\n            VarsRelationship::Difference => self.to_combined_vars(other),\n        }\n    }\n\n    /// Construct a tuple of 2 `Self` types whose `vars` are linked by the explicit union\n    /// of their own variables.\n    ///\n    /// Gradient values contained in fields will be shuffled in memory.\n    fn to_combined_vars(&self, other: &Self) -> (Self, Self)\n    where\n        Self: Sized,\n    {\n        let comb_vars = Arc::new(IndexSet::from_iter(\n            self.vars().union(other.vars()).cloned(),\n        ));\n        (\n            self.to_new_vars(&comb_vars, Some(VarsRelationship::Difference)),\n            other.to_new_vars(&comb_vars, Some(VarsRelationship::Difference)),\n        )\n    }\n\n    /// Compare if two `Dual` structs share the same `vars` by Arc pointer equivalence.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual, Vars};\n    /// let x1 = Dual::new(1.5, vec![\"x\".to_string()]);\n    /// let x2 = Dual::new(2.5, vec![\"x\".to_string()]);\n    /// assert_eq!(x1.ptr_eq(&x2), false); // Vars are the same but not a shared Arc pointer\n    /// ```\n    fn ptr_eq(&self, other: &Self) -> bool {\n        Arc::ptr_eq(self.vars(), other.vars())\n    }\n}\n\nimpl Vars for Dual {\n    /// Get a reference to the Arc pointer for the `IndexSet` containing the struct's variables.\n    fn vars(&self) -> &Arc<IndexSet<String>> {\n        &self.vars\n    }\n\n    /// Construct a new `Dual` with `vars` set as the given Arc pointer and gradients shuffled in memory.\n    ///\n    /// Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual, Vars};\n    /// let x = Dual::new(1.5, vec![\"x\".to_string()]);\n    /// let xy = Dual::new(2.5, vec![\"x\".to_string(), \"y\".to_string()]);\n    /// let x_y = x.to_new_vars(xy.vars(), None);\n    /// // x_y: <Dual: 1.5, (x, y), [1.0, 0.0]>\n    /// assert_eq!(x_y, Dual::try_new(1.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0]).unwrap());\n    fn to_new_vars(\n        &self,\n        arc_vars: &Arc<IndexSet<String>>,\n        state: Option<VarsRelationship>,\n    ) -> Self {\n        let match_val = state.unwrap_or_else(|| self.vars_cmp(arc_vars));\n        let dual_: Array1<f64> = match match_val {\n            VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n                self.dual.clone()\n            }\n            _ => {\n                let lookup_or_zero = |v| match self.vars.get_index_of(v) {\n                    Some(idx) => self.dual[idx],\n                    None => 0.0_f64,\n                };\n                Array1::from_vec(arc_vars.iter().map(lookup_or_zero).collect())\n            }\n        };\n        Self {\n            real: self.real,\n            vars: Arc::clone(arc_vars),\n            dual: dual_,\n        }\n    }\n}\n\nimpl Vars for Dual2 {\n    /// Get a reference to the Arc pointer for the `IndexSet` containing the struct's variables.\n    fn vars(&self) -> &Arc<IndexSet<String>> {\n        &self.vars\n    }\n\n    /// Construct a new `Dual2` with `vars` set as the given Arc pointer and gradients shuffled in memory.\n    ///\n    /// Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual2, Vars};\n    /// let x = Dual2::new(1.5, vec![\"x\".to_string()]);\n    /// let xy = Dual2::new(2.5, vec![\"x\".to_string(), \"y\".to_string()]);\n    /// let x_y = x.to_new_vars(xy.vars(), None);\n    /// // x_y: <Dual2: 1.5, (x, y), [1.0, 0.0], [[0.0, 0.0], [0.0, 0.0]]>\n    /// assert_eq!(x_y, Dual2::try_new(1.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0], vec![]).unwrap());\n    fn to_new_vars(\n        &self,\n        arc_vars: &Arc<IndexSet<String>>,\n        state: Option<VarsRelationship>,\n    ) -> Self {\n        let dual_: Array1<f64>;\n        let mut dual2_: Array2<f64> = Array2::zeros((arc_vars.len(), arc_vars.len()));\n        let match_val = state.unwrap_or_else(|| self.vars_cmp(arc_vars));\n        match match_val {\n            VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n                dual_ = self.dual.clone();\n                dual2_.clone_from(&self.dual2);\n            }\n            _ => {\n                let lookup_or_zero = |v| match self.vars.get_index_of(v) {\n                    Some(idx) => self.dual[idx],\n                    None => 0.0_f64,\n                };\n                dual_ = Array1::from_vec(arc_vars.iter().map(lookup_or_zero).collect());\n\n                let indices: Vec<Option<usize>> =\n                    arc_vars.iter().map(|x| self.vars.get_index_of(x)).collect();\n                for (i, row_index) in indices.iter().enumerate() {\n                    match row_index {\n                        Some(row_value) => {\n                            for (j, col_index) in indices.iter().enumerate() {\n                                match col_index {\n                                    Some(col_value) => {\n                                        dual2_[[i, j]] = self.dual2[[*row_value, *col_value]]\n                                    }\n                                    None => {}\n                                }\n                            }\n                        }\n                        None => {}\n                    }\n                }\n            }\n        }\n        Self {\n            real: self.real,\n            vars: Arc::clone(arc_vars),\n            dual: dual_,\n            dual2: dual2_,\n        }\n    }\n}\n\n/// Provides calculations of first order gradients to all, or a set of provided, `vars`.\npub trait Gradient1: Vars {\n    /// Get a reference to the Array containing the first order gradients.\n    fn dual(&self) -> &Array1<f64>;\n\n    /// Return a set of first order gradients ordered by the given vector.\n    ///\n    /// Duplicate `vars` are dropped before parsing.\n    fn gradient1(&self, vars: Vec<String>) -> Array1<f64> {\n        let arc_vars = Arc::new(IndexSet::from_iter(vars));\n        let state = self.vars_cmp(&arc_vars);\n        match state {\n            VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n                self.dual().clone()\n            }\n            _ => {\n                let mut dual_ = Array1::<f64>::zeros(arc_vars.len());\n                for (i, index) in arc_vars\n                    .iter()\n                    .map(|x| self.vars().get_index_of(x))\n                    .enumerate()\n                {\n                    if let Some(value) = index {\n                        dual_[i] = self.dual()[value]\n                    }\n                }\n                dual_\n            }\n        }\n    }\n}\n\nimpl Gradient1 for Dual {\n    fn dual(&self) -> &Array1<f64> {\n        &self.dual\n    }\n}\n\nimpl Gradient1 for Dual2 {\n    fn dual(&self) -> &Array1<f64> {\n        &self.dual\n    }\n}\n\n/// Provides calculations of second order gradients to all, or a set of provided, `vars`.\npub trait Gradient2: Gradient1 {\n    /// Get a reference to the Array containing the second order gradients.\n    fn dual2(&self) -> &Array2<f64>;\n\n    /// Return a set of first order gradients ordered by the given vector.\n    ///\n    /// Duplicate `vars` are dropped before parsing.\n    fn gradient2(&self, vars: Vec<String>) -> Array2<f64> {\n        let arc_vars = Arc::new(IndexSet::from_iter(vars));\n        let state = self.vars_cmp(&arc_vars);\n        match state {\n            VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n                2.0_f64 * self.dual2()\n            }\n            _ => {\n                let indices: Vec<Option<usize>> = arc_vars\n                    .iter()\n                    .map(|x| self.vars().get_index_of(x))\n                    .collect();\n                let mut dual2_ = Array::zeros((arc_vars.len(), arc_vars.len()));\n                for (i, row_index) in indices.iter().enumerate() {\n                    for (j, col_index) in indices.iter().enumerate() {\n                        match row_index {\n                            Some(row_value) => match col_index {\n                                Some(col_value) => {\n                                    dual2_[[i, j]] = self.dual2()[[*row_value, *col_value]]\n                                }\n                                None => {}\n                            },\n                            None => {}\n                        }\n                    }\n                }\n                2_f64 * dual2_\n            }\n        }\n    }\n\n    fn gradient1_manifold(&self, vars: Vec<String>) -> Array1<Dual2> {\n        let indices: Vec<Option<usize>> =\n            vars.iter().map(|x| self.vars().get_index_of(x)).collect();\n\n        let default_zero = Dual2::new(0., vars.clone());\n        let mut grad: Array1<Dual2> = Array1::zeros(vars.len());\n        for (i, i_idx) in indices.iter().enumerate() {\n            match i_idx {\n                Some(i_val) => {\n                    let mut dual: Array1<f64> = Array1::zeros(vars.len());\n                    for (j, j_idx) in indices.iter().enumerate() {\n                        match j_idx {\n                            Some(j_val) => dual[j] = self.dual2()[[*i_val, *j_val]] * 2.0,\n                            None => {}\n                        }\n                    }\n                    grad[i] = Dual2 {\n                        real: self.dual()[*i_val],\n                        vars: Arc::clone(&default_zero.vars),\n                        dual2: Array2::zeros((vars.len(), vars.len())),\n                        dual,\n                    };\n                }\n                None => grad[i] = default_zero.clone(),\n            }\n        }\n        grad\n    }\n}\n\nimpl Gradient2 for Dual2 {\n    fn dual2(&self) -> &Array2<f64> {\n        &self.dual2\n    }\n}\n\nimpl Dual {\n    /// Constructs a new `Dual`.\n    ///\n    /// - `vars` should be **unique**; duplicates will be removed by the `IndexSet`.\n    ///\n    /// Gradient values for each of the provided `vars` is set to 1.0_f64.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::Dual;\n    /// let x = Dual::new(2.5, vec![\"x\".to_string()]);\n    /// // x: <Dual: 2.5, (x), [1.0]>\n    /// ```\n    pub fn new(real: f64, vars: Vec<String>) -> Self {\n        let unique_vars_ = Arc::new(IndexSet::from_iter(vars));\n        Self {\n            real,\n            dual: Array1::ones(unique_vars_.len()),\n            vars: unique_vars_,\n        }\n    }\n\n    /// Constructs a new `Dual`.\n    ///\n    /// - `vars` should be **unique**; duplicates will be removed by the `IndexSet`.\n    /// - `dual` can be empty; if so each gradient with respect to each `vars` is set to 1.0_f64.\n    ///\n    /// `try_new` should be used instead of `new` when gradient values other than 1.0_f64 are to\n    /// be initialised.\n    ///\n    /// # Errors\n    ///\n    /// If the length of `dual` and of `vars` are not the same after parsing.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::Dual;\n    /// let x = Dual::try_new(2.5, vec![\"x\".to_string()], vec![4.2]).unwrap();\n    /// // x: <Dual: 2.5, (x), [4.2]>\n    /// ```\n    pub fn try_new(real: f64, vars: Vec<String>, dual: Vec<f64>) -> Result<Self, PyErr> {\n        let unique_vars_ = Arc::new(IndexSet::from_iter(vars));\n        let dual_ = if dual.is_empty() {\n            Array1::ones(unique_vars_.len())\n        } else {\n            Array1::from_vec(dual)\n        };\n        if unique_vars_.len() != dual_.len() {\n            Err(PyValueError::new_err(\n                \"`vars` and `dual` must have the same length.\",\n            ))\n        } else {\n            Ok(Self {\n                real,\n                vars: unique_vars_,\n                dual: dual_,\n            })\n        }\n    }\n\n    /// Construct a new `Dual` cloning the `vars` Arc pointer from another.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::Dual;\n    /// let x = Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0]).unwrap();\n    /// let y1 = Dual::new_from(&x, 1.5, vec![\"y\".to_string()]);\n    /// ```\n    ///\n    /// This is semantically the same as:\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual, Vars};\n    /// # let x = Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0]).unwrap();\n    /// # let y1 = Dual::new_from(&x, 1.5, vec![\"y\".to_string()]);\n    /// let y2 = Dual::new(1.5, vec![\"y\".to_string()]).to_new_vars(x.vars(), None);\n    /// assert_eq!(y1, y2);\n    /// ```\n    pub fn new_from<T: Vars>(other: &T, real: f64, vars: Vec<String>) -> Self {\n        let new = Self::new(real, vars);\n        new.to_new_vars(other.vars(), None)\n    }\n\n    /// Construct a new `Dual` cloning the `vars` Arc pointer from another.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual, Vars};\n    /// let x = Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0]).unwrap();\n    /// let y1 = Dual::try_new_from(&x, 1.5, vec![\"y\".to_string()], vec![3.2]).unwrap();\n    /// ```\n    ///\n    /// This is semantically the same as:\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual, Vars};\n    /// # let x = Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0]).unwrap();\n    /// # let y1 = Dual::try_new_from(&x, 1.5, vec![\"y\".to_string()], vec![3.2]).unwrap();\n    /// let y2 = Dual::try_new(1.5, vec![\"y\".to_string()], vec![3.2]).unwrap().to_new_vars(x.vars(), None);\n    /// assert_eq!(y1, y2);\n    /// ```\n    pub fn try_new_from<T: Vars>(\n        other: &T,\n        real: f64,\n        vars: Vec<String>,\n        dual: Vec<f64>,\n    ) -> Result<Self, PyErr> {\n        let new = Self::try_new(real, vars, dual)?;\n        Ok(new.to_new_vars(other.vars(), None))\n    }\n\n    /// Construct a new `Dual` cloning the `vars` Arc pointer from another.\n    ///\n    pub fn clone_from<T: Vars>(other: &T, real: f64, dual: Array1<f64>) -> Self {\n        assert_eq!(other.vars().len(), dual.len());\n        Dual {\n            real,\n            vars: Arc::clone(other.vars()),\n            dual,\n        }\n    }\n\n    /// Get the real component value of the struct.\n    pub fn real(&self) -> f64 {\n        self.real\n    }\n}\n\nimpl Dual2 {\n    /// Constructs a new `Dual2`.\n    ///\n    /// - `vars` should be **unique**; duplicates will be removed by the `IndexSet`.\n    ///\n    /// Gradient values for each of the provided `vars` is set to 1.0_f64.\n    /// Second order gradient values for each combination of provided `vars` is set\n    /// to 0.0_f64.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::Dual2;\n    /// let x = Dual2::new(2.5, vec![\"x\".to_string()]);\n    /// // x: <Dual2: 2.5, (x), [1.0], [[0.0]]>\n    /// ```\n    pub fn new(real: f64, vars: Vec<String>) -> Self {\n        let unique_vars_ = Arc::new(IndexSet::from_iter(vars));\n        Self {\n            real,\n            dual: Array1::ones(unique_vars_.len()),\n            dual2: Array2::zeros((unique_vars_.len(), unique_vars_.len())),\n            vars: unique_vars_,\n        }\n    }\n\n    /// Constructs a new `Dual2`.\n    ///\n    /// - `vars` should be **unique**; duplicates will be removed by the `IndexSet`.\n    /// - `dual` can be empty; if so each gradient with respect to each `vars` is set to 1.0_f64.\n    /// - `dual2` can be empty; if so each gradient with respect to each `vars` is set to 0.0_f64.\n    ///   Input as a flattened 2d-array in row major order.\n    ///\n    /// # Errors\n    ///\n    /// If the length of `dual` and of `vars` are not the same after parsing.\n    /// If the shape of two dimension `dual2` does not match `vars` after parsing.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::Dual2;\n    /// let x = Dual2::try_new(2.5, vec![\"x\".to_string()], vec![], vec![]).unwrap();\n    /// // x: <Dual2: 2.5, (x), [1.0], [[0.0]]>\n    /// ```\n    pub fn try_new(\n        real: f64,\n        vars: Vec<String>,\n        dual: Vec<f64>,\n        dual2: Vec<f64>,\n    ) -> Result<Self, PyErr> {\n        let unique_vars_ = Arc::new(IndexSet::from_iter(vars));\n        let dual_ = if dual.is_empty() {\n            Array1::ones(unique_vars_.len())\n        } else {\n            Array1::from_vec(dual)\n        };\n        if unique_vars_.len() != dual_.len() {\n            return Err(PyValueError::new_err(\n                \"`vars` and `dual` must have the same length.\",\n            ));\n        }\n\n        let dual2_ = if dual2.is_empty() {\n            Array2::zeros((unique_vars_.len(), unique_vars_.len()))\n        } else {\n            if dual2.len() != (unique_vars_.len() * unique_vars_.len()) {\n                return Err(PyValueError::new_err(\n                    \"`vars` and `dual2` must have compatible lengths.\",\n                ));\n            }\n            Array::from_vec(dual2)\n                .into_shape_with_order((unique_vars_.len(), unique_vars_.len()))\n                .expect(\"Reshaping failed, which should not occur because shape is pre-checked.\")\n        };\n        Ok(Self {\n            real,\n            vars: unique_vars_,\n            dual: dual_,\n            dual2: dual2_,\n        })\n    }\n\n    /// Construct a new `Dual2` cloning the `vars` Arc pointer from another.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::Dual2;\n    /// let x = Dual2::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0], vec![]).unwrap();\n    /// let y1 = Dual2::new_from(&x, 1.5, vec![\"y\".to_string()]);\n    /// ```\n    ///\n    /// This is semantically the same as:\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual2, Vars};\n    /// # let x = Dual2::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0], vec![]).unwrap();\n    /// # let y1 = Dual2::new_from(&x, 1.5, vec![\"y\".to_string()]);\n    /// let y = Dual2::new(1.5, vec![\"y\".to_string()]).to_new_vars(x.vars(), None);\n    /// ```\n    pub fn new_from<T: Vars>(other: &T, real: f64, vars: Vec<String>) -> Self {\n        let new = Self::new(real, vars);\n        new.to_new_vars(other.vars(), None)\n    }\n\n    /// Construct a new `Dual2` cloning the `vars` Arc pointer from another.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::dual::Dual2;\n    /// let x = Dual2::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0], vec![]).unwrap();\n    /// let y1 = Dual2::new_from(&x, 1.5, vec![\"y\".to_string()]);\n    /// ```\n    ///\n    /// This is semantically the same as:\n    ///\n    /// ```rust\n    /// # use rateslib::dual::{Dual2, Vars};\n    /// # let x = Dual2::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0], vec![]).unwrap();\n    /// # let y1 = Dual2::new_from(&x, 1.5, vec![\"y\".to_string()]);\n    /// let y2 = Dual2::new(1.5, vec![\"y\".to_string()]).to_new_vars(x.vars(), None);\n    /// assert_eq!(y1, y2);\n    /// ```\n    pub fn try_new_from<T: Vars>(\n        other: &T,\n        real: f64,\n        vars: Vec<String>,\n        dual: Vec<f64>,\n        dual2: Vec<f64>,\n    ) -> Result<Self, PyErr> {\n        let new = Self::try_new(real, vars, dual, dual2)?;\n        Ok(new.to_new_vars(other.vars(), None))\n    }\n\n    /// Construct a new `Dual2` cloning the `vars` Arc pointer from another.\n    ///\n    pub fn clone_from<T: Vars>(\n        other: &T,\n        real: f64,\n        dual: Array1<f64>,\n        dual2: Array2<f64>,\n    ) -> Self {\n        assert_eq!(other.vars().len(), dual.len());\n        assert_eq!(other.vars().len(), dual2.len_of(Axis(0)));\n        assert_eq!(other.vars().len(), dual2.len_of(Axis(1)));\n        Dual2 {\n            real,\n            vars: Arc::clone(other.vars()),\n            dual,\n            dual2,\n        }\n    }\n\n    /// Get the real component value of the struct.\n    pub fn real(&self) -> f64 {\n        self.real\n    }\n}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::dual::dual::Dual2;\n    use std::ops::{Add, Div, Mul, Sub};\n    use std::time::Instant;\n\n    #[test]\n    fn new() {\n        let x = Dual::new(1.0, vec![\"a\".to_string(), \"a\".to_string()]);\n        assert_eq!(x.real, 1.0_f64);\n        assert_eq!(\n            *x.vars,\n            IndexSet::<String>::from_iter(vec![\"a\".to_string()])\n        );\n        assert_eq!(x.dual, Array1::from_vec(vec![1.0_f64]));\n    }\n\n    #[test]\n    fn new_with_dual() {\n        let x = Dual::try_new(1.0, vec![\"a\".to_string(), \"a\".to_string()], vec![2.5]).unwrap();\n        assert_eq!(x.real, 1.0_f64);\n        assert_eq!(\n            *x.vars,\n            IndexSet::<String>::from_iter(vec![\"a\".to_string()])\n        );\n        assert_eq!(x.dual, Array1::from_vec(vec![2.5_f64]));\n    }\n\n    #[test]\n    fn new_len_mismatch() {\n        let result =\n            Dual::try_new(1.0, vec![\"a\".to_string(), \"a\".to_string()], vec![1.0, 2.0]).is_err();\n        assert!(result);\n    }\n\n    #[test]\n    fn ptr_eq() {\n        let x = Dual::new(1.0, vec![\"a\".to_string()]);\n        let y = Dual::new(1.0, vec![\"a\".to_string()]);\n        assert!(x.ptr_eq(&y) == false);\n    }\n\n    #[test]\n    fn to_new_vars() {\n        let x = Dual::try_new(1.5, vec![\"a\".to_string(), \"b\".to_string()], vec![1., 2.]).unwrap();\n        let y = Dual::try_new(2.0, vec![\"a\".to_string(), \"c\".to_string()], vec![3., 3.]).unwrap();\n        let z = x.to_new_vars(&y.vars, None);\n        assert_eq!(z.real, 1.5_f64);\n        assert!(y.ptr_eq(&z));\n        assert_eq!(z.dual, Array1::from_vec(vec![1.0, 0.0]));\n        let u = x.to_new_vars(x.vars(), None);\n        assert!(u.ptr_eq(&x))\n    }\n\n    #[test]\n    fn new_from() {\n        let x = Dual::try_new(2.0, vec![\"a\".to_string(), \"b\".to_string()], vec![3., 3.]).unwrap();\n        let y = Dual::try_new_from(\n            &x,\n            2.0,\n            vec![\"a\".to_string(), \"c\".to_string()],\n            vec![3., 3.],\n        )\n        .unwrap();\n        assert_eq!(y.real, 2.0_f64);\n        assert!(y.ptr_eq(&x));\n        assert_eq!(y.dual, Array1::from_vec(vec![3.0, 0.0]));\n    }\n\n    #[test]\n    fn vars() {\n        let x = Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0]).unwrap();\n        let y = Dual::new(1.5, vec![\"y\".to_string()]).to_new_vars(x.vars(), None);\n        assert!(x.ptr_eq(&y));\n        assert_eq!(y.dual, Array1::from_vec(vec![0.0, 1.0]));\n    }\n\n    #[test]\n    fn vars_cmp() {\n        let x = Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.0, 0.0]).unwrap();\n        let y = Dual::new(1.5, vec![\"y\".to_string()]);\n        let y2 = Dual::new(1.5, vec![\"y\".to_string()]);\n        let z = x.to_new_vars(y.vars(), None);\n        let u = Dual::new(1.5, vec![\"u\".to_string()]);\n        assert_eq!(x.vars_cmp(y.vars()), VarsRelationship::Superset);\n        assert_eq!(y.vars_cmp(z.vars()), VarsRelationship::ArcEquivalent);\n        assert_eq!(y.vars_cmp(y2.vars()), VarsRelationship::ValueEquivalent);\n        assert_eq!(y.vars_cmp(x.vars()), VarsRelationship::Subset);\n        assert_eq!(y.vars_cmp(u.vars()), VarsRelationship::Difference);\n    }\n\n    #[test]\n    fn default() {\n        let x = Dual::default();\n        assert_eq!(x.real, 0.0_f64);\n        assert_eq!(x.vars.len(), 0_usize);\n        assert_eq!(x.dual, Array1::<f64>::from_vec(vec![]));\n    }\n\n    // OPS TESTS\n\n    #[test]\n    fn unitialised_derivs_eq_1() {\n        let d = Dual::new(2.3, Vec::from([String::from(\"a\"), String::from(\"b\")]));\n        for (_, val) in d.dual.indexed_iter() {\n            assert!(*val == 1.0)\n        }\n    }\n\n    #[test]\n    fn gradient1_no_equiv() {\n        let d1 =\n            Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.1, 2.2]).unwrap();\n        let result = d1.gradient1(vec![\"y\".to_string(), \"z\".to_string(), \"x\".to_string()]);\n        let expected = Array1::from_vec(vec![2.2, 0.0, 1.1]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn gradient1_equiv() {\n        let d1 =\n            Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.1, 2.2]).unwrap();\n        let result = d1.gradient1(vec![\"x\".to_string(), \"y\".to_string()]);\n        let expected = Array1::from_vec(vec![1.1, 2.2]);\n        assert_eq!(result, expected)\n    }\n\n    // PROFILING\n\n    #[test]\n    fn vars_cmp_profile() {\n        // Setup\n        let vars = 500_usize;\n        let x = Dual::try_new(\n            1.5,\n            (1..=vars).map(|x| x.to_string()).collect(),\n            (1..=vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        let y = Dual::try_new(\n            1.5,\n            (1..=vars).map(|x| x.to_string()).collect(),\n            (1..=vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        let z = Dual::new_from(&x, 1.0, Vec::new());\n        let u = Dual::try_new(\n            1.5,\n            (1..vars).map(|x| x.to_string()).collect(),\n            (1..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        let s = Dual::try_new(\n            1.5,\n            (0..(vars - 1)).map(|x| x.to_string()).collect(), // 2..Vars+1 13us  0..Vars-1  48ns\n            (1..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n\n        println!(\"\\nProfiling vars_cmp (VarsRelationship::ArcEquivalent):\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..100000 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                x.vars_cmp(&z.vars);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.2?}\", elapsed / 100000);\n\n        println!(\"\\nProfiling vars_cmp (VarsRelationship::ValueEquivalent):\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..1000 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                x.vars_cmp(&y.vars);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.2?}\", elapsed / 1000);\n\n        println!(\"\\nProfiling vars_cmp (VarsRelationship::Superset):\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..1000 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                x.vars_cmp(&u.vars);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.2?}\", elapsed / 1000);\n\n        println!(\"\\nProfiling vars_cmp (VarsRelationship::Different):\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..1000 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                x.vars_cmp(&s.vars);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.2?}\", elapsed / 1000);\n    }\n\n    #[test]\n    fn to_union_vars_profile() {\n        // Setup\n        let vars = 500_usize;\n        let x = Dual::try_new(\n            1.5,\n            (1..=vars).map(|x| x.to_string()).collect(),\n            (0..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        let y = Dual::try_new(\n            1.5,\n            (1..=vars).map(|x| x.to_string()).collect(),\n            (0..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        let z = Dual::new_from(&x, 1.0, Vec::new());\n        let u = Dual::try_new(\n            1.5,\n            (1..vars).map(|x| x.to_string()).collect(),\n            (1..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        let s = Dual::try_new(\n            1.5,\n            (0..(vars - 1)).map(|x| x.to_string()).collect(),\n            (0..(vars - 1)).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n\n        println!(\"\\nProfiling to_union_vars (VarsRelationship::ArcEquivalent):\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..100000 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                x.to_union_vars(&z, None);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.2?}\", elapsed / 100000);\n\n        println!(\"\\nProfiling to_union_vars (VarsRelationship::ValueEquivalent):\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..1000 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                x.to_union_vars(&y, None);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.2?}\", elapsed / 1000);\n\n        println!(\"\\nProfiling to_union_vars (VarsRelationship::Superset):\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..100 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                x.to_union_vars(&u, None);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.2?}\", elapsed / 100);\n\n        println!(\"\\nProfiling to_union_vars (VarsRelationship::Different):\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..100 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                x.to_union_vars(&s, None);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.2?}\", elapsed / 100);\n    }\n\n    #[test]\n    fn std_ops_ref_profile() {\n        fn four_ops<T>(a: &T, b: &T, c: &T, d: &T) -> T\n        where\n            for<'a> &'a T: Add<&'a T, Output = T>\n                + Sub<&'a T, Output = T>\n                + Div<&'a T, Output = T>\n                + Mul<&'a T, Output = T>,\n        {\n            &(&(a + b) * &(c / d)) - a\n        }\n\n        let vars = 500_usize;\n        let a = Dual::try_new(\n            1.5,\n            (1..=vars).map(|x| x.to_string()).collect(),\n            (0..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        // let b = Dual::new(\n        //     3.5,\n        //     (2..=(VARS+1)).map(|x| x.to_string()).collect(),\n        //     (0..VARS).map(|x| x as f64).collect(),\n        // );\n        // let c = Dual::new(\n        //     5.5,\n        //     (3..=(VARS+2)).map(|x| x.to_string()).collect(),\n        //     (0..VARS).map(|x| x as f64).collect(),\n        // );\n        // let d = Dual::new(\n        //     6.5,\n        //     (4..=(VARS+3)).map(|x| x.to_string()).collect(),\n        //     (0..VARS).map(|x| x as f64).collect(),\n        // );\n        let b = Dual::try_new_from(\n            &a,\n            3.5,\n            (1..=vars).map(|x| x.to_string()).collect(),\n            (0..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        let c = Dual::try_new_from(\n            &a,\n            5.5,\n            (1..=vars).map(|x| x.to_string()).collect(),\n            (0..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n        let d = Dual::try_new_from(\n            &a,\n            6.5,\n            (1..=vars).map(|x| x.to_string()).collect(),\n            (0..vars).map(|x| x as f64).collect(),\n        )\n        .unwrap();\n\n        println!(\"\\nProfiling f64 std ops:\");\n        let now = Instant::now();\n        // Code block to measure.\n        {\n            for _i in 0..1000 {\n                // Arc::ptr_eq(&x.vars, &y.vars);\n                let _x = four_ops(&a, &b, &c, &d);\n            }\n        }\n        let elapsed = now.elapsed();\n        println!(\"\\nElapsed: {:.9?}\", elapsed / 1000);\n    }\n\n    // copied from old dual2.rs\n\n    use ndarray::arr2;\n\n    #[test]\n    fn clone_arc2() {\n        let d1 = Dual2::new(20.0, vec![\"a\".to_string()]);\n        let d2 = d1.clone();\n        assert!(Arc::ptr_eq(&d1.vars, &d2.vars))\n    }\n\n    #[test]\n    fn default_dual2() {\n        let result = Dual2::default();\n        let expected = Dual2::new(0.0, Vec::new());\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn to_new_ordered_vars2() {\n        let d1 = Dual2::new(20.0, vec![\"a\".to_string()]);\n        let d2 = Dual2::new(20.0, vec![\"a\".to_string(), \"b\".to_string()]);\n        let d3 = d1.to_new_vars(&d2.vars, None);\n        assert!(Arc::ptr_eq(&d3.vars, &d2.vars));\n        assert!(d3.dual.len() == 2);\n        let d4 = d2.to_new_vars(&d1.vars, None);\n        assert!(Arc::ptr_eq(&d4.vars, &d1.vars));\n        assert!(d4.dual.len() == 1);\n    }\n\n    #[test]\n    fn new_dual2() {\n        Dual2::new(2.3, Vec::from([String::from(\"a\")]));\n    }\n\n    #[test]\n    fn new_dual_error2() {\n        assert!(Dual2::try_new(\n            2.3,\n            Vec::from([String::from(\"a\"), String::from(\"b\")]),\n            Vec::from([1.0]),\n            Vec::new(),\n        )\n        .is_err());\n    }\n\n    #[test]\n    fn new_dual2_error() {\n        assert!(Dual2::try_new(\n            2.3,\n            Vec::from([String::from(\"a\"), String::from(\"b\")]),\n            Vec::from([1.0, 2.3]),\n            Vec::from([1.0, 2.4, 3.4]),\n        )\n        .is_err());\n    }\n\n    #[test]\n    fn try_new_from2() {\n        let x = Dual2::new(1.2, vec![\"x\".to_string(), \"y\".to_string()]);\n        let y = Dual2::try_new_from(&x, 3.2, vec![\"y\".to_string()], vec![1.9], vec![2.1]).unwrap();\n        let z = Dual2::try_new(\n            3.2,\n            vec![\"x\".to_string(), \"y\".to_string()],\n            vec![0., 1.9],\n            vec![0., 0., 0., 2.1],\n        )\n        .unwrap();\n        assert_eq!(y, z);\n    }\n\n    #[test]\n    fn to_new_vars2() {\n        let d1 = Dual2::new(2.5, vec![\"x\".to_string()]);\n        let d2 = Dual2::new(3.5, vec![\"x\".to_string()]);\n        let d3 = d1.to_new_vars(d2.vars(), None);\n        assert!(d3.ptr_eq(&d2));\n        assert_eq!(d3.real, 2.5);\n        assert_eq!(d3.dual, Array1::from_vec(vec![1.0]));\n    }\n\n    #[test]\n    fn gradient2_equivval() {\n        let d1 = Dual2::try_new(\n            2.5,\n            vec![\"x\".to_string(), \"y\".to_string()],\n            vec![2.3, 4.5],\n            vec![1.0, 2.5, 2.5, 5.0],\n        )\n        .unwrap();\n        let result = d1.gradient2(vec![\"x\".to_string(), \"y\".to_string()]);\n        let expected = arr2(&[[2., 5.], [5., 10.]]);\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn gradient2_diffvars2() {\n        let d1 = Dual2::try_new(\n            2.5,\n            vec![\"x\".to_string(), \"y\".to_string()],\n            vec![2.3, 4.5],\n            vec![1.0, 2.5, 2.5, 5.0],\n        )\n        .unwrap();\n        let result = d1.gradient2(vec![\"z\".to_string(), \"y\".to_string()]);\n        let expected = arr2(&[[0., 0.], [0., 10.]]);\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn uninitialised_derivs_eq_one2() {\n        let d = Dual2::new(2.3, Vec::from([String::from(\"a\"), String::from(\"b\")]));\n        for (_, val) in d.dual.indexed_iter() {\n            assert!(*val == 1.0)\n        }\n    }\n\n    #[test]\n    fn ops_equiv2() {\n        let d1 = Dual2::try_new(1.5, vec![\"x\".to_string()], vec![1.0], vec![0.0]).unwrap();\n        let d2 = Dual2::try_new(2.5, vec![\"x\".to_string()], vec![2.0], vec![0.0]).unwrap();\n        let result = &d1 + &d2;\n        assert_eq!(\n            result,\n            Dual2::try_new(4.0, vec![\"x\".to_string()], vec![3.0], vec![0.0]).unwrap()\n        );\n        let result = &d1 - &d2;\n        assert_eq!(\n            result,\n            Dual2::try_new(-1.0, vec![\"x\".to_string()], vec![-1.0], vec![0.0]).unwrap()\n        );\n    }\n\n    #[test]\n    fn grad_manifold() {\n        let d1 = Dual2::try_new(\n            2.0,\n            vec![\"x\".to_string(), \"y\".to_string(), \"z\".to_string()],\n            vec![1., 2., 3.],\n            vec![2., 3., 4., 3., 5., 6., 4., 6., 7.],\n        )\n        .unwrap();\n        let result = d1.gradient1_manifold(vec![\"y\".to_string(), \"z\".to_string()]);\n        assert_eq!(result[0].real, 2.);\n        assert_eq!(result[1].real, 3.);\n        assert_eq!(result[0].dual, Array1::from_vec(vec![10., 12.]));\n        assert_eq!(result[1].dual, Array1::from_vec(vec![12., 14.]));\n        assert_eq!(result[0].dual2, Array2::<f64>::zeros((2, 2)));\n        assert_eq!(result[1].dual2, Array2::<f64>::zeros((2, 2)));\n    }\n\n    // #[test]\n    // #[should_panic]\n    // fn no_dual_cross(){\n    //     let a = Dual::new(2.0, Vec::new(), Vec::new());\n    //     let b = Dual2::new(3.0, Vec::new(), Vec::new(), Vec::new());\n    //     a + b\n    // }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/add.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2, Vars, VarsRelationship};\nuse crate::dual::enums::Number;\nuse auto_ops::{impl_op_ex, impl_op_ex_commutative};\nuse std::sync::Arc;\n\n// Add f64\nimpl_op_ex_commutative!(+ |a: &Dual, b: &f64| -> Dual { Dual {vars: Arc::clone(&a.vars), real: a.real + b, dual: a.dual.clone()} });\nimpl_op_ex_commutative!(+ |a: &Dual2, b: &f64| -> Dual2 {\n    Dual2 {vars: Arc::clone(&a.vars), real: a.real + b, dual: a.dual.clone(), dual2: a.dual2.clone()}\n});\n\n// Add for Dual\nimpl_op_ex!(+ |a: &Dual, b: &Dual| -> Dual {\n    let state = a.vars_cmp(b.vars());\n    match state {\n        VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n            Dual {real: a.real + b.real, dual: &a.dual + &b.dual, vars: Arc::clone(&a.vars)}\n        }\n        _ => {\n            let (x, y) = a.to_union_vars(b, Some(state));\n            Dual {real: x.real + y.real, dual: &x.dual + &y.dual, vars: Arc::clone(&x.vars)}\n        }\n    }\n});\n\n// Add for Dual2\nimpl_op_ex!(+ |a: &Dual2, b: &Dual2| -> Dual2 {\n    let state = a.vars_cmp(b.vars());\n    match state {\n        VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n            Dual2 {\n                real: a.real + b.real,\n                dual: &a.dual + &b.dual,\n                dual2: &a.dual2 + &b.dual2,\n                vars: Arc::clone(&a.vars)}\n        }\n        _ => {\n            let (x, y) = a.to_union_vars(b, Some(state));\n            Dual2 {\n                real: x.real + y.real,\n                dual: &x.dual + &y.dual,\n                dual2: &x.dual2 + &y.dual2,\n                vars: Arc::clone(&x.vars)}\n        }\n    }\n});\n\n// Add for Number\nimpl_op_ex!(+ |a: &Number, b: &Number| -> Number {\n    match (a,b) {\n        (Number::F64(f), Number::F64(f2)) => Number::F64(f + f2),\n        (Number::F64(f), Number::Dual(d2)) => Number::Dual(f + d2),\n        (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f + d2),\n        (Number::Dual(d), Number::F64(f2)) => Number::Dual(d + f2),\n        (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d + d2),\n        (Number::Dual(_), Number::Dual2(_)) => panic!(\"Cannot mix dual types: Dual + Dual2\"),\n        (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d + f2),\n        (Number::Dual2(_), Number::Dual(_)) => panic!(\"Cannot mix dual types: Dual2 + Dual\"),\n        (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d + d2),\n    }\n});\n\n// Add for Number\nimpl_op_ex_commutative!(+ |a: &Number, b: &f64| -> Number {\n    match a {\n        Number::F64(f) => Number::F64(f + b),\n        Number::Dual(d) => Number::Dual(d + b),\n        Number::Dual2(d) => Number::Dual2(d + b),\n    }\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn add_f64() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = 10.0 + d1 + 15.0;\n        let expected = Dual::try_new(\n            26.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn add() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let d2 = Dual::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![0.0, 3.0],\n        )\n        .unwrap();\n        let expected = Dual::try_new(\n            3.0,\n            vec![\"v0\".to_string(), \"v1\".to_string(), \"v2\".to_string()],\n            vec![1.0, 2.0, 3.0],\n        )\n        .unwrap();\n        let result = d1 + d2;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn add_f64_2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = 10.0 + d1 + 15.0;\n        let expected = Dual2::try_new(\n            26.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn add2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let d2 = Dual2::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![0.0, 3.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let expected = Dual2::try_new(\n            3.0,\n            vec![\"v0\".to_string(), \"v1\".to_string(), \"v2\".to_string()],\n            vec![1.0, 2.0, 3.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = d1 + d2;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn test_enum() {\n        let f = Number::F64(2.0);\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        assert_eq!(&f + &d, Number::Dual(Dual::new(5.0, vec![\"x\".to_string()])));\n\n        assert_eq!(\n            &d + &d,\n            Number::Dual(Dual::try_new(6.0, vec![\"x\".to_string()], vec![2.0]).unwrap())\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_enum_panic() {\n        let d = Number::Dual2(Dual2::new(2.0, vec![\"y\".to_string()]));\n        let d2 = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let _ = d + d2;\n    }\n\n    #[test]\n    fn test_enum_f64() {\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let res = 2.5_f64 + d;\n        assert_eq!(res, Number::Dual(Dual::new(5.5, vec![\"x\".to_string()])));\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/convert.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::{ADOrder, Number};\nuse std::convert::From;\n\n/// Convert a `Number` of one `ADOrder` to another and consume the value.\n///\n/// **Why use this method?**\n///\n/// `From` is implemented to convert into all the types `f64`, `Dual` and `Dual2` from each other,\n/// however, when doing so there is no variable information attached for an `f64`. For example,\n///\n/// ```rust\n/// # use rateslib::dual::Dual;\n/// let d = Dual::from(2.5_f64);\n/// assert_eq!(d, Dual::new(2.5_f64, vec![]));\n/// ```\n///\n/// On the other hand using a `Number` enum can convert any value to a dual data type tagged\n/// with specific variables. `vars` are only used in this instance when converting an `f64` to a\n/// dual type.\n///\n/// ```rust\n/// # use rateslib::dual::{Number, set_order, ADOrder, Dual};\n/// let f_ = Number::F64(2.5_f64);\n/// let d_ = set_order(f_, ADOrder::One, vec![\"x\".to_string()]);\n/// let d = Dual::from(d_);\n/// assert_eq!(d, Dual::new(2.5_f64, vec![\"x\".to_string()]));\n/// ```\n///\npub fn set_order(value: Number, order: ADOrder, vars: Vec<String>) -> Number {\n    match (value, order) {\n        (Number::F64(f), ADOrder::Zero) => Number::F64(f),\n        (Number::Dual(d), ADOrder::Zero) => Number::F64(d.real),\n        (Number::Dual2(d), ADOrder::Zero) => Number::F64(d.real),\n        (Number::F64(f), ADOrder::One) => Number::Dual(Dual::new(f, vars)),\n        (Number::Dual(d), ADOrder::One) => Number::Dual(d),\n        (Number::Dual2(d), ADOrder::One) => Number::Dual(Dual::from(d)),\n        (Number::F64(f), ADOrder::Two) => Number::Dual2(Dual2::new(f, vars)),\n        (Number::Dual(d), ADOrder::Two) => Number::Dual2(Dual2::from(d)),\n        (Number::Dual2(d), ADOrder::Two) => Number::Dual2(d),\n    }\n}\n\n/// Convert a `Number` of one `ADOrder` to another.\n///\n/// Similar to `set_order` except the value is not consumed during conversion.\npub fn set_order_clone(value: &Number, order: ADOrder, vars: Vec<String>) -> Number {\n    match (value, order) {\n        (Number::F64(f), ADOrder::Zero) => Number::F64(*f),\n        (Number::Dual(d), ADOrder::Zero) => Number::F64(d.real),\n        (Number::Dual2(d), ADOrder::Zero) => Number::F64(d.real),\n        (Number::F64(f), ADOrder::One) => Number::Dual(Dual::new(*f, vars)),\n        (Number::Dual(d), ADOrder::One) => Number::Dual(d.clone()),\n        (Number::Dual2(d), ADOrder::One) => Number::Dual(Dual::from(d)),\n        (Number::F64(f), ADOrder::Two) => Number::Dual2(Dual2::new(*f, vars)),\n        (Number::Dual(d), ADOrder::Two) => Number::Dual2(Dual2::from(d)),\n        (Number::Dual2(d), ADOrder::Two) => Number::Dual2(d.clone()),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_set_order_with_conversion() {\n        let f = 2.5_f64;\n        let d = set_order(Number::F64(f), ADOrder::One, vec![\"var1\".to_string()]);\n        assert_eq!(d, Number::Dual(Dual::new(2.5, vec![\"var1\".to_string()])));\n\n        let d2 = set_order(d, ADOrder::Two, vec![]);\n        assert_eq!(d2, Number::Dual2(Dual2::new(2.5, vec![\"var1\".to_string()])));\n\n        let f = set_order(d2, ADOrder::Zero, vec![]);\n        assert_eq!(f, Number::F64(2.5_f64));\n    }\n\n    #[test]\n    fn test_docstring() {\n        let f_ = Number::F64(2.5_f64);\n        let d_ = set_order(f_, ADOrder::One, vec![\"x\".to_string()]);\n        let d = Dual::from(d_);\n        assert_eq!(d, Dual::new(2.5_f64, vec![\"x\".to_string()])); // true\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/div.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse auto_ops::impl_op_ex;\nuse num_traits::Pow;\nuse std::sync::Arc;\n\nimpl_op_ex!(/ |a: &Dual, b: &f64| -> Dual { Dual {vars: Arc::clone(&a.vars), real: a.real / b, dual: (1_f64/b) * &a.dual} });\nimpl_op_ex!(/ |a: &f64, b: &Dual| -> Dual { a * b.clone().pow(-1.0) });\nimpl_op_ex!(/ |a: &Dual2, b: &f64| -> Dual2 {\n    Dual2 {vars: Arc::clone(&a.vars), real: a.real / b, dual: (1_f64/b) * &a.dual, dual2: (1_f64/b) * &a.dual2}\n});\nimpl_op_ex!(/ |a: &f64, b: &Dual2| -> Dual2 { a * b.clone().pow(-1.0) });\n\n// impl Div for Dual\nimpl_op_ex!(/ |a: &Dual, b: &Dual| -> Dual {\n    let b_ = Dual {real: 1.0 / b.real, vars: Arc::clone(&b.vars), dual: -1.0 / (b.real * b.real) * &b.dual};\n    a * b_\n});\n\n// impl Div for Dual2\nimpl_op_ex!(/ |a: &Dual2, b: &Dual2| -> Dual2 { a * b.clone().pow(-1.0) });\n\n// Div for Number\nimpl_op_ex!(/ |a: &Number, b: &Number| -> Number {\n    match (a,b) {\n        (Number::F64(f), Number::F64(f2)) => Number::F64(f / f2),\n        (Number::F64(f), Number::Dual(d2)) => Number::Dual(f / d2),\n        (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f / d2),\n        (Number::Dual(d), Number::F64(f2)) => Number::Dual(d / f2),\n        (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d / d2),\n        (Number::Dual(_), Number::Dual2(_)) => panic!(\"Cannot mix dual types: Dual / Dual2\"),\n        (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d / f2),\n        (Number::Dual2(_), Number::Dual(_)) => panic!(\"Cannot mix dual types: Dual2 / Dual\"),\n        (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d / d2),\n    }\n});\n\n// Div for Number\nimpl_op_ex!(/ |a: &Number, b: &f64| -> Number {\n    match a {\n        Number::F64(f) => Number::F64(f / b),\n        Number::Dual(d) => Number::Dual(d / b),\n        Number::Dual2(d) => Number::Dual2(d / b),\n    }\n});\n\n// Div for Number\nimpl_op_ex!(/ |a: &f64, b: &Number| -> Number {\n    match b {\n        Number::F64(f) => Number::F64(a / f),\n        Number::Dual(d) => Number::Dual(a / d),\n        Number::Dual2(d) => Number::Dual2(a / d),\n    }\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn div_f64() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = d1 / 2.0;\n        let expected = Dual::try_new(\n            0.5,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![0.5, 1.0],\n        )\n        .unwrap();\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn f64_div() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = 2.0 / d1.clone();\n        let expected = Dual::new(2.0, vec![]) / d1;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn div() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let d2 = Dual::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![0.0, 3.0],\n        )\n        .unwrap();\n        let expected = Dual::try_new(\n            0.5,\n            vec![\"v0\".to_string(), \"v1\".to_string(), \"v2\".to_string()],\n            vec![0.5, 1.0, -0.75],\n        )\n        .unwrap();\n        let result = d1 / d2;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn div_f64_2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = d1 / 2.0;\n        let expected = Dual2::try_new(\n            0.5,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![0.5, 1.0],\n            Vec::new(),\n        )\n        .unwrap();\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn f64_div2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = 2.0 / d1.clone();\n        let expected = Dual2::new(2.0, vec![]) / d1;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn div2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let d2 = Dual2::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![0.0, 3.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let expected = Dual2::try_new(\n            0.5,\n            vec![\"v0\".to_string(), \"v1\".to_string(), \"v2\".to_string()],\n            vec![0.5, 1.0, -0.75],\n            vec![0., 0., -0.375, 0., 0., -0.75, -0.375, -0.75, 1.125],\n        )\n        .unwrap();\n        let result = d1 / d2;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn test_enum() {\n        let f = Number::F64(2.0);\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        assert_eq!(\n            &f / &d,\n            Number::Dual(\n                Dual::try_new(2.0 / 3.0, vec![\"x\".to_string()], vec![-2.0 / 9.0]).unwrap()\n            )\n        );\n\n        assert_eq!(\n            &d / &d,\n            Number::Dual(Dual::try_new(1.0, vec![\"x\".to_string()], vec![0.0]).unwrap())\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_enum_panic() {\n        let d = Number::Dual2(Dual2::new(2.0, vec![\"y\".to_string()]));\n        let d2 = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let _ = d / d2;\n    }\n\n    #[test]\n    fn test_enum_f64() {\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let res = 2.0_f64 / d;\n        assert_eq!(\n            res,\n            Number::Dual(2.0 / Dual::new(3.0, vec![\"x\".to_string()]))\n        );\n\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let res = d / 2.0_f64;\n        assert_eq!(\n            res,\n            Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]) / 2.0)\n        );\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/eq.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2, Vars, VarsRelationship};\nuse crate::dual::enums::Number;\n\n/// Measures value equivalence of `Dual`.\n///\n/// Returns `true` if:\n///\n/// - `real` components are equal: `lhs.real == rhs.real`.\n/// - `dual` components are equal after aligning `vars`.\nimpl PartialEq<Dual> for Dual {\n    fn eq(&self, other: &Dual) -> bool {\n        if self.real != other.real {\n            false\n        } else {\n            let state = self.vars_cmp(other.vars());\n            match state {\n                VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n                    self.dual.iter().eq(other.dual.iter())\n                }\n                _ => {\n                    let (x, y) = self.to_union_vars(other, Some(state));\n                    x.dual.iter().eq(y.dual.iter())\n                }\n            }\n        }\n    }\n}\n\nimpl PartialEq<f64> for Dual {\n    fn eq(&self, other: &f64) -> bool {\n        Dual::new(*other, [].to_vec()) == *self\n    }\n}\n\nimpl PartialEq<f64> for Dual2 {\n    fn eq(&self, other: &f64) -> bool {\n        Dual2::new(*other, Vec::new()) == *self\n    }\n}\n\nimpl PartialEq<Dual> for f64 {\n    fn eq(&self, other: &Dual) -> bool {\n        Dual::new(*self, [].to_vec()) == *other\n    }\n}\n\nimpl PartialEq<Dual2> for f64 {\n    fn eq(&self, other: &Dual2) -> bool {\n        Dual2::new(*self, Vec::new()) == *other\n    }\n}\n\nimpl PartialEq<Dual2> for Dual2 {\n    fn eq(&self, other: &Dual2) -> bool {\n        if self.real != other.real {\n            false\n        } else {\n            let state = self.vars_cmp(other.vars());\n            match state {\n                VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n                    self.dual.iter().eq(other.dual.iter())\n                        && self.dual2.iter().eq(other.dual2.iter())\n                }\n                _ => {\n                    let (x, y) = self.to_union_vars(other, Some(state));\n                    x.dual.iter().eq(y.dual.iter()) && x.dual2.iter().eq(y.dual2.iter())\n                }\n            }\n        }\n    }\n}\n\nimpl PartialEq<Number> for Number {\n    fn eq(&self, other: &Number) -> bool {\n        match (self, other) {\n            (Number::F64(f), Number::F64(f2)) => f == f2,\n            (Number::F64(f), Number::Dual(d2)) => f == d2,\n            (Number::F64(f), Number::Dual2(d2)) => f == d2,\n            (Number::Dual(d), Number::F64(f2)) => d == f2,\n            (Number::Dual(d), Number::Dual(d2)) => d == d2,\n            (Number::Dual(_), Number::Dual2(_)) => {\n                panic!(\"Cannot mix dual types: Dual == Dual2\")\n            }\n            (Number::Dual2(d), Number::F64(f2)) => d == f2,\n            (Number::Dual2(_), Number::Dual(_)) => {\n                panic!(\"Cannot mix dual types: Dual2 == Dual\")\n            }\n            (Number::Dual2(d), Number::Dual2(d2)) => d == d2,\n        }\n    }\n}\n\nimpl PartialEq<f64> for Number {\n    fn eq(&self, other: &f64) -> bool {\n        match self {\n            Number::F64(f) => f == other,\n            Number::Dual(d) => d == other,\n            Number::Dual2(d) => d == other,\n        }\n    }\n}\n\nimpl PartialEq<Number> for f64 {\n    fn eq(&self, other: &Number) -> bool {\n        match other {\n            Number::F64(f) => f == self,\n            Number::Dual(d) => d == self,\n            Number::Dual2(d) => d == self,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn eq_ne() {\n        // Dual with vars - f64\n        assert!(Dual::new(0.0, Vec::from([String::from(\"a\")])) != 0.0);\n        // Dual with no vars - f64 (+reverse)\n        assert!(Dual::new(2.0, Vec::new()) == 2.0);\n        assert!(2.0 == Dual::new(2.0, Vec::new()));\n        // Dual - Dual (various real, vars, gradient mismatch)\n        let d = Dual::try_new(2.0, Vec::from([String::from(\"a\")]), Vec::from([2.3])).unwrap();\n        assert!(d == Dual::try_new(2.0, Vec::from([String::from(\"a\")]), Vec::from([2.3])).unwrap());\n        assert!(d != Dual::try_new(2.0, Vec::from([String::from(\"b\")]), Vec::from([2.3])).unwrap());\n        assert!(d != Dual::try_new(3.0, Vec::from([String::from(\"a\")]), Vec::from([2.3])).unwrap());\n        assert!(d != Dual::try_new(2.0, Vec::from([String::from(\"a\")]), Vec::from([1.3])).unwrap());\n        // Dual - Dual (missing Vars are zero and upcasted)\n        assert!(\n            d == Dual::try_new(\n                2.0,\n                Vec::from([String::from(\"a\"), String::from(\"b\")]),\n                Vec::from([2.3, 0.0])\n            )\n            .unwrap()\n        );\n    }\n\n    #[test]\n    fn eq_ne2() {\n        // Dual with vars - f64\n        assert!(Dual2::new(0.0, Vec::from([String::from(\"a\")])) != 0.0);\n        // Dual with no vars - f64 (+reverse)\n        assert!(Dual2::new(2.0, Vec::new()) == 2.0);\n        assert!(2.0 == Dual2::new(2.0, Vec::new()));\n        // Dual - Dual (various real, vars, gradient mismatch)\n        let d = Dual2::try_new(\n            2.0,\n            Vec::from([String::from(\"a\")]),\n            Vec::from([2.3]),\n            Vec::new(),\n        )\n        .unwrap();\n        assert!(\n            d == Dual2::try_new(\n                2.0,\n                Vec::from([String::from(\"a\")]),\n                Vec::from([2.3]),\n                Vec::new()\n            )\n            .unwrap()\n        );\n        assert!(\n            d != Dual2::try_new(\n                2.0,\n                Vec::from([String::from(\"b\")]),\n                Vec::from([2.3]),\n                Vec::new()\n            )\n            .unwrap()\n        );\n        assert!(\n            d != Dual2::try_new(\n                3.0,\n                Vec::from([String::from(\"a\")]),\n                Vec::from([2.3]),\n                Vec::new()\n            )\n            .unwrap()\n        );\n        assert!(\n            d != Dual2::try_new(\n                2.0,\n                Vec::from([String::from(\"a\")]),\n                Vec::from([1.3]),\n                Vec::new()\n            )\n            .unwrap()\n        );\n        // Dual - Dual (missing Vars are zero and upcasted)\n        assert!(\n            d == Dual2::try_new(\n                2.0,\n                Vec::from([String::from(\"a\"), String::from(\"b\")]),\n                Vec::from([2.3, 0.0]),\n                Vec::new()\n            )\n            .unwrap()\n        );\n    }\n\n    #[test]\n    fn test_enum_ne() {\n        let d = Number::Dual(Dual::new(2.0, vec![\"x\".to_string()]));\n        let d2 = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        assert_ne!(d, d2)\n    }\n\n    #[test]\n    fn test_enum() {\n        let d = Number::Dual(Dual::new(2.0, vec![\"x\".to_string()]));\n        let d2 = Number::Dual(Dual::new(2.0, vec![\"x\".to_string()]));\n        assert_eq!(d, d2)\n    }\n\n    #[test]\n    fn test_cross_enum_eq() {\n        let f = Number::F64(2.5_f64);\n        let d = Number::Dual(Dual::new(2.5_f64, vec![]));\n        assert_eq!(f, d);\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_cross_enum_eq_error() {\n        let d2 = Number::Dual2(Dual2::new(2.5_f64, vec![]));\n        let d = Number::Dual(Dual::new(2.5_f64, vec![]));\n        assert_eq!(d2, d);\n    }\n\n    #[test]\n    fn test_enum_f64() {\n        let n = Number::Dual(Dual::new(2.0, vec![]));\n        assert!(n == 2.0);\n        assert!(2.0 == 2.0);\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/from.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse ndarray::{Array2, Axis};\n\nimpl From<Dual> for f64 {\n    fn from(value: Dual) -> Self {\n        value.real\n    }\n}\n\nimpl From<Dual2> for f64 {\n    fn from(value: Dual2) -> Self {\n        value.real\n    }\n}\n\nimpl From<&Dual> for f64 {\n    fn from(value: &Dual) -> Self {\n        value.real\n    }\n}\n\nimpl From<&Dual2> for f64 {\n    fn from(value: &Dual2) -> Self {\n        value.real\n    }\n}\n\nimpl From<f64> for Dual {\n    fn from(value: f64) -> Self {\n        Self::new(value, vec![])\n    }\n}\n\n// impl From<Dual> for Dual {\n//     fn from(value: Dual) -> Self {\n//         value.clone()\n//     }\n// }\n\nimpl From<Dual2> for Dual {\n    fn from(value: Dual2) -> Self {\n        Dual {\n            real: value.real,\n            vars: value.vars.clone(),\n            dual: value.dual,\n        }\n    }\n}\n\nimpl From<&Dual2> for Dual {\n    fn from(value: &Dual2) -> Self {\n        Dual {\n            real: value.real,\n            vars: value.vars.clone(),\n            dual: value.dual.clone(),\n        }\n    }\n}\n\nimpl From<f64> for Dual2 {\n    fn from(value: f64) -> Self {\n        Self::new(value, vec![])\n    }\n}\n\nimpl From<Dual> for Dual2 {\n    fn from(value: Dual) -> Self {\n        let n = value.dual.len_of(Axis(0));\n        Dual2 {\n            real: value.real,\n            vars: value.vars.clone(),\n            dual: value.dual,\n            dual2: Array2::zeros((n, n)),\n        }\n    }\n}\n\nimpl From<&Dual> for Dual2 {\n    fn from(value: &Dual) -> Self {\n        let n = value.dual.len_of(Axis(0));\n        Dual2 {\n            real: value.real,\n            vars: value.vars.clone(),\n            dual: value.dual.clone(),\n            dual2: Array2::zeros((n, n)),\n        }\n    }\n}\n\nimpl From<Number> for f64 {\n    fn from(value: Number) -> Self {\n        match value {\n            Number::F64(f) => f,\n            Number::Dual(d) => d.real,\n            Number::Dual2(d) => d.real,\n        }\n    }\n}\n\nimpl From<Number> for Dual {\n    fn from(value: Number) -> Self {\n        match value {\n            Number::F64(f) => Dual::new(f, vec![]),\n            Number::Dual(d) => d,\n            Number::Dual2(d) => Dual::from(d),\n        }\n    }\n}\n\nimpl From<Number> for Dual2 {\n    fn from(value: Number) -> Self {\n        match value {\n            Number::F64(f) => Dual2::new(f, vec![]),\n            Number::Dual(d) => Dual2::from(d),\n            Number::Dual2(d) => d,\n        }\n    }\n}\n\nimpl From<&Number> for f64 {\n    fn from(value: &Number) -> Self {\n        match value {\n            Number::F64(f) => *f,\n            Number::Dual(d) => d.real,\n            Number::Dual2(d) => d.real,\n        }\n    }\n}\n\nimpl From<&Number> for Dual {\n    fn from(value: &Number) -> Self {\n        match value {\n            Number::F64(f) => Dual::new(*f, vec![]),\n            Number::Dual(d) => d.clone(),\n            Number::Dual2(d) => Dual::from(d),\n        }\n    }\n}\n\nimpl From<&Number> for Dual2 {\n    fn from(value: &Number) -> Self {\n        match value {\n            Number::F64(f) => Dual2::new(*f, vec![]),\n            Number::Dual(d) => Dual2::from(d),\n            Number::Dual2(d) => d.clone(),\n        }\n    }\n}\n\nimpl From<&f64> for Number {\n    fn from(value: &f64) -> Self {\n        Number::F64(*value)\n    }\n}\n\nimpl From<f64> for Number {\n    fn from(value: f64) -> Self {\n        Number::F64(value)\n    }\n}\n\nimpl From<&Dual> for Number {\n    fn from(value: &Dual) -> Self {\n        Number::Dual(value.clone())\n    }\n}\n\nimpl From<Dual> for Number {\n    fn from(value: Dual) -> Self {\n        Number::Dual(value)\n    }\n}\n\nimpl From<&Dual2> for Number {\n    fn from(value: &Dual2) -> Self {\n        Number::Dual2(value.clone())\n    }\n}\n\nimpl From<Dual2> for Number {\n    fn from(value: Dual2) -> Self {\n        Number::Dual2(value)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn from_dual_into_dual2() {\n        let d1 = Dual::new(2.5, vec![\"x\".to_string(), \"y\".to_string()]);\n        let d2: Dual2 = d1.into();\n        let result = Dual2::new(2.5, vec![\"x\".to_string(), \"y\".to_string()]);\n        assert_eq!(d2, result);\n    }\n\n    #[test]\n    fn from_dual_into_f64() {\n        let d1 = Dual::new(2.5, vec![\"x\".to_string(), \"y\".to_string()]);\n        let d2: f64 = d1.into();\n        let result = 2.5_f64;\n        assert_eq!(d2, result);\n    }\n\n    // #[test]\n    // fn from_dual_to_dual_unchanged() {\n    //     let d1 = Dual::new(2.5, vec![\"x\".to_string(), \"y\".to_string()]);\n    //     let d2: Dual = Dual::from(d1);\n    //     assert_eq!(d2, d1);\n    // }\n\n    #[test]\n    fn from_f64_into_dual() {\n        let d = Dual::from(4.0_f64);\n        assert_eq!(d, Dual::new(4.0_f64, vec![]));\n    }\n\n    #[test]\n    fn from_dual2_into_dual() {\n        let d2: Dual2 = Dual2::new(2.0, vec![\"x\".to_string(), \"y\".to_string()]);\n        let d1: Dual = d2.into();\n        let result = Dual::new(2.0, vec![\"x\".to_string(), \"y\".to_string()]);\n        assert_eq!(d1, result);\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/math_funcs.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse crate::dual::linalg::fouter11_;\nuse num_traits::Pow;\nuse statrs::distribution::{ContinuousCDF, Normal};\nuse std::f64::consts::PI;\nuse std::sync::Arc;\n\n/// Functions for common mathematical operations.\npub trait MathFuncs {\n    /// Return the exponential of a value.\n    fn exp(&self) -> Self;\n    /// Return the natural logarithm of a value.\n    fn log(&self) -> Self;\n    /// Return the standard normal cumulative distribution function of a value.\n    fn norm_cdf(&self) -> Self;\n    /// Return the inverse standard normal cumulative distribution function of a value.\n    fn inv_norm_cdf(&self) -> Self;\n}\n\nimpl MathFuncs for Dual {\n    fn exp(&self) -> Self {\n        let c = self.real.exp();\n        Dual {\n            real: c,\n            vars: Arc::clone(&self.vars),\n            dual: c * &self.dual,\n        }\n    }\n    fn log(&self) -> Self {\n        Dual {\n            real: self.real.ln(),\n            vars: Arc::clone(&self.vars),\n            dual: (1.0 / self.real) * &self.dual,\n        }\n    }\n    fn norm_cdf(&self) -> Self {\n        let n = Normal::new(0.0, 1.0).unwrap();\n        let base = n.cdf(self.real);\n        let scalar = 1.0 / (2.0 * PI).sqrt() * (-0.5_f64 * self.real.pow(2.0_f64)).exp();\n        Dual {\n            real: base,\n            vars: Arc::clone(&self.vars),\n            dual: scalar * &self.dual,\n        }\n    }\n    fn inv_norm_cdf(&self) -> Self {\n        let n = Normal::new(0.0, 1.0).unwrap();\n        let base = n.inverse_cdf(self.real);\n        let scalar = (2.0 * PI).sqrt() * (0.5_f64 * base.pow(2.0_f64)).exp();\n        Dual {\n            real: base,\n            vars: Arc::clone(&self.vars),\n            dual: scalar * &self.dual,\n        }\n    }\n}\n\nimpl MathFuncs for Dual2 {\n    fn exp(&self) -> Self {\n        let c = self.real.exp();\n        Dual2 {\n            real: c,\n            vars: Arc::clone(&self.vars),\n            dual: c * &self.dual,\n            dual2: c * (&self.dual2 + 0.5 * fouter11_(&self.dual.view(), &self.dual.view())),\n        }\n    }\n    fn log(&self) -> Self {\n        let scalar = 1.0 / self.real;\n        Dual2 {\n            real: self.real.ln(),\n            vars: Arc::clone(&self.vars),\n            dual: scalar * &self.dual,\n            dual2: scalar * &self.dual2\n                - fouter11_(&self.dual.view(), &self.dual.view()) * 0.5 * (scalar * scalar),\n        }\n    }\n    fn norm_cdf(&self) -> Self {\n        let n = Normal::new(0.0, 1.0).unwrap();\n        let base = n.cdf(self.real);\n        let scalar = 1.0 / (2.0 * PI).sqrt() * (-0.5_f64 * self.real.pow(2.0_f64)).exp();\n        let scalar2 = scalar * -self.real;\n        let cross_beta = fouter11_(&self.dual.view(), &self.dual.view());\n        Dual2 {\n            real: base,\n            vars: Arc::clone(&self.vars),\n            dual: scalar * &self.dual,\n            dual2: scalar * &self.dual2 + 0.5_f64 * scalar2 * cross_beta,\n        }\n    }\n    fn inv_norm_cdf(&self) -> Self {\n        let n = Normal::new(0.0, 1.0).unwrap();\n        let base = n.inverse_cdf(self.real);\n        let scalar = (2.0 * PI).sqrt() * (0.5_f64 * base.pow(2.0_f64)).exp();\n        let scalar2 = scalar.pow(2.0_f64) * base;\n        let cross_beta = fouter11_(&self.dual.view(), &self.dual.view());\n        Dual2 {\n            real: base,\n            vars: Arc::clone(&self.vars),\n            dual: scalar * &self.dual,\n            dual2: scalar * &self.dual2 + 0.5_f64 * scalar2 * cross_beta,\n        }\n    }\n}\n\nimpl MathFuncs for f64 {\n    fn inv_norm_cdf(&self) -> Self {\n        Normal::new(0.0, 1.0).unwrap().inverse_cdf(*self)\n    }\n    fn norm_cdf(&self) -> Self {\n        Normal::new(0.0, 1.0).unwrap().cdf(*self)\n    }\n    fn exp(&self) -> Self {\n        f64::exp(*self)\n    }\n    fn log(&self) -> Self {\n        f64::ln(*self)\n    }\n}\n\nmacro_rules! math_func {\n    ($self: ident, $name: ident) => {\n        match $self {\n            Number::F64(f) => Number::F64(f.$name()),\n            Number::Dual(d) => Number::Dual(d.$name()),\n            Number::Dual2(d) => Number::Dual2(d.$name()),\n        }\n    };\n}\n\nimpl MathFuncs for Number {\n    fn inv_norm_cdf(&self) -> Self {\n        math_func!(self, inv_norm_cdf)\n    }\n    fn norm_cdf(&self) -> Self {\n        math_func!(self, norm_cdf)\n    }\n    fn exp(&self) -> Self {\n        math_func!(self, exp)\n    }\n    fn log(&self) -> Self {\n        math_func!(self, log)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn exp() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = d1.exp();\n        assert!(Arc::ptr_eq(&d1.vars, &result.vars));\n        let c = 1.0_f64.exp();\n        let expected = Dual::try_new(\n            c,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0 * c, 2.0 * c],\n        )\n        .unwrap();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn log() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = d1.log();\n        assert!(Arc::ptr_eq(&d1.vars, &result.vars));\n        let c = 1.0_f64.ln();\n        let expected =\n            Dual::try_new(c, vec![\"v0\".to_string(), \"v1\".to_string()], vec![1.0, 2.0]).unwrap();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn exp2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = d1.exp();\n        assert!(Arc::ptr_eq(&d1.vars, &result.vars));\n        let c = 1.0_f64.exp();\n        let expected = Dual2::try_new(\n            c,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0 * c, 2.0 * c],\n            vec![\n                1.0_f64.exp() * 0.5,\n                1.0_f64.exp(),\n                1.0_f64.exp(),\n                1.0_f64.exp() * 2.,\n            ],\n        )\n        .unwrap();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn log2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = d1.log();\n        assert!(Arc::ptr_eq(&d1.vars, &result.vars));\n        let c = 1.0_f64.ln();\n        let expected = Dual2::try_new(\n            c,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            vec![-0.5, -1.0, -1.0, -2.0],\n        )\n        .unwrap();\n        println!(\"{:?}\", result.dual2);\n        assert_eq!(result, expected);\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nmod add;\npub mod convert;\nmod div;\nmod eq;\nmod from;\npub mod math_funcs;\nmod mul;\nmod neg;\nmod num;\npub mod numeric_ops;\nmod one;\nmod ord;\nmod pow;\nmod rem;\nmod signed;\nmod sub;\nmod sum;\nmod zero;\n"
  },
  {
    "path": "rust/dual/dual_ops/mul.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2, Vars, VarsRelationship};\nuse crate::dual::enums::Number;\nuse crate::dual::linalg::fouter11_;\nuse auto_ops::{impl_op_ex, impl_op_ex_commutative};\nuse ndarray::Array2;\nuse std::sync::Arc;\n\n// Mul\nimpl_op_ex_commutative!(*|a: &Dual, b: &f64| -> Dual {\n    Dual {\n        vars: Arc::clone(&a.vars),\n        real: a.real * b,\n        dual: *b * &a.dual,\n    }\n});\nimpl_op_ex_commutative!(*|a: &Dual2, b: &f64| -> Dual2 {\n    Dual2 {\n        vars: Arc::clone(&a.vars),\n        real: a.real * b,\n        dual: *b * &a.dual,\n        dual2: *b * &a.dual2,\n    }\n});\n\n// impl Mul for Dual\nimpl_op_ex!(*|a: &Dual, b: &Dual| -> Dual {\n    let state = a.vars_cmp(b.vars());\n    match state {\n        VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => Dual {\n            real: a.real * b.real,\n            dual: &a.dual * b.real + &b.dual * a.real,\n            vars: Arc::clone(&a.vars),\n        },\n        _ => {\n            let (x, y) = a.to_union_vars(b, Some(state));\n            Dual {\n                real: x.real * y.real,\n                dual: &x.dual * y.real + &y.dual * x.real,\n                vars: Arc::clone(&x.vars),\n            }\n        }\n    }\n});\n\n// impl Mul for Dual2\nimpl_op_ex!(*|a: &Dual2, b: &Dual2| -> Dual2 {\n    let state = a.vars_cmp(b.vars());\n    match state {\n        VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n            let mut dual2: Array2<f64> = &a.dual2 * b.real + &b.dual2 * a.real;\n            let cross_beta = fouter11_(&a.dual.view(), &b.dual.view());\n            dual2 = dual2 + 0.5_f64 * (&cross_beta + &cross_beta.t());\n            Dual2 {\n                real: a.real * b.real,\n                dual: &a.dual * b.real + &b.dual * a.real,\n                vars: Arc::clone(&a.vars),\n                dual2,\n            }\n        }\n        _ => {\n            let (x, y) = a.to_union_vars(b, Some(state));\n            let mut dual2: Array2<f64> = &x.dual2 * y.real + &y.dual2 * x.real;\n            let cross_beta = fouter11_(&x.dual.view(), &y.dual.view());\n            dual2 = dual2 + 0.5_f64 * (&cross_beta + &cross_beta.t());\n            Dual2 {\n                real: x.real * y.real,\n                dual: &x.dual * y.real + &y.dual * x.real,\n                vars: Arc::clone(&x.vars),\n                dual2,\n            }\n        }\n    }\n});\n\n// Mul for Number\nimpl_op_ex!(*|a: &Number, b: &Number| -> Number {\n    match (a, b) {\n        (Number::F64(f), Number::F64(f2)) => Number::F64(f * f2),\n        (Number::F64(f), Number::Dual(d2)) => Number::Dual(f * d2),\n        (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f * d2),\n        (Number::Dual(d), Number::F64(f2)) => Number::Dual(d * f2),\n        (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d * d2),\n        (Number::Dual(_), Number::Dual2(_)) => {\n            panic!(\"Cannot mix dual types: Dual * Dual2\")\n        }\n        (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d * f2),\n        (Number::Dual2(_), Number::Dual(_)) => {\n            panic!(\"Cannot mix dual types: Dual2 * Dual\")\n        }\n        (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d * d2),\n    }\n});\n\n// Mul for Number\nimpl_op_ex_commutative!(*|a: &Number, b: &f64| -> Number {\n    match a {\n        Number::F64(f) => Number::F64(f * b),\n        Number::Dual(d) => Number::Dual(d * b),\n        Number::Dual2(d) => Number::Dual2(d * b),\n    }\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn mul_f64() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = 10.0 * d1 * 2.0;\n        let expected = Dual::try_new(\n            20.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![20.0, 40.0],\n        )\n        .unwrap();\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn mul() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let d2 = Dual::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![0.0, 3.0],\n        )\n        .unwrap();\n        let expected = Dual::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v1\".to_string(), \"v2\".to_string()],\n            vec![2.0, 4.0, 3.0],\n        )\n        .unwrap();\n        let result = d1 * d2;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn mul_f64_2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = 10.0 * d1 * 2.0;\n        let expected = Dual2::try_new(\n            20.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![20.0, 40.0],\n            Vec::new(),\n        )\n        .unwrap();\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn mul2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let d2 = Dual2::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![0.0, 3.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let expected = Dual2::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v1\".to_string(), \"v2\".to_string()],\n            vec![2.0, 4.0, 3.0],\n            vec![0., 0., 1.5, 0., 0., 3., 1.5, 3., 0.],\n        )\n        .unwrap();\n        let result = d1 * d2;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn test_enum() {\n        let f = Number::F64(2.0);\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        assert_eq!(\n            &f * &d,\n            Number::Dual(Dual::try_new(6.0, vec![\"x\".to_string()], vec![2.0]).unwrap())\n        );\n\n        assert_eq!(\n            &d * &d,\n            Number::Dual(Dual::try_new(9.0, vec![\"x\".to_string()], vec![6.0]).unwrap())\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_enum_panic() {\n        let d = Number::Dual2(Dual2::new(2.0, vec![\"y\".to_string()]));\n        let d2 = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let _ = d * d2;\n    }\n\n    #[test]\n    fn test_enum_f64() {\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let res = 2.0_f64 * d;\n        assert_eq!(\n            res,\n            Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]) * 2.0)\n        );\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/neg.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse auto_ops::impl_op;\nuse std::sync::Arc;\n\nimpl_op!(-|a: Dual| -> Dual {\n    Dual {\n        vars: a.vars,\n        real: -a.real,\n        dual: -a.dual,\n    }\n});\nimpl_op!(-|a: &Dual| -> Dual {\n    Dual {\n        vars: Arc::clone(&a.vars),\n        real: -a.real,\n        dual: &a.dual * -1.0,\n    }\n});\n\nimpl_op!(-|a: Dual2| -> Dual2 {\n    Dual2 {\n        vars: a.vars,\n        real: -a.real,\n        dual: -a.dual,\n        dual2: -a.dual2,\n    }\n});\n\nimpl_op!(-|a: &Dual2| -> Dual2 {\n    Dual2 {\n        vars: Arc::clone(&a.vars),\n        real: -a.real,\n        dual: &a.dual * -1.0,\n        dual2: &a.dual2 * -1.0,\n    }\n});\n\n// Neg for Number\nimpl_op!(-|a: &Number| -> Number {\n    match a {\n        Number::F64(f) => Number::F64(-f),\n        Number::Dual(d) => Number::Dual(-d),\n        Number::Dual2(d) => Number::Dual2(-d),\n    }\n});\n\n// Neg for Number\nimpl_op!(-|a: Number| -> Number {\n    match a {\n        Number::F64(f) => Number::F64(-f),\n        Number::Dual(d) => Number::Dual(-d),\n        Number::Dual2(d) => Number::Dual2(-d),\n    }\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use ndarray::Array1;\n\n    #[test]\n    fn negate() {\n        let d = Dual::try_new(\n            2.3,\n            Vec::from([String::from(\"a\"), String::from(\"b\")]),\n            Vec::from([2., -1.4]),\n        )\n        .unwrap();\n        let d2 = -d.clone();\n        assert!(d2.real == -2.3);\n        assert!(Arc::ptr_eq(&d.vars, &d2.vars));\n        assert!(d2.dual[0] == -2.0);\n        assert!(d2.dual[1] == 1.4);\n    }\n\n    #[test]\n    fn neg_ref() {\n        let d1 =\n            Dual::try_new(2.5, vec![\"x\".to_string(), \"y\".to_string()], vec![1.1, 2.2]).unwrap();\n        let d2 = -&d1;\n        assert_eq!(d2.real, -2.5);\n        assert_eq!(d2.dual, Array1::from_vec(vec![-1.1, -2.2]));\n    }\n\n    #[test]\n    fn negate2() {\n        let d = Dual2::try_new(\n            2.3,\n            Vec::from([String::from(\"a\"), String::from(\"b\")]),\n            Vec::from([2., -1.4]),\n            Vec::from([1.0, -1.0, -1.0, 2.0]),\n        )\n        .unwrap();\n        let d2 = -d.clone();\n        assert!(d2.real == -2.3);\n        assert!(Arc::ptr_eq(&d.vars, &d2.vars));\n        assert!(d2.dual[0] == -2.0);\n        assert!(d2.dual[1] == 1.4);\n        assert!(d2.dual2[[1, 0]] == 1.0);\n    }\n\n    #[test]\n    fn negate_ref2() {\n        let d = Dual2::try_new(\n            2.3,\n            Vec::from([String::from(\"a\"), String::from(\"b\")]),\n            Vec::from([2., -1.4]),\n            Vec::from([1.0, -1.0, -1.0, 2.0]),\n        )\n        .unwrap();\n        let d2 = -&d;\n        assert!(d2.real == -2.3);\n        assert!(Arc::ptr_eq(&d.vars, &d2.vars));\n        assert!(d2.dual[0] == -2.0);\n        assert!(d2.dual[1] == 1.4);\n        assert!(d2.dual2[[1, 0]] == 1.0);\n    }\n\n    #[test]\n    fn test_enum() {\n        let f = Number::F64(2.0);\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        assert_eq!(-f, Number::F64(-2.0));\n        assert_eq!(\n            -d,\n            Number::Dual(Dual::try_new(-3.0, vec![\"x\".to_string()], vec![-1.0]).unwrap())\n        );\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/num.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse num_traits::Num;\n\nimpl Num for Dual {\n    // PartialEq + Zero + One + NumOps (Add + Sub + Mul + Div + Rem)\n    type FromStrRadixErr = String;\n    fn from_str_radix(_src: &str, _radix: u32) -> Result<Self, Self::FromStrRadixErr> {\n        Err(\"No implementation for sting radix for Dual\".to_string())\n    }\n}\n\nimpl Num for Dual2 {\n    type FromStrRadixErr = String;\n    fn from_str_radix(_src: &str, _radix: u32) -> Result<Self, Self::FromStrRadixErr> {\n        Err(\"No implementation for sting radix for Dual2\".to_string())\n    }\n}\n\nimpl Num for Number {\n    type FromStrRadixErr = String;\n    fn from_str_radix(_src: &str, _radix: u32) -> Result<Self, Self::FromStrRadixErr> {\n        Err(\"No implementation for sting radix for Number\".to_string())\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/numeric_ops.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse std::ops::{Add, Div, Mul, Sub};\n\npub trait NumberOps<T>:\n    Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Sized + Clone\n{\n}\nimpl<'a, T: 'a> NumberOps<T> for &'a T where\n    &'a T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>\n{\n}\nimpl NumberOps<Dual> for Dual {}\nimpl NumberOps<Dual2> for Dual2 {}\nimpl NumberOps<f64> for f64 {}\nimpl NumberOps<Number> for Number {}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_fieldops() {\n        fn test_ops<T>(a: &T, b: &T) -> T\n        where\n            for<'a> &'a T: NumberOps<T>,\n        {\n            &(a + b) - a\n        }\n\n        fn test_ops2<T>(a: T, b: T) -> T\n        where\n            T: NumberOps<T>,\n        {\n            (a.clone() + b) - a\n        }\n\n        let x = 1.0;\n        let y = 2.0;\n        let z = test_ops(&x, &y);\n        println!(\"{:?}\", z);\n\n        let z = test_ops2(x, y);\n        println!(\"{:?}\", z);\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/one.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse num_traits::One;\n\nimpl One for Dual {\n    fn one() -> Dual {\n        Dual::new(1.0, Vec::new())\n    }\n}\n\nimpl One for Dual2 {\n    fn one() -> Dual2 {\n        Dual2::new(1.0, Vec::new())\n    }\n}\n\nimpl One for Number {\n    fn one() -> Number {\n        Number::F64(1.0_f64)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn one() {\n        let d = Dual::one();\n        assert_eq!(d, Dual::new(1.0, vec![]));\n    }\n\n    #[test]\n    fn one2() {\n        let d = Dual2::one();\n        assert_eq!(d, Dual2::new(1.0, vec![]));\n    }\n\n    #[test]\n    fn one_enum() {\n        let d = Number::one();\n        assert_eq!(d, Number::F64(1.0));\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/ord.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse std::cmp::Ordering;\n\n/// Compares `Dual` by `real` component only.\nimpl PartialOrd<Dual> for Dual {\n    fn partial_cmp(&self, other: &Dual) -> Option<Ordering> {\n        self.real.partial_cmp(&other.real)\n    }\n}\n\nimpl PartialOrd<f64> for Dual {\n    fn partial_cmp(&self, other: &f64) -> Option<Ordering> {\n        self.real.partial_cmp(other)\n    }\n}\n\nimpl PartialOrd<f64> for Dual2 {\n    fn partial_cmp(&self, other: &f64) -> Option<Ordering> {\n        self.real.partial_cmp(other)\n    }\n}\n\nimpl PartialOrd<Dual2> for Dual2 {\n    fn partial_cmp(&self, other: &Dual2) -> Option<Ordering> {\n        self.real.partial_cmp(&other.real)\n    }\n}\n\nimpl PartialOrd<Dual> for f64 {\n    fn partial_cmp(&self, other: &Dual) -> Option<Ordering> {\n        self.partial_cmp(&other.real)\n    }\n}\n\nimpl PartialOrd<Dual2> for f64 {\n    fn partial_cmp(&self, other: &Dual2) -> Option<Ordering> {\n        self.partial_cmp(&other.real)\n    }\n}\n\nimpl PartialOrd<Number> for Number {\n    fn partial_cmp(&self, other: &Number) -> Option<Ordering> {\n        match (self, other) {\n            (Number::F64(f), Number::F64(f2)) => f.partial_cmp(f2),\n            (Number::F64(f), Number::Dual(d2)) => f.partial_cmp(d2),\n            (Number::F64(f), Number::Dual2(d2)) => f.partial_cmp(d2),\n            (Number::Dual(d), Number::F64(f2)) => d.partial_cmp(f2),\n            (Number::Dual(d), Number::Dual(d2)) => d.partial_cmp(d2),\n            (Number::Dual(_), Number::Dual2(_)) => {\n                panic!(\"Cannot mix dual types: Dual compare Dual2\")\n            }\n            (Number::Dual2(d), Number::F64(f2)) => d.partial_cmp(f2),\n            (Number::Dual2(_), Number::Dual(_)) => {\n                panic!(\"Cannot mix dual types: Dual2 compare Dual\")\n            }\n            (Number::Dual2(d), Number::Dual2(d2)) => d.partial_cmp(d2),\n        }\n    }\n}\n\nimpl PartialOrd<f64> for Number {\n    fn partial_cmp(&self, other: &f64) -> Option<Ordering> {\n        match self {\n            Number::F64(f) => f.partial_cmp(other),\n            Number::Dual(d) => d.partial_cmp(other),\n            Number::Dual2(d) => d.partial_cmp(other),\n        }\n    }\n}\n\nimpl PartialOrd<Number> for f64 {\n    fn partial_cmp(&self, other: &Number) -> Option<Ordering> {\n        match other {\n            Number::F64(f) => self.partial_cmp(f),\n            Number::Dual(d) => self.partial_cmp(d),\n            Number::Dual2(d) => self.partial_cmp(d),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn ord() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        assert!(d1 < 2.0);\n        assert!(d1 > 0.5);\n        assert!(d1 <= 1.0);\n        assert!(d1 >= 1.0);\n        assert!(1.0 <= d1);\n        assert!(1.0 >= d1);\n        assert!(2.0 > d1);\n        assert!(0.5 < d1);\n        let d2 = Dual::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        assert!(d2 > d1);\n        assert!(d1 < d2);\n        let d3 = Dual::try_new(1.0, vec![\"v3\".to_string()], vec![10.0]).unwrap();\n        assert!(d1 >= d3);\n        assert!(d1 <= d3);\n    }\n\n    #[test]\n    fn ord2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        assert!(d1 < 2.0);\n        assert!(d1 > 0.5);\n        assert!(d1 <= 1.0);\n        assert!(d1 >= 1.0);\n        assert!(1.0 <= d1);\n        assert!(1.0 >= d1);\n        assert!(2.0 > d1);\n        assert!(0.5 < d1);\n        let d2 = Dual2::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        assert!(d2 > d1);\n        assert!(d1 < d2);\n        let d3 = Dual2::try_new(1.0, vec![\"v3\".to_string()], vec![10.0], Vec::new()).unwrap();\n        assert!(d1 >= d3);\n        assert!(d1 <= d3);\n    }\n\n    #[test]\n    fn test_enum() {\n        let d = Number::Dual(Dual::new(2.0, vec![\"x\".to_string()]));\n        let d2 = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        assert!(d <= d2)\n    }\n\n    #[test]\n    fn test_cross_enum_eq() {\n        let f = Number::F64(2.5_f64);\n        let d = Number::Dual(Dual::new(3.5_f64, vec![]));\n        assert!(f <= d);\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_cross_enum_eq_error() {\n        let d2 = Number::Dual2(Dual2::new(2.5_f64, vec![]));\n        let d = Number::Dual(Dual::new(2.5_f64, vec![]));\n        assert!(d <= d2);\n    }\n\n    #[test]\n    fn test_cross_enum_f64() {\n        let d2 = Number::Dual2(Dual2::new(2.5_f64, vec![]));\n        assert!(d2 <= 3.0_f64);\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/pow.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2, Vars, VarsRelationship};\nuse crate::dual::enums::Number;\nuse crate::dual::linalg::fouter11_;\nuse num_traits::Pow;\nuse std::sync::Arc;\n\nimpl Pow<&Dual> for f64 {\n    type Output = Dual;\n    fn pow(self, power: &Dual) -> Self::Output {\n        Dual {\n            real: self.pow(power.real),\n            vars: Arc::clone(power.vars()),\n            dual: &power.dual * self.pow(power.real) * self.ln(),\n        }\n    }\n}\n\nimpl Pow<Dual> for f64 {\n    type Output = Dual;\n    fn pow(self, power: Dual) -> Self::Output {\n        Dual {\n            real: self.pow(power.real),\n            vars: power.vars,\n            dual: power.dual * self.pow(power.real) * self.ln(),\n        }\n    }\n}\n\nimpl Pow<&Dual> for &f64 {\n    type Output = Dual;\n    fn pow(self, power: &Dual) -> Self::Output {\n        (*self).pow(power)\n    }\n}\n\nimpl Pow<Dual> for &f64 {\n    type Output = Dual;\n    fn pow(self, power: Dual) -> Self::Output {\n        (*self).pow(power)\n    }\n}\n\nimpl Pow<&Dual2> for f64 {\n    type Output = Dual2;\n    fn pow(self, power: &Dual2) -> Self::Output {\n        let df_dp = self.ln() * self.pow(power.real);\n        let d2f_dp2 = df_dp * self.ln();\n        Dual2 {\n            real: self.pow(power.real),\n            vars: Arc::clone(power.vars()),\n            dual: &power.dual * self.pow(power.real) * self.ln(),\n            dual2: df_dp * &power.dual2\n                + 0.5_f64 * d2f_dp2 * fouter11_(&power.dual.view(), &power.dual.view()),\n        }\n    }\n}\n\nimpl Pow<Dual2> for f64 {\n    type Output = Dual2;\n    fn pow(self, power: Dual2) -> Self::Output {\n        let df_dp = self.ln() * self.pow(power.real);\n        let d2f_dp2 = df_dp * self.ln();\n        Dual2 {\n            real: self.pow(power.real),\n            vars: power.vars,\n            dual: &power.dual * self.pow(power.real) * self.ln(),\n            dual2: df_dp * &power.dual2\n                + 0.5_f64 * d2f_dp2 * fouter11_(&power.dual.view(), &power.dual.view()),\n        }\n    }\n}\n\nimpl Pow<&Dual2> for &f64 {\n    type Output = Dual2;\n    fn pow(self, power: &Dual2) -> Self::Output {\n        (*self).pow(power)\n    }\n}\n\nimpl Pow<Dual2> for &f64 {\n    type Output = Dual2;\n    fn pow(self, power: Dual2) -> Self::Output {\n        (*self).pow(power)\n    }\n}\n\nimpl Pow<f64> for Dual {\n    type Output = Dual;\n    fn pow(self, power: f64) -> Self::Output {\n        Dual {\n            real: self.real.pow(power),\n            vars: self.vars,\n            dual: self.dual * power * self.real.pow(power - 1.0),\n        }\n    }\n}\n\nimpl Pow<&f64> for Dual {\n    type Output = Dual;\n    fn pow(self, power: &f64) -> Self::Output {\n        self.pow(*power)\n    }\n}\n\nimpl Pow<f64> for &Dual {\n    type Output = Dual;\n    fn pow(self, power: f64) -> Self::Output {\n        Dual {\n            real: self.real.pow(power),\n            vars: Arc::clone(self.vars()),\n            dual: &self.dual * power * self.real.pow(power - 1.0),\n        }\n    }\n}\n\nimpl Pow<&f64> for &Dual {\n    type Output = Dual;\n    fn pow(self, power: &f64) -> Self::Output {\n        self.pow(*power)\n    }\n}\n\nimpl Pow<&Dual> for &Dual {\n    type Output = Dual;\n    fn pow(self, power: &Dual) -> Self::Output {\n        let state = self.vars_cmp(power.vars());\n        match state {\n            VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => Dual {\n                real: self.real.pow(power.real),\n                vars: Arc::clone(&self.vars),\n                dual: power.real * self.real.pow(power.real - 1_f64) * &self.dual\n                    + self.real.ln() * self.real.pow(power.real) * &power.dual,\n            },\n            _ => {\n                let (z, p) = self.to_union_vars(power, None);\n                Dual {\n                    real: z.real.pow(p.real),\n                    vars: Arc::clone(z.vars()),\n                    dual: p.real * z.real.pow(p.real - 1_f64) * &z.dual\n                        + z.real.ln() * z.real.pow(p.real) * &p.dual,\n                }\n            }\n        }\n    }\n}\n\nimpl Pow<&Dual> for Dual {\n    type Output = Dual;\n    fn pow(self, power: &Dual) -> Self::Output {\n        (&self).pow(power)\n    }\n}\n\nimpl Pow<Dual> for &Dual {\n    type Output = Dual;\n    fn pow(self, power: Dual) -> Self::Output {\n        self.pow(&power)\n    }\n}\n\nimpl Pow<Dual> for Dual {\n    type Output = Dual;\n    fn pow(self, power: Dual) -> Self::Output {\n        (&self).pow(&power)\n    }\n}\n\nimpl Pow<f64> for Dual2 {\n    type Output = Dual2;\n    fn pow(self, power: f64) -> Self::Output {\n        let coeff = power * self.real.powf(power - 1.);\n        let coeff2 = 0.5 * power * (power - 1.) * self.real.powf(power - 2.);\n        let beta_cross = fouter11_(&self.dual.view(), &self.dual.view());\n        Dual2 {\n            real: self.real.powf(power),\n            vars: self.vars,\n            dual: self.dual * coeff,\n            dual2: self.dual2 * coeff + beta_cross * coeff2,\n        }\n    }\n}\n\nimpl Pow<&f64> for Dual2 {\n    type Output = Dual2;\n    fn pow(self, power: &f64) -> Self::Output {\n        self.pow(*power)\n    }\n}\n\nimpl Pow<f64> for &Dual2 {\n    type Output = Dual2;\n    fn pow(self, power: f64) -> Self::Output {\n        let coeff = power * self.real.powf(power - 1.);\n        let coeff2 = 0.5 * power * (power - 1.) * self.real.powf(power - 2.);\n        let beta_cross = fouter11_(&self.dual.view(), &self.dual.view());\n        Dual2 {\n            real: self.real.powf(power),\n            vars: Arc::clone(self.vars()),\n            dual: &self.dual * coeff,\n            dual2: &self.dual2 * coeff + beta_cross * coeff2,\n        }\n    }\n}\n\nimpl Pow<&f64> for &Dual2 {\n    type Output = Dual2;\n    fn pow(self, power: &f64) -> Self::Output {\n        self.pow(*power)\n    }\n}\n\nimpl Pow<&Dual2> for &Dual2 {\n    type Output = Dual2;\n    fn pow(self, power: &Dual2) -> Self::Output {\n        let state = self.vars_cmp(power.vars());\n        match state {\n            VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => {\n                let f_z = power.real * self.real.pow(power.real - 1_f64);\n                let f_p = self.real.pow(power.real) * self.real.ln();\n                let f_zz = power.real * (power.real - 1_f64) * self.real.pow(power.real - 2_f64);\n                let f_pp = self.real.ln() * self.real.ln() * self.real.pow(power.real);\n                let f_pz =\n                    (power.real * self.real.ln() + 1_f64) * self.real.pow(power.real - 1_f64);\n                let cross_beta = fouter11_(&power.dual.view(), &self.dual.view());\n                Dual2 {\n                    real: self.real.pow(power.real),\n                    vars: Arc::clone(self.vars()),\n                    dual: f_z * &self.dual + f_p * &power.dual,\n                    dual2: f_z * &self.dual2\n                        + f_p * &power.dual2\n                        + 0.5_f64 * f_zz * fouter11_(&self.dual.view(), &self.dual.view())\n                        + 0.5_f64 * f_pz * (&cross_beta + &cross_beta.t())\n                        + 0.5_f64 * f_pp * fouter11_(&power.dual.view(), &power.dual.view()),\n                }\n            }\n            _ => {\n                let (z, p) = self.to_union_vars(power, None);\n                let f_z = p.real * z.real.pow(p.real - 1_f64);\n                let f_p = z.real.pow(p.real) * z.real.ln();\n                let f_zz = p.real * (p.real - 1_f64) * z.real.pow(p.real - 2_f64);\n                let f_pp = z.real.ln() * z.real.ln() * z.real.pow(p.real);\n                let f_pz = (p.real * z.real.ln() + 1_f64) * z.real.pow(p.real - 1_f64);\n                let cross_beta = fouter11_(&p.dual.view(), &z.dual.view());\n                Dual2 {\n                    real: z.real.pow(p.real),\n                    vars: Arc::clone(z.vars()),\n                    dual: f_z * &z.dual + f_p * &p.dual,\n                    dual2: f_z * &z.dual2\n                        + f_p * &p.dual2\n                        + 0.5_f64 * f_zz * fouter11_(&z.dual.view(), &z.dual.view())\n                        + 0.5_f64 * f_pz * (&cross_beta + &cross_beta.t())\n                        + 0.5_f64 * f_pp * fouter11_(&p.dual.view(), &p.dual.view()),\n                }\n            }\n        }\n    }\n}\n\nimpl Pow<&Dual2> for Dual2 {\n    type Output = Dual2;\n    fn pow(self, power: &Dual2) -> Self::Output {\n        (&self).pow(power)\n    }\n}\n\nimpl Pow<Dual2> for &Dual2 {\n    type Output = Dual2;\n    fn pow(self, power: Dual2) -> Self::Output {\n        self.pow(&power)\n    }\n}\n\nimpl Pow<Dual2> for Dual2 {\n    type Output = Dual2;\n    fn pow(self, power: Dual2) -> Self::Output {\n        (&self).pow(&power)\n    }\n}\n\nimpl Pow<f64> for Number {\n    type Output = Number;\n    fn pow(self, power: f64) -> Self::Output {\n        match self {\n            Number::F64(f) => Number::F64(f.pow(power)),\n            Number::Dual(d) => Number::Dual(d.pow(power)),\n            Number::Dual2(d) => Number::Dual2(d.pow(power)),\n        }\n    }\n}\n\nimpl Pow<&f64> for Number {\n    type Output = Number;\n    fn pow(self, power: &f64) -> Self::Output {\n        match self {\n            Number::F64(f) => Number::F64(f.pow(power)),\n            Number::Dual(d) => Number::Dual(d.pow(power)),\n            Number::Dual2(d) => Number::Dual2(d.pow(power)),\n        }\n    }\n}\n\nimpl Pow<f64> for &Number {\n    type Output = Number;\n    fn pow(self, power: f64) -> Self::Output {\n        match self {\n            Number::F64(f) => Number::F64(f.pow(power)),\n            Number::Dual(d) => Number::Dual(d.pow(power)),\n            Number::Dual2(d) => Number::Dual2(d.pow(power)),\n        }\n    }\n}\n\nimpl Pow<&f64> for &Number {\n    type Output = Number;\n    fn pow(self, power: &f64) -> Self::Output {\n        match self {\n            Number::F64(f) => Number::F64(f.pow(power)),\n            Number::Dual(d) => Number::Dual(d.pow(power)),\n            Number::Dual2(d) => Number::Dual2(d.pow(power)),\n        }\n    }\n}\n\nimpl Pow<Number> for Number {\n    type Output = Number;\n    fn pow(self, power: Number) -> Self::Output {\n        match (self, power) {\n            (Number::F64(f), Number::F64(f2)) => Number::F64(f.pow(f2)),\n            (Number::F64(f), Number::Dual(d2)) => Number::Dual(f.pow(d2)),\n            (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f.pow(d2)),\n            (Number::Dual(d), Number::F64(f2)) => Number::Dual(d.pow(f2)),\n            (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d.pow(d2)),\n            (Number::Dual(_), Number::Dual2(_)) => {\n                panic!(\"Cannot mix dual types: Dual/Dual2\")\n            }\n            (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d.pow(f2)),\n            (Number::Dual2(_), Number::Dual(_)) => {\n                panic!(\"Cannot mix dual types: Dual2/Dual\")\n            }\n            (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d.pow(d2)),\n        }\n    }\n}\n\nimpl Pow<&Number> for Number {\n    type Output = Number;\n    fn pow(self, power: &Number) -> Self::Output {\n        match (self, power) {\n            (Number::F64(f), Number::F64(f2)) => Number::F64(f.pow(f2)),\n            (Number::F64(f), Number::Dual(d2)) => Number::Dual(f.pow(d2)),\n            (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f.pow(d2)),\n            (Number::Dual(d), Number::F64(f2)) => Number::Dual(d.pow(f2)),\n            (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d.pow(d2)),\n            (Number::Dual(_), Number::Dual2(_)) => {\n                panic!(\"Cannot mix dual types: Dual/Dual2\")\n            }\n            (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d.pow(f2)),\n            (Number::Dual2(_), Number::Dual(_)) => {\n                panic!(\"Cannot mix dual types: Dual2/Dual\")\n            }\n            (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d.pow(d2)),\n        }\n    }\n}\n\nimpl Pow<Number> for &Number {\n    type Output = Number;\n    fn pow(self, power: Number) -> Self::Output {\n        match (self, power) {\n            (Number::F64(f), Number::F64(f2)) => Number::F64(f.pow(f2)),\n            (Number::F64(f), Number::Dual(d2)) => Number::Dual(f.pow(d2)),\n            (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f.pow(d2)),\n            (Number::Dual(d), Number::F64(f2)) => Number::Dual(d.pow(f2)),\n            (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d.pow(d2)),\n            (Number::Dual(_), Number::Dual2(_)) => {\n                panic!(\"Cannot mix dual types: Dual/Dual2\")\n            }\n            (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d.pow(f2)),\n            (Number::Dual2(_), Number::Dual(_)) => {\n                panic!(\"Cannot mix dual types: Dual2/Dual\")\n            }\n            (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d.pow(d2)),\n        }\n    }\n}\n\nimpl Pow<&Number> for &Number {\n    type Output = Number;\n    fn pow(self, power: &Number) -> Self::Output {\n        match (self, power) {\n            (Number::F64(f), Number::F64(f2)) => Number::F64(f.pow(f2)),\n            (Number::F64(f), Number::Dual(d2)) => Number::Dual(f.pow(d2)),\n            (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f.pow(d2)),\n            (Number::Dual(d), Number::F64(f2)) => Number::Dual(d.pow(f2)),\n            (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d.pow(d2)),\n            (Number::Dual(_), Number::Dual2(_)) => {\n                panic!(\"Cannot mix dual types: Dual/Dual2\")\n            }\n            (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d.pow(f2)),\n            (Number::Dual2(_), Number::Dual(_)) => {\n                panic!(\"Cannot mix dual types: Dual2/Dual\")\n            }\n            (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d.pow(d2)),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::dual::dual_ops::math_funcs::MathFuncs;\n    use ndarray::Array1;\n\n    fn is_close(a: &f64, b: &f64, abs_tol: Option<f64>) -> bool {\n        // used rather than equality for float numbers\n        (a - b).abs() < abs_tol.unwrap_or(1e-8)\n    }\n\n    fn assert_is_close_vecs(v1: &Vec<f64>, v2: &Vec<f64>) {\n        let v: Vec<bool> = v1\n            .iter()\n            .zip(v2.iter())\n            .map(|(x, y)| is_close(&x, &y, None))\n            .collect();\n        assert!(v.iter().all(|x| *x));\n    }\n\n    #[test]\n    fn inv() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = d1.clone() * d1.pow(-1.0);\n        let expected = Dual::new(1.0, vec![]);\n        assert!(result == expected)\n    }\n\n    #[test]\n    fn pow_ref() {\n        let d1 = Dual::new(3.0, vec![\"x\".to_string()]);\n        let d2 = (&d1).pow(2.0);\n        assert_eq!(d2.real, 9.0);\n        assert_eq!(d2.dual, Array1::from_vec(vec![6.0]));\n    }\n\n    #[test]\n    fn pow_ref2() {\n        let d1 = Dual2::new(3.0, vec![\"x\".to_string()]);\n        let d2 = (&d1).pow(2.0);\n        assert_eq!(d2.real, 9.0);\n        assert_eq!(d2.dual, Array1::from_vec(vec![6.0]));\n    }\n\n    #[test]\n    fn inv2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = d1.clone() * d1.pow(-1.0);\n        let expected = Dual2::new(1.0, vec![]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn test_enum() {\n        let f = Number::F64(2.0);\n        let d = Dual::new(3.0, vec![\"x\".to_string()]);\n        assert_eq!(Number::F64(4.0_f64), f.pow(2.0_f64));\n\n        let res = (&d).pow(2.0_f64);\n        assert_eq!(Number::Dual(res), Number::Dual(d).pow(2.0_f64));\n    }\n\n    #[test]\n    fn test_dual_dual() {\n        let z = Dual::new(2.0_f64, vec![\"x\".to_string()]);\n        let p = Dual::new(3.0_f64, vec![\"p\".to_string()]);\n        let result = (&z).pow(&p);\n        let expected = Dual::try_new(\n            8.0,\n            vec![\"x\".to_string(), \"p\".to_string()],\n            vec![12.0, 2.0_f64.ln() * 8.0],\n        )\n        .unwrap();\n        assert_eq!(result, expected);\n\n        let result2 = (&z).pow(p);\n        assert_eq!(result2, expected);\n        let p = Dual::new(3.0_f64, vec![\"p\".to_string()]);\n        let result3 = (z).pow(&p);\n        assert_eq!(result3, expected);\n        let z = Dual::new(2.0_f64, vec![\"x\".to_string()]);\n        let result4 = (z).pow(p);\n        assert_eq!(result4, expected);\n    }\n\n    #[test]\n    fn test_f64_dual() {\n        let p = Dual::new(3.0_f64, vec![\"p\".to_string()]);\n        let result = (&2_f64).pow(&p);\n        let expected = Dual::try_new(8.0, vec![\"p\".to_string()], vec![2.0_f64.ln() * 8.0]).unwrap();\n        assert_eq!(result, expected);\n\n        let result2 = (&2_f64).pow(p);\n        assert_eq!(result2, expected);\n        let p = Dual::new(3.0_f64, vec![\"p\".to_string()]);\n        let result3 = (2_f64).pow(&p);\n        assert_eq!(result3, expected);\n        let result4 = (2_f64).pow(p);\n        assert_eq!(result4, expected);\n    }\n\n    #[test]\n    fn test_f64_dual2() {\n        let p = Dual2::new(3.0_f64, vec![\"p\".to_string()]);\n        let result = (&2_f64).pow(&p);\n        let expected = Dual2::try_new(\n            8.0,\n            vec![\"p\".to_string()],\n            vec![2.0_f64.ln() * 8.0],\n            vec![2_f64.ln() * 2_f64.ln() * 4_f64],\n        )\n        .unwrap();\n        assert_eq!(result, expected);\n\n        let result2 = (&2_f64).pow(p);\n        assert_eq!(result2, expected);\n        let p = Dual2::new(3.0_f64, vec![\"p\".to_string()]);\n        let result3 = (2_f64).pow(&p);\n        assert_eq!(result3, expected);\n        let result4 = (2_f64).pow(p);\n        assert_eq!(result4, expected);\n    }\n\n    #[test]\n    fn test_dual2_dual2() {\n        // test all ref and deref binary ops\n        let p = Dual2::new(3.0_f64, vec![\"p\".to_string()]);\n        let z = Dual2::new(3.0_f64, vec![\"z\".to_string()]);\n        let x = Dual2::new(3.0_f64, vec![\"x\".to_string()]);\n        let y = Dual2::new(3.0_f64, vec![\"y\".to_string()]);\n        let mut _result = (&z).pow(&p);\n        _result = z.pow(&p);\n        _result = (&x).pow(p);\n        _result = x.pow(y);\n    }\n\n    #[test]\n    fn test_dual2_dual2_branch_equivalence() {\n        // test match branches yield the same calculation for Var equivalence and difference\n        let p = Dual2::try_new(\n            3.0_f64,\n            vec![\"p\".to_string(), \"s\".to_string()],\n            vec![1.1, 2.1],\n            vec![1.1, 2.2, 2.2, 1.4],\n        )\n        .unwrap();\n        let z = Dual2::try_new(\n            2.0_f64,\n            vec![\"s\".to_string(), \"p\".to_string()],\n            vec![1.9, 2.9],\n            vec![3.4, 1.2, 1.2, 0.1],\n        )\n        .unwrap();\n        let z_p = Dual2::try_new_from(\n            &p,\n            2.0_f64,\n            vec![\"p\".to_string(), \"s\".to_string()],\n            vec![2.9, 1.9],\n            vec![0.1, 1.2, 1.2, 3.4],\n        )\n        .unwrap();\n        let result1 = (&p).pow(z);\n        let result2 = p.pow(z_p);\n        assert_eq!(result1, result2);\n    }\n\n    #[test]\n    fn test_dual2_dual2_op_equivalence() {\n        // test the analytical derivative calculations match those expected from exp and log\n        let p = Dual2::try_new(\n            3.0_f64,\n            vec![\"p\".to_string(), \"s\".to_string()],\n            vec![1.1, 2.1],\n            vec![1.1, 2.2, 2.2, 1.4],\n        )\n        .unwrap();\n        let z = Dual2::try_new(\n            2.0_f64,\n            vec![\"s\".to_string(), \"p\".to_string()],\n            vec![1.9, 2.9],\n            vec![3.4, 1.2, 1.2, 0.1],\n        )\n        .unwrap();\n        let r1 = (&z).pow(&p);\n        let r2 = (z.log() * p).exp();\n        assert_is_close_vecs(&r1.dual.to_vec(), &r2.dual.to_vec());\n        assert_is_close_vecs(\n            &r1.dual2.into_raw_vec_and_offset().0,\n            &r2.dual2.into_raw_vec_and_offset().0,\n        );\n    }\n\n    #[test]\n    fn test_number_number() {\n        // test implemented crosses\n        fn x1() -> Number {\n            Number::F64(2.3)\n        }\n        fn x2() -> Number {\n            Number::Dual(Dual::new(2.3, vec![]))\n        }\n        fn x3() -> Number {\n            Number::Dual2(Dual2::new(1.1, vec![]))\n        }\n\n        let mut _res: Number;\n        _res = (&x1()).pow(&x1());\n        _res = (&x1()).pow(&x2());\n        _res = (&x1()).pow(&x3());\n        _res = (&x2()).pow(&x1());\n        _res = (&x2()).pow(&x2());\n        _res = (&x3()).pow(&x1());\n        _res = (&x3()).pow(&x3());\n\n        _res = (x1()).pow(&x1());\n        _res = (x1()).pow(&x2());\n        _res = (x1()).pow(&x3());\n        _res = (x2()).pow(&x1());\n        _res = (x2()).pow(&x2());\n        _res = (x3()).pow(&x1());\n        _res = (x3()).pow(&x3());\n\n        _res = (&x1()).pow(x1());\n        _res = (&x1()).pow(x2());\n        _res = (&x1()).pow(x3());\n        _res = (&x2()).pow(x1());\n        _res = (&x2()).pow(x2());\n        _res = (&x3()).pow(x1());\n        _res = (&x3()).pow(x3());\n\n        _res = (x1()).pow(x1());\n        _res = (x1()).pow(x2());\n        _res = (x1()).pow(x3());\n        _res = (x2()).pow(x1());\n        _res = (x2()).pow(x2());\n        _res = (x3()).pow(x1());\n        _res = (x3()).pow(x3());\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/rem.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse auto_ops::impl_op_ex;\nuse std::sync::Arc;\n\nimpl_op_ex!(% |a: &Dual, b: &f64| -> Dual { Dual {vars: Arc::clone(&a.vars), real: a.real % b, dual: a.dual.clone()} });\nimpl_op_ex!(% |a: &f64, b: &Dual| -> Dual { Dual::new(*a, Vec::new()) % b });\nimpl_op_ex!(% |a: &Dual2, b: &f64| -> Dual2 {\n    Dual2 {vars: Arc::clone(&a.vars), real: a.real % b, dual: a.dual.clone(), dual2: a.dual2.clone()}\n});\nimpl_op_ex!(% |a: &f64, b: &Dual2| -> Dual2 {\n    Dual2::new(*a, Vec::new()) % b }\n);\n\n// impl REM for Dual\nimpl_op_ex!(% |a: &Dual, b: &Dual| -> Dual {\n    let d = f64::trunc(a.real / b.real);\n    a - d * b\n});\n\n// impl Rem for Dual2\nimpl_op_ex!(% |a: &Dual2, b: &Dual2| -> Dual2 {\n    let d = f64::trunc(a.real / b.real);\n    a - d * b\n});\n\n// Rem for Number\nimpl_op_ex!(% |a: &Number, b: &Number| -> Number {\n    match (a,b) {\n        (Number::F64(f), Number::F64(f2)) => Number::F64(f % f2),\n        (Number::F64(f), Number::Dual(d2)) => Number::Dual(f % d2),\n        (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f % d2),\n        (Number::Dual(d), Number::F64(f2)) => Number::Dual(d % f2),\n        (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d % d2),\n        (Number::Dual(_), Number::Dual2(_)) => panic!(\"Cannot mix dual types: Dual % Dual2\"),\n        (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d % f2),\n        (Number::Dual2(_), Number::Dual(_)) => panic!(\"Cannot mix dual types: Dual2 % Dual\"),\n        (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d % d2),\n    }\n});\n\n// Rem for Number\nimpl_op_ex!(% |a: &Number, b: &f64| -> Number {\n    match a {\n        Number::F64(f) => Number::F64(f % b),\n        Number::Dual(d) => Number::Dual(d % b),\n        Number::Dual2(d) => Number::Dual2(d % b),\n    }\n});\n\n// Rem for Number\nimpl_op_ex!(% |a: &f64, b: &Number| -> Number {\n    match b {\n        Number::F64(f) => Number::F64(a % f),\n        Number::Dual(d) => Number::Dual(a % d),\n        Number::Dual2(d) => Number::Dual2(a % d),\n    }\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn rem_() {\n        let d1 = Dual::try_new(10.0, vec![\"x\".to_string()], vec![2.0]).unwrap();\n        let d2 = Dual::new(3.0, vec![\"x\".to_string()]);\n        let result = d1 % d2;\n        let expected = Dual::try_new(1.0, vec![\"x\".to_string()], vec![-1.0]).unwrap();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn rem_f64_() {\n        let d1 = Dual::try_new(10.0, vec![\"x\".to_string()], vec![2.0]).unwrap();\n        let result = &d1 % 3.0_f64;\n        assert_eq!(\n            result,\n            Dual::try_new(1.0, vec![\"x\".to_string()], vec![2.0]).unwrap()\n        );\n\n        let result = 11.0_f64 % d1;\n        assert_eq!(\n            result,\n            Dual::try_new(1.0, vec![\"x\".to_string()], vec![-2.0]).unwrap()\n        );\n    }\n\n    #[test]\n    fn rem_2() {\n        let d1 = Dual2::try_new(10.0, vec![\"x\".to_string()], vec![2.0], vec![]).unwrap();\n        let d2 = Dual2::new(3.0, vec![\"x\".to_string()]);\n        let result = d1 % d2;\n        let expected = Dual2::try_new(1.0, vec![\"x\".to_string()], vec![-1.0], vec![]).unwrap();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn rem_f64_2() {\n        let d1 = Dual2::try_new(10.0, vec![\"x\".to_string()], vec![2.0], vec![]).unwrap();\n        let result = &d1 % 3.0_f64;\n        assert_eq!(\n            result,\n            Dual2::try_new(1.0, vec![\"x\".to_string()], vec![2.0], vec![]).unwrap()\n        );\n\n        let result = 11.0_f64 % d1;\n        assert_eq!(\n            result,\n            Dual2::try_new(1.0, vec![\"x\".to_string()], vec![-2.0], vec![]).unwrap()\n        );\n    }\n\n    #[test]\n    fn test_enum() {\n        let f = Number::F64(4.0);\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        assert_eq!(\n            &f % &d,\n            Number::Dual(Dual::try_new(1.0, vec![\"x\".to_string()], vec![-1.0]).unwrap())\n        );\n\n        assert_eq!(\n            &d % &d,\n            Number::Dual(Dual::try_new(0.0, vec![\"x\".to_string()], vec![0.0]).unwrap())\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_enum_panic() {\n        let d = Number::Dual2(Dual2::new(2.0, vec![\"y\".to_string()]));\n        let d2 = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let _ = d % d2;\n    }\n\n    #[test]\n    fn test_enum_f64() {\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let res = 2.0_f64 % d;\n        assert_eq!(\n            res,\n            Number::Dual(2.0 % Dual::new(3.0, vec![\"x\".to_string()]))\n        );\n\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let res = d % 2.0_f64;\n        assert_eq!(\n            res,\n            Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]) % 2.0)\n        );\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/signed.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse num_traits::Signed;\nuse std::sync::Arc;\n\n/// Sign for `Dual` is evaluated in terms of the `real` component.\nimpl Signed for Dual {\n    /// Determine the absolute value of `Dual`.\n    ///\n    /// If `real` is negative the returned `Dual` will negate both its `real` value and\n    /// `dual`.\n    ///\n    /// <div class=\"warning\">This behaviour is undefined at zero. The derivative of the `abs` function is\n    /// not defined there and care needs to be taken when implying gradients.</div>\n    fn abs(&self) -> Self {\n        if self.real > 0.0 {\n            Dual {\n                real: self.real,\n                vars: Arc::clone(&self.vars),\n                dual: self.dual.clone(),\n            }\n        } else {\n            Dual {\n                real: -self.real,\n                vars: Arc::clone(&self.vars),\n                dual: -1.0 * &self.dual,\n            }\n        }\n    }\n\n    fn abs_sub(&self, other: &Self) -> Self {\n        if self <= other {\n            Dual::new(0.0, Vec::new())\n        } else {\n            self - other\n        }\n    }\n\n    fn signum(&self) -> Self {\n        Dual::new(self.real.signum(), Vec::new())\n    }\n\n    fn is_positive(&self) -> bool {\n        self.real.is_sign_positive()\n    }\n\n    fn is_negative(&self) -> bool {\n        self.real.is_sign_negative()\n    }\n}\n\nimpl Signed for Dual2 {\n    fn abs(&self) -> Self {\n        if self.real > 0.0 {\n            Dual2 {\n                real: self.real,\n                vars: Arc::clone(&self.vars),\n                dual: self.dual.clone(),\n                dual2: self.dual2.clone(),\n            }\n        } else {\n            Dual2 {\n                real: -self.real,\n                vars: Arc::clone(&self.vars),\n                dual: -1.0 * &self.dual,\n                dual2: -1.0 * &self.dual2,\n            }\n        }\n    }\n\n    fn abs_sub(&self, other: &Self) -> Self {\n        if self <= other {\n            Dual2::new(0.0, Vec::new())\n        } else {\n            self - other\n        }\n    }\n\n    fn signum(&self) -> Self {\n        Dual2::new(self.real.signum(), Vec::new())\n    }\n\n    fn is_positive(&self) -> bool {\n        self.real.is_sign_positive()\n    }\n\n    fn is_negative(&self) -> bool {\n        self.real.is_sign_negative()\n    }\n}\n\nimpl Signed for Number {\n    fn abs(&self) -> Self {\n        match self {\n            Number::F64(f) => Number::F64(f.abs()),\n            Number::Dual(d) => Number::Dual(d.abs()),\n            Number::Dual2(d) => Number::Dual2(d.abs()),\n        }\n    }\n\n    fn abs_sub(&self, other: &Self) -> Self {\n        match (self, other) {\n            (Number::F64(f), Number::F64(f2)) => Number::F64(f.abs_sub(f2)),\n            (Number::F64(f), Number::Dual(d2)) => Number::Dual(Dual::new(*f, vec![]).abs_sub(d2)),\n            (Number::F64(f), Number::Dual2(d2)) => {\n                Number::Dual2(Dual2::new(*f, vec![]).abs_sub(d2))\n            }\n            (Number::Dual(d), Number::F64(f2)) => Number::Dual(d.abs_sub(&Dual::new(*f2, vec![]))),\n            (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d.abs_sub(d2)),\n            (Number::Dual(_), Number::Dual2(_)) => {\n                panic!(\"Cannot mix dual types: Dual / Dual2\")\n            }\n            (Number::Dual2(d), Number::F64(f2)) => {\n                Number::Dual2(d.abs_sub(&Dual2::new(*f2, vec![])))\n            }\n            (Number::Dual2(_), Number::Dual(_)) => {\n                panic!(\"Cannot mix dual types: Dual2 / Dual\")\n            }\n            (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d.abs_sub(d2)),\n        }\n    }\n\n    fn signum(&self) -> Self {\n        match self {\n            Number::F64(f) => Number::F64(f.signum()),\n            Number::Dual(d) => Number::Dual(d.signum()),\n            Number::Dual2(d) => Number::Dual2(d.signum()),\n        }\n    }\n\n    fn is_positive(&self) -> bool {\n        match self {\n            Number::F64(f) => f.is_positive(),\n            Number::Dual(d) => d.is_positive(),\n            Number::Dual2(d) => d.is_positive(),\n        }\n    }\n\n    fn is_negative(&self) -> bool {\n        match self {\n            Number::F64(f) => f.is_negative(),\n            Number::Dual(d) => d.is_negative(),\n            Number::Dual2(d) => d.is_negative(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use num_traits::{One, Zero};\n\n    #[test]\n    fn signed() {\n        let d1 = Dual::new(3.0, vec![\"x\".to_string()]);\n        let d2 = Dual::new(-2.0, vec![\"x\".to_string()]);\n\n        assert!(d2.is_negative());\n        assert!(d1.is_positive());\n        assert_eq!(d2.signum(), -1.0 * Dual::one());\n        assert_eq!(d1.signum(), Dual::one());\n        assert_eq!(d1.abs_sub(&d2), Dual::new(5.0, Vec::new()));\n        assert_eq!(d2.abs_sub(&d1), Dual::zero());\n    }\n\n    #[test]\n    fn signed_2() {\n        let d1 = Dual2::new(3.0, vec![\"x\".to_string()]);\n        let d2 = Dual2::new(-2.0, vec![\"x\".to_string()]);\n\n        assert!(d2.is_negative());\n        assert!(d1.is_positive());\n        assert_eq!(d2.signum(), -1.0 * Dual2::one());\n        assert_eq!(d1.signum(), Dual2::one());\n        assert_eq!(d1.abs_sub(&d2), Dual2::new(5.0, Vec::new()));\n        assert_eq!(d2.abs_sub(&d1), Dual2::zero());\n    }\n\n    #[test]\n    fn abs() {\n        let d1 = Dual::try_new(\n            -2.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = d1.abs();\n        let expected = Dual::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![-1.0, -2.0],\n        )\n        .unwrap();\n        assert_eq!(result, expected);\n\n        let result = d1.abs();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn abs2() {\n        let d1 = Dual2::try_new(\n            -2.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = d1.abs();\n        let expected = Dual2::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![-1.0, -2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        assert_eq!(result, expected);\n\n        let result = result.abs();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_enum() {\n        let d = Number::Dual(Dual::new(-2.5, vec![\"x\".to_string()]));\n        assert!(!d.is_positive());\n        assert!(d.is_negative());\n        assert_eq!(\n            d.abs(),\n            Number::Dual(Dual::try_new(2.5, vec![\"x\".to_string()], vec![-1.0]).unwrap())\n        );\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/sub.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2, Vars, VarsRelationship};\nuse crate::dual::enums::Number;\nuse auto_ops::impl_op_ex;\nuse std::sync::Arc;\n\n// Sub\nimpl_op_ex!(-|a: &Dual, b: &f64| -> Dual {\n    Dual {\n        vars: Arc::clone(&a.vars),\n        real: a.real - b,\n        dual: a.dual.clone(),\n    }\n});\nimpl_op_ex!(-|a: &f64, b: &Dual| -> Dual {\n    Dual {\n        vars: Arc::clone(&b.vars),\n        real: a - b.real,\n        dual: -(b.dual.clone()),\n    }\n});\nimpl_op_ex!(-|a: &Dual2, b: &f64| -> Dual2 {\n    Dual2 {\n        vars: Arc::clone(&a.vars),\n        real: a.real - b,\n        dual: a.dual.clone(),\n        dual2: a.dual2.clone(),\n    }\n});\nimpl_op_ex!(-|a: &f64, b: &Dual2| -> Dual2 {\n    Dual2 {\n        vars: Arc::clone(&b.vars),\n        real: a - b.real,\n        dual: -(b.dual.clone()),\n        dual2: -(b.dual2.clone()),\n    }\n});\n\n// impl Sub for Dual\nimpl_op_ex!(-|a: &Dual, b: &Dual| -> Dual {\n    let state = a.vars_cmp(b.vars());\n    match state {\n        VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => Dual {\n            real: a.real - b.real,\n            dual: &a.dual - &b.dual,\n            vars: Arc::clone(&a.vars),\n        },\n        _ => {\n            let (x, y) = a.to_union_vars(b, Some(state));\n            Dual {\n                real: x.real - y.real,\n                dual: &x.dual - &y.dual,\n                vars: Arc::clone(&x.vars),\n            }\n        }\n    }\n});\n\n// impl Sub\nimpl_op_ex!(-|a: &Dual2, b: &Dual2| -> Dual2 {\n    let state = a.vars_cmp(b.vars());\n    match state {\n        VarsRelationship::ArcEquivalent | VarsRelationship::ValueEquivalent => Dual2 {\n            real: a.real - b.real,\n            dual: &a.dual - &b.dual,\n            dual2: &a.dual2 - &b.dual2,\n            vars: Arc::clone(&a.vars),\n        },\n        _ => {\n            let (x, y) = a.to_union_vars(b, Some(state));\n            Dual2 {\n                real: x.real - y.real,\n                dual: &x.dual - &y.dual,\n                dual2: &x.dual2 - &y.dual2,\n                vars: Arc::clone(&x.vars),\n            }\n        }\n    }\n});\n\n// Sub for Number\nimpl_op_ex!(-|a: &Number, b: &Number| -> Number {\n    match (a, b) {\n        (Number::F64(f), Number::F64(f2)) => Number::F64(f - f2),\n        (Number::F64(f), Number::Dual(d2)) => Number::Dual(f - d2),\n        (Number::F64(f), Number::Dual2(d2)) => Number::Dual2(f - d2),\n        (Number::Dual(d), Number::F64(f2)) => Number::Dual(d - f2),\n        (Number::Dual(d), Number::Dual(d2)) => Number::Dual(d - d2),\n        (Number::Dual(_), Number::Dual2(_)) => {\n            panic!(\"Cannot mix dual types: Dual - Dual2\")\n        }\n        (Number::Dual2(d), Number::F64(f2)) => Number::Dual2(d - f2),\n        (Number::Dual2(_), Number::Dual(_)) => {\n            panic!(\"Cannot mix dual types: Dual2 - Dual\")\n        }\n        (Number::Dual2(d), Number::Dual2(d2)) => Number::Dual2(d - d2),\n    }\n});\n\n// Sub for Number\nimpl_op_ex!(-|a: &Number, b: &f64| -> Number {\n    match a {\n        Number::F64(f) => Number::F64(f - b),\n        Number::Dual(d) => Number::Dual(d - b),\n        Number::Dual2(d) => Number::Dual2(d - b),\n    }\n});\n\n// Sub for Number\nimpl_op_ex!(-|a: &f64, b: &Number| -> Number {\n    match b {\n        Number::F64(f) => Number::F64(a - f),\n        Number::Dual(d) => Number::Dual(a - d),\n        Number::Dual2(d) => Number::Dual2(a - d),\n    }\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn sub_f64() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = (10.0 - d1) - 15.0;\n        let expected = Dual::try_new(\n            -6.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![-1.0, -2.0],\n        )\n        .unwrap();\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn sub() {\n        let d1 = Dual::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let d2 = Dual::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![0.0, 3.0],\n        )\n        .unwrap();\n        let expected = Dual::try_new(\n            -1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string(), \"v2\".to_string()],\n            vec![1.0, 2.0, -3.0],\n        )\n        .unwrap();\n        let result = d1 - d2;\n        assert_eq!(result, expected)\n    }\n    #[test]\n    fn sub_f64_2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = (10.0 - d1) - 15.0;\n        let expected = Dual2::try_new(\n            -6.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![-1.0, -2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn sub2() {\n        let d1 = Dual2::try_new(\n            1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string()],\n            vec![1.0, 2.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let d2 = Dual2::try_new(\n            2.0,\n            vec![\"v0\".to_string(), \"v2\".to_string()],\n            vec![0.0, 3.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let expected = Dual2::try_new(\n            -1.0,\n            vec![\"v0\".to_string(), \"v1\".to_string(), \"v2\".to_string()],\n            vec![1.0, 2.0, -3.0],\n            Vec::new(),\n        )\n        .unwrap();\n        let result = d1 - d2;\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn test_enum() {\n        let f = Number::F64(2.0);\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        assert_eq!(\n            &f - &d,\n            Number::Dual(Dual::try_new(-1.0, vec![\"x\".to_string()], vec![-1.0]).unwrap())\n        );\n\n        assert_eq!(\n            &d - &d,\n            Number::Dual(Dual::try_new(0.0, vec![\"x\".to_string()], vec![0.0]).unwrap())\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_enum_panic() {\n        let d = Number::Dual2(Dual2::new(2.0, vec![\"y\".to_string()]));\n        let d2 = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let _ = d - d2;\n    }\n\n    #[test]\n    fn test_enum_f64() {\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let res = 2.5_f64 - d;\n        assert_eq!(\n            res,\n            Number::Dual(Dual::new(0.5, vec![\"x\".to_string()]) * -1.0)\n        );\n\n        let d = Number::Dual(Dual::new(3.0, vec![\"x\".to_string()]));\n        let res = d - 2.5_f64;\n        assert_eq!(res, Number::Dual(Dual::new(0.5, vec![\"x\".to_string()])));\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/sum.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse std::iter::Sum;\n\nimpl Sum for Dual {\n    fn sum<I>(iter: I) -> Self\n    where\n        I: Iterator<Item = Dual>,\n    {\n        iter.fold(Dual::new(0.0, [].to_vec()), |acc, x| acc + x)\n    }\n}\n\nimpl Sum for Dual2 {\n    fn sum<I>(iter: I) -> Self\n    where\n        I: Iterator<Item = Dual2>,\n    {\n        iter.fold(Dual2::new(0.0, Vec::new()), |acc, x| acc + x)\n    }\n}\n\nimpl Sum for Number {\n    fn sum<I>(iter: I) -> Self\n    where\n        I: Iterator<Item = Number>,\n    {\n        iter.fold(Number::F64(0.0_f64), |acc, x| acc + x)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_enum() {\n        let v = vec![\n            Number::F64(2.5_f64),\n            Number::Dual(Dual::new(1.5, vec![\"x\".to_string()])),\n            Number::Dual(Dual::new(3.5, vec![\"x\".to_string()])),\n        ];\n        let s: Number = v.into_iter().sum();\n        assert_eq!(\n            s,\n            Number::Dual(Dual::try_new(7.5, vec![\"x\".to_string()], vec![2.0]).unwrap())\n        );\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_ops/zero.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::enums::Number;\nuse num_traits::Zero;\n\nimpl Zero for Dual {\n    fn zero() -> Dual {\n        Dual::new(0.0, Vec::new())\n    }\n\n    fn is_zero(&self) -> bool {\n        *self == Dual::new(0.0, Vec::new())\n    }\n}\n\nimpl Zero for Dual2 {\n    fn zero() -> Dual2 {\n        Dual2::new(0.0, Vec::new())\n    }\n\n    fn is_zero(&self) -> bool {\n        *self == Dual2::new(0.0, Vec::new())\n    }\n}\n\nimpl Zero for Number {\n    fn zero() -> Number {\n        Number::F64(0.0_f64)\n    }\n\n    fn is_zero(&self) -> bool {\n        match self {\n            Number::F64(f) => *f == 0.0_f64,\n            Number::Dual(d) => *d == Dual::new(0.0, vec![]),\n            Number::Dual2(d) => *d == Dual2::new(0.0, vec![]),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn is_zero_() {\n        assert!(Dual::zero().is_zero())\n    }\n\n    #[test]\n    fn is_zero2() {\n        let d = Dual2::zero();\n        assert!(d.is_zero());\n    }\n\n    #[test]\n    fn is_zero_enum() {\n        let d = Number::Dual2(Dual2::zero());\n        assert!(d.is_zero());\n    }\n}\n"
  },
  {
    "path": "rust/dual/dual_py.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper module to export Rust dual data types to Python using pyo3 bindings.\n\nuse crate::dual::dual::{Dual, Dual2, Gradient1, Gradient2, Vars};\nuse crate::dual::dual_ops::math_funcs::MathFuncs;\nuse crate::dual::enums::{ADOrder, Number};\nuse crate::json::json_py::DeserializedObj;\nuse crate::json::JSON;\nuse num_traits::{Pow, Signed};\nuse numpy::{Element, PyArray1, PyArray2, PyArrayDescr, ToPyArray};\nuse pyo3::exceptions::{PyTypeError, PyValueError};\nuse pyo3::prelude::*;\nuse std::sync::Arc;\n\nunsafe impl Element for Dual {\n    const IS_COPY: bool = false;\n    fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> {\n        PyArrayDescr::object(py)\n    }\n\n    fn clone_ref(&self, _py: Python<'_>) -> Self {\n        self.clone()\n    }\n}\nunsafe impl Element for Dual2 {\n    const IS_COPY: bool = false;\n    fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> {\n        PyArrayDescr::object(py)\n    }\n\n    fn clone_ref(&self, _py: Python<'_>) -> Self {\n        self.clone()\n    }\n}\n\n// https://github.com/PyO3/pyo3/discussions/3911\n// #[derive(Debug, Clone, PartialEq, PartialOrd, FromPyObject)]\n// pub enum DualsOrPyFloat<'py> {\n//     Dual(Dual),\n//     Dual2(Dual2),\n//     Float(&'py PyFloat),\n// }\n\n#[pymethods]\nimpl ADOrder {\n    // Pickling\n    #[new]\n    fn new_py(ad: u8) -> PyResult<ADOrder> {\n        match ad {\n            0_u8 => Ok(ADOrder::Zero),\n            1_u8 => Ok(ADOrder::One),\n            2_u8 => Ok(ADOrder::Two),\n            _ => Err(PyValueError::new_err(\"unreachable code on ADOrder pickle.\")),\n        }\n    }\n    fn __getnewargs__<'py>(&self) -> PyResult<(u8,)> {\n        match self {\n            ADOrder::Zero => Ok((0_u8,)),\n            ADOrder::One => Ok((1_u8,)),\n            ADOrder::Two => Ok((2_u8,)),\n        }\n    }\n}\n\n#[pymethods]\nimpl Dual {\n    #[new]\n    fn new_py(real: f64, vars: Vec<String>, dual: Vec<f64>) -> PyResult<Self> {\n        Dual::try_new(real, vars, dual)\n    }\n\n    /// Create a :class:`~rateslib.dual.Dual` object with ``vars`` linked with another.\n    ///\n    /// Parameters\n    /// ----------\n    /// other: Dual\n    ///     The other `Dual` from which `vars` are linked.\n    /// real: float\n    ///     The real coefficient of the dual number.\n    /// vars: list[str]\n    ///     The labels of the variables for which to record derivatives. If empty,\n    ///     the dual number represents a constant, equivalent to a float.\n    /// dual: list[float]\n    ///     First derivative information contained as coefficient of linear manifold.\n    ///     Defaults to an array of ones the length of ``vars`` if empty.\n    ///\n    /// Returns\n    /// -------\n    /// Dual\n    ///\n    /// Notes\n    /// -----\n    /// Variables are constantly checked when operations are performed between dual numbers. In Rust the variables\n    /// are stored within an ARC pointer. It is much faster to check the equivalence of two ARC pointers than if the elements\n    /// within a variables Set, say, are the same *and* in the same order. This method exists to create dual data types\n    /// with shared ARC pointers directly.\n    ///\n    /// .. ipython:: python\n    ///\n    ///    from rateslib import Dual\n    ///\n    ///    x1 = Dual(1.0, [\"x\"], [])\n    ///    x2 = Dual(2.0, [\"x\"], [])\n    ///    # x1 and x2 have the same variables ([\"x\"]) but it is a different object\n    ///    x1.ptr_eq(x2)\n    ///\n    ///    x3 = Dual.vars_from(x1, 3.0, [\"x\"], [])\n    ///    # x3 contains shared object variables with x1\n    ///    x1.ptr_eq(x3)\n    #[staticmethod]\n    fn vars_from(other: &Dual, real: f64, vars: Vec<String>, dual: Vec<f64>) -> PyResult<Self> {\n        Dual::try_new_from(other, real, vars, dual)\n    }\n\n    /// The real coefficient of the dual number - its value.\n    #[getter]\n    #[pyo3(name = \"real\")]\n    fn real_py(&self) -> PyResult<f64> {\n        Ok(self.real())\n    }\n\n    /// The string labels of the variables for which to record derivatives.\n    #[getter]\n    #[pyo3(name = \"vars\")]\n    fn vars_py(&self) -> PyResult<Vec<&String>> {\n        Ok(Vec::from_iter(self.vars().iter()))\n    }\n\n    /// First derivative information contained as coefficient of linear manifold.\n    #[getter]\n    #[pyo3(name = \"dual\")]\n    fn dual_py<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyArray1<f64>>> {\n        Ok(self.dual().to_pyarray(py))\n    }\n\n    /// Not available on `Dual`.\n    #[getter]\n    #[pyo3(name = \"dual2\")]\n    fn dual2_py<'py>(&'py self, _py: Python<'py>) -> PyResult<Bound<'py, PyArray2<f64>>> {\n        Err(PyValueError::new_err(\n            \"`Dual` variable cannot possess `dual2` attribute.\",\n        ))\n    }\n\n    /// Return the first derivatives of Self.\n    ///\n    /// Parameters\n    /// ----------\n    /// vars: tuple/list of str\n    ///     Name of the variables which to return gradients for.\n    ///\n    /// Returns\n    /// -------\n    /// ndarray\n    #[pyo3(name = \"grad1\")]\n    fn grad1<'py>(\n        &'py self,\n        py: Python<'py>,\n        vars: Vec<String>,\n    ) -> PyResult<Bound<'py, PyArray1<f64>>> {\n        Ok(self.gradient1(vars).to_pyarray(py))\n    }\n\n    /// Not available for :class:`~rateslib.dual.Dual`.\n    #[pyo3(name = \"grad2\")]\n    fn grad2<'py>(\n        &'py self,\n        _py: Python<'py>,\n        _vars: Vec<String>,\n    ) -> PyResult<Bound<'py, PyArray2<f64>>> {\n        Err(PyValueError::new_err(\n            \"Cannot evaluate second order derivative on a Dual.\",\n        ))\n    }\n\n    /// Evaluate if the ARC pointers of two `Dual` data types are equivalent.\n    ///\n    /// Parameters\n    /// ----------\n    /// other: Dual\n    ///     The comparison object.\n    ///\n    /// Returns\n    /// -------\n    /// bool\n    #[pyo3(name = \"ptr_eq\")]\n    fn ptr_eq_py(&self, other: &Dual) -> PyResult<bool> {\n        Ok(Arc::ptr_eq(self.vars(), other.vars()))\n    }\n\n    fn __repr__(&self) -> PyResult<String> {\n        let mut _vars = Vec::from_iter(self.vars().iter().take(3).map(String::as_str)).join(\", \");\n        let mut _dual =\n            Vec::from_iter(self.dual().iter().take(3).map(|x| format!(\"{:.1}\", x))).join(\", \");\n        if self.vars().len() > 3 {\n            _vars.push_str(\", ...\");\n            _dual.push_str(\", ...\");\n        }\n        let fs = format!(\"<Dual: {:.6}, ({}), [{}]>\", self.real(), _vars, _dual);\n        Ok(fs)\n    }\n\n    fn __eq__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual(d) => Ok(d.eq(self)),\n            Number::F64(f) => Ok(Dual::new(f, Vec::new()).eq(self)),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __lt__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual(d) => Ok(self < &d),\n            Number::F64(f) => Ok(self < &f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __le__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual(d) => Ok(self <= &d),\n            Number::F64(f) => Ok(self <= &f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __gt__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual(d) => Ok(self > &d),\n            Number::F64(f) => Ok(self > &f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __ge__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual(d) => Ok(self >= &d),\n            Number::F64(f) => Ok(self >= &f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __neg__(&self) -> Self {\n        -self\n    }\n\n    fn __add__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual(d) => Ok(self + d),\n            Number::F64(f) => Ok(self + f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Dual operation with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __radd__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual(d) => Ok(self + d),\n            Number::F64(f) => Ok(self + f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Dual operation with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __sub__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual(d) => Ok(self - d),\n            Number::F64(f) => Ok(self - f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Dual operation with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __rsub__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual(d) => Ok(d - self),\n            Number::F64(f) => Ok(f - self),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Dual operation with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __mul__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual(d) => Ok(self * d),\n            Number::F64(f) => Ok(self * f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Dual operation with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __rmul__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual(d) => Ok(d * self),\n            Number::F64(f) => Ok(f * self),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Dual operation with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __truediv__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual(d) => Ok(self / d),\n            Number::F64(f) => Ok(self / f),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Dual operation with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __rtruediv__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual(d) => Ok(d / self),\n            Number::F64(f) => Ok(f / self),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Dual operation with incompatible type (Dual2).\",\n            )),\n        }\n    }\n\n    fn __pow__(&self, power: Number, modulo: Option<i32>) -> PyResult<Self> {\n        if modulo.unwrap_or(0) != 0 {\n            panic!(\"Power function with mod not available for Dual.\")\n        }\n        match power {\n            Number::F64(f) => Ok(self.clone().pow(f)),\n            Number::Dual(d_) => Ok(self.pow(d_)),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Power operation does not permit Dual/Dual2 type crossing.\",\n            )),\n        }\n    }\n\n    fn __rpow__(&self, other: Number, modulo: Option<i32>) -> PyResult<Self> {\n        if modulo.unwrap_or(0) != 0 {\n            panic!(\"Power function with mod not available for Dual.\")\n        }\n        match other {\n            Number::F64(f) => Ok(f.pow(self)),\n            Number::Dual(d_) => Ok(d_.pow(self)),\n            Number::Dual2(_) => Err(PyTypeError::new_err(\n                \"Power operation does not permit Dual/Dual2 type crossing.\",\n            )),\n        }\n    }\n\n    fn __exp__(&self) -> Self {\n        self.exp()\n    }\n\n    fn __abs__(&self) -> Self {\n        self.abs()\n    }\n\n    fn __log__(&self) -> Self {\n        self.log()\n    }\n\n    fn __norm_cdf__(&self) -> Self {\n        self.norm_cdf()\n    }\n\n    fn __norm_inv_cdf__(&self) -> Self {\n        self.inv_norm_cdf()\n    }\n\n    fn __float__(&self) -> f64 {\n        self.real()\n    }\n\n    // JSON\n    /// Create a JSON string representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::Dual(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\"Failed to serialize `Dual` to JSON.\")),\n        }\n    }\n\n    // Pickling\n    pub fn __getnewargs__(&self) -> PyResult<(f64, Vec<String>, Vec<f64>)> {\n        Ok((\n            self.real,\n            self.vars().iter().cloned().collect(),\n            self.dual.to_vec(),\n        ))\n    }\n\n    /// Convert self into a :class:`~rateslib.dual.Dual2` with 2nd order manifold set to zero.\n    #[pyo3(name = \"to_dual2\")]\n    fn to_dual2_py(&self) -> Dual2 {\n        self.clone().into()\n    }\n}\n\n#[pymethods]\nimpl Dual2 {\n    /// Python wrapper to construct a new `Dual2`.\n    #[new]\n    pub fn new_py(real: f64, vars: Vec<String>, dual: Vec<f64>, dual2: Vec<f64>) -> PyResult<Self> {\n        Dual2::try_new(real, vars, dual, dual2)\n    }\n\n    /// Create a :class:`~rateslib.dual.Dual2` object with ``vars`` linked with another.\n    ///\n    /// Parameters\n    /// ----------\n    /// other: Dual\n    ///     The other `Dual` from which `vars` are linked.\n    /// real: float\n    ///     The real coefficient of the dual number.\n    /// vars: list(str)\n    ///     The labels of the variables for which to record derivatives. If empty,\n    ///     the dual number represents a constant, equivalent to a float.\n    /// dual: list(float)\n    ///     First derivative information contained as coefficient of linear manifold.\n    ///     Defaults to an array of ones the length of ``vars`` if empty.\n    /// dual2: list(float)\n    ///     Second derivative information contained as coefficients of a quadratic manifold.\n    ///     These values represent a 2d array but must be given as a 1d list of values in\n    ///     row-major order.\n    ///     Defaults to a 2-d array of zeros of size NxN where N is length of ``vars`` if not\n    ///     given.\n    ///\n    /// Returns\n    /// -------\n    /// Dual2\n    ///\n    /// Notes\n    /// --------\n    /// For examples see also...\n    ///\n    /// .. seealso::\n    ///\n    ///    :meth:`~rateslib.dual.Dual.vars_from`: Create a *Dual* with ``vars`` linked to another.\n    ///\n    #[staticmethod]\n    pub fn vars_from(\n        other: &Dual2,\n        real: f64,\n        vars: Vec<String>,\n        dual: Vec<f64>,\n        dual2: Vec<f64>,\n    ) -> PyResult<Self> {\n        Dual2::try_new_from(other, real, vars, dual, dual2)\n    }\n\n    /// The real coefficient of the dual number - its value.\n    #[getter]\n    #[pyo3(name = \"real\")]\n    fn real_py(&self) -> PyResult<f64> {\n        Ok(self.real)\n    }\n\n    /// The string labels of the variables for which to record derivatives.\n    #[getter]\n    #[pyo3(name = \"vars\")]\n    fn vars_py(&self) -> PyResult<Vec<&String>> {\n        Ok(Vec::from_iter(self.vars.iter()))\n    }\n\n    /// First derivative information contained as coefficient of linear manifold.\n    #[getter]\n    #[pyo3(name = \"dual\")]\n    fn dual_py<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyArray1<f64>>> {\n        Ok(self.dual.to_pyarray(py))\n    }\n\n    /// Second derivative information contained as coefficient of quadratic manifold.\n    #[getter]\n    #[pyo3(name = \"dual2\")]\n    fn dual2_py<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<f64>>> {\n        Ok(self.dual2.to_pyarray(py))\n    }\n\n    /// Return the first derivatives of a Self.\n    ///\n    /// Parameters\n    /// ----------\n    /// vars: tuple/list of str\n    ///     Name of the variables which to return gradients for.\n    ///\n    /// Returns\n    /// -------\n    /// ndarray\n    #[pyo3(name = \"grad1\")]\n    fn grad1_py<'py>(\n        &'py self,\n        py: Python<'py>,\n        vars: Vec<String>,\n    ) -> PyResult<Bound<'py, PyArray1<f64>>> {\n        Ok(self.gradient1(vars).to_pyarray(py))\n    }\n\n    /// Return the second derivatives of Self.\n    ///\n    /// Parameters\n    /// ----------\n    /// vars: tuple/list of str\n    ///     Name of the variables which to return gradients for.\n    ///\n    /// Returns\n    /// -------\n    /// ndarray\n    #[pyo3(name = \"grad2\")]\n    fn grad2_py<'py>(\n        &'py self,\n        py: Python<'py>,\n        vars: Vec<String>,\n    ) -> PyResult<Bound<'py, PyArray2<f64>>> {\n        Ok(self.gradient2(vars).to_pyarray(py))\n    }\n\n    /// Return the first derivatives of Self remapped as dual numbers.\n    ///\n    /// This preserves second derivative information so that first derivatives maintain\n    /// their own sensitivity to the variables.\n    ///\n    /// Parameters\n    /// ----------\n    /// vars: tuple/list of str\n    ///     Name of the variables which to return gradients for.\n    ///\n    /// Returns\n    /// -------\n    /// ndarray\n    #[pyo3(name = \"grad1_manifold\")]\n    fn grad1_manifold_py<'py>(\n        &'py self,\n        _py: Python<'py>,\n        vars: Vec<String>,\n    ) -> PyResult<Vec<Dual2>> {\n        let out = self.gradient1_manifold(vars);\n        Ok(out.into_raw_vec_and_offset().0)\n    }\n\n    /// Evaluate if the ARC pointers of two `Dual2` data types are equivalent. See\n    /// :meth:`~rateslib.dual.Dual.ptr_eq`.\n    #[pyo3(name = \"ptr_eq\")]\n    fn ptr_eq_py(&self, other: &Dual2) -> PyResult<bool> {\n        Ok(self.ptr_eq(other))\n    }\n\n    fn __repr__(&self) -> PyResult<String> {\n        let mut _vars = Vec::from_iter(self.vars.iter().take(3).map(String::as_str)).join(\", \");\n        let mut _dual =\n            Vec::from_iter(self.dual.iter().take(3).map(|x| format!(\"{:.1}\", x))).join(\", \");\n        if self.vars.len() > 3 {\n            _vars.push_str(\", ...\");\n            _dual.push_str(\", ...\");\n        }\n        let fs = format!(\n            \"<Dual2: {:.6}, ({}), [{}], [[...]]>\",\n            self.real, _vars, _dual\n        );\n        Ok(fs)\n    }\n\n    fn __eq__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual2(d) => Ok(d.eq(self)),\n            Number::F64(f) => Ok(Dual2::new(f, Vec::new()).eq(self)),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual2 with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __lt__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual2(d) => Ok(self < &d),\n            Number::F64(f) => Ok(self < &f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual2 with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __le__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual2(d) => Ok(self <= &d),\n            Number::F64(f) => Ok(self <= &f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual2 with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __gt__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual2(d) => Ok(self > &d),\n            Number::F64(f) => Ok(self > &f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual2 with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __ge__(&self, other: Number) -> PyResult<bool> {\n        match other {\n            Number::Dual2(d) => Ok(self >= &d),\n            Number::F64(f) => Ok(self >= &f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Cannot compare Dual2 with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __neg__(&self) -> Self {\n        -self\n    }\n\n    fn __add__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual2(d) => Ok(self + d),\n            Number::F64(f) => Ok(self + f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Dual2 operation with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __radd__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual2(d) => Ok(self + d),\n            Number::F64(f) => Ok(self + f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Dual2 operation with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __sub__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual2(d) => Ok(self - d),\n            Number::F64(f) => Ok(self - f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Dual2 operation with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __rsub__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual2(d) => Ok(d - self),\n            Number::F64(f) => Ok(f - self),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Dual2 operation with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __mul__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual2(d) => Ok(self * d),\n            Number::F64(f) => Ok(self * f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Dual2 operation with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __rmul__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual2(d) => Ok(d * self),\n            Number::F64(f) => Ok(f * self),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Dual2 operation with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __truediv__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual2(d) => Ok(self / d),\n            Number::F64(f) => Ok(self / f),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Dual2 operation with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __rtruediv__(&self, other: Number) -> PyResult<Self> {\n        match other {\n            Number::Dual2(d) => Ok(d / self),\n            Number::F64(f) => Ok(f / self),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Dual2 operation with incompatible type (Dual).\",\n            )),\n        }\n    }\n\n    fn __pow__(&self, power: Number, modulo: Option<i32>) -> PyResult<Self> {\n        if modulo.unwrap_or(0) != 0 {\n            panic!(\"Power function with mod not available for Dual.\")\n        }\n        match power {\n            Number::F64(f) => Ok(self.clone().pow(f)),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Power operation does not permit Dual/Dual2 type crossing.\",\n            )),\n            Number::Dual2(d) => Ok(self.pow(d)),\n        }\n    }\n\n    fn __rpow__(&self, other: Number, modulo: Option<i32>) -> PyResult<Self> {\n        if modulo.unwrap_or(0) != 0 {\n            panic!(\"Power function with mod not available for Dual2.\")\n        }\n        match other {\n            Number::F64(f) => Ok(f.pow(self)),\n            Number::Dual(_d) => Err(PyTypeError::new_err(\n                \"Power operation does not permit Dual/Dual2 type crossing.\",\n            )),\n            Number::Dual2(d_) => Ok(d_.pow(self)),\n        }\n    }\n\n    fn __exp__(&self) -> Self {\n        self.exp()\n    }\n\n    fn __abs__(&self) -> Self {\n        self.abs()\n    }\n\n    fn __log__(&self) -> Self {\n        self.log()\n    }\n\n    fn __norm_cdf__(&self) -> Self {\n        self.norm_cdf()\n    }\n\n    fn __norm_inv_cdf__(&self) -> Self {\n        self.inv_norm_cdf()\n    }\n\n    fn __float__(&self) -> f64 {\n        self.real\n    }\n\n    // JSON\n    /// Create a JSON string representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::Dual2(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `Dual2` to JSON.\",\n            )),\n        }\n    }\n\n    // Pickling\n    fn __getnewargs__(&self) -> PyResult<(f64, Vec<String>, Vec<f64>, Vec<f64>)> {\n        Ok((\n            self.real,\n            self.vars().iter().cloned().collect(),\n            self.dual.to_vec(),\n            self.dual2.clone().into_raw_vec_and_offset().0,\n        ))\n    }\n\n    /// Convert self into a :class:`~rateslib.dual.Dual` dropping 2nd order manifold coefficients.\n    #[pyo3(name = \"to_dual\")]\n    fn to_dual_py(&self) -> Dual {\n        self.clone().into()\n    }\n}\n"
  },
  {
    "path": "rust/dual/enums.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::{Dual, Dual2};\nuse crate::splines::{PPSplineDual, PPSplineDual2, PPSplineF64};\nuse ndarray::{Array1, Array2};\nuse pyo3::{pyclass, FromPyObject, IntoPyObject, PyErr};\nuse serde::{Deserialize, Serialize};\n\n/// Defines the order of gradients available in a calculation with AD.\n#[pyclass(module = \"rateslib.rs\", eq, eq_int, from_py_object)]\n#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]\npub enum ADOrder {\n    /// Floating point arithmetic only.\n    Zero,\n    /// Derivatives available to first order.\n    One,\n    /// Derivatives available to second order.\n    Two,\n}\n\n/// Container for the three core numeric types; [f64], [Dual] and [Dual2].\n#[derive(Debug, Clone, FromPyObject, Serialize, Deserialize, IntoPyObject)]\npub enum Number {\n    Dual(Dual),\n    Dual2(Dual2),\n    F64(f64),\n}\n\n/// Container for [Vec] of each core numeric type.\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\npub enum NumberVec {\n    F64(Vec<f64>),\n    Dual(Vec<Dual>),\n    Dual2(Vec<Dual2>),\n}\n\n/// Container for [Array1] of each core numeric type.\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\npub enum NumberArray1 {\n    F64(Array1<f64>),\n    Dual(Array1<Dual>),\n    Dual2(Array1<Dual2>),\n}\n\n/// Container for [Array2] of each core numeric type.\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\npub enum NumberArray2 {\n    F64(Array2<f64>),\n    Dual(Array2<Dual>),\n    Dual2(Array2<Dual2>),\n}\n\n/// Container for [PPSpline](crate::splines::PPSpline) definitive type variants.\n#[derive(Clone, Serialize, Deserialize, PartialEq, IntoPyObject)]\npub enum NumberPPSpline {\n    F64(PPSplineF64),\n    Dual(PPSplineDual),\n    Dual2(PPSplineDual2),\n}\n\n/// Generic trait indicating a function exists to map one [Number] to another.\n///\n/// An example of this trait is used by certain [PPSpline](crate::splines::PPSpline) indicating\n/// that an x-value as\n/// some [Number] can be mapped under spline interpolation to some y-value as another [Number].\npub trait NumberMapping {\n    fn mapped_value(&self, x: &Number) -> Result<Number, PyErr>;\n}\n"
  },
  {
    "path": "rust/dual/linalg/linalg_dual.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Perform linear algebra operations on arrays containing generic data types.\n\nuse itertools::Itertools;\nuse ndarray::prelude::*;\nuse ndarray::Zip;\nuse num_traits::identities::Zero;\nuse num_traits::Signed;\nuse std::cmp::PartialOrd;\nuse std::iter::Sum;\nuse std::ops::{Div, Mul, Sub};\n\n// Tensor ops\n\n/// Outer product of two 1d-arrays containing generic objects.\npub fn douter11_<T>(a: &ArrayView1<T>, b: &ArrayView1<T>) -> Array2<T>\nwhere\n    for<'a> &'a T: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    Array1::from_vec(\n        a.iter()\n            .cartesian_product(b.iter())\n            .map(|(x, y)| x * y)\n            .collect(),\n    )\n    .into_shape_with_order((a.len(), b.len()))\n    .expect(\"Pre checked dimensions\")\n}\n\n/// Inner product between two 1d-arrays.\npub fn dmul11_<T>(a: &ArrayView1<T>, b: &ArrayView1<T>) -> T\nwhere\n    for<'a> &'a T: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    assert_eq!(a.len(), b.len());\n    a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()\n}\n\n/// Matrix multiplication between a 2d-array and a 1d-array.\npub fn dmul21_<T>(a: &ArrayView2<T>, b: &ArrayView1<T>) -> Array1<T>\nwhere\n    for<'a> &'a T: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    assert_eq!(a.len_of(Axis(1)), b.len_of(Axis(0)));\n    Array1::from_vec(a.axis_iter(Axis(0)).map(|row| dmul11_(&row, b)).collect())\n}\n\n/// Matrix multiplication between two 2d-arrays.\npub fn dmul22_<T>(a: &ArrayView2<T>, b: &ArrayView2<T>) -> Array2<T>\nwhere\n    for<'a> &'a T: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    assert_eq!(a.len_of(Axis(1)), b.len_of(Axis(0)));\n    Array1::<T>::from_vec(\n        a.axis_iter(Axis(0))\n            .cartesian_product(b.axis_iter(Axis(1)))\n            .map(|(row, col)| dmul11_(&row, &col))\n            .collect(),\n    )\n    .into_shape_with_order((a.len_of(Axis(0)), b.len_of(Axis(1))))\n    .expect(\"Dim are pre-checked\")\n}\n\n// Linalg solver\n\npub(crate) fn argabsmax<T>(a: ArrayView1<T>) -> usize\nwhere\n    T: Signed + PartialOrd,\n{\n    let vi: (&T, usize) = a\n        .iter()\n        .zip(0..)\n        .max_by(|x, y| x.0.abs().partial_cmp(&y.0.abs()).unwrap())\n        .unwrap();\n    vi.1\n}\n\n// pub(crate) fn argabsmax2<T>(a: ArrayView2<T>) -> (usize, usize)\n// where\n//     T: Signed + PartialOrd,\n// {\n//     let vi: (&T, usize) = a\n//         .iter()\n//         .zip(0..)\n//         .max_by(|x, y| x.0.abs().partial_cmp(&y.0.abs()).unwrap())\n//         .unwrap();\n//     let n = a.len_of(Axis(0));\n//     (vi.1 / n, vi.1 % n)\n// }\n\npub(crate) fn row_swap<T>(p: &mut Array2<T>, j: &usize, kr: &usize) {\n    let (mut pt, mut pb) = p.slice_mut(s![.., ..]).split_at(Axis(0), *kr);\n    let (r1, r2) = (pt.row_mut(*j), pb.row_mut(0));\n    Zip::from(r1).and(r2).for_each(std::mem::swap);\n}\n\n// pub(crate) fn col_swap<T>(p: &mut Array2<T>, j: &usize, kc: &usize)\n// {\n//     let (mut pl, mut pr) = p.slice_mut(s![.., ..]).split_at(Axis(1), *kc);\n//     let (c1, c2) = (pl.column_mut(*j), pr.column_mut(0));\n//     Zip::from(c1).and(c2).for_each(std::mem::swap);\n// }\n\npub(crate) fn el_swap<T>(p: &mut Array1<T>, j: &usize, k: &usize) {\n    let (mut pl, mut pr) = p.slice_mut(s![..]).split_at(Axis(0), *k);\n    std::mem::swap(&mut pl[*j], &mut pr[0]);\n}\n\n// fn partial_pivot_matrix<T>(a: &ArrayView2<T>) -> (Array2<f64>, Array2<f64>, Array2<T>)\n// where\n//     T: Signed + Num + PartialOrd + Clone,\n// {\n//     // pivot square matrix\n//     let n = a.len_of(Axis(0));\n//     let mut p: Array2<f64> = Array::eye(n);\n//     let q: Array2<f64> = Array::eye(n);\n//     let mut pa = a.to_owned();\n//     for j in 0..n {\n//         let k = argabsmax(pa.slice(s![j.., j])) + j;\n//         if j != k {\n//             // define row swaps j <-> k  (note that k > j by definition)\n//             let (mut pt, mut pb) = p.slice_mut(s![.., ..]).split_at(Axis(0), k);\n//             let (r1, r2) = (pt.row_mut(j), pb.row_mut(0));\n//             Zip::from(r1).and(r2).for_each(std::mem::swap);\n//\n//             let (mut pt, mut pb) = pa.slice_mut(s![.., ..]).split_at(Axis(0), k);\n//             let (r1, r2) = (pt.row_mut(j), pb.row_mut(0));\n//             Zip::from(r1).and(r2).for_each(std::mem::swap);\n//         }\n//     }\n//     (p, q, pa)\n// }\n//\n// fn complete_pivot_matrix<T>(a: &ArrayView2<T>) -> (Array2<f64>, Array2<f64>, Array2<T>)\n// where\n//     T: Signed + Num + PartialOrd + Clone,\n// {\n//     // pivot square matrix\n//     let n = a.len_of(Axis(0));\n//     let mut p: Array2<f64> = Array::eye(n);\n//     let mut q: Array2<f64> = Array::eye(n);\n//     let mut at = a.to_owned();\n//\n//     for j in 0..n {\n//         // iterate diagonally through\n//         let (mut kr, mut kc) = argabsmax2(at.slice(s![j.., j..]));\n//         kr += j;\n//         kc += j; // align with out scope array indices\n//\n//         match (kr, kc) {\n//             (kr, kc) if kr > j && kc > j => {\n//                 row_swap(&mut p, &j, &kr);\n//                 row_swap(&mut at, &j, &kr);\n//                 col_swap(&mut q, &j, &kc);\n//                 col_swap(&mut at, &j, &kc);\n//             }\n//             (kr, kc) if kr > j && kc == j => {\n//                 row_swap(&mut p, &j, &kr);\n//                 row_swap(&mut at, &j, &kr);\n//             }\n//             (kr, kc) if kr == j && kc > j => {\n//                 col_swap(&mut q, &j, &kc);\n//                 col_swap(&mut at, &j, &kc);\n//             }\n//             _ => {}\n//         }\n//     }\n//     (p, q, at)\n// }\n//\n// fn rook_pivot_matrix<T>(a: &ArrayView2<T>) -> (Array2<f64>, Array2<f64>, Array2<T>)\n// where\n//     T: Signed + Num + PartialOrd + Clone,\n// {\n//     // Implement a modified Rook Pivot.\n//     // If Original is the largest Abs in the row, and it is greater than some\n//     // tolerance then use that. This prevents row swapping where the rightmost columns\n//     // are zero, which ultimately leads to failure in sparse matrices.\n//\n//     // pivot square matrix\n//     let n = a.len_of(Axis(0));\n//     let mut p: Array2<f64> = Array::eye(n);\n//     let mut q: Array2<f64> = Array::eye(n);\n//     let mut at = a.to_owned();\n//\n//     for j in 0..n {\n//         // iterate diagonally through\n//         let kr = argabsmax(at.slice(s![j.., j])) + j;\n//         let kc = argabsmax(at.slice(s![j, j..])) + j;\n//\n//         match (kr, kc) {\n//             (kr, kc) if kr > j && kc > j => {\n//                 if at[[kr, j]].abs() > at[[j, kc]].abs() {\n//                     row_swap(&mut p, &j, &kr);\n//                     row_swap(&mut at, &j, &kr);\n//                 } else {\n//                     col_swap(&mut q, &j, &kc);\n//                     col_swap(&mut at, &j, &kc);\n//                 }\n//             }\n//             (kr, kc) if kr > j && kc == j => {\n//                 // MODIFIER as explained:\n//                 // if !(at[[j, j]].abs() > 1e-8) {\n//                     row_swap(&mut p, &j, &kr);\n//                     row_swap(&mut at, &j, &kr);\n//                 // }\n//             }\n//             (kr, kc) if kr == j && kc > j => {\n//                 col_swap(&mut q, &j, &kc);\n//                 col_swap(&mut at, &j, &kc);\n//             }\n//             _ => {}\n//         }\n//     }\n//     (p, q, at)\n// }\n//\n// pub enum PivotMethod {\n//     Partial,\n//     Complete,\n//     Rook,\n// }\n\n// pub fn pluq_decomp<T>(\n//     a: &ArrayView2<T>,\n//     pivot: PivotMethod,\n// ) -> (Array2<f64>, Array2<T>, Array2<T>, Array2<f64>)\n// where\n//     T: Signed + Num + PartialOrd + Clone + One + Zero + Sum + for<'a> Div<&'a T, Output = T>,\n//     for<'a> &'a T: Mul<&'a T, Output = T> + Sub<T, Output = T>,\n// {\n//     let n: usize = a.len_of(Axis(0));\n//     let mut l: Array2<T> = Array2::zeros((n, n));\n//     let mut u: Array2<T> = Array2::zeros((n, n));\n//     let p;\n//     let q;\n//     let paq;\n//     match pivot {\n//         PivotMethod::Partial => (p, q, paq) = partial_pivot_matrix(a),\n//         PivotMethod::Complete => (p, q, paq) = complete_pivot_matrix(a),\n//         PivotMethod::Rook => {\n//             (p, q, paq) = rook_pivot_matrix(a);\n//         }\n//     }\n//\n//     let one = T::one();\n//     for j in 0..n {\n//         l[[j, j]] = one.clone(); // all diagonal entries of L are set to unity\n//\n//         for i in 0..j + 1 {\n//             // LaTeX: u_{ij} = a_{ij} - \\sum_{k=1}^{i-1} u_{kj} l_{ik}\n//             let sx = dmul11_(&l.slice(s![i, ..i]), &u.slice(s![..i, j]));\n//             u[[i, j]] = &paq[[i, j]] - sx;\n//         }\n//\n//         for i in j..n {\n//             // LaTeX: l_{ij} = \\frac{1}{u_{jj}} (a_{ij} - \\sum_{k=1}^{j-1} u_{kj} l_{ik})\n//             let sy = dmul11_(&l.slice(s![i, ..j]), &u.slice(s![..j, j]));\n//             l[[i, j]] = (&paq[[i, j]] - sy) / &u[[j, j]];\n//         }\n//     }\n//     (p, l, u, q)\n// }\n\n// fn dsolve_lower21_<T>(l: &ArrayView2<T>, b: &ArrayView1<T>) -> Array1<T>\n// where\n//     T: Clone + Sum + Zero,\n//     for<'a> &'a T: Sub<&'a T, Output = T> + Mul<&'a T, Output = T> + Div<&'a T, Output = T>\n// {\n//     let n: usize = l.len_of(Axis(0));\n//     let mut x: Array1<T> = Array::zeros(n);\n//     for i in 0..n {\n//         let v = &b[i] - &dmul11_(&l.slice(s![i, ..i]), &x.slice(s![..i]));\n//         x[i] = &v / &l[[i, i]]\n//     }\n//     x\n// }\n\nfn dsolve_upper21_<T>(u: &ArrayView2<T>, b: &ArrayView1<T>) -> Array1<T>\nwhere\n    T: Clone + Sum + Zero,\n    for<'a> &'a T: Sub<&'a T, Output = T> + Mul<&'a T, Output = T> + Div<&'a T, Output = T>,\n{\n    let n: usize = u.len_of(Axis(0));\n    let mut x: Array1<T> = Array::zeros(n);\n    for i in (0..n).rev() {\n        let v = &b[i] - &dmul11_(&u.slice(s![i, (i + 1)..]), &x.slice(s![(i + 1)..]));\n        x[i] = &v / &u[[i, i]]\n    }\n    x\n}\n\nfn dsolve21_<T>(a: &ArrayView2<T>, b: &ArrayView1<T>) -> Array1<T>\nwhere\n    T: PartialOrd + Signed + Clone + Zero + Sum,\n    for<'a> &'a T: Sub<&'a T, Output = T> + Mul<&'a T, Output = T> + Div<&'a T, Output = T>,\n{\n    assert!(a.is_square());\n    let n = a.len_of(Axis(0));\n    assert_eq!(b.len_of(Axis(0)), n);\n\n    // a_ and b_ will be pivoted and amended throughout the solution\n    let mut a_ = a.to_owned();\n    let mut b_ = b.to_owned();\n\n    for j in 0..n {\n        let k = argabsmax(a_.slice(s![j.., j])) + j;\n        if j != k {\n            // define row swaps j <-> k  (note that k > j by definition)\n            row_swap(&mut a_, &j, &k);\n            el_swap(&mut b_, &j, &k);\n        }\n        // perform reduction on subsequent rows below j\n        for l in (j + 1)..n {\n            let scl = &a_[[l, j]] / &a_[[j, j]];\n            a_[[l, j]] = T::zero();\n            for m in (j + 1)..n {\n                a_[[l, m]] = &a_[[l, m]] - &(&scl * &a_[[j, m]]);\n            }\n            b_[l] = &b_[l] - &(&scl * &b_[j]);\n        }\n    }\n    dsolve_upper21_(&a_.view(), &b_.view())\n}\n\n// fn dsolve_upper_1d<T>(u: &ArrayView2<T>, b: &ArrayView1<T>) -> Array1<T>\n// where\n//     T: Clone + Sum + Zero + for<'a> Div<&'a T, Output = T>,\n//     for<'a> &'a T: Sub<T, Output = T> + Mul<&'a T, Output = T>,\n// {\n//     // reverse all dimensions and solve as lower triangular\n//     dsolve_lower_1d(&u.slice(s![..;-1, ..;-1]), &b.slice(s![..;-1]))\n//         .slice(s![..;-1])\n//         .to_owned()\n// }\n\n// fn dsolve21_<T>(a: &ArrayView2<T>, b: &ArrayView1<T>) -> Array1<T>\n// where\n//     T: PartialOrd + Signed + Clone + Sum + Zero + for<'a> Div<&'a T, Output = T>,\n//     for<'a> &'a T: Mul<&'a f64, Output = T> + Sub<T, Output = T> + Mul<&'a T, Output = T>,\n//     for<'a> &'a f64: Mul<&'a T, Output = T>,\n// {\n//     let (p, l, u, q) = pluq_decomp::<T>(&a.view(), PivotMethod::Complete);\n//     let pb: Array1<T> = fdmul21_(&p.view(), &b.view());\n//     let z: Array1<T> = dsolve_lower_1d(&l.view(), &pb.view());\n//     let y: Array1<T> = dsolve_upper_1d(&u.view(), &z.view());\n//     let x: Array1<T> = fdmul21_(&q.view(), &y.view());\n//     x\n// }\n\n/// Solve a linear system of equations, ax = b, using Gaussian elimination and partial pivoting.\n///\n/// - `a` is a 2d-array.\n/// - `b` is a 1d-array.\n/// - `allow_lsq` can be set to `true` if the number of rows in `a` is greater than its number of columns.\npub fn dsolve<T>(a: &ArrayView2<T>, b: &ArrayView1<T>, allow_lsq: bool) -> Array1<T>\nwhere\n    T: PartialOrd + Signed + Clone + Sum + Zero,\n    for<'a> &'a T: Sub<&'a T, Output = T> + Mul<&'a T, Output = T> + Div<&'a T, Output = T>,\n{\n    if allow_lsq {\n        let a_ = dmul22_(&a.t(), a);\n        let b_ = dmul21_(&a.t(), b);\n        dsolve21_(&a_.view(), &b_.view())\n    } else {\n        dsolve21_(a, b)\n    }\n}\n\n// UNIT TESTS\n\n//\n\n//\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::dual::dual::{Dual, Vars};\n    use std::sync::Arc;\n\n    // fn is_close(a: &f64, b: &f64, abs_tol: Option<f64>) -> bool {\n    //     // used rather than equality for float numbers\n    //     (a - b).abs() < abs_tol.unwrap_or(1e-8)\n    // }\n\n    #[test]\n    fn argabsmx_i32() {\n        let a: Array1<i32> = arr1(&[1, 4, 2, -5, 2]);\n        let result = argabsmax(a.view());\n        let expected: usize = 3;\n        assert_eq!(result, expected);\n    }\n\n    //     #[test]\n    //     fn argabsmx2_i32() {\n    //         let a: Array2<i32> = arr2(&[[-1, 2, 100], [-5, -2000, 0], [0, 0, 0]]);\n    //         let result = argabsmax2(a.view());\n    //         let expected: (usize, usize) = (1, 1);\n    //         assert_eq!(result, expected);\n    //     }\n\n    #[test]\n    fn argabsmx_dual() {\n        let a: Array1<Dual> = arr1(&[\n            Dual::new(1.0, Vec::new()),\n            Dual::try_new(-2.5, Vec::from([\"a\".to_string()]), Vec::from([2.0])).unwrap(),\n        ]);\n        let result = argabsmax(a.view());\n        let expected: usize = 1;\n        assert_eq!(result, expected);\n    }\n\n    //     #[test]\n    //     fn lower_tri_dual() {\n    //         let a = arr2(&[\n    //             [\n    //                 Dual::new(1.0, Vec::new()),\n    //                 Dual::new(0.0, Vec::new()),\n    //             ],\n    //             [\n    //                 Dual::new(2.0, Vec::new()),\n    //                 Dual::new(1.0, Vec::new()),\n    //             ],\n    //         ]);\n    //         let b = arr1(&[\n    //             Dual::new(2.0, Vec::new()),\n    //             Dual::new(5.0, Vec::new()),\n    //         ]);\n    //         let x = dsolve_lower21_(&a.view(), &b.view());\n    //         let expected_x = arr1(&[\n    //             Dual::new(2.0, Vec::new()),\n    //             Dual::new(1.0, Vec::new()),\n    //         ]);\n    //         assert_eq!(x, expected_x);\n    //     }\n\n    #[test]\n    fn upper_tri_dual() {\n        let a = arr2(&[\n            [Dual::new(1.0, Vec::new()), Dual::new(2.0, Vec::new())],\n            [Dual::new(0.0, Vec::new()), Dual::new(1.0, Vec::new())],\n        ]);\n        let b = arr1(&[Dual::new(2.0, Vec::new()), Dual::new(5.0, Vec::new())]);\n        let x = dsolve_upper21_(&a.view(), &b.view());\n        let expected_x = arr1(&[Dual::new(-8.0, Vec::new()), Dual::new(5.0, Vec::new())]);\n        assert_eq!(x, expected_x);\n    }\n\n    #[test]\n    fn dsolve_dual() {\n        let a: Array2<Dual> = Array2::eye(2);\n        let b: Array1<Dual> = arr1(&[\n            Dual::new(2.0, vec![\"x\".to_string()]),\n            Dual::new(5.0, vec![\"x\".to_string(), \"y\".to_string()]),\n        ]);\n        let result = dsolve(&a.view(), &b.view(), false);\n        let expected = arr1(&[\n            Dual::new(2.0, vec![\"x\".to_string()]),\n            Dual::new(5.0, vec![\"x\".to_string(), \"y\".to_string()]),\n        ]);\n        assert_eq!(result, expected);\n        assert!(Arc::ptr_eq(&result[0].vars(), &result[1].vars()));\n    }\n\n    #[test]\n    #[should_panic]\n    fn dmul11_p() {\n        dmul11_(&arr1(&[1.0, 2.0]).view(), &arr1(&[1.0]).view());\n    }\n\n    #[test]\n    #[should_panic]\n    fn dmul22_p() {\n        dmul22_(\n            &arr2(&[[1.0, 2.0], [2.0, 3.0]]).view(),\n            &arr2(&[[1.0, 2.0]]).view(),\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn dmul21_p() {\n        dmul21_(\n            &arr2(&[[1.0, 2.0], [2.0, 3.0]]).view(),\n            &arr1(&[1.0]).view(),\n        );\n    }\n}\n"
  },
  {
    "path": "rust/dual/linalg/linalg_f64.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Perform linear algebraic operations between arrays of generic type and arrays of f64.\n\nuse crate::dual::linalg::linalg_dual::{argabsmax, dmul22_, el_swap, row_swap};\nuse itertools::Itertools;\nuse ndarray::prelude::*;\nuse num_traits::identities::Zero;\nuse num_traits::Signed;\nuse std::cmp::PartialOrd;\nuse std::iter::Sum;\nuse std::ops::{Mul, Sub};\n\n/// Outer product of two 1d-arrays containing f64s.\npub fn fouter11_(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> Array2<f64> {\n    Array1::from_vec(\n        a.iter()\n            .cartesian_product(b.iter())\n            .map(|(x, y)| x * y)\n            .collect(),\n    )\n    .into_shape_with_order((a.len(), b.len()))\n    .expect(\"Pre checked dimensions\")\n}\n\n// F64 Crossover\n\n/// Inner product of two 1d-arrays.\n///\n/// The LHS contains f64s and the RHS is generic.\npub fn fdmul11_<T>(a: &ArrayView1<f64>, b: &ArrayView1<T>) -> T\nwhere\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    assert_eq!(a.len(), b.len());\n    a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()\n}\n\n/// Matrix multiplication of a 2d-array with a 1d-array.\n///\n/// The LHS contains f64s and the RHS is generic.\npub fn fdmul21_<T>(a: &ArrayView2<f64>, b: &ArrayView1<T>) -> Array1<T>\nwhere\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    assert_eq!(a.len_of(Axis(1)), b.len_of(Axis(0)));\n    Array1::from_vec(a.axis_iter(Axis(0)).map(|row| fdmul11_(&row, b)).collect())\n}\n\n/// Matrix multiplication of a 2d-array with a 1d-array.\n///\n/// The LHS is generic and the RHS contains f64s.\npub fn dfmul21_<T>(a: &ArrayView2<T>, b: &ArrayView1<f64>) -> Array1<T>\nwhere\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    assert_eq!(a.len_of(Axis(1)), b.len_of(Axis(0)));\n    Array1::from_vec(a.axis_iter(Axis(0)).map(|row| fdmul11_(b, &row)).collect())\n}\n\n/// Matrix multiplication of two 2d-arrays.\n///\n/// The LHS contains f64s and the RHS is generic.\npub fn fdmul22_<T>(a: &ArrayView2<f64>, b: &ArrayView2<T>) -> Array2<T>\nwhere\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    assert_eq!(a.len_of(Axis(1)), b.len_of(Axis(0)));\n    Array1::<T>::from_vec(\n        a.axis_iter(Axis(0))\n            .cartesian_product(b.axis_iter(Axis(1)))\n            .map(|(row, col)| fdmul11_(&row, &col))\n            .collect(),\n    )\n    .into_shape_with_order((a.len_of(Axis(0)), b.len_of(Axis(1))))\n    .expect(\"Dim are pre-checked\")\n}\n\n/// Matrix multiplication of two 2d-arrays.\n///\n/// The LHS is generic and the RHS contains f64s.\npub fn dfmul22_<T>(a: &ArrayView2<T>, b: &ArrayView2<f64>) -> Array2<T>\nwhere\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n    T: Sum,\n{\n    assert_eq!(a.len_of(Axis(1)), b.len_of(Axis(0)));\n    Array1::<T>::from_vec(\n        a.axis_iter(Axis(0))\n            .cartesian_product(b.axis_iter(Axis(1)))\n            .map(|(row, col)| fdmul11_(&col, &row))\n            .collect(),\n    )\n    .into_shape_with_order((a.len_of(Axis(0)), b.len_of(Axis(1))))\n    .expect(\"Dim are pre-checked\")\n}\n\nfn fdsolve_upper21_<T>(u: &ArrayView2<f64>, b: &ArrayView1<T>) -> Array1<T>\nwhere\n    T: Sum + Zero + Clone,\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n    for<'a> &'a T: Sub<&'a T, Output = T>,\n{\n    let n: usize = u.len_of(Axis(0));\n    let mut x: Array1<T> = Array::zeros(n);\n    for i in (0..n).rev() {\n        let v = &b[i] - &fdmul11_(&u.slice(s![i, (i + 1)..]), &x.slice(s![(i + 1)..]));\n        x[i] = &(1.0_f64 / &u[[i, i]]) * &v\n    }\n    x\n}\n\nfn fdsolve21_<T>(a: &ArrayView2<f64>, b: &ArrayView1<T>) -> Array1<T>\nwhere\n    T: PartialOrd + Signed + Clone + Zero + Sum,\n    for<'a> &'a f64: Mul<&'a T, Output = T> + Mul<&'a f64, Output = f64>,\n    for<'a> &'a T: Sub<&'a T, Output = T>,\n{\n    assert!(a.is_square());\n    let n = a.len_of(Axis(0));\n    assert_eq!(b.len_of(Axis(0)), n);\n\n    // a_ and b_ will be pivoted and amended throughout the solution\n    let mut a_ = a.to_owned();\n    let mut b_ = b.to_owned();\n\n    for j in 0..n {\n        let k = argabsmax(a_.slice(s![j.., j])) + j;\n        if j != k {\n            // define row swaps j <-> k  (note that k > j by definition)\n            row_swap(&mut a_, &j, &k);\n            el_swap(&mut b_, &j, &k);\n        }\n        // perform reduction on subsequent rows below j\n        for l in (j + 1)..n {\n            let scl: f64 = a_[[l, j]] / a_[[j, j]];\n            a_[[l, j]] = 0.0_f64;\n            for m in (j + 1)..n {\n                a_[[l, m]] -= scl * a_[[j, m]];\n            }\n            b_[l] = &b_[l] - &(&scl * &b_[j]);\n        }\n    }\n    fdsolve_upper21_(&a_.view(), &b_.view())\n}\n\n/// Solve a linear system, ax = b, using Gaussian elimination and partial pivoting.\n///\n/// The LHS contains f64s and the RHS is generic. `allow_lsq` can be `true` is the number of\n/// rows in `a` is greater than the number of columns.\npub fn fdsolve<T>(a: &ArrayView2<f64>, b: &ArrayView1<T>, allow_lsq: bool) -> Array1<T>\nwhere\n    T: PartialOrd + Signed + Clone + Zero + Sum,\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n    for<'a> &'a T: Sub<&'a T, Output = T>,\n{\n    if allow_lsq {\n        let a_: Array2<f64> = dmul22_(&a.t(), a);\n        let b_: Array1<T> = fdmul21_(&a.t(), b);\n        fdsolve21_(&a_.view(), &b_.view())\n    } else {\n        fdsolve21_(a, b)\n    }\n}\n\n// UNIT TESTS\n\n//\n\n//\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::dual::dual::{Dual, Vars};\n    use std::sync::Arc;\n\n    // fn is_close(a: &f64, b: &f64, abs_tol: Option<f64>) -> bool {\n    //     // used rather than equality for float numbers\n    //     (a - b).abs() < abs_tol.unwrap_or(1e-8)\n    // }\n\n    #[test]\n    fn outer_prod() {\n        let a = arr1(&[1.0, 2.0]);\n        let b = arr1(&[2.0, 1.0, 3.0]);\n        let c = fouter11_(&a.view(), &b.view());\n        let result = arr2(&[[2., 1., 3.], [4., 2., 6.]]);\n        assert_eq!(result, c)\n    }\n\n    #[test]\n    fn fdupper_tri_dual() {\n        let a = arr2(&[[1., 2.], [0., 1.]]);\n        let b = arr1(&[Dual::new(2.0, Vec::new()), Dual::new(5.0, Vec::new())]);\n        let x = fdsolve_upper21_(&a.view(), &b.view());\n        let expected_x = arr1(&[Dual::new(-8.0, Vec::new()), Dual::new(5.0, Vec::new())]);\n        assert_eq!(x, expected_x);\n    }\n\n    #[test]\n    fn fdsolve_dual() {\n        let a: Array2<f64> = Array2::eye(2);\n        let b: Array1<Dual> = arr1(&[\n            Dual::new(2.0, vec![\"x\".to_string()]),\n            Dual::new(5.0, vec![\"x\".to_string(), \"y\".to_string()]),\n        ]);\n        let result: Array1<Dual> = fdsolve(&a.view(), &b.view(), false);\n        let expected = arr1(&[\n            Dual::new(2.0, vec![\"x\".to_string()]),\n            Dual::new(5.0, vec![\"x\".to_string(), \"y\".to_string()]),\n        ]);\n        assert_eq!(result, expected);\n        assert!(Arc::ptr_eq(&result[0].vars(), &result[1].vars()));\n    }\n\n    #[test]\n    #[should_panic]\n    fn fdmul11_p() {\n        fdmul11_(&arr1(&[1.0, 2.0]).view(), &arr1(&[1.0]).view());\n    }\n\n    #[test]\n    #[should_panic]\n    fn fdmul22_p() {\n        fdmul22_(\n            &arr2(&[[1.0, 2.0], [2.0, 3.0]]).view(),\n            &arr2(&[[1.0, 2.0]]).view(),\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn dfmul22_p() {\n        dfmul22_(\n            &arr2(&[[1.0, 2.0], [2.0, 3.0]]).view(),\n            &arr2(&[[1.0, 2.0]]).view(),\n        );\n    }\n}\n"
  },
  {
    "path": "rust/dual/linalg/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Perform linear algebra operations involving Arrays of [f64], [Dual](crate::dual::Dual) and [Dual2](crate::dual::Dual2).\n\nmod linalg_dual;\nmod linalg_f64;\n\npub use crate::dual::linalg::linalg_dual::{dmul11_, dmul21_, dmul22_, douter11_, dsolve};\npub use crate::dual::linalg::linalg_f64::{\n    dfmul21_, dfmul22_, fdmul11_, fdmul21_, fdmul22_, fdsolve, fouter11_,\n};\n"
  },
  {
    "path": "rust/dual/linalg_py.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper module to export Rust linalg operations to Python using pyo3 bindings.\n\nuse crate::dual::dual::{Dual, Dual2};\nuse crate::dual::linalg::{dsolve, fdsolve};\nuse ndarray::{Array1, ArrayView2};\nuse num_traits::identities::Zero;\nuse num_traits::Signed;\nuse numpy::{PyArray2, PyArrayMethods};\nuse pyo3::prelude::*;\nuse std::cmp::PartialOrd;\nuse std::iter::Sum;\nuse std::ops::{Div, Mul, Sub};\n\nfn dsolve_py<T>(a: Vec<T>, b: Vec<T>, allow_lsq: bool) -> Vec<T>\nwhere\n    T: PartialOrd + Signed + Clone + Sum + Zero,\n    for<'a> &'a T: Sub<&'a T, Output = T> + Mul<&'a T, Output = T> + Div<&'a T, Output = T>,\n{\n    // requires row major order of numpy.\n    // &'py PyArray1<Dual>\n    let a1 = Array1::from_vec(a);\n    let b_ = Array1::from_vec(b);\n    let (r, c) = (a1.len() / b_.len(), b_.len());\n    let a2 = a1\n        .into_shape_with_order((r, c))\n        .expect(\"Inputs `a` and `b` for dual solve were incorrect shapes\");\n    let out = dsolve(&a2.view(), &b_.view(), allow_lsq);\n    out.into_raw_vec_and_offset().0\n}\n\n/// Wrapper to solve ax = b, when `a` and `b` contain `Dual` data types.\n#[pyfunction]\n#[pyo3(name = \"_dsolve1\")]\npub fn dsolve1_py(\n    _py: Python<'_>,\n    a: Vec<Dual>,\n    b: Vec<Dual>,\n    allow_lsq: bool,\n) -> PyResult<Vec<Dual>> {\n    Ok(dsolve_py(a, b, allow_lsq))\n}\n\n/// Wrapper to solve ax = b, when `a` and `b` contain `Dual2` data types.\n#[pyfunction]\n#[pyo3(name = \"_dsolve2\")]\npub fn dsolve2_py(\n    _py: Python<'_>,\n    a: Vec<Dual2>,\n    b: Vec<Dual2>,\n    allow_lsq: bool,\n) -> PyResult<Vec<Dual2>> {\n    Ok(dsolve_py(a, b, allow_lsq))\n}\n\nfn fdsolve_py<T>(a: ArrayView2<f64>, b: Vec<T>, allow_lsq: bool) -> Vec<T>\nwhere\n    T: PartialOrd + Signed + Clone + Sum + Zero,\n    for<'a> &'a T: Sub<&'a T, Output = T>,\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n{\n    let b_ = Array1::from_vec(b);\n    let out = fdsolve(&a.view(), &b_.view(), allow_lsq);\n    out.into_raw_vec_and_offset().0\n}\n\n/// Wrapper to solve ax = b, when `b` contains `Dual` data types.\n#[pyfunction]\n#[pyo3(name = \"_fdsolve1\")]\npub fn fdsolve1_py(\n    _py: Python<'_>,\n    a: &Bound<'_, PyArray2<f64>>,\n    b: Vec<Dual>,\n    allow_lsq: bool,\n) -> PyResult<Vec<Dual>> {\n    unsafe { Ok(fdsolve_py(a.as_array(), b, allow_lsq)) }\n}\n\n/// Wrapper to solve ax = b, when `b` contains `Dual2` data types.\n#[pyfunction]\n#[pyo3(name = \"_fdsolve2\")]\npub fn fdsolve2_py(\n    _py: Python<'_>,\n    a: &Bound<'_, PyArray2<f64>>,\n    b: Vec<Dual2>,\n    allow_lsq: bool,\n) -> PyResult<Vec<Dual2>> {\n    unsafe { Ok(fdsolve_py(a.as_array(), b, allow_lsq)) }\n}\n"
  },
  {
    "path": "rust/dual/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Toolset for forward mode automatic differentiation (AD).\n//!\n//! # AD Architecture\n//!\n//! The entire *rateslib* library is built around three core numeric types: [f64],\n//! [Dual] and [Dual2]. Obviously [f64] allows for traditional computation, which benefits\n//! from efficient calculation leveraging BLAS, while [Dual] and [Dual2] reduce performance\n//! of traditional calculation but provide efficient calculation of first order and second order\n//! derivatives, respectively. Derivatives are calculated using forward mode AD,\n//! similar, but not identical, to the\n//! [Julia ForwardDiff library](https://github.com/JuliaDiff/ForwardDiff.jl).\n//!\n//! Mathematical operations are defined to give dual numbers the ability to combine, and\n//! flexibly reference different variables at any point during calculations.\n//!\n\npub mod docs;\n\nmod dual;\npub use crate::dual::dual::{\n    set_order, set_order_clone, Dual, Dual2, Gradient1, Gradient2, MathFuncs, NumberOps, Vars,\n    VarsRelationship,\n};\n\nmod dual_ops;\npub(crate) mod dual_py;\n\npub mod linalg;\npub(crate) mod linalg_py;\n\nmod enums;\npub use crate::dual::enums::{\n    ADOrder, Number, NumberArray1, NumberArray2, NumberMapping, NumberPPSpline, NumberVec,\n};\n\n/// Utility for creating an ordered list of variable tags from a string and enumerator\npub(crate) fn get_variable_tags(name: &str, range: usize) -> Vec<String> {\n    Vec::from_iter((0..range).map(|i| name.to_string() + &i.to_string()))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_get_variable_tags() {\n        let result = get_variable_tags(\"x\", 3);\n        assert_eq!(\n            result,\n            vec![\"x0\".to_string(), \"x1\".to_string(), \"x2\".to_string()]\n        )\n    }\n}\n"
  },
  {
    "path": "rust/enums/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n// pub mod docs;\n\nmod parameters;\npub use crate::enums::parameters::{FloatFixingMethod, IROptionMetric, LegIndexBase};\n\npub(crate) mod py;\npub(crate) use crate::enums::py::PyFloatFixingMethod;\npub(crate) use crate::enums::py::PyIROptionMetric;\n"
  },
  {
    "path": "rust/enums/parameters.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse pyo3::prelude::*;\nuse serde::{Deserialize, Serialize};\n\n/// Specifier for date adjustment rules.\n#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\npub enum FloatFixingMethod {\n    /// RFR periods are settled with cashflow dates determined (separately as part of a Schedule) with a lag.\n    RFRPaymentDelay {},\n    /// RFR fixings and associated DCFs use values taken from 'n' business days prior.\n    RFRObservationShift(i32),\n    /// The final 'n' RFR fixings' values are taken as the most recent published value.\n    RFRLockout(i32),\n    /// RFR fixings use values taken from 'n' business days prior (no DCF shift).\n    RFRLookback(i32),\n    /// Uses arithmetic averaging instead compounding on the RFRPaymentDelay method.\n    RFRPaymentDelayAverage {},\n    /// Uses arithmetic averaging instead compounding on the RFRObservationShift method.\n    RFRObservationShiftAverage(i32),\n    /// Uses arithmetic averaging instead compounding on the RFRLockout method.\n    RFRLockoutAverage(i32),\n    /// Uses arithmetic averaging instead compounding on the RFRLookback method.\n    RFRLookbackAverage(i32),\n    /// Uses a tenor IBOR type rate calculation with the fixing lagged by 'n' business days.\n    IBOR(i32),\n}\n\nimpl FloatFixingMethod {\n    /// Return a fixing lag parameter associated with the variant.\n    pub fn method_param(&self) -> i32 {\n        match self {\n            FloatFixingMethod::RFRPaymentDelay {}\n            | FloatFixingMethod::RFRPaymentDelayAverage {} => 0_i32,\n            FloatFixingMethod::RFRObservationShift(param)\n            | FloatFixingMethod::RFRObservationShiftAverage(param)\n            | FloatFixingMethod::RFRLookback(param)\n            | FloatFixingMethod::RFRLookbackAverage(param)\n            | FloatFixingMethod::RFRLockout(param)\n            | FloatFixingMethod::RFRLockoutAverage(param)\n            | FloatFixingMethod::IBOR(param) => *param,\n        }\n    }\n}\n\n/// Specifier for the rate metric on IR Option types.\n#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\npub enum IROptionMetric {\n    /// Option premium expressed as a percentage of the notional.\n    PercentNotional {},\n    /// Option premium expressed as a cash quantity.\n    Premium {},\n    /// Volatility expressed in normalized basis points, i.e. used in the Bachelier pricing model.\n    NormalVol {},\n    /// Log-normal Black volatility applying a basis-points shift to the forward and strike.\n    BlackVolShift(i32),\n}\n\n/// Enumerable type for index base determination on each Period in a Leg.\n///\n/// This is a **simple** enum type and does not require initialization with additional parameters.\n#[pyclass(module = \"rateslib.rs\", eq, eq_int, hash, frozen, from_py_object)]\n#[derive(Debug, Hash, Copy, Clone, Serialize, Deserialize, PartialEq)]\npub enum LegIndexBase {\n    /// Set the index base on every period as the initial base date of the Leg.\n    Initial = 0,\n    /// Set the index base date of each period successively as the reference value for the\n    // previous period.\n    PeriodOnPeriod = 1,\n}\n"
  },
  {
    "path": "rust/enums/py/float_fixing_method.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper module to export to Python using pyo3 bindings.\n\nuse crate::enums::FloatFixingMethod;\nuse crate::json::{DeserializedObj, JSON};\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::types::PyTuple;\nuse serde::{Deserialize, Serialize};\n\n/// Enumerable type to defined floating period rate methods.\n///\n/// .. rubric:: Variants\n///\n/// .. ipython:: python\n///    :suppress:\n///\n///    from rateslib.rs import FloatFixingMethod\n///    variants = [item for item in FloatFixingMethod.__dict__ if \\\n///        \"__\" != item[:2] and \\\n///        item not in ['to_json', 'method_param'] \\\n///    ]\n///\n/// .. ipython:: python\n///\n///    variants\n///\n/// Note that this is a **complex** enum type and requires initialization with one integer parameter\n/// in **all** cases except for the *RFRPaymentDelay* and *RFRPaymentDelayAverage* variants (whose\n/// payment dates are defined by the *Schedule* itself). For example:\n///\n/// .. ipython:: python\n///\n///    _ = FloatFixingMethod.RFRPaymentDelay()\n///    _ = FloatFixingMethod.RFRPaymentDelayAverage()\n///    _ = FloatFixingMethod.IBOR(2)         # parameter is the lagged fixing days\n///    _ = FloatFixingMethod.RFRLockout(5)   # parameter is the days locked out (must be greater than 1)\n///    _ = FloatFixingMethod.RFRLookback(5)  # parameter is the shifted number of days\n///    _ = FloatFixingMethod.RFRObservationShift(5)  # parameter is the shifted number of days\n///\n#[pyclass(module = \"rateslib.rs\", name = \"FloatFixingMethod\", eq, from_py_object)]\n#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]\npub(crate) enum PyFloatFixingMethod {\n    #[pyo3(constructor = (_u8=0))]\n    RFRPaymentDelay { _u8: u8 },\n    #[pyo3(constructor = (param, _u8=1))]\n    RFRObservationShift { param: i32, _u8: u8 },\n    #[pyo3(constructor = (param, _u8=2))]\n    RFRLockout { param: i32, _u8: u8 },\n    #[pyo3(constructor = (param, _u8=3))]\n    RFRLookback { param: i32, _u8: u8 },\n    #[pyo3(constructor = (_u8=4))]\n    RFRPaymentDelayAverage { _u8: u8 },\n    #[pyo3(constructor = (param, _u8=5))]\n    RFRObservationShiftAverage { param: i32, _u8: u8 },\n    #[pyo3(constructor = (param, _u8=6))]\n    RFRLockoutAverage { param: i32, _u8: u8 },\n    #[pyo3(constructor = (param, _u8=7))]\n    RFRLookbackAverage { param: i32, _u8: u8 },\n    #[pyo3(constructor = (param, _u8=8))]\n    IBOR { param: i32, _u8: u8 },\n}\n\n/// Used for providing pickle support for PyFloatFixingMethod\nenum PyFloatFixingMethodNewArgs {\n    NoArgs(u8),\n    I32(i32, u8),\n}\n\nimpl<'py> IntoPyObject<'py> for PyFloatFixingMethodNewArgs {\n    type Target = PyTuple;\n    type Output = Bound<'py, Self::Target>;\n    type Error = std::convert::Infallible;\n\n    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {\n        match self {\n            PyFloatFixingMethodNewArgs::NoArgs(x) => Ok((x,).into_pyobject(py).unwrap()),\n            PyFloatFixingMethodNewArgs::I32(x, y) => Ok((x, y).into_pyobject(py).unwrap()),\n        }\n    }\n}\n\nimpl<'py> FromPyObject<'py, 'py> for PyFloatFixingMethodNewArgs {\n    type Error = PyErr;\n\n    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {\n        let ext: PyResult<(u8,)> = obj.extract();\n        if ext.is_ok() {\n            let (x,) = ext.unwrap();\n            return Ok(PyFloatFixingMethodNewArgs::NoArgs(x));\n        }\n        let ext: PyResult<(i32, u8)> = obj.extract();\n        if ext.is_ok() {\n            let (x, y) = ext.unwrap();\n            return Ok(PyFloatFixingMethodNewArgs::I32(x, y));\n        }\n        Err(PyValueError::new_err(\"Undefined behaviour\"))\n    }\n}\n\nimpl From<FloatFixingMethod> for PyFloatFixingMethod {\n    fn from(value: FloatFixingMethod) -> Self {\n        match value {\n            FloatFixingMethod::RFRPaymentDelay {} => {\n                PyFloatFixingMethod::RFRPaymentDelay { _u8: 0 }\n            }\n            FloatFixingMethod::RFRObservationShift(n) => {\n                PyFloatFixingMethod::RFRObservationShift { param: n, _u8: 1 }\n            }\n            FloatFixingMethod::RFRLockout(n) => {\n                PyFloatFixingMethod::RFRLockout { param: n, _u8: 2 }\n            }\n            FloatFixingMethod::RFRLookback(n) => {\n                PyFloatFixingMethod::RFRLookback { param: n, _u8: 3 }\n            }\n            FloatFixingMethod::RFRPaymentDelayAverage {} => {\n                PyFloatFixingMethod::RFRPaymentDelayAverage { _u8: 4 }\n            }\n            FloatFixingMethod::RFRObservationShiftAverage(n) => {\n                PyFloatFixingMethod::RFRObservationShiftAverage { param: n, _u8: 5 }\n            }\n            FloatFixingMethod::RFRLockoutAverage(n) => {\n                PyFloatFixingMethod::RFRLockoutAverage { param: n, _u8: 6 }\n            }\n            FloatFixingMethod::RFRLookbackAverage(n) => {\n                PyFloatFixingMethod::RFRLookbackAverage { param: n, _u8: 7 }\n            }\n            FloatFixingMethod::IBOR(n) => PyFloatFixingMethod::IBOR { param: n, _u8: 8 },\n        }\n    }\n}\n\nimpl From<PyFloatFixingMethod> for FloatFixingMethod {\n    fn from(value: PyFloatFixingMethod) -> Self {\n        match value {\n            PyFloatFixingMethod::RFRPaymentDelay { _u8: _ } => {\n                FloatFixingMethod::RFRPaymentDelay {}\n            }\n            PyFloatFixingMethod::RFRObservationShift { param: n, _u8: _ } => {\n                FloatFixingMethod::RFRObservationShift(n)\n            }\n            PyFloatFixingMethod::RFRLockout { param: n, _u8: _ } => {\n                FloatFixingMethod::RFRLockout(n)\n            }\n            PyFloatFixingMethod::RFRLookback { param: n, _u8: _ } => {\n                FloatFixingMethod::RFRLookback(n)\n            }\n            PyFloatFixingMethod::RFRPaymentDelayAverage { _u8: _ } => {\n                FloatFixingMethod::RFRPaymentDelayAverage {}\n            }\n            PyFloatFixingMethod::RFRObservationShiftAverage { param: n, _u8: _ } => {\n                FloatFixingMethod::RFRObservationShiftAverage(n)\n            }\n            PyFloatFixingMethod::RFRLockoutAverage { param: n, _u8: _ } => {\n                FloatFixingMethod::RFRLockoutAverage(n)\n            }\n            PyFloatFixingMethod::RFRLookbackAverage { param: n, _u8: _ } => {\n                FloatFixingMethod::RFRLookbackAverage(n)\n            }\n            PyFloatFixingMethod::IBOR { param: n, _u8: _ } => FloatFixingMethod::IBOR(n),\n        }\n    }\n}\n\n#[pymethods]\nimpl PyFloatFixingMethod {\n    /// Return a parameter associated with the fixing method.\n    ///\n    /// Returns\n    /// -------\n    /// int\n    #[pyo3(name = \"method_param\")]\n    fn method_param_py(&self) -> i32 {\n        let fixing_method: FloatFixingMethod = (*self).into();\n        fixing_method.method_param()\n    }\n\n    fn __str__(&self) -> String {\n        match self {\n            PyFloatFixingMethod::RFRPaymentDelay { _u8: _ } => \"rfr_payment_delay\".to_string(),\n            PyFloatFixingMethod::RFRObservationShift { param: _, _u8: _ } => {\n                \"rfr_observation_shift\".to_string()\n            }\n            PyFloatFixingMethod::RFRLockout { param: _, _u8: _ } => \"rfr_lockout\".to_string(),\n            PyFloatFixingMethod::RFRLookback { param: _, _u8: _ } => \"rfr_lookback\".to_string(),\n            PyFloatFixingMethod::RFRPaymentDelayAverage { _u8: _ } => {\n                \"rfr_payment_delay_avg\".to_string()\n            }\n            PyFloatFixingMethod::RFRObservationShiftAverage { param: _, _u8: _ } => {\n                \"rfr_observation_shift_avg\".to_string()\n            }\n            PyFloatFixingMethod::RFRLockoutAverage { param: _, _u8: _ } => {\n                \"rfr_lockout_avg\".to_string()\n            }\n            PyFloatFixingMethod::RFRLookbackAverage { param: _, _u8: _ } => {\n                \"rfr_lookback_avg\".to_string()\n            }\n            PyFloatFixingMethod::IBOR { param: _, _u8: _ } => \"ibor\".to_string(),\n        }\n    }\n\n    fn __getnewargs__(&self) -> PyFloatFixingMethodNewArgs {\n        match self {\n            PyFloatFixingMethod::RFRPaymentDelay { _u8: u } => {\n                PyFloatFixingMethodNewArgs::NoArgs(*u)\n            }\n            PyFloatFixingMethod::RFRObservationShift { param: n, _u8: u } => {\n                PyFloatFixingMethodNewArgs::I32(*n, *u)\n            }\n            PyFloatFixingMethod::RFRLockout { param: n, _u8: u } => {\n                PyFloatFixingMethodNewArgs::I32(*n, *u)\n            }\n            PyFloatFixingMethod::RFRLookback { param: n, _u8: u } => {\n                PyFloatFixingMethodNewArgs::I32(*n, *u)\n            }\n            PyFloatFixingMethod::RFRPaymentDelayAverage { _u8: u } => {\n                PyFloatFixingMethodNewArgs::NoArgs(*u)\n            }\n            PyFloatFixingMethod::RFRObservationShiftAverage { param: n, _u8: u } => {\n                PyFloatFixingMethodNewArgs::I32(*n, *u)\n            }\n            PyFloatFixingMethod::RFRLockoutAverage { param: n, _u8: u } => {\n                PyFloatFixingMethodNewArgs::I32(*n, *u)\n            }\n            PyFloatFixingMethod::RFRLookbackAverage { param: n, _u8: u } => {\n                PyFloatFixingMethodNewArgs::I32(*n, *u)\n            }\n            PyFloatFixingMethod::IBOR { param: n, _u8: u } => {\n                PyFloatFixingMethodNewArgs::I32(*n, *u)\n            }\n        }\n    }\n\n    #[new]\n    fn new_py(args: PyFloatFixingMethodNewArgs) -> PyFloatFixingMethod {\n        match args {\n            PyFloatFixingMethodNewArgs::NoArgs(0) => {\n                PyFloatFixingMethod::RFRPaymentDelay { _u8: 0 }\n            }\n            PyFloatFixingMethodNewArgs::I32(n, 1) => {\n                PyFloatFixingMethod::RFRObservationShift { param: n, _u8: 1 }\n            }\n            PyFloatFixingMethodNewArgs::I32(n, 2) => {\n                PyFloatFixingMethod::RFRLockout { param: n, _u8: 2 }\n            }\n            PyFloatFixingMethodNewArgs::I32(n, 3) => {\n                PyFloatFixingMethod::RFRLookback { param: n, _u8: 3 }\n            }\n            PyFloatFixingMethodNewArgs::NoArgs(4) => {\n                PyFloatFixingMethod::RFRPaymentDelayAverage { _u8: 4 }\n            }\n            PyFloatFixingMethodNewArgs::I32(n, 5) => {\n                PyFloatFixingMethod::RFRObservationShiftAverage { param: n, _u8: 5 }\n            }\n            PyFloatFixingMethodNewArgs::I32(n, 6) => {\n                PyFloatFixingMethod::RFRLockoutAverage { param: n, _u8: 6 }\n            }\n            PyFloatFixingMethodNewArgs::I32(n, 7) => {\n                PyFloatFixingMethod::RFRLookbackAverage { param: n, _u8: 7 }\n            }\n            PyFloatFixingMethodNewArgs::I32(n, 8) => PyFloatFixingMethod::IBOR { param: n, _u8: 8 },\n            _ => panic!(\"Undefined behaviour.\"),\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        let fixing_method: FloatFixingMethod = (*self).into();\n        format!(\"<rl.FloatFixingMethod.{:?} at {:p}>\", fixing_method, self)\n    }\n\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::PyFloatFixingMethod(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `FloatFixingMethod` to JSON.\",\n            )),\n        }\n    }\n}\n"
  },
  {
    "path": "rust/enums/py/ir_option_metric.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper module to export to Python using pyo3 bindings.\n\nuse crate::enums::IROptionMetric;\nuse crate::json::{DeserializedObj, JSON};\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::types::PyTuple;\nuse serde::{Deserialize, Serialize};\n\n/// Enumerable type for IR Option rate metrics.\n///\n/// .. rubric:: Variants\n///\n/// .. ipython:: python\n///    :suppress:\n///\n///    from rateslib.rs import IROptionMetric\n///    variants = [item for item in IROptionMetric.__dict__ if \\\n///        \"__\" != item[:2] and \\\n///        item not in ['to_json', 'method_param', 'shift'] \\\n///    ]\n///\n/// .. ipython:: python\n///\n///    variants\n///\n/// Note that this is a **complex** enum type and requires initialization with additional parameters\n/// in the case of *BlackVolShift* which requires a positive basis points shift. For example:\n///\n/// .. ipython:: python\n///\n///    metric1 = IROptionMetric.PercentNotional()\n///    metric2 = IROptionMetric.BlackVolShift(100)\n///\n#[pyclass(module = \"rateslib.rs\", name = \"IROptionMetric\", eq, from_py_object)]\n#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]\npub(crate) enum PyIROptionMetric {\n    #[pyo3(constructor = (_u8=0))]\n    PercentNotional { _u8: u8 },\n    #[pyo3(constructor = (_u8=1))]\n    Premium { _u8: u8 },\n    #[pyo3(constructor = (_u8=2))]\n    NormalVol { _u8: u8 },\n    #[pyo3(constructor = (param, _u8=3))]\n    BlackVolShift { param: i32, _u8: u8 },\n}\n\n/// Used for providing pickle support for PyIROptionMetric\nenum PyIROptionMetricNewArgs {\n    NoArgs(u8),\n    I32(i32, u8),\n}\n\nimpl<'py> IntoPyObject<'py> for PyIROptionMetricNewArgs {\n    type Target = PyTuple;\n    type Output = Bound<'py, Self::Target>;\n    type Error = std::convert::Infallible;\n\n    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {\n        match self {\n            PyIROptionMetricNewArgs::NoArgs(x) => Ok((x,).into_pyobject(py).unwrap()),\n            PyIROptionMetricNewArgs::I32(x, y) => Ok((x, y).into_pyobject(py).unwrap()),\n        }\n    }\n}\n\nimpl<'py> FromPyObject<'py, 'py> for PyIROptionMetricNewArgs {\n    type Error = PyErr;\n\n    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {\n        let ext: PyResult<(u8,)> = obj.extract();\n        if ext.is_ok() {\n            let (x,) = ext.unwrap();\n            return Ok(PyIROptionMetricNewArgs::NoArgs(x));\n        }\n        let ext: PyResult<(i32, u8)> = obj.extract();\n        if ext.is_ok() {\n            let (x, y) = ext.unwrap();\n            return Ok(PyIROptionMetricNewArgs::I32(x, y));\n        }\n        Err(PyValueError::new_err(\"Undefined behaviour\"))\n    }\n}\n\nimpl From<IROptionMetric> for PyIROptionMetric {\n    fn from(value: IROptionMetric) -> Self {\n        match value {\n            IROptionMetric::PercentNotional {} => PyIROptionMetric::PercentNotional { _u8: 0 },\n            IROptionMetric::Premium {} => PyIROptionMetric::Premium { _u8: 1 },\n            IROptionMetric::NormalVol {} => PyIROptionMetric::NormalVol { _u8: 2 },\n            IROptionMetric::BlackVolShift(n) => {\n                PyIROptionMetric::BlackVolShift { param: n, _u8: 3 }\n            }\n        }\n    }\n}\n\nimpl From<PyIROptionMetric> for IROptionMetric {\n    fn from(value: PyIROptionMetric) -> Self {\n        match value {\n            PyIROptionMetric::NormalVol { _u8: _ } => IROptionMetric::NormalVol {},\n            PyIROptionMetric::PercentNotional { _u8: _ } => IROptionMetric::PercentNotional {},\n            PyIROptionMetric::Premium { _u8: _ } => IROptionMetric::Premium {},\n            PyIROptionMetric::BlackVolShift { param: n, _u8: _ } => {\n                IROptionMetric::BlackVolShift(n)\n            }\n        }\n    }\n}\n\n#[pymethods]\nimpl PyIROptionMetric {\n    /// Return the shift associated with the Black Vol metric.\n    ///\n    /// Returns\n    /// -------\n    /// int\n    #[pyo3(name = \"shift\")]\n    fn shift_py(&self) -> i32 {\n        match self {\n            PyIROptionMetric::BlackVolShift { param: n, _u8: _ } => *n,\n            _ => 0_i32,\n        }\n    }\n\n    fn __str__(&self) -> String {\n        match self {\n            PyIROptionMetric::NormalVol { _u8: _ } => \"normal_vol\".to_string(),\n            PyIROptionMetric::PercentNotional { _u8: _ } => \"percent_notional\".to_string(),\n            PyIROptionMetric::Premium { _u8: _ } => \"premium\".to_string(),\n            PyIROptionMetric::BlackVolShift { param: n, _u8: _ } => {\n                format!(\"black_vol_shift_{}\", n)\n            }\n        }\n    }\n\n    fn __getnewargs__(&self) -> PyIROptionMetricNewArgs {\n        match self {\n            PyIROptionMetric::NormalVol { _u8: u } => PyIROptionMetricNewArgs::NoArgs(*u),\n            PyIROptionMetric::PercentNotional { _u8: u } => PyIROptionMetricNewArgs::NoArgs(*u),\n            PyIROptionMetric::Premium { _u8: u } => PyIROptionMetricNewArgs::NoArgs(*u),\n            PyIROptionMetric::BlackVolShift { param: n, _u8: u } => {\n                PyIROptionMetricNewArgs::I32(*n, *u)\n            }\n        }\n    }\n\n    #[new]\n    fn new_py(args: PyIROptionMetricNewArgs) -> PyIROptionMetric {\n        match args {\n            PyIROptionMetricNewArgs::NoArgs(0) => PyIROptionMetric::PercentNotional { _u8: 0 },\n            PyIROptionMetricNewArgs::NoArgs(1) => PyIROptionMetric::Premium { _u8: 1 },\n            PyIROptionMetricNewArgs::NoArgs(2) => PyIROptionMetric::NormalVol { _u8: 2 },\n            PyIROptionMetricNewArgs::I32(n, 3) => {\n                PyIROptionMetric::BlackVolShift { param: n, _u8: 3 }\n            }\n            _ => panic!(\"Undefined behaviour.\"),\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        let metric: IROptionMetric = (*self).into();\n        format!(\"<rl.IROptionMetric.{:?} at {:p}>\", metric, self)\n    }\n\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::PyIROptionMetric(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `IROptionMetric` to JSON.\",\n            )),\n        }\n    }\n}\n"
  },
  {
    "path": "rust/enums/py/leg_index_base.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::enums::parameters::LegIndexBase;\nuse crate::json::{DeserializedObj, JSON};\n\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\n\n#[pymethods]\nimpl LegIndexBase {\n    // JSON\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::LegIndexBase(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `LegIndexBase` to JSON.\",\n            )),\n        }\n    }\n\n    // Pickling\n    #[new]\n    fn new_py(item: usize) -> PyResult<LegIndexBase> {\n        match item {\n            _ if item == LegIndexBase::Initial as usize => Ok(LegIndexBase::Initial),\n            _ if item == LegIndexBase::PeriodOnPeriod as usize => Ok(LegIndexBase::PeriodOnPeriod),\n            _ => Err(PyValueError::new_err(\n                \"unreachable code on LegIndexBase pickle. Please report\",\n            )),\n        }\n    }\n    fn __getnewargs__<'py>(&self) -> PyResult<(usize,)> {\n        Ok((*self as usize,))\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"<rl.LegIndexBase.{:?} at {:p}>\", self, self)\n    }\n}\n"
  },
  {
    "path": "rust/enums/py/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\npub(crate) mod float_fixing_method;\npub(crate) mod ir_option_metric;\npub(crate) mod leg_index_base;\n\npub(crate) use crate::enums::py::float_fixing_method::PyFloatFixingMethod;\npub(crate) use crate::enums::py::ir_option_metric::PyIROptionMetric;\n"
  },
  {
    "path": "rust/fx/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\npub mod rates;\npub mod rates_py;\n"
  },
  {
    "path": "rust/fx/rates/ccy.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse internment::Intern;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\n\n/// A currency identified by 3-ascii ISO code.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]\npub struct Ccy {\n    pub(crate) name: Intern<String>,\n}\n\nimpl Ccy {\n    /// Constructs a new `Ccy`.\n    ///\n    /// Use **only** 3-ascii names. e.g. *\"usd\"*, aligned with ISO representation. `name` is converted\n    /// to lowercase to promote performant equality between \"USD\" and \"usd\".\n    ///\n    /// Panics if `name` is not 3 bytes in length.\n    pub fn try_new(name: &str) -> Result<Self, PyErr> {\n        let ccy: String = name.to_string().to_lowercase();\n        if ccy.len() != 3 {\n            return Err(PyValueError::new_err(\n                \"`Ccy` must be 3 ascii character in length, e.g. 'usd'.\",\n            ));\n        }\n        Ok(Ccy {\n            name: Intern::new(ccy),\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn ccy_creation() {\n        let a = Ccy::try_new(\"usd\").unwrap();\n        let b = Ccy::try_new(\"USD\").unwrap();\n        assert_eq!(a, b)\n    }\n\n    #[test]\n    fn ccy_creation_error() {\n        match Ccy::try_new(\"FOUR\") {\n            Ok(_) => assert!(false),\n            Err(_) => assert!(true),\n        }\n    }\n}\n"
  },
  {
    "path": "rust/fx/rates/fxpair.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::fx::rates::ccy::Ccy;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::PyErr;\nuse serde::{Deserialize, Serialize};\nuse std::fmt;\n\n/// A container of a two-pair `Ccy` cross.\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub struct FXPair(pub(crate) Ccy, pub(crate) Ccy);\n\nimpl FXPair {\n    /// Constructs a new `FXPair`, as a combination of two distinct `Ccy`s.\n    pub fn try_new(lhs: &str, rhs: &str) -> Result<Self, PyErr> {\n        let lhs_ = Ccy::try_new(lhs)?;\n        let rhs_ = Ccy::try_new(rhs)?;\n        if lhs_ == rhs_ {\n            return Err(PyValueError::new_err(\n                \"`FXPair` must be created from two distinct currencies, not same.\",\n            ));\n        }\n        Ok(FXPair(lhs_, rhs_))\n    }\n}\n\nimpl fmt::Display for FXPair {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{}{}\", self.0.name, self.1.name)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn fxpair_creation() {\n        let a = FXPair::try_new(\"usd\", \"eur\").unwrap();\n        let b = FXPair::try_new(\"USD\", \"EUR\").unwrap();\n        assert_eq!(a, b)\n    }\n\n    #[test]\n    fn fxpair_creation_error() {\n        match FXPair::try_new(\"usd\", \"USD\") {\n            Ok(_) => assert!(false),\n            Err(_) => assert!(true),\n        }\n    }\n}\n"
  },
  {
    "path": "rust/fx/rates/fxrate.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::Number;\nuse crate::fx::rates::fxpair::FXPair;\nuse chrono::NaiveDateTime;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\n\n/// An FX rate containing `FXPair`, `rate` and `settlement` info.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\npub struct FXRate {\n    pub(crate) pair: FXPair,\n    pub(crate) rate: Number,\n    pub(crate) settlement: Option<NaiveDateTime>,\n}\n\nimpl FXRate {\n    pub fn try_new(\n        lhs: &str,\n        rhs: &str,\n        rate: Number,\n        settlement: Option<NaiveDateTime>,\n    ) -> Result<Self, PyErr> {\n        Ok(FXRate {\n            pair: FXPair::try_new(lhs, rhs)?,\n            rate,\n            settlement,\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn fxrate_creation() {\n        FXRate::try_new(\"usd\", \"eur\", Number::F64(1.20), None).unwrap();\n    }\n}\n"
  },
  {
    "path": "rust/fx/rates/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Create objects related to the management and valuation of monetary amounts in different\n//! currencies, measured at different settlement dates in time.\n\nuse crate::dual::{set_order_clone, ADOrder, Dual, Dual2, Number, NumberArray2};\nuse crate::json::JSON;\nuse chrono::prelude::*;\nuse indexmap::set::IndexSet;\nuse itertools::Itertools;\nuse ndarray::{Array2, ArrayViewMut2, Axis};\nuse num_traits::{One, Zero};\nuse pyo3::exceptions::PyValueError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashSet;\nuse std::ops::{Div, Mul};\n\npub(crate) mod ccy;\npub use crate::fx::rates::ccy::Ccy;\n\npub(crate) mod fxpair;\npub use crate::fx::rates::fxpair::FXPair;\n\npub(crate) mod fxrate;\npub use crate::fx::rates::fxrate::FXRate;\n\n/// A multi-currency FX market deriving all crosses from a vector of `FXRate`s.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\n#[serde(from = \"FXRatesDataModel\")]\npub struct FXRates {\n    pub(crate) fx_rates: Vec<FXRate>,\n    pub(crate) currencies: IndexSet<Ccy>,\n    #[serde(skip)]\n    pub(crate) fx_array: NumberArray2,\n}\n\n#[derive(Deserialize)]\nstruct FXRatesDataModel {\n    fx_rates: Vec<FXRate>,\n    currencies: IndexSet<Ccy>,\n}\n\nimpl std::convert::From<FXRatesDataModel> for FXRates {\n    fn from(model: FXRatesDataModel) -> Self {\n        let base = model.currencies.first().unwrap();\n        Self::try_new(model.fx_rates, Some(*base)).expect(\"FXRates data model contains bad data.\")\n    }\n}\n\nimpl FXRates {\n    pub fn try_new(fx_rates: Vec<FXRate>, base: Option<Ccy>) -> Result<Self, PyErr> {\n        // Validations:\n        // 1. fx_rates is non-zero length\n        // 2. currencies are not under or over overspecified\n        // 3. settlement dates are all consistent.\n        // 4. No Dual2 data types are provided as input\n\n        // 1.\n        if fx_rates.is_empty() {\n            return Err(PyValueError::new_err(\n                \"`fx_rates` must contain at least on fx rate.\",\n            ));\n        }\n\n        let mut currencies: IndexSet<Ccy> = IndexSet::with_capacity(fx_rates.len() + 1_usize);\n        if let Some(ccy) = base {\n            currencies.insert(ccy);\n        }\n        for fxr in fx_rates.iter() {\n            currencies.insert(fxr.pair.0);\n            currencies.insert(fxr.pair.1);\n        }\n        let q = currencies.len();\n\n        // 2.\n        if q > (fx_rates.len() + 1) {\n            return Err(PyValueError::new_err(\n                \"FX Array cannot be solved. `fx_rates` is underspecified.\",\n            ));\n        } else if q < (fx_rates.len() + 1) {\n            return Err(PyValueError::new_err(\n                \"FX Array cannot be solved. `fx_rates` is overspecified.\",\n            ));\n        }\n\n        // 3.\n        let settlement: Option<NaiveDateTime> = fx_rates[0].settlement;\n        match settlement {\n            Some(date) => {\n                if !(&fx_rates\n                    .iter()\n                    .all(|d| d.settlement.map_or(false, |v| v == date)))\n                {\n                    return Err(PyValueError::new_err(\n                        \"`fx_rates` must have consistent `settlement` dates across all rates.\",\n                    ));\n                }\n            }\n            None => {\n                if !(&fx_rates\n                    .iter()\n                    .all(|d| d.settlement.map_or(true, |_v| false)))\n                {\n                    return Err(PyValueError::new_err(\n                        \"`fx_rates` must have consistent `settlement` dates across all rates.\",\n                    ));\n                }\n            }\n        }\n\n        let fx_array = create_fx_array(&currencies, &fx_rates, ADOrder::One)?;\n        Ok(FXRates {\n            fx_rates,\n            fx_array,\n            currencies,\n        })\n    }\n\n    pub fn get_ccy_index(&self, currency: &Ccy) -> Option<usize> {\n        self.currencies.get_index_of(currency)\n    }\n\n    pub fn rate(&self, lhs: &Ccy, rhs: &Ccy) -> Option<Number> {\n        let dom_idx = self.currencies.get_index_of(lhs)?;\n        let for_idx = self.currencies.get_index_of(rhs)?;\n        match &self.fx_array {\n            NumberArray2::F64(arr) => Some(Number::F64(arr[[dom_idx, for_idx]])),\n            NumberArray2::Dual(arr) => Some(Number::Dual(arr[[dom_idx, for_idx]].clone())),\n            NumberArray2::Dual2(arr) => Some(Number::Dual2(arr[[dom_idx, for_idx]].clone())),\n        }\n    }\n\n    pub fn update(&mut self, fx_rates: Vec<FXRate>) -> Result<(), PyErr> {\n        // validate that the input vector contains FX pairs that are already associated with the instance\n        if !(fx_rates\n            .iter()\n            .all(|v| self.fx_rates.iter().any(|x| x.pair == v.pair)))\n        {\n            return Err(PyValueError::new_err(\n                \"The given `fx_rates` pairs are not contained in the `FXRates` object.\",\n            ));\n        }\n        let mut fx_rates_: Vec<FXRate> = self.fx_rates.clone();\n        for fxr in fx_rates.into_iter() {\n            let idx = fx_rates_.iter().enumerate().fold(0_usize, |a, (i, v)| {\n                if fxr.pair.eq(&v.pair) {\n                    i\n                } else {\n                    a\n                }\n            });\n            fx_rates_[idx] = fxr;\n        }\n        let new_fxr = FXRates::try_new(fx_rates_, Some(self.currencies[0]))?;\n        self.fx_rates.clone_from(&new_fxr.fx_rates);\n        self.currencies.clone_from(&new_fxr.currencies);\n        self.fx_array = new_fxr.fx_array.clone();\n        Ok(())\n    }\n\n    pub fn set_ad_order(&mut self, ad: ADOrder) -> Result<(), PyErr> {\n        match (ad, &self.fx_array) {\n            (ADOrder::Zero, NumberArray2::F64(_))\n            | (ADOrder::One, NumberArray2::Dual(_))\n            | (ADOrder::Two, NumberArray2::Dual2(_)) => {\n                // leave the NumberArray2 unchanged.\n                Ok(())\n            }\n            (ADOrder::One, NumberArray2::F64(_)) => {\n                // rebuild the derivatives\n                let fx_array = create_fx_array(&self.currencies, &self.fx_rates, ADOrder::One)?;\n                self.fx_array = fx_array;\n                Ok(())\n            }\n            (ADOrder::Two, NumberArray2::F64(_)) => {\n                // rebuild the derivatives\n                let fx_array = create_fx_array(&self.currencies, &self.fx_rates, ADOrder::Two)?;\n                self.fx_array = fx_array;\n                Ok(())\n            }\n            (ADOrder::One, NumberArray2::Dual2(arr)) => {\n                let n: usize = arr.len_of(Axis(0));\n                let fx_array = NumberArray2::Dual(\n                    Array2::<Dual>::from_shape_vec(\n                        (n, n),\n                        arr.clone().into_iter().map(|d| d.into()).collect(),\n                    )\n                    .unwrap(),\n                );\n                self.fx_array = fx_array;\n                Ok(())\n            }\n            (ADOrder::Zero, NumberArray2::Dual(arr)) => {\n                // covert dual into f64\n                let n: usize = arr.len_of(Axis(0));\n                let fx_array = NumberArray2::F64(\n                    Array2::<f64>::from_shape_vec(\n                        (n, n),\n                        arr.clone().into_iter().map(|d| d.real).collect(),\n                    )\n                    .unwrap(),\n                );\n                self.fx_array = fx_array;\n                Ok(())\n            }\n            (ADOrder::Zero, NumberArray2::Dual2(arr)) => {\n                // covert dual into f64\n                let n: usize = arr.len_of(Axis(0));\n                let fx_array = NumberArray2::F64(\n                    Array2::<f64>::from_shape_vec(\n                        (n, n),\n                        arr.clone().into_iter().map(|d| d.real).collect(),\n                    )\n                    .unwrap(),\n                );\n                self.fx_array = fx_array;\n                Ok(())\n            }\n            (ADOrder::Two, NumberArray2::Dual(_)) => {\n                // rebuild derivatives\n                let fx_array = create_fx_array(&self.currencies, &self.fx_rates, ADOrder::Two)?;\n                self.fx_array = fx_array;\n                Ok(())\n            }\n        }\n    }\n}\n\n/// Return a one-hot mapping, in 2-d array form of the initial connections between currencies,\n/// given the pairs associated with the FX rates.\nfn create_initial_edges(currencies: &IndexSet<Ccy>, fx_pairs: &[FXPair]) -> Array2<i16> {\n    let mut edges: Array2<i16> = Array2::eye(currencies.len());\n    for pair in fx_pairs.iter() {\n        let row = currencies.get_index_of(&pair.0).unwrap();\n        let col = currencies.get_index_of(&pair.1).unwrap();\n        edges[[row, col]] = 1_i16;\n        edges[[col, row]] = 1_i16;\n    }\n    edges\n}\n\n/// Return a 2-d array containing all calculated FX rates as initially provided.\n///\n/// T will be an f64, Dual or Dual2\nfn create_initial_fx_array<T>(\n    currencies: &IndexSet<Ccy>,\n    fx_pairs: &[FXPair],\n    fx_rates: &[T],\n) -> Array2<T>\nwhere\n    T: Clone + One + Zero,\n    for<'a> f64: Div<&'a T, Output = T>,\n{\n    assert_eq!(fx_pairs.len(), fx_rates.len());\n    let mut fx_array: Array2<T> = Array2::eye(currencies.len());\n\n    for (i, pair) in fx_pairs.iter().enumerate() {\n        let row = currencies.get_index_of(&pair.0).unwrap();\n        let col = currencies.get_index_of(&pair.1).unwrap();\n        fx_array[[row, col]] = fx_rates[i].clone();\n        fx_array[[col, row]] = 1_f64 / &fx_array[[row, col]];\n    }\n    fx_array\n}\n\nfn mut_arrays_remaining_elements<T>(\n    mut fx_array: ArrayViewMut2<T>,\n    mut edges: ArrayViewMut2<i16>,\n    mut prev_value: HashSet<usize>,\n) -> Result<bool, PyErr>\nwhere\n    for<'a> &'a T: Mul<&'a T, Output = T>,\n    for<'a> f64: Div<&'a T, Output = T>,\n{\n    // check for stopping criteria if all edges, i.e. FX rates have been populated.\n    if edges.sum() == ((edges.len_of(Axis(0)) * edges.len_of(Axis(1))) as i16) {\n        return Ok(true);\n    }\n\n    // otherwise, find the number of edges connected with each currency\n    // that is not in the list of pre-checked values\n    let available_edges_and_nodes: Vec<(i16, usize)> = edges\n        .sum_axis(Axis(1))\n        .into_iter()\n        .zip(0_usize..)\n        .filter(|(_v, i)| !prev_value.contains(i))\n        .into_iter()\n        .collect();\n    // and from those find the index of the currency with the most edges\n    let sampled_node = available_edges_and_nodes\n        .into_iter()\n        .max_by_key(|(value, _)| *value)\n        .map(|(_, idx)| idx);\n\n    let node: usize;\n    match sampled_node {\n        None => {\n            // The `prev_value` list contain every node and the `edges` matrix is not solved,\n            // hence this cannot be solved.\n            return Err(PyValueError::new_err(\n                \"FX Array cannot be solved. There are degenerate FX rate pairs.\\n\\\n                    For example ('eurusd' + 'usdeur') or ('usdeur', 'eurjpy', 'usdjpy').\",\n            ));\n        }\n        Some(node_) => node = node_,\n    }\n\n    // `combinations` is a list of pairs that can be formed from the edges associated\n    // with `node`, but which have not yet been populated. These will be populated\n    // in the next stage.\n    let combinations: Vec<Vec<usize>> = edges\n        .row(node)\n        .iter()\n        .zip(0_usize..)\n        .filter(|(v, i)| **v == 1_i16 && *i != node)\n        .map(|(_v, i)| i)\n        .combinations(2)\n        .filter(|v| edges[[v[0], v[1]]] == 0_i16)\n        .collect();\n\n    // iterate through the unpopulated combinations and determine the FX rate between those\n    // nodes calculating via the FX rate with the central node.\n    let mut counter: i16 = 0;\n    for c in combinations {\n        counter += 1_i16;\n        edges[[c[0], c[1]]] = 1_i16;\n        edges[[c[1], c[0]]] = 1_i16;\n        fx_array[[c[0], c[1]]] = &fx_array[[c[0], node]] * &fx_array[[node, c[1]]];\n        fx_array[[c[1], c[0]]] = 1.0_f64 / &fx_array[[c[0], c[1]]];\n    }\n\n    if counter == 0 {\n        // then that discovered node not yielded any results, so add it to the list of checked\n        // prev values checked and run again, recursively.\n        prev_value.insert(node);\n        return mut_arrays_remaining_elements(fx_array.view_mut(), edges.view_mut(), prev_value);\n    } else {\n        // a population has been successful. Re run the algorithm placing the most recently\n        // sampled node in the set of prev values, so that an infinite loop is avoide and a new\n        // node will be sampled next time.\n        return mut_arrays_remaining_elements(\n            fx_array.view_mut(),\n            edges.view_mut(),\n            HashSet::from([node]),\n        );\n    }\n}\n\n/// Creates an FX Array with the sparse graph network algorithm defining Dual variables directly.\nfn create_fx_array(\n    currencies: &IndexSet<Ccy>,\n    fx_rates: &[FXRate],\n    ad: ADOrder,\n) -> Result<NumberArray2, PyErr> {\n    let fx_pairs: Vec<FXPair> = fx_rates.iter().map(|x| x.pair).collect();\n    let vars: Vec<String> = fx_pairs.iter().map(|x| format!(\"fx_{}\", x)).collect();\n    let mut edges = create_initial_edges(currencies, &fx_pairs);\n    let fx_rates_: Vec<Number> = fx_rates\n        .iter()\n        .enumerate()\n        .map(|(i, x)| set_order_clone(&x.rate, ad, vec![vars[i].clone()]))\n        .collect();\n    match ad {\n        ADOrder::Zero => {\n            let fx_rates__: Vec<f64> = fx_rates_.iter().map(f64::from).collect();\n            let mut fx_array_: Array2<f64> =\n                create_initial_fx_array(currencies, &fx_pairs, &fx_rates__);\n            let _ = mut_arrays_remaining_elements(\n                fx_array_.view_mut(),\n                edges.view_mut(),\n                HashSet::new(),\n            )?;\n            Ok(NumberArray2::F64(fx_array_))\n        }\n        ADOrder::One => {\n            let fx_rates__: Vec<Dual> = fx_rates_.iter().map(Dual::from).collect();\n            let mut fx_array_: Array2<Dual> =\n                create_initial_fx_array(currencies, &fx_pairs, &fx_rates__);\n            let _ = mut_arrays_remaining_elements(\n                fx_array_.view_mut(),\n                edges.view_mut(),\n                HashSet::new(),\n            )?;\n            Ok(NumberArray2::Dual(fx_array_))\n        }\n        ADOrder::Two => {\n            let fx_rates__: Vec<Dual2> = fx_rates_.iter().map(Dual2::from).collect();\n            let mut fx_array_: Array2<Dual2> =\n                create_initial_fx_array(currencies, &fx_pairs, &fx_rates__);\n            let _ = mut_arrays_remaining_elements(\n                fx_array_.view_mut(),\n                edges.view_mut(),\n                HashSet::new(),\n            )?;\n            Ok(NumberArray2::Dual2(fx_array_))\n        }\n    }\n}\n\nimpl JSON for FXRates {}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::ndt;\n    use ndarray::arr2;\n\n    #[test]\n    fn fxrates_rate() {\n        let fxr = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(1.08), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"jpy\", Number::F64(110.0), Some(ndt(2004, 1, 1))).unwrap(),\n            ],\n            None,\n        )\n        .unwrap();\n\n        let expected = arr2(&[\n            [1.0, 1.08, 118.8],\n            [0.9259259, 1.0, 110.0],\n            [0.0084175, 0.0090909, 1.0],\n        ]);\n\n        let arr: Vec<f64> = match fxr.fx_array {\n            NumberArray2::Dual(arr) => arr.iter().map(|x| x.real()).collect(),\n            _ => panic!(\"unreachable\"),\n        };\n        assert!(arr\n            .iter()\n            .zip(expected.iter())\n            .all(|(x, y)| (x - y).abs() < 1e-6))\n    }\n\n    #[test]\n    fn fxrates_multi_chain() {\n        let fxr = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(0.5), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"gbp\", Number::F64(1.25), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"gbp\", \"jpy\", Number::F64(100.0), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"nok\", \"jpy\", Number::F64(10.0), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"nok\", \"brl\", Number::F64(5.0), Some(ndt(2004, 1, 1))).unwrap(),\n            ],\n            Some(Ccy::try_new(\"usd\").unwrap()),\n        )\n        .unwrap();\n        let expected = arr2(&[\n            [1.0, 2.0, 1.25, 125.0, 12.5, 62.5],\n            [0.5, 1.0, 0.625, 62.5, 6.25, 31.25],\n            [0.8, 1.6, 1.0, 100.0, 10.0, 50.0],\n            [0.008, 0.016, 0.01, 1.0, 0.1, 0.5],\n            [0.08, 0.16, 0.10, 10.0, 1.0, 5.0],\n            [0.016, 0.032, 0.02, 2.0, 0.2, 1.0],\n        ]);\n\n        let arr: Vec<f64> = match fxr.fx_array {\n            NumberArray2::Dual(arr) => arr.iter().map(|x| x.real()).collect(),\n            _ => panic!(\"unreachable\"),\n        };\n        println!(\"arr: {:?}\", arr);\n        assert!(arr\n            .iter()\n            .zip(expected.iter())\n            .all(|(x, y)| (x - y).abs() < 1e-6))\n    }\n\n    #[test]\n    fn fxrates_single_central_currency() {\n        let fxr = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(0.5), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"gbp\", Number::F64(1.25), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"jpy\", Number::F64(100.0), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"nok\", Number::F64(10.0), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"brl\", Number::F64(50.0), Some(ndt(2004, 1, 1))).unwrap(),\n            ],\n            Some(Ccy::try_new(\"usd\").unwrap()),\n        )\n        .unwrap();\n        let expected = arr2(&[\n            [1.0, 2.0, 1.25, 100.0, 10.0, 50.0],\n            [0.5, 1.0, 0.625, 50.0, 5.0, 25.0],\n            [0.8, 1.6, 1.0, 80.0, 8.0, 40.0],\n            [0.01, 0.02, 0.0125, 1.0, 0.1, 0.5],\n            [0.1, 0.2, 0.125, 10.0, 1.0, 5.0],\n            [0.02, 0.04, 0.025, 2.0, 0.2, 1.0],\n        ]);\n\n        let arr: Vec<f64> = match fxr.fx_array {\n            NumberArray2::Dual(arr) => arr.iter().map(|x| x.real()).collect(),\n            _ => panic!(\"unreachable\"),\n        };\n        println!(\"arr: {:?}\", arr);\n        assert!(arr\n            .iter()\n            .zip(expected.iter())\n            .all(|(x, y)| (x - y).abs() < 1e-6))\n    }\n\n    #[test]\n    fn fxrates_creation_error() {\n        let fxr = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(1.0), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"eur\", Number::F64(1.0), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"sek\", \"nok\", Number::F64(1.0), Some(ndt(2004, 1, 1))).unwrap(),\n            ],\n            None,\n        );\n        match fxr {\n            Ok(_) => assert!(false),\n            Err(_) => assert!(true),\n        }\n    }\n\n    #[test]\n    fn fxrates_eq() {\n        let fxr = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(1.08), None).unwrap(),\n                FXRate::try_new(\"usd\", \"jpy\", Number::F64(110.0), None).unwrap(),\n            ],\n            None,\n        )\n        .unwrap();\n\n        let fxr2 = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(1.08), None).unwrap(),\n                FXRate::try_new(\"usd\", \"jpy\", Number::F64(110.0), None).unwrap(),\n            ],\n            None,\n        )\n        .unwrap();\n\n        assert_eq!(fxr, fxr2)\n    }\n\n    #[test]\n    fn fxrates_update() {\n        let mut fxr = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(1.08), None).unwrap(),\n                FXRate::try_new(\"usd\", \"jpy\", Number::F64(110.0), None).unwrap(),\n            ],\n            None,\n        )\n        .unwrap();\n        let _ = fxr.update(vec![FXRate::try_new(\n            \"usd\",\n            \"jpy\",\n            Number::F64(120.0),\n            None,\n        )\n        .unwrap()]);\n        let rate = fxr\n            .rate(&Ccy::try_new(\"eur\").unwrap(), &Ccy::try_new(\"usd\").unwrap())\n            .unwrap();\n        match rate {\n            Number::Dual(d) => assert_eq!(d.real, 1.08),\n            _ => panic!(\"failure\"),\n        };\n        let rate = fxr\n            .rate(&Ccy::try_new(\"usd\").unwrap(), &Ccy::try_new(\"jpy\").unwrap())\n            .unwrap();\n        match rate {\n            Number::Dual(d) => assert_eq!(d.real, 120.0),\n            _ => panic!(\"failure\"),\n        }\n    }\n\n    #[test]\n    fn second_order_gradients_on_set_order() {\n        let mut fxr = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"usd\", \"nok\", Number::F64(10.0), None).unwrap(),\n                FXRate::try_new(\"eur\", \"nok\", Number::F64(8.0), None).unwrap(),\n            ],\n            None,\n        )\n        .unwrap();\n        let _ = fxr.set_ad_order(ADOrder::Two);\n        let d1 = Dual2::new(10.0, vec![\"fx_usdnok\".to_string()]);\n        let d2 = Dual2::new(8.0, vec![\"fx_eurnok\".to_string()]);\n        let d3 = d1 / d2;\n        let rate: Dual2 = fxr\n            .rate(&Ccy::try_new(\"usd\").unwrap(), &Ccy::try_new(\"eur\").unwrap())\n            .unwrap()\n            .into();\n        assert_eq!(d3, rate)\n    }\n}\n"
  },
  {
    "path": "rust/fx/rates_py.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper module to export Rust FX rate data types to Python using pyo3 bindings.\n\nuse crate::dual::{ADOrder, Number, NumberArray2};\nuse crate::fx::rates::{Ccy, FXRate, FXRates};\nuse bincode::config::legacy;\nuse bincode::serde::{decode_from_slice, encode_to_vec};\nuse chrono::prelude::*;\nuse ndarray::Axis;\nuse pyo3::prelude::*;\n// use std::collections::HashMap;\nuse pyo3::exceptions::PyValueError;\n// use pyo3::exceptions::PyValueError;\nuse crate::json::json_py::DeserializedObj;\nuse crate::json::JSON;\nuse pyo3::types::PyBytes;\n\n#[pymethods]\nimpl Ccy {\n    #[new]\n    fn new_py(name: &str) -> PyResult<Self> {\n        Ccy::try_new(name)\n    }\n\n    #[getter]\n    #[pyo3(name = \"name\")]\n    fn name_py(&self) -> PyResult<String> {\n        Ok(self.name.to_string())\n    }\n\n    fn __repr__(&self) -> PyResult<String> {\n        Ok(format!(\"<Ccy: '{}'>\", self.name))\n    }\n\n    fn __eq__(&self, other: &Self) -> bool {\n        self.name == other.name\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self) -> PyResult<(String,)> {\n        Ok(((*(self.name)).clone(),))\n    }\n}\n\n#[pymethods]\nimpl FXRate {\n    #[new]\n    #[pyo3(signature = (lhs, rhs, rate, settlement=None))]\n    fn new_py(\n        lhs: &str,\n        rhs: &str,\n        rate: Number,\n        settlement: Option<NaiveDateTime>,\n    ) -> PyResult<Self> {\n        FXRate::try_new(lhs, rhs, rate, settlement)\n    }\n\n    #[getter]\n    #[pyo3(name = \"rate\")]\n    fn rate_py(&self) -> PyResult<Number> {\n        Ok(self.rate.clone())\n    }\n\n    #[getter]\n    #[pyo3(name = \"ad\")]\n    fn ad_py(&self) -> u8 {\n        match self.rate {\n            Number::F64(_) => 0,\n            Number::Dual(_) => 1,\n            Number::Dual2(_) => 2,\n        }\n    }\n\n    #[getter]\n    #[pyo3(name = \"settlement\")]\n    fn settlement_py(&self) -> PyResult<Option<NaiveDateTime>> {\n        Ok(self.settlement)\n    }\n\n    #[getter]\n    #[pyo3(name = \"pair\")]\n    fn pair_py(&self) -> PyResult<String> {\n        Ok(format!(\"{}\", self.pair))\n    }\n\n    fn __repr__(&self) -> PyResult<String> {\n        match &self.rate {\n            Number::F64(f) => Ok(format!(\"<FXRate: '{}' {}>\", self.pair, f)),\n            Number::Dual(d) => Ok(format!(\n                \"<FXRate: '{}' <Dual: {}, ..>>\",\n                self.pair,\n                d.real()\n            )),\n            Number::Dual2(d) => Ok(format!(\n                \"<FXRate: '{}' <Dual2: {}, ..>>\",\n                self.pair,\n                d.real()\n            )),\n        }\n    }\n\n    fn __eq__(&self, other: &Self) -> bool {\n        self == other\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self) -> PyResult<(String, String, Number, Option<NaiveDateTime>)> {\n        Ok((\n            (*(self.pair.0.name)).clone(),\n            (*(self.pair.1.name)).clone(),\n            self.rate.clone(),\n            self.settlement,\n        ))\n    }\n}\n\n#[pymethods]\nimpl FXRates {\n    // #[new]\n    // fn new_py(\n    //     fx_rates: HashMap<String, Number>,\n    //     settlement: NaiveDateTime,\n    //     base: Option<String>,\n    // ) -> PyResult<Self> {\n    //     let base_ = match base {\n    //         None => None,\n    //         Some(v) => Some(Ccy::try_new(&v)?),\n    //     };\n    //     let fx_rates_ = fx_rates\n    //         .into_iter()\n    //         .map(|(k, v)| FXRate::try_new(&k[..3], &k[3..], v, Some(settlement)))\n    //         .collect::<Result<Vec<_>, _>>()?;\n    //     FXRates::try_new(fx_rates_, settlement, base_)\n    // }\n    #[new]\n    #[pyo3(signature = (fx_rates, base=None))]\n    fn new_py(fx_rates: Vec<FXRate>, base: Option<Ccy>) -> PyResult<Self> {\n        FXRates::try_new(fx_rates, base)\n    }\n\n    #[getter]\n    #[pyo3(name = \"fx_rates\")]\n    fn fx_rates_py(&self) -> PyResult<Vec<FXRate>> {\n        Ok(self.fx_rates.clone())\n    }\n\n    #[getter]\n    #[pyo3(name = \"currencies\")]\n    fn currencies_py(&self) -> PyResult<Vec<Ccy>> {\n        Ok(Vec::from_iter(self.currencies.iter().cloned()))\n    }\n\n    #[getter]\n    #[pyo3(name = \"ad\")]\n    fn ad_py(&self) -> PyResult<u8> {\n        match &self.fx_array {\n            NumberArray2::F64(_) => Ok(0),\n            NumberArray2::Dual(_) => Ok(1),\n            NumberArray2::Dual2(_) => Ok(2),\n        }\n    }\n\n    #[getter]\n    #[pyo3(name = \"base\")]\n    fn base_py(&self) -> PyResult<Ccy> {\n        Ok(self.currencies[0])\n    }\n\n    #[getter]\n    #[pyo3(name = \"fx_vector\")]\n    fn fx_vector_py(&self) -> PyResult<Vec<Number>> {\n        match &self.fx_array {\n            NumberArray2::F64(arr) => Ok(arr.row(0).iter().map(|x| Number::F64(*x)).collect()),\n            NumberArray2::Dual(arr) => {\n                Ok(arr.row(0).iter().map(|x| Number::Dual(x.clone())).collect())\n            }\n            NumberArray2::Dual2(arr) => Ok(arr\n                .row(0)\n                .iter()\n                .map(|x| Number::Dual2(x.clone()))\n                .collect()),\n        }\n    }\n\n    #[getter]\n    #[pyo3(name = \"fx_array\")]\n    fn fx_array_py(&self) -> PyResult<Vec<Vec<Number>>> {\n        match &self.fx_array {\n            NumberArray2::F64(arr) => Ok(arr\n                .lanes(Axis(1))\n                .into_iter()\n                .map(|row| row.iter().map(|d| Number::F64(*d)).collect())\n                .collect()),\n            NumberArray2::Dual(arr) => Ok(arr\n                .lanes(Axis(1))\n                .into_iter()\n                .map(|row| row.iter().map(|d| Number::Dual(d.clone())).collect())\n                .collect()),\n            NumberArray2::Dual2(arr) => Ok(arr\n                .lanes(Axis(1))\n                .into_iter()\n                .map(|row| row.iter().map(|d| Number::Dual2(d.clone())).collect())\n                .collect()),\n        }\n    }\n\n    #[pyo3(name = \"get_ccy_index\")]\n    fn get_ccy_index_py(&self, currency: Ccy) -> Option<usize> {\n        self.get_ccy_index(&currency)\n    }\n\n    #[pyo3(name = \"rate\")]\n    fn rate_py(&self, lhs: &Ccy, rhs: &Ccy) -> PyResult<Option<Number>> {\n        Ok(self.rate(lhs, rhs))\n    }\n\n    #[pyo3(name = \"update\")]\n    fn update_py(&mut self, fx_rates: Vec<FXRate>) -> PyResult<()> {\n        self.update(fx_rates)\n    }\n\n    #[pyo3(name = \"set_ad_order\")]\n    fn set_ad_order_py(&mut self, ad: ADOrder) -> PyResult<()> {\n        self.set_ad_order(ad)?;\n        Ok(())\n    }\n\n    // JSON\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::FXRates(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `UnionCal` to JSON.\",\n            )),\n        }\n    }\n\n    // Pickling\n    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {\n        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;\n        Ok(())\n    }\n    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {\n        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))\n    }\n    pub fn __getnewargs__<'py>(&self) -> PyResult<(Vec<FXRate>, Option<Ccy>)> {\n        Ok((self.fx_rates.clone(), Some(self.currencies[0])))\n    }\n\n    // Equality\n    fn __eq__(&self, other: FXRates) -> bool {\n        self.eq(&other)\n    }\n\n    fn __copy__(&self) -> Self {\n        self.clone()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::ndt;\n\n    #[test]\n    fn fxrates_eq() {\n        let fxr = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(1.08), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"jpy\", Number::F64(110.0), Some(ndt(2004, 1, 1))).unwrap(),\n            ],\n            None,\n        )\n        .unwrap();\n\n        let fxr2 = FXRates::try_new(\n            vec![\n                FXRate::try_new(\"eur\", \"usd\", Number::F64(1.08), Some(ndt(2004, 1, 1))).unwrap(),\n                FXRate::try_new(\"usd\", \"jpy\", Number::F64(110.0), Some(ndt(2004, 1, 1))).unwrap(),\n            ],\n            None,\n        )\n        .unwrap();\n\n        assert!(fxr.__eq__(fxr2))\n    }\n}\n"
  },
  {
    "path": "rust/fx_volatility/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\npub mod sabr_funcs;\n"
  },
  {
    "path": "rust/fx_volatility/sabr_funcs.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::linalg::fouter11_;\nuse crate::dual::{Dual, Dual2, MathFuncs, Number, Vars};\n\nuse num_traits::{Pow, Signed};\nuse pyo3::{pyfunction, PyErr};\nuse std::sync::Arc;\n\n#[pyfunction]\npub(crate) fn _sabr_x0(\n    k: Number,\n    f: Number,\n    _t: Number,\n    a: Number,\n    b: Number,\n    _p: Number,\n    _v: Number,\n    derivative: u8,\n) -> Result<(Number, Option<Number>), PyErr> {\n    // X0 = a / ((fk)^((1-b)/2) * (1 + (1-b)^2/24 ln^2(f/k) + (1-b)^4/1920 ln^4(f/k) )\n    //If ``derivative`` is 1 also returns dX0/dk, calculated using sympy.\n    //If ``derivative`` is 2 also returns dX0/df, calculated using sympy.\n    let x0 = 1_f64 / &k;\n    let x1 = 1_f64 / 24_f64 - &b / 24_f64;\n    let x2 = (&f * &x0).log();\n    let x3 = (1_f64 - &b).pow(4_f64);\n    let x4 = &x1 * (&x2).pow(2_f64) + (&x2).pow(4_f64) * &x3 / 1920_f64 + 1_f64;\n    let x5 = &b / 2_f64 - 0.5_f64;\n    let x6 = &a * (&f * &k).pow(&x5);\n\n    let x = &x6\n        / ((&x2).pow(4_f64) * (1_f64 - &b).pow(4_f64) / 1920_f64\n            + (&x2).pow(2_f64) * (1_f64 / 24_f64 - &b / 24_f64)\n            + 1_f64);\n\n    let dx: Option<Number> = match derivative {\n        1 => Some(\n            &x0 * x5 * &x6 / &x4\n                + &x6 * (2_f64 * &x0 * x1 * &x2 + &x0 * (&x2).pow(3_f64) * x3 / 480_f64)\n                    / x4.pow(2_f64),\n        ),\n        2 => {\n            let y0 = &b - 1_f64;\n            let y2 = (&y0).pow(2_f64) * (&x2).pow(2_f64);\n            let y3 = (&y0).pow(4_f64) * (&x2).pow(4_f64) + 80_f64 * &y2 + 1920_f64;\n            Some(\n                960_f64 * &a * &y0 * (&f * &k).pow(x5) * (-8_f64 * y0 * x2 * (&y2 + 40_f64) + &y3)\n                    / (f * y3.pow(2_f64)),\n            )\n        }\n        _ => None,\n    };\n\n    Ok((x, dx))\n}\n\n#[pyfunction]\npub(crate) fn _sabr_x1(\n    k: Number,\n    f: Number,\n    t: Number,\n    a: Number,\n    b: Number,\n    p: Number,\n    v: Number,\n    derivative: u8,\n) -> Result<(Number, Option<Number>), PyErr> {\n    let x0 = 1_f64 / &k;\n    let x1 = &b / 2_f64 - 0.5_f64;\n    let x2 = &f * &k;\n    let x3 = &b - 1_f64;\n    let x = &t\n        * ((&a).pow(2_f64) * (&x2).pow(&x3) * (&x3).pow(2_f64) / 24_f64\n            + 0.25_f64 * &a * &b * &p * &v * (&x2).pow(&x1)\n            + (&v).pow(2_f64) * (2_f64 - 3_f64 * (&p).pow(2_f64)) / 24_f64)\n        + 1_f64;\n\n    let dx: Option<Number> = match derivative {\n        1 => Some(\n            &t * ((&a).pow(2_f64) * &x0 * (&x2).pow(&x3) * (&x3).pow(3_f64) / 24_f64\n                + 0.25 * &a * &b * p * &v * x0 * &x1 * x2.pow(x1)),\n        ),\n        2 => Some(\n            &a * &t\n                * &x3\n                * (&a * (&x3).pow(2_f64) * (&x2).pow(x3) + 3_f64 * b * p * v * x2.pow(x1))\n                / (24_f64 * f),\n        ),\n        _ => None,\n    };\n\n    Ok((x, dx))\n}\n\n#[pyfunction]\npub(crate) fn _sabr_x2(\n    k: Number,\n    f: Number,\n    _t: Number,\n    a: Number,\n    b: Number,\n    p: Number,\n    v: Number,\n    derivative: u8,\n) -> Result<(Number, Option<Number>), PyErr> {\n    let x0 = 1_f64 / &k;\n    let x1 = (&f * &x0).log();\n    let x2 = 1_f64 / &a;\n    let x3 = &f * &k;\n    let x4 = &b / 2_f64 - 0.5_f64;\n    let x5 = (&x3).pow(-&x4);\n    let x6 = &v * &x2 * &x5;\n\n    let z = &x6 * &x1;\n    let chi = (((1_f64 - 2_f64 * &p * &z + &z * &z).pow(0.5_f64) + &z - &p) / (1_f64 - &p)).log();\n\n    let x: Number;\n    if z.abs() > 1e-15_f64 {\n        x = &z / &chi;\n    } else {\n        // handle the undefined quotient case when f=k by directly specifying dual numbers\n        let p_f64 = f64::from(&p);\n\n        x = match &z {\n            Number::F64(_z) => Number::F64(1_f64),\n            Number::Dual(z_) => Number::Dual(Dual {\n                real: 1_f64,\n                dual: &z_.dual * p_f64 * -0.5_f64,\n                vars: Arc::clone(&z_.vars),\n            }),\n            Number::Dual2(z_) => {\n                let (z_cast, p_cast): (Dual2, Dual2) = match &p {\n                    Number::F64(p_) => {\n                        let temp = Dual2::new_from(z_, *p_, vec![]);\n                        z_.to_union_vars(&temp, None)\n                    }\n                    Number::Dual(_) => panic!(\"Unexpected Dual/Dual2 type crossing in _sabr_x2\"),\n                    Number::Dual2(p_) => z_.to_union_vars(p_, None),\n                };\n                let f_z = -0.5_f64 * p_f64;\n                // f_p = 0.0\n                let f_zz = (2_f64 - 3_f64 * p_f64 * p_f64) / 6_f64;\n                let f_zp = -0.5_f64;\n                // f_pp = 0.0\n\n                let mut dual2 = f_z * &z_cast.dual2.clone();\n                dual2 =\n                    dual2 + 0.5_f64 * f_zz * fouter11_(&z_cast.dual.view(), &z_cast.dual.view());\n                // dual2 += 0.5 * f_pp * np.outer(p_.dual, p_.dual)\n                dual2 = dual2\n                    + 0.5_f64\n                        * f_zp\n                        * (fouter11_(&z_cast.dual.view(), &p_cast.dual.view())\n                            + fouter11_(&p_cast.dual.view(), &z_cast.dual.view()));\n                Number::Dual2(Dual2 {\n                    real: 1_f64,\n                    vars: Arc::clone(&z_cast.vars),\n                    dual: &z_cast.dual * p_f64 * -0.5_f64,\n                    dual2,\n                })\n            }\n        };\n    }\n\n    let dx: Option<Number>;\n    match derivative {\n        1 => {\n            if z.abs() > 1e-15_f64 {\n                let x7 = &x1 * &x6;\n                let x8 = &p * &x7;\n                let x9 = (&x1).pow(2_f64);\n                let x10 = (&a).pow(-2_f64);\n                let x11 = (&v).pow(2_f64);\n                let x12 = &b - 1_f64;\n                let x13 = (&x3).pow(-&x12);\n                let x14 = &x10 * &x11 * &x13;\n                let x15 = (&x14 * &x9 - 2_f64 * &x8 + 1_f64).pow(0.5_f64);\n                let x16 = -&p + &x15 + &x7;\n                let x17 = (&x16 / (1_f64 - &p)).log();\n                let x18 = 1_f64 / &x17;\n                let x19 = &x0 * &x6;\n                let x20 = -&x4;\n                let x21 = 1.0 * &x0;\n\n                dx = Some(\n                    &v * &x0 * &x1 * &x18 * &x2 * &x20 * &x5\n                        - &x18 * &x19\n                        - &x7\n                            * (&x0 * &x20 * &x7 - x19\n                                + (1.0_f64 * p * v * &x0 * x2 * x5\n                                    - 0.5_f64 * x0 * x10 * x11 * x12 * x13 * x9\n                                    - x1 * x14 * &x21\n                                    - x20 * x21 * x8)\n                                    / x15)\n                            / (x16 * (&x17).pow(2_f64)),\n                )\n            } else {\n                let dx_dz = _sabr_dx2_dz(&z, &p);\n\n                let y0 = 1_f64 / &k;\n                let y1 = &b / 2_f64 - 0.5_f64;\n                let y2 = &v * &x0 / (&a * (&f * &k).pow(&y1));\n                let dz = -y2 * (y1 * (&f * y0).log() + 1_f64);\n\n                dx = Some(dx_dz * dz);\n            }\n        }\n        2 => {\n            if z.abs() > 1e-15_f64 {\n                let y0 = (&a).pow(2_f64);\n                let y1 = 1_f64 / &y0;\n                let y3 = (&x3).pow(-x4);\n                let y4 = &a * &p;\n                let y6 = &v * &x1;\n                let y7 = &y3 * &y6;\n                let y8 = &b - 1_f64;\n                let y9 = (&x3).pow(-&y8);\n                let y10 =\n                    (&y1 * (&v * &v * &x1 * &x1 * &y9 + &y0 - 2_f64 * &y4 * &y7)).pow(0.5_f64);\n                let y11 = &a * (-&p + &y10) + &y7;\n                let y12 = &a * &y10;\n                let y13 = ((&a * &p - &y12 - &y7) / (&a * (&p - 1_f64))).log();\n                let y14 = &x1 * &y8 - 2_f64;\n                let y15 = -&y14;\n\n                dx = Some(\n                    &v * &y1\n                        * &y3\n                        * (&y11 * &y12 * &y13 * &y15\n                            + &y6 * (&y12 * &y14 * &y3 + &y14 * &y6 * &y9 + &y15 * &y3 * &y4))\n                        / (2_f64 * &f * &y10 * &y11 * (&y13).pow(2_f64)),\n                )\n            } else {\n                let dx_dz = _sabr_dx2_dz(&z, &p);\n                let dz = &v * &x5 * (-(&b - 1_f64) * &x1 + 2_f64) / (2_f64 * &a * &f);\n                dx = Some(dx_dz * dz);\n            }\n        }\n        _ => dx = None,\n    };\n\n    Ok((x, dx))\n}\n\nfn _sabr_dx2_dz(z: &Number, p: &Number) -> Number {\n    let p_f64 = f64::from(p);\n    match z {\n        Number::F64(_) => Number::F64(-p_f64 / 2_f64),\n        Number::Dual(z_) => {\n            let (z_cast, p_cast): (Dual, Dual) = match &p {\n                Number::F64(p_) => {\n                    let temp = Dual::new_from(z_, *p_, vec![]);\n                    z_.to_union_vars(&temp, None)\n                }\n                Number::Dual(p_) => z_.to_union_vars(p_, None),\n                Number::Dual2(_) => panic!(\"Unexpected Dual/Dual2 type crossing in _sabr_x2\"),\n            };\n            let mut dual = -0.5_f64 * &p_cast.dual;\n            dual = dual + (2_f64 - 3_f64 * p_f64 * p_f64) / 6_f64 * &z_cast.dual;\n            Number::Dual(Dual {\n                real: -0.5_f64 * p_f64,\n                vars: Arc::clone(&z_cast.vars),\n                dual,\n            })\n        }\n        Number::Dual2(z_) => {\n            let (z_cast, p_cast): (Dual2, Dual2) = match &p {\n                Number::F64(p_) => {\n                    let temp = Dual2::new_from(z_, *p_, vec![]);\n                    z_.to_union_vars(&temp, None)\n                }\n                Number::Dual(_) => panic!(\"Unexpected Dual/Dual2 type crossing in _sabr_x2\"),\n                Number::Dual2(p_) => z_.to_union_vars(p_, None),\n            };\n            let mut dual = -0.5_f64 * &p_cast.dual;\n            dual = dual + (2_f64 - 3_f64 * p_f64 * p_f64) / 6_f64 * &z_cast.dual;\n            let mut dual2 = (2_f64 - 3_f64 * p_f64 * p_f64) / 6_f64 * &z_cast.dual2;\n            dual2 = dual2 - 0.5_f64 * &p_cast.dual2;\n            dual2 = dual2\n                + p_f64 * (5_f64 - 6_f64 * p_f64 * p_f64) / 8_f64\n                    * fouter11_(&z_cast.dual.view(), &z_cast.dual.view());\n            dual2 = dual2\n                - 0.5_f64\n                    * p_f64\n                    * (fouter11_(&z_cast.dual.view(), &p_cast.dual.view())\n                        + fouter11_(&p_cast.dual.view(), &z_cast.dual.view()));\n            Number::Dual2(Dual2 {\n                real: -0.5_f64 * p_f64,\n                vars: Arc::clone(&z_cast.vars),\n                dual: dual,\n                dual2: dual2,\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "rust/json/json_py.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper to allow de/serializable objects in Rust to be passed to/from Python using pyo3\n//! bindings.\n//!\n//! Any pyclass that is serializable is added as a DeserializedObj and then converted to JSON.\n//! Having been deserialized it is matched, unpacked and passed back to Python.\n//!\n\nuse crate::curves::curve_py::Curve;\nuse crate::dual::{Dual, Dual2};\nuse crate::enums::{LegIndexBase, PyFloatFixingMethod, PyIROptionMetric};\nuse crate::fx::rates::FXRates;\nuse crate::json::JSON;\nuse crate::scheduling::{\n    Cal, Convention, Frequency, Imm, NamedCal, PyAdjuster, RollDay, Schedule, StubInference,\n    UnionCal,\n};\nuse crate::splines::{PPSplineDual, PPSplineDual2, PPSplineF64};\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse serde::{Deserialize, Serialize};\n\n/// Container for all of the Python exposed Rust objects which are deserializable.\n///\n/// This allows a single `from_json` function to automatically detect the type and\n/// convert it directly to a usable type in Python.\n#[derive(Serialize, Deserialize, FromPyObject, IntoPyObject)]\npub(crate) enum DeserializedObj {\n    Dual(Dual),\n    Dual2(Dual2),\n    Cal(Cal),\n    UnionCal(UnionCal),\n    NamedCal(NamedCal),\n    FXRates(FXRates),\n    Curve(Curve),\n    PPSplineF64(PPSplineF64),\n    PPSplineDual(PPSplineDual),\n    PPSplineDual2(PPSplineDual2),\n    StubInference(StubInference),\n    Imm(Imm),\n    RollDay(RollDay),\n    Frequency(Frequency),\n    PyAdjuster(PyAdjuster),\n    Schedule(Schedule),\n    Convention(Convention),\n    PyFloatFixingMethod(PyFloatFixingMethod),\n    LegIndexBase(LegIndexBase),\n    PyIROptionMetric(PyIROptionMetric),\n}\n\nimpl JSON for DeserializedObj {}\n\n#[pyfunction]\n#[pyo3(name = \"from_json\")]\npub(crate) fn from_json_py(_py: Python<'_>, json: &str) -> PyResult<DeserializedObj> {\n    match DeserializedObj::from_json(json) {\n        Ok(v) => Ok(v),\n        Err(e) => Err(PyValueError::new_err(format!(\n            \"Could not create Class or Struct from given JSON.\\n{}\",\n            e\n        ))),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::dual::Dual;\n    #[test]\n    fn test_serialized_object() {\n        let x = Dual::new(2.5, vec![\"x\".to_string()]);\n        let json = DeserializedObj::Dual(x.clone()).to_json().unwrap();\n        println!(\"{}\", json);\n        assert_eq!(json,\"{\\\"Dual\\\":{\\\"real\\\":2.5,\\\"vars\\\":[\\\"x\\\"],\\\"dual\\\":{\\\"v\\\":1,\\\"dim\\\":[1],\\\"data\\\":[1.0]}}}\");\n\n        let y = DeserializedObj::from_json(&json).unwrap();\n        match y {\n            DeserializedObj::Dual(d) => assert_eq!(x, d),\n            _ => assert!(false),\n        }\n    }\n}\n"
  },
  {
    "path": "rust/json/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Allows serialization and deserialization to JSON, with the ``serde`` crate.\n\npub(crate) mod json_py;\npub(crate) use crate::json::json_py::DeserializedObj;\n\nuse serde::{Deserialize, Serialize};\nuse serde_json;\n\n/// Handles the `to` and `from` JSON conversion.\npub trait JSON: Serialize + for<'de> Deserialize<'de> {\n    /// Return a JSON string representing the object.\n    fn to_json(&self) -> serde_json::Result<String> {\n        serde_json::to_string(self)\n    }\n\n    /// Create an object from a JSON string representation.\n    fn from_json(json: &str) -> serde_json::Result<Self> {\n        serde_json::from_str(json)\n    }\n}\n"
  },
  {
    "path": "rust/lib.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! This is the documentation for rateslib-rs\n//!\n//! <div class=\"warning\">This library is in development. Only parts of *rateslib (Python)* have been ported\n//! successfully, completely and with sound documentation to Rust.</div>\n\n#![doc(html_favicon_url = \"https://rateslib.readthedocs.io/en/stable/_static/favicon.ico\")]\n// #![doc(html_logo_url = \"https://rateslib.readthedocs.io/en/stable/_static/rateslib_logo2a.png\")]\n\n#[cfg(test)]\nmod tests;\n\npub mod json;\nuse crate::json::json_py::from_json_py;\n\n// type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>;\n// type GenericResult<T> = Result<T, GenericError>;\n\nuse pyo3::prelude::*;\n\npub mod dual;\nuse dual::linalg_py::{dsolve1_py, dsolve2_py, fdsolve1_py, fdsolve2_py};\nuse dual::{ADOrder, Dual, Dual2};\n\npub mod splines;\nuse splines::spline_py::{bspldnev_single, bsplev_single};\nuse splines::{PPSplineDual, PPSplineDual2, PPSplineF64};\n\npub mod curves;\nuse curves::curve_py::Curve;\nuse curves::interpolation::interpolation_py::index_left_f64;\nuse curves::{\n    FlatBackwardInterpolator, FlatForwardInterpolator, LinearInterpolator,\n    LinearZeroRateInterpolator, LogLinearInterpolator, Modifier, NullInterpolator,\n    _get_modifier_str,\n};\n\npub mod fx;\nuse fx::rates::ccy::Ccy;\nuse fx::rates::{FXRate, FXRates};\n\npub mod fx_volatility;\nuse fx_volatility::sabr_funcs::{_sabr_x0, _sabr_x1, _sabr_x2};\n\npub mod scheduling;\nuse scheduling::{\n    Cal, CalendarManager, Convention, Frequency, Imm, NamedCal, PyAdjuster, RollDay, Schedule,\n    StubInference, UnionCal,\n};\n\npub mod enums;\nuse enums::{LegIndexBase, PyFloatFixingMethod, PyIROptionMetric};\n\n#[pymodule]\nfn rs(m: &Bound<'_, PyModule>) -> PyResult<()> {\n    // JSON\n    m.add_function(wrap_pyfunction!(from_json_py, m)?)?;\n\n    // Automatic Differentiation\n    m.add_class::<Dual>()?;\n    m.add_class::<Dual2>()?;\n    m.add_class::<ADOrder>()?;\n    m.add_function(wrap_pyfunction!(dsolve1_py, m)?)?;\n    m.add_function(wrap_pyfunction!(dsolve2_py, m)?)?;\n    m.add_function(wrap_pyfunction!(fdsolve1_py, m)?)?;\n    m.add_function(wrap_pyfunction!(fdsolve2_py, m)?)?;\n\n    // Splines\n    m.add_class::<PPSplineF64>()?;\n    m.add_class::<PPSplineDual>()?;\n    m.add_class::<PPSplineDual2>()?;\n    m.add_function(wrap_pyfunction!(bsplev_single, m)?)?;\n    m.add_function(wrap_pyfunction!(bspldnev_single, m)?)?;\n\n    // Curves\n    m.add_class::<Curve>()?;\n    m.add_class::<Modifier>()?;\n    m.add_function(wrap_pyfunction!(index_left_f64, m)?)?;\n    m.add_class::<FlatBackwardInterpolator>()?;\n    m.add_class::<FlatForwardInterpolator>()?;\n    m.add_class::<LinearInterpolator>()?;\n    m.add_class::<LogLinearInterpolator>()?;\n    m.add_class::<LinearZeroRateInterpolator>()?;\n    m.add_class::<NullInterpolator>()?;\n\n    // Scheduling\n    m.add_class::<Cal>()?;\n    m.add_class::<UnionCal>()?;\n    m.add_class::<NamedCal>()?;\n    m.add_class::<CalendarManager>()?;\n    m.add_class::<Convention>()?;\n    m.add_class::<PyAdjuster>()?;\n    m.add_function(wrap_pyfunction!(_get_modifier_str, m)?)?;\n\n    m.add_class::<Frequency>()?;\n    m.add_class::<RollDay>()?;\n    m.add_class::<Imm>()?;\n    m.add_class::<StubInference>()?;\n    m.add_class::<Schedule>()?;\n\n    // FX\n    m.add_class::<Ccy>()?;\n    m.add_class::<FXRate>()?;\n    m.add_class::<FXRates>()?;\n\n    // FX Volatility\n    m.add_function(wrap_pyfunction!(_sabr_x0, m)?)?;\n    m.add_function(wrap_pyfunction!(_sabr_x1, m)?)?;\n    m.add_function(wrap_pyfunction!(_sabr_x2, m)?)?;\n\n    // Rates and Indexes\n    m.add_class::<PyFloatFixingMethod>()?;\n    m.add_class::<LegIndexBase>()?;\n    m.add_class::<PyIROptionMetric>()?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "rust/main.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse pyo3::prelude::*;\nuse pyo3::types::PyTuple;\nuse rateslib::dual::{Dual, Number, NumberOps};\nuse std::time::SystemTime;\n\nfn ops<T>(a: &T, b: &T) -> T\nwhere\n    T: NumberOps<T>,\n    for<'a> &'a T: NumberOps<T>,\n{\n    &(&(&(a + b) - a) * b) / a\n}\n\nfn ops2(a: f64, b: &Dual) -> Dual {\n    &(&(&(a + b) - a) * b) / a\n}\n\nfn main() -> PyResult<()> {\n    pyo3::prepare_freethreaded_python();\n    Python::with_gil(|py| {\n        let obj: Bound<'_, PyTuple> = (1_u32, 2.3_f64).into_pyobject(py).unwrap();\n        let ext: PyResult<(u32, f64)> = obj.extract();\n        println!(\"{:?}\", ext);\n        Ok(())\n    })\n\n    //     let a0 = 2.5_f64;\n    //     let b0 = 3.5_f64;\n    //     let a1 = Dual::new(2.5_f64, vec![\"x\".to_string(), \"y\".to_string()]);\n    //     let b1 = Dual::new_from(&a1, 3.5_f64, vec![\"x\".to_string(), \"y\".to_string()]);\n    //     let a2 = Number::Dual(a1.clone());\n    //     let b2 = Number::Dual(b1.clone());\n    //     let a3 = Number::F64(2.5_f64);\n    //     let b3 = Number::F64(3.5_f64);\n    //\n    //     let now = SystemTime::now();\n    //\n    //     for _i in 0..10000 {\n    //         let _ = ops(&a0, &b0);\n    //     }\n    //     println!(\"{:.5?} time taken for f64\", now.elapsed());\n    //\n    //     for _i in 0..10000 {\n    //         let _ = ops(&a3, &b3);\n    //     }\n    //     println!(\"{:.5?} time taken for Number F64 wrapper\", now.elapsed());\n    //\n    //     for _i in 0..10000 {\n    //         let _ = ops(&a1, &b1);\n    //     }\n    //     println!(\"{:.5?} time taken for Dual\", now.elapsed());\n    //\n    //     for _i in 0..10000 {\n    //         let _ = ops(&a2, &b2);\n    //     }\n    //     println!(\"{:.5?} time taken for Number Dual wrapper\", now.elapsed());\n    //\n    //     for _i in 0..10000 {\n    //         let _ = ops(&a2, &a3);\n    //     }\n    //     println!(\n    //         \"{:.5?} time taken for Number F64/Dual wrapper\",\n    //         now.elapsed()\n    //     );\n    //\n    //     for _i in 0..10000 {\n    //         let _ = ops2(a0, &a1);\n    //     }\n    //     println!(\"{:.5?} time taken for F64/Dual special func\", now.elapsed());\n}\n"
  },
  {
    "path": "rust/scheduling/calendars/adjuster.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::scheduling::DateRoll;\nuse chrono::prelude::*;\nuse chrono::Days;\nuse serde::{Deserialize, Serialize};\n\n/// Specifier for date adjustment rules.\n#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\npub enum Adjuster {\n    /// Actual date without adjustment.\n    Actual {},\n    /// Following adjustment rule.\n    Following {},\n    /// Modified following adjustment rule.\n    ModifiedFollowing {},\n    /// Previous adjustment rule.\n    Previous {},\n    /// Modified previous adjustment rule.\n    ModifiedPrevious {},\n    /// Following adjustment rule, enforcing settlement calendar.\n    FollowingSettle {},\n    /// Modified following adjustment rule, enforcing settlement calendar.\n    ModifiedFollowingSettle {},\n    /// Previous adjustment rule, enforcing settlement calendar.\n    PreviousSettle {},\n    /// Modified previous adjustment rule, enforcing settlement calendar.\n    ModifiedPreviousSettle {},\n    /// A set number of business days, defined by a given calendar,\n    /// using calendar lag rules and enforcing settlement calendars.\n    BusDaysLagSettle(i32),\n    /// A set number of calendar days enforcing settlement calendars, defined by a\n    /// given calendar.\n    CalDaysLagSettle(i32),\n    /// Following adjustment rule except uses actual date for the last date in a vector.\n    FollowingExLast {},\n    /// Following adjustment rule, enforcing settlement, except uses actual date for the last date in a vector.\n    FollowingExLastSettle {},\n    /// A set number of business days, enforcing settlement, except uses the period start date for a vector.\n    BusDaysLagSettleInAdvance(i32),\n}\n\n/// Perform date adjustment according to calendar definitions, i.e. a known [`DateRoll`].\npub trait Adjustment {\n    /// Adjust a date under an adjustment rule.\n    fn adjust<T: DateRoll>(&self, udate: &NaiveDateTime, calendar: &T) -> NaiveDateTime;\n\n    /// Perform a reverse adjustment to derive potential unadjusted date candidates.\n    fn reverse<T: DateRoll>(&self, adate: &NaiveDateTime, calendar: &T) -> Vec<NaiveDateTime>;\n\n    /// Adjust a vector of dates under an adjustment rule;\n    fn adjusts<T: DateRoll>(&self, udates: &Vec<NaiveDateTime>, calendar: &T)\n        -> Vec<NaiveDateTime>;\n}\n\n/// Perform date adjustment according to adjustment rules, i.e. a given [`Adjuster`].\npub trait CalendarAdjustment {\n    /// Adjust a date under an adjustment rule.\n    fn adjust(&self, udate: &NaiveDateTime, adjuster: &Adjuster) -> NaiveDateTime\n    where\n        Self: Sized + DateRoll,\n    {\n        adjuster.adjust(udate, self)\n    }\n\n    /// Adjust a vector of dates under an adjustment rule;\n    fn adjusts(&self, udates: &Vec<NaiveDateTime>, adjuster: &Adjuster) -> Vec<NaiveDateTime>\n    where\n        Self: Sized + DateRoll,\n    {\n        adjuster.adjusts(udates, self)\n    }\n}\n\nimpl Adjustment for Adjuster {\n    fn adjust<T: DateRoll>(&self, udate: &NaiveDateTime, calendar: &T) -> NaiveDateTime {\n        match self {\n            Adjuster::Actual {} => *udate,\n            Adjuster::Following {} => calendar.roll_forward_bus_day(udate),\n            Adjuster::Previous {} => calendar.roll_backward_bus_day(udate),\n            Adjuster::ModifiedFollowing {} => calendar.roll_mod_forward_bus_day(udate),\n            Adjuster::ModifiedPrevious {} => calendar.roll_mod_backward_bus_day(udate),\n            Adjuster::FollowingSettle {} => calendar.roll_forward_settled_bus_day(udate),\n            Adjuster::PreviousSettle {} => calendar.roll_backward_settled_bus_day(udate),\n            Adjuster::ModifiedFollowingSettle {} => {\n                calendar.roll_forward_mod_settled_bus_day(udate)\n            }\n            Adjuster::ModifiedPreviousSettle {} => {\n                calendar.roll_backward_mod_settled_bus_day(udate)\n            }\n            Adjuster::BusDaysLagSettle(n) => calendar.lag_bus_days(udate, *n, true),\n            Adjuster::CalDaysLagSettle(n) => {\n                let adj = if *n < 0 {\n                    Adjuster::PreviousSettle {}\n                } else {\n                    Adjuster::FollowingSettle {}\n                };\n                calendar.add_cal_days(udate, *n, &adj)\n            }\n            Adjuster::FollowingExLast {} => calendar.roll_forward_bus_day(udate), // no vector\n            Adjuster::FollowingExLastSettle {} => calendar.roll_forward_settled_bus_day(udate), // no vector\n            Adjuster::BusDaysLagSettleInAdvance(n) => calendar.lag_bus_days(udate, *n, true), // no vector\n        }\n    }\n\n    fn reverse<T: DateRoll>(&self, adate: &NaiveDateTime, calendar: &T) -> Vec<NaiveDateTime> {\n        match self {\n            Adjuster::Actual {} => vec![*adate],\n            Adjuster::Following {} => reverse_forward_type(adate, self, calendar),\n            Adjuster::Previous {} => reverse_backward_type(adate, self, calendar),\n            Adjuster::ModifiedFollowing {} => reverse_modified_type(adate, self, calendar),\n            Adjuster::ModifiedPrevious {} => reverse_modified_type(adate, self, calendar),\n            Adjuster::FollowingSettle {} => reverse_forward_type(adate, self, calendar),\n            Adjuster::PreviousSettle {} => reverse_backward_type(adate, self, calendar),\n            Adjuster::ModifiedFollowingSettle {} => reverse_modified_type(adate, self, calendar),\n            Adjuster::ModifiedPreviousSettle {} => reverse_modified_type(adate, self, calendar),\n            Adjuster::BusDaysLagSettle(n) => reverse_lag_settle_type(adate, self, calendar, n),\n            Adjuster::CalDaysLagSettle(n) => reverse_lag_settle_type(adate, self, calendar, n),\n            Adjuster::FollowingExLast {} => reverse_forward_type(adate, self, calendar), // no vector\n            Adjuster::FollowingExLastSettle {} => reverse_forward_type(adate, self, calendar), // no vector\n            Adjuster::BusDaysLagSettleInAdvance(n) => {\n                reverse_lag_settle_type(adate, self, calendar, n)\n            } // no vector\n        }\n    }\n\n    fn adjusts<T: DateRoll>(\n        &self,\n        udates: &Vec<NaiveDateTime>,\n        calendar: &T,\n    ) -> Vec<NaiveDateTime> {\n        let mut non_vector_adates: Vec<NaiveDateTime> = udates\n            .iter()\n            .map(|udate| self.adjust(udate, calendar))\n            .collect();\n\n        // mutate for vector adjustment\n        match self {\n            Adjuster::FollowingExLast {} | Adjuster::FollowingExLastSettle {} => {\n                non_vector_adates[udates.len() - 1] = udates[udates.len() - 1];\n            }\n            Adjuster::BusDaysLagSettleInAdvance(_n) => {\n                for i in (1..udates.len()).rev() {\n                    non_vector_adates[i] = non_vector_adates[i - 1];\n                }\n            }\n            _ => {}\n        }\n        non_vector_adates\n    }\n}\n\nfn reverse_forward_type<T: DateRoll>(\n    adate: &NaiveDateTime,\n    adjuster: &Adjuster,\n    calendar: &T,\n) -> Vec<NaiveDateTime> {\n    let mut ret: Vec<NaiveDateTime>;\n    if (*adjuster).adjust(adate, calendar) == *adate {\n        // adate is valid reversal\n        ret = vec![*adate];\n    } else {\n        // adate is an unadjusted date and is not valid: it has no reversal.\n        return vec![];\n    }\n    let mut date = *adate - Days::new(1);\n    while (*adjuster).adjust(&date, calendar) == *adate {\n        ret.push(date);\n        date = date - Days::new(1);\n    }\n    ret\n}\n\nfn reverse_backward_type<T: DateRoll>(\n    adate: &NaiveDateTime,\n    adjuster: &Adjuster,\n    calendar: &T,\n) -> Vec<NaiveDateTime> {\n    let mut ret: Vec<NaiveDateTime>;\n    if (*adjuster).adjust(adate, calendar) == *adate {\n        // adate is valid reversal\n        ret = vec![*adate];\n    } else {\n        // adate is an unadjusted date and is not valid: it has no reversal.\n        return vec![];\n    }\n    let mut date = *adate + Days::new(1);\n    while (*adjuster).adjust(&date, calendar) == *adate {\n        ret.push(date);\n        date = date + Days::new(1);\n    }\n    ret\n}\n\nfn reverse_modified_type<T: DateRoll>(\n    adate: &NaiveDateTime,\n    adjuster: &Adjuster,\n    calendar: &T,\n) -> Vec<NaiveDateTime> {\n    let mut ret: Vec<NaiveDateTime>;\n    if (*adjuster).adjust(adate, calendar) == *adate {\n        // adate is valid reversal of itself\n        ret = vec![*adate];\n    } else {\n        // adate is an unadjusted date and is not valid: it has no reversal.\n        return vec![];\n    }\n    let mut date = *adate - Days::new(1);\n    let mut adj = (*adjuster).adjust(&date, calendar);\n    while adj == *adate && date.month() == adate.month() {\n        ret.push(date);\n        date = date - Days::new(1);\n        adj = (*adjuster).adjust(&date, calendar);\n    }\n    date = *adate + Days::new(1);\n    adj = (*adjuster).adjust(&date, calendar);\n    while adj == *adate && date.month() == adate.month() {\n        ret.push(date);\n        date = date + Days::new(1);\n        adj = (*adjuster).adjust(&date, calendar);\n    }\n    ret\n}\n\nfn reverse_lag_settle_type<T: DateRoll>(\n    adate: &NaiveDateTime,\n    adjuster: &Adjuster,\n    calendar: &T,\n    n: &i32,\n) -> Vec<NaiveDateTime> {\n    if (Adjuster::FollowingSettle {}).adjust(adate, calendar) != *adate {\n        // input adjusted date has no candidate reversals, return empty vec\n        vec![]\n    } else {\n        // will generally only be necessary when lagging by zero days\n        let mut ret: Vec<NaiveDateTime> = vec![];\n        if (*adjuster).adjust(adate, calendar) == *adate {\n            ret.push(*adate);\n        }\n\n        let mut date = *adate;\n        let mut adj_date: NaiveDateTime;\n        if *n < 0 {\n            loop {\n                date = date + Days::new(1);\n                adj_date = (*adjuster).adjust(&date, calendar);\n                if adj_date == *adate {\n                    ret.push(date);\n                } else if adj_date > *adate {\n                    break;\n                }\n            }\n        } else {\n            loop {\n                date = date - Days::new(1);\n                adj_date = (*adjuster).adjust(&date, calendar);\n                if adj_date == *adate {\n                    ret.push(date);\n                } else if adj_date < *adate {\n                    break;\n                }\n            }\n        }\n        ret\n    }\n}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::{ndt, Cal, Calendar, UnionCal};\n\n    fn fixture_hol_cal() -> Cal {\n        let hols = vec![ndt(2015, 9, 5), ndt(2015, 9, 7)]; // Saturday and Monday\n        Cal::new(hols, vec![5, 6])\n    }\n\n    #[test]\n    fn test_equality() {\n        assert_eq!(Adjuster::Following {}, Adjuster::Following {});\n        assert_eq!(Adjuster::BusDaysLagSettle(3), Adjuster::BusDaysLagSettle(3));\n        assert_ne!(Adjuster::BusDaysLagSettle(3), Adjuster::BusDaysLagSettle(5));\n    }\n\n    #[test]\n    fn test_adjusts() {\n        let cal = fixture_hol_cal();\n        let udates = vec![\n            ndt(2015, 9, 4),\n            ndt(2015, 9, 5),\n            ndt(2015, 9, 6),\n            ndt(2015, 9, 7),\n        ];\n        let result = Adjuster::Following {}.adjusts(&udates, &cal);\n        assert_eq!(\n            result,\n            vec![\n                ndt(2015, 9, 4),\n                ndt(2015, 9, 8),\n                ndt(2015, 9, 8),\n                ndt(2015, 9, 8)\n            ]\n        );\n    }\n\n    #[test]\n    fn test_adjusts_ex_last() {\n        // the last date in the vector is unadjusted\n        let cal = fixture_hol_cal();\n        let udates = vec![\n            ndt(2015, 9, 4),\n            ndt(2015, 9, 5),\n            ndt(2015, 9, 6),\n            ndt(2015, 9, 7),\n        ];\n        let result = Adjuster::FollowingExLast {}.adjusts(&udates, &cal);\n        assert_eq!(\n            result,\n            vec![\n                ndt(2015, 9, 4),\n                ndt(2015, 9, 8),\n                ndt(2015, 9, 8),\n                ndt(2015, 9, 7)\n            ]\n        );\n    }\n\n    #[test]\n    fn test_adjusts_in_advance() {\n        // the vector is adjusted to in advance\n        let cal = fixture_hol_cal();\n        let udates = vec![\n            ndt(2015, 9, 4),\n            ndt(2015, 9, 5),\n            ndt(2015, 9, 6),\n            ndt(2015, 9, 7),\n        ];\n        let result = Adjuster::BusDaysLagSettleInAdvance(0).adjusts(&udates, &cal);\n        assert_eq!(\n            result,\n            vec![\n                ndt(2015, 9, 4),\n                ndt(2015, 9, 4),\n                ndt(2015, 9, 8),\n                ndt(2015, 9, 8)\n            ]\n        );\n    }\n\n    #[test]\n    fn test_reverse() {\n        let cal = Cal::new(\n            vec![\n                ndt(2000, 1, 31),\n                ndt(2000, 1, 29),\n                ndt(2000, 1, 10),\n                ndt(2000, 1, 11),\n                ndt(2000, 1, 16),\n                ndt(2000, 1, 1),\n                ndt(2000, 1, 3),\n            ],\n            vec![],\n        );\n\n        let options: Vec<(NaiveDateTime, Adjuster, Vec<NaiveDateTime>)> = vec![\n            // No reversals for holidays\n            (ndt(2000, 1, 1), Adjuster::Following {}, vec![]),\n            (ndt(2000, 1, 1), Adjuster::Previous {}, vec![]),\n            (ndt(2000, 1, 1), Adjuster::ModifiedPrevious {}, vec![]),\n            (ndt(2000, 1, 11), Adjuster::CalDaysLagSettle(3), vec![]),\n            (ndt(2000, 1, 11), Adjuster::BusDaysLagSettle(3), vec![]),\n            // Valid reversals for adjusted dates.\n            (\n                ndt(2000, 1, 2),\n                Adjuster::Following {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 1)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::Following {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 29)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::FollowingSettle {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 1)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::FollowingSettle {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 29)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::Previous {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 3)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::Previous {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 31)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::PreviousSettle {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 3)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::PreviousSettle {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 31)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::FollowingExLast {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 1)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::FollowingExLast {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 29)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::FollowingExLastSettle {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 1)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::FollowingExLastSettle {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 29)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::ModifiedFollowing {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 1)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::ModifiedFollowing {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 29), ndt(2000, 1, 31)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::ModifiedPrevious {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 1), ndt(2000, 1, 3)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::ModifiedPrevious {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 31)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::ModifiedFollowingSettle {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 1)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::ModifiedFollowingSettle {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 29), ndt(2000, 1, 31)],\n            ),\n            (\n                ndt(2000, 1, 2),\n                Adjuster::ModifiedPreviousSettle {},\n                vec![ndt(2000, 1, 2), ndt(2000, 1, 1), ndt(2000, 1, 3)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::ModifiedPreviousSettle {},\n                vec![ndt(2000, 1, 30), ndt(2000, 1, 31)],\n            ),\n            (ndt(2000, 1, 2), Adjuster::Actual {}, vec![ndt(2000, 1, 2)]),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::Actual {},\n                vec![ndt(2000, 1, 30)],\n            ),\n            (\n                ndt(2000, 1, 30),\n                Adjuster::Actual {},\n                vec![ndt(2000, 1, 30)],\n            ),\n            (\n                ndt(2000, 1, 15),\n                Adjuster::CalDaysLagSettle(5),\n                vec![ndt(2000, 1, 10)],\n            ),\n            (\n                ndt(2000, 1, 12),\n                Adjuster::CalDaysLagSettle(5),\n                vec![ndt(2000, 1, 7), ndt(2000, 1, 6), ndt(2000, 1, 5)],\n            ),\n            (\n                ndt(2000, 1, 18),\n                Adjuster::BusDaysLagSettle(5),\n                vec![ndt(2000, 1, 12)],\n            ),\n            (\n                ndt(2000, 1, 17),\n                Adjuster::BusDaysLagSettle(5),\n                vec![ndt(2000, 1, 11), ndt(2000, 1, 10), ndt(2000, 1, 9)],\n            ),\n        ];\n        for option in options {\n            let result = option.1.reverse(&option.0, &Calendar::Cal(cal.clone()));\n            assert_eq!(result, option.2)\n        }\n    }\n\n    #[test]\n    fn test_forward_book_reverse() {\n        // Test Following and FollowingSettle from the book diagram\n        let cal = Cal::new(vec![ndt(2000, 6, 27), ndt(2000, 6, 30)], vec![]);\n        let settle = Cal::new(\n            vec![\n                ndt(2000, 6, 26),\n                ndt(2000, 6, 29),\n                ndt(2000, 6, 30),\n                ndt(2000, 7, 1),\n            ],\n            vec![],\n        );\n        let uni = UnionCal::new(vec![cal], Some(vec![settle]));\n\n        // adjustments for a Following Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 26), ndt(2000, 6, 26)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 6, 29)),\n            (ndt(2000, 6, 30), ndt(2000, 7, 1)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 1)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result = Adjuster::Following {}.adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversals for a Following Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 6, 26), vec![ndt(2000, 6, 26)]),\n            (ndt(2000, 6, 27), vec![]),\n            (ndt(2000, 6, 28), vec![ndt(2000, 6, 28), ndt(2000, 6, 27)]),\n            (ndt(2000, 6, 29), vec![ndt(2000, 6, 29)]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![ndt(2000, 7, 1), ndt(2000, 6, 30)]),\n            (ndt(2000, 7, 2), vec![ndt(2000, 7, 2)]),\n        ];\n        for option in options {\n            let result =\n                Adjuster::Following {}.reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // adjustments for a FollowingSettle Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 26), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 7, 2)),\n            (ndt(2000, 6, 30), ndt(2000, 7, 2)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 2)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result =\n                Adjuster::FollowingSettle {}.adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversals for a FollowingSettle Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 6, 26), vec![]),\n            (ndt(2000, 6, 27), vec![]),\n            (\n                ndt(2000, 6, 28),\n                vec![ndt(2000, 6, 28), ndt(2000, 6, 27), ndt(2000, 6, 26)],\n            ),\n            (ndt(2000, 6, 29), vec![]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![]),\n            (\n                ndt(2000, 7, 2),\n                vec![\n                    ndt(2000, 7, 2),\n                    ndt(2000, 7, 1),\n                    ndt(2000, 6, 30),\n                    ndt(2000, 6, 29),\n                ],\n            ),\n        ];\n        for option in options {\n            let result =\n                Adjuster::FollowingSettle {}.reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n    }\n\n    #[test]\n    fn test_backward_book_reverse() {\n        // Test Previous and PreviousSettle from the book diagram\n        let cal = Cal::new(vec![ndt(2000, 6, 27), ndt(2000, 6, 30)], vec![]);\n        let settle = Cal::new(\n            vec![\n                ndt(2000, 6, 26),\n                ndt(2000, 6, 29),\n                ndt(2000, 6, 30),\n                ndt(2000, 7, 1),\n            ],\n            vec![],\n        );\n        let uni = UnionCal::new(vec![cal], Some(vec![settle]));\n\n        // adjustments for a Previous Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 26), ndt(2000, 6, 26)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 26)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 6, 29)),\n            (ndt(2000, 6, 30), ndt(2000, 6, 29)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 1)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result = Adjuster::Previous {}.adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversals for a Previous Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 6, 26), vec![ndt(2000, 6, 26), ndt(2000, 6, 27)]),\n            (ndt(2000, 6, 27), vec![]),\n            (ndt(2000, 6, 28), vec![ndt(2000, 6, 28)]),\n            (ndt(2000, 6, 29), vec![ndt(2000, 6, 29), ndt(2000, 6, 30)]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![ndt(2000, 7, 1)]),\n            (ndt(2000, 7, 2), vec![ndt(2000, 7, 2)]),\n        ];\n        for option in options {\n            let result = Adjuster::Previous {}.reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // adjustments for a PreviousSettle Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 26), ndt(2000, 6, 25)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 25)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 30), ndt(2000, 6, 28)),\n            (ndt(2000, 7, 1), ndt(2000, 6, 28)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result =\n                Adjuster::PreviousSettle {}.adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversals for a PreviousSettle Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (\n                ndt(2000, 6, 25),\n                vec![ndt(2000, 6, 25), ndt(2000, 6, 26), ndt(2000, 6, 27)],\n            ),\n            (ndt(2000, 6, 26), vec![]),\n            (ndt(2000, 6, 27), vec![]),\n            (\n                ndt(2000, 6, 28),\n                vec![\n                    ndt(2000, 6, 28),\n                    ndt(2000, 6, 29),\n                    ndt(2000, 6, 30),\n                    ndt(2000, 7, 1),\n                ],\n            ),\n            (ndt(2000, 6, 29), vec![]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![]),\n            (ndt(2000, 7, 2), vec![ndt(2000, 7, 2)]),\n        ];\n        for option in options {\n            let result =\n                Adjuster::PreviousSettle {}.reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n    }\n\n    #[test]\n    fn test_modified_forward_book_reverse() {\n        // Test ModifiedFollowing and ModifiedFollowingSettle from the book diagram\n        let cal = Cal::new(vec![ndt(2000, 6, 27), ndt(2000, 6, 30)], vec![]);\n        let settle = Cal::new(\n            vec![\n                ndt(2000, 6, 26),\n                ndt(2000, 6, 29),\n                ndt(2000, 6, 30),\n                ndt(2000, 7, 1),\n            ],\n            vec![],\n        );\n        let uni = UnionCal::new(vec![cal], Some(vec![settle]));\n\n        // adjustments for a ModifiedFollowing Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 26), ndt(2000, 6, 26)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 6, 29)),\n            (ndt(2000, 6, 30), ndt(2000, 6, 29)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 1)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result =\n                Adjuster::ModifiedFollowing {}.adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversals for a ModifiedFollowing Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 6, 26), vec![ndt(2000, 6, 26)]),\n            (ndt(2000, 6, 27), vec![]),\n            (ndt(2000, 6, 28), vec![ndt(2000, 6, 28), ndt(2000, 6, 27)]),\n            (ndt(2000, 6, 29), vec![ndt(2000, 6, 29), ndt(2000, 6, 30)]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![ndt(2000, 7, 1)]),\n            (ndt(2000, 7, 2), vec![ndt(2000, 7, 2)]),\n        ];\n        for option in options {\n            let result =\n                Adjuster::ModifiedFollowing {}.reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // adjustments for a ModifiedFollowingSettle Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 26), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 30), ndt(2000, 6, 28)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 2)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result = Adjuster::ModifiedFollowingSettle {}\n                .adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversals for a ModifiedFollowingSettle Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 6, 26), vec![]),\n            (ndt(2000, 6, 27), vec![]),\n            (\n                ndt(2000, 6, 28),\n                vec![\n                    ndt(2000, 6, 28),\n                    ndt(2000, 6, 27),\n                    ndt(2000, 6, 26),\n                    ndt(2000, 6, 29),\n                    ndt(2000, 6, 30),\n                ],\n            ),\n            (ndt(2000, 6, 29), vec![]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![]),\n            (ndt(2000, 7, 2), vec![ndt(2000, 7, 2), ndt(2000, 7, 1)]),\n        ];\n        for option in options {\n            let result = Adjuster::ModifiedFollowingSettle {}\n                .reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n    }\n\n    #[test]\n    fn test_modified_backward_book_reverse() {\n        // Test ModifiedPrevious and ModifiedPreviousSettle from the book diagram\n        let cal = Cal::new(vec![ndt(2000, 6, 27), ndt(2000, 6, 30)], vec![]);\n        let settle = Cal::new(\n            vec![\n                ndt(2000, 6, 26),\n                ndt(2000, 6, 29),\n                ndt(2000, 6, 30),\n                ndt(2000, 7, 1),\n            ],\n            vec![],\n        );\n        let uni = UnionCal::new(vec![cal], Some(vec![settle]));\n\n        // adjustments for a ModifiedPrevious Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 26), ndt(2000, 6, 26)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 26)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 6, 29)),\n            (ndt(2000, 6, 30), ndt(2000, 6, 29)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 1)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result =\n                Adjuster::ModifiedPrevious {}.adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversals for a ModifiedPrevious Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 6, 25), vec![ndt(2000, 6, 25)]),\n            (ndt(2000, 6, 26), vec![ndt(2000, 6, 26), ndt(2000, 6, 27)]),\n            (ndt(2000, 6, 27), vec![]),\n            (ndt(2000, 6, 28), vec![ndt(2000, 6, 28)]),\n            (ndt(2000, 6, 29), vec![ndt(2000, 6, 29), ndt(2000, 6, 30)]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![ndt(2000, 7, 1)]),\n            (ndt(2000, 7, 2), vec![ndt(2000, 7, 2)]),\n        ];\n        for option in options {\n            let result =\n                Adjuster::ModifiedPrevious {}.reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // adjustments for a ModifiedPreviousSettle Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 26), ndt(2000, 6, 25)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 25)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 30), ndt(2000, 6, 28)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 2)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result = Adjuster::ModifiedPreviousSettle {}\n                .adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversals for a ModifiedPreviousSettle Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (\n                ndt(2000, 6, 25),\n                vec![ndt(2000, 6, 25), ndt(2000, 6, 26), ndt(2000, 6, 27)],\n            ),\n            (ndt(2000, 6, 26), vec![]),\n            (ndt(2000, 6, 27), vec![]),\n            (\n                ndt(2000, 6, 28),\n                vec![ndt(2000, 6, 28), ndt(2000, 6, 29), ndt(2000, 6, 30)],\n            ),\n            (ndt(2000, 6, 29), vec![]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![]),\n            (ndt(2000, 7, 2), vec![ndt(2000, 7, 2), ndt(2000, 7, 1)]),\n        ];\n        for option in options {\n            let result = Adjuster::ModifiedPreviousSettle {}\n                .reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n    }\n\n    #[test]\n    fn test_bus_days_lag_settle_reverse() {\n        // Test BusDaysLagSettle(2) from the book diagram\n        let cal = Cal::new(vec![ndt(2000, 6, 27), ndt(2000, 6, 30)], vec![]);\n        let settle = Cal::new(\n            vec![\n                ndt(2000, 6, 26),\n                ndt(2000, 6, 29),\n                ndt(2000, 6, 30),\n                ndt(2000, 7, 1),\n            ],\n            vec![],\n        );\n        let uni = UnionCal::new(vec![cal], Some(vec![settle]));\n\n        // adjustments for a BusDaysLagSettle(2) Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 25), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 26), ndt(2000, 7, 2)),\n            (ndt(2000, 6, 27), ndt(2000, 7, 2)),\n            (ndt(2000, 6, 28), ndt(2000, 7, 2)),\n            (ndt(2000, 6, 29), ndt(2000, 7, 2)),\n            (ndt(2000, 6, 30), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result =\n                Adjuster::BusDaysLagSettle(2).adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversal for a BusDaysLagSettle(2) Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 6, 28), vec![ndt(2000, 6, 25), ndt(2000, 6, 24)]),\n            (ndt(2000, 6, 29), vec![]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![]),\n            (\n                ndt(2000, 7, 2),\n                vec![\n                    ndt(2000, 6, 30),\n                    ndt(2000, 6, 29),\n                    ndt(2000, 6, 28),\n                    ndt(2000, 6, 27),\n                    ndt(2000, 6, 26),\n                ],\n            ),\n        ];\n        for option in options {\n            let result =\n                Adjuster::BusDaysLagSettle(2).reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // adjustments for a BusDaysLagSettle(1) Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 25), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 26), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 28), ndt(2000, 7, 2)),\n            (ndt(2000, 6, 29), ndt(2000, 7, 2)),\n            (ndt(2000, 6, 30), ndt(2000, 7, 2)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result =\n                Adjuster::BusDaysLagSettle(1).adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversal for a BusDaysLagSettle(1) Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (\n                ndt(2000, 6, 28),\n                vec![ndt(2000, 6, 27), ndt(2000, 6, 26), ndt(2000, 6, 25)],\n            ),\n            (ndt(2000, 6, 29), vec![]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![]),\n            (\n                ndt(2000, 7, 2),\n                vec![\n                    ndt(2000, 7, 1),\n                    ndt(2000, 6, 30),\n                    ndt(2000, 6, 29),\n                    ndt(2000, 6, 28),\n                ],\n            ),\n        ];\n        for option in options {\n            let result =\n                Adjuster::BusDaysLagSettle(1).reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // adjustments for a BusDaysLagSettle(0) Adjuster\n        let options: Vec<(NaiveDateTime, NaiveDateTime)> = vec![\n            (ndt(2000, 6, 25), ndt(2000, 6, 25)),\n            (ndt(2000, 6, 26), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 27), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 28), ndt(2000, 6, 28)),\n            (ndt(2000, 6, 29), ndt(2000, 7, 2)),\n            (ndt(2000, 6, 30), ndt(2000, 7, 2)),\n            (ndt(2000, 7, 1), ndt(2000, 7, 2)),\n            (ndt(2000, 7, 2), ndt(2000, 7, 2)),\n        ];\n        for option in options {\n            let result =\n                Adjuster::BusDaysLagSettle(0).adjust(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n\n        // reversal for a BusDaysLagSettle(0) Adjuster\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 6, 25), vec![ndt(2000, 6, 25)]),\n            (\n                ndt(2000, 6, 28),\n                vec![ndt(2000, 6, 28), ndt(2000, 6, 27), ndt(2000, 6, 26)],\n            ),\n            (ndt(2000, 6, 29), vec![]),\n            (ndt(2000, 6, 30), vec![]),\n            (ndt(2000, 7, 1), vec![]),\n            (\n                ndt(2000, 7, 2),\n                vec![\n                    ndt(2000, 7, 2),\n                    ndt(2000, 7, 1),\n                    ndt(2000, 6, 30),\n                    ndt(2000, 6, 29),\n                ],\n            ),\n        ];\n        for option in options {\n            let result =\n                Adjuster::BusDaysLagSettle(0).reverse(&option.0, &Calendar::UnionCal(uni.clone()));\n            assert_eq!(result, option.1)\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/calendars/cal.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse chrono::prelude::*;\nuse chrono::Weekday;\nuse indexmap::set::IndexSet;\nuse pyo3::exceptions::PyKeyError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashSet;\n\nuse crate::scheduling::{CalWrapper, CalendarAdjustment, CalendarManager, DateRoll};\n\n/// A basic business day calendar containing holidays.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]\npub struct Cal {\n    /// A vector of specific dates that are defined as **non-business** days.\n    pub holidays: IndexSet<NaiveDateTime>,\n    /// A vector of days in the week that are defined as **non-business** days. E.g. `[5, 6]` for Saturday and Sunday.\n    pub week_mask: HashSet<Weekday>,\n    // pub(crate) meta: Vec<String>,\n}\n\nimpl DateRoll for Cal {\n    fn is_weekday(&self, date: &NaiveDateTime) -> bool {\n        !self.week_mask.contains(&date.weekday())\n    }\n\n    fn is_holiday(&self, date: &NaiveDateTime) -> bool {\n        self.holidays.contains(date)\n    }\n\n    fn is_settlement(&self, _date: &NaiveDateTime) -> bool {\n        true\n    }\n}\n\nimpl CalendarAdjustment for Cal {}\n\nimpl Cal {\n    /// Create a [`Cal`].\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{Cal, ndt, DateRoll};\n    /// let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]); // With May Bank Holiday\n    /// let spot = cal.add_bus_days(&ndt(2017, 4, 28), 2, true);\n    /// # let spot = spot.unwrap();\n    /// assert_eq!(ndt(2017, 5, 3), spot);\n    /// ```\n    pub fn new(\n        holidays: Vec<NaiveDateTime>,\n        week_mask: Vec<u8>,\n        // rules: Vec<&str>\n    ) -> Self {\n        Cal {\n            holidays: IndexSet::from_iter(holidays),\n            week_mask: HashSet::from_iter(\n                week_mask.into_iter().map(|v| Weekday::try_from(v).unwrap()),\n            ),\n            // meta: rules.into_iter().map(|x| x.to_string()).collect(),\n        }\n    }\n\n    /// Return a [`Cal`] specified by a pre-defined named identifier.\n    ///\n    /// For available 3-digit names see `named` module documentation.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::scheduling::Cal;\n    /// let ldn_cal = Cal::try_from_name(\"ldn\").unwrap();\n    /// ```\n    pub fn try_from_name(name: &str) -> Result<Cal, PyErr> {\n        let cm = CalendarManager::new();\n        let named_cal = cm.get(name)?;\n        match (*named_cal.inner).clone() {\n            CalWrapper::Cal(cal) => Ok(cal),\n            CalWrapper::UnionCal(_) => Err(PyKeyError::new_err(\n                \"`name` was key for a UnionCal not a Cal.\",\n            )),\n        }\n    }\n}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::{ndt, Adjuster};\n\n    fn fixture_hol_cal() -> Cal {\n        let hols = vec![ndt(2015, 9, 5), ndt(2015, 9, 7)]; // Saturday and Monday\n        Cal::new(hols, vec![5, 6])\n    }\n\n    #[test]\n    fn test_is_holiday() {\n        let cal = fixture_hol_cal();\n        let hol =\n            NaiveDateTime::parse_from_str(\"2015-09-07 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let no_hol =\n            NaiveDateTime::parse_from_str(\"2015-09-10 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let saturday =\n            NaiveDateTime::parse_from_str(\"2024-01-06 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        assert!(cal.is_holiday(&hol)); // In hol list\n        assert!(!cal.is_holiday(&no_hol)); // Not in hol list\n        assert!(!cal.is_holiday(&saturday)); // Not in hol list\n    }\n\n    #[test]\n    fn test_is_weekday() {\n        let cal = fixture_hol_cal();\n        let hol =\n            NaiveDateTime::parse_from_str(\"2015-09-07 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let no_hol =\n            NaiveDateTime::parse_from_str(\"2015-09-10 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let saturday =\n            NaiveDateTime::parse_from_str(\"2024-01-06 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let sunday =\n            NaiveDateTime::parse_from_str(\"2024-01-07 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        assert!(cal.is_weekday(&hol)); // Monday\n        assert!(cal.is_weekday(&no_hol)); //Thursday\n        assert!(!cal.is_weekday(&saturday)); // Saturday\n        assert!(!cal.is_weekday(&sunday)); // Sunday\n    }\n\n    #[test]\n    fn test_calendar_adjust() {\n        let cal = fixture_hol_cal();\n        let result = cal.adjust(&ndt(2015, 9, 5), &Adjuster::Following {});\n        assert_eq!(ndt(2015, 9, 8), result);\n    }\n\n    #[test]\n    fn test_calendar_adjusts() {\n        let cal = fixture_hol_cal();\n        let result = cal.adjusts(\n            &vec![ndt(2015, 9, 5), ndt(2015, 9, 6)],\n            &Adjuster::Following {},\n        );\n        assert_eq!(vec![ndt(2015, 9, 8), ndt(2015, 9, 8)], result);\n    }\n\n    // Pre defined named calendars\n\n    #[test]\n    fn test_get_cal() {\n        let result = Cal::try_from_name(\"bus\").unwrap();\n        let expected = Cal::new(vec![], vec![5, 6]);\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_all() {\n        let cal = Cal::try_from_name(\"all\").unwrap();\n        assert!(cal.is_bus_day(\n            &NaiveDateTime::parse_from_str(\"2024-11-11 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_nyc() {\n        let cal = Cal::try_from_name(\"nyc\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-11-11 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_tgt() {\n        let cal = Cal::try_from_name(\"tgt\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-05-01 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_ldn() {\n        let cal = Cal::try_from_name(\"ldn\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-08-26 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_stk() {\n        let cal = Cal::try_from_name(\"stk\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-06-06 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_osl() {\n        let cal = Cal::try_from_name(\"osl\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-05-17 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_zur() {\n        let cal = Cal::try_from_name(\"zur\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-08-01 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_tro() {\n        let cal = Cal::try_from_name(\"tro\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-09-30 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_tyo() {\n        let cal = Cal::try_from_name(\"tyo\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-1-3 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_fed() {\n        let cal = Cal::try_from_name(\"fed\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2024-11-11 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_get_calendar_error() {\n        match Cal::try_from_name(\"badname\") {\n            Ok(_) => assert!(false),\n            Err(_) => assert!(true),\n        }\n    }\n\n    #[test]\n    fn test_syd() {\n        let cal = Cal::try_from_name(\"syd\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2022-09-22 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_wlg() {\n        let cal = Cal::try_from_name(\"wlg\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2034-07-07 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n\n    #[test]\n    fn test_mum() {\n        let cal = Cal::try_from_name(\"mum\").unwrap();\n        assert!(cal.is_holiday(\n            &NaiveDateTime::parse_from_str(\"2025-01-26 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        ));\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/calendars/calendar.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse chrono::prelude::*;\nuse pyo3::{FromPyObject, IntoPyObject};\nuse serde::{Deserialize, Serialize};\nuse std::convert::From;\n\nuse crate::scheduling::{Cal, CalendarAdjustment, DateRoll, NamedCal, UnionCal};\n\n/// Create a `NaiveDateTime` with default null time.\n///\n/// Panics if date values are invalid.\npub fn ndt(year: i32, month: u32, day: u32) -> NaiveDateTime {\n    NaiveDate::from_ymd_opt(year, month, day)\n        .expect(\"`year`, `month` `day` are invalid.\")\n        .and_hms_opt(0, 0, 0)\n        .unwrap()\n}\n\n/// Container for calendar types.\n#[derive(Debug, Clone, PartialEq, FromPyObject, Serialize, Deserialize, IntoPyObject)]\npub enum Calendar {\n    Cal(Cal),\n    UnionCal(UnionCal),\n    NamedCal(NamedCal),\n}\n\nimpl From<Cal> for Calendar {\n    fn from(item: Cal) -> Self {\n        Calendar::Cal(item)\n    }\n}\n\nimpl From<UnionCal> for Calendar {\n    fn from(item: UnionCal) -> Self {\n        Calendar::UnionCal(item)\n    }\n}\n\nimpl From<NamedCal> for Calendar {\n    fn from(item: NamedCal) -> Self {\n        Calendar::NamedCal(item)\n    }\n}\n\nimpl DateRoll for Calendar {\n    fn is_weekday(&self, date: &NaiveDateTime) -> bool {\n        match self {\n            Calendar::Cal(c) => c.is_weekday(date),\n            Calendar::UnionCal(c) => c.is_weekday(date),\n            Calendar::NamedCal(c) => c.is_weekday(date),\n        }\n    }\n\n    fn is_holiday(&self, date: &NaiveDateTime) -> bool {\n        match self {\n            Calendar::Cal(c) => c.is_holiday(date),\n            Calendar::UnionCal(c) => c.is_holiday(date),\n            Calendar::NamedCal(c) => c.is_holiday(date),\n        }\n    }\n\n    fn is_settlement(&self, date: &NaiveDateTime) -> bool {\n        match self {\n            Calendar::Cal(c) => c.is_settlement(date),\n            Calendar::UnionCal(c) => c.is_settlement(date),\n            Calendar::NamedCal(c) => c.is_settlement(date),\n        }\n    }\n}\n\nimpl CalendarAdjustment for Calendar {}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_docstring() {\n        let ldn = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]); // UK Monday 1st May Bank Holiday\n        let tky = Cal::new(\n            vec![ndt(2017, 5, 3), ndt(2017, 5, 4), ndt(2017, 5, 5)],\n            vec![5, 6],\n        );\n\n        let date = ndt(2017, 4, 28); // Friday 28th April 2017\n        let spot = ldn.add_bus_days(&date, 2, true).unwrap();\n        assert_eq!(spot, ndt(2017, 5, 3));\n\n        let ldn_tky = UnionCal::new(vec![ldn, tky], None);\n        let spot = ldn_tky.add_bus_days(&date, 2, true).unwrap();\n        assert_eq!(spot, ndt(2017, 5, 8));\n\n        let tgt = Cal::new(vec![], vec![5, 6]);\n        let nyc = Cal::new(vec![ndt(2023, 6, 19)], vec![5, 6]); // Juneteenth Holiday\n        let tgt_nyc = UnionCal::new(vec![tgt], vec![nyc].into());\n\n        let date = ndt(2023, 6, 16);\n        let spot = tgt_nyc.add_bus_days(&date, 2, true).unwrap();\n        assert_eq!(spot, ndt(2023, 6, 20));\n\n        let date = ndt(2023, 6, 15);\n        let spot = tgt_nyc.add_bus_days(&date, 2, true).unwrap();\n        assert_eq!(spot, ndt(2023, 6, 20));\n\n        let spot = tgt_nyc.add_bus_days(&date, 2, false).unwrap();\n        assert_eq!(spot, ndt(2023, 6, 19));\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/calendars/dateroll.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse chrono::prelude::*;\nuse chrono::Days;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::PyErr;\nuse std::cmp::Ordering;\n\nuse crate::scheduling::{Adjuster, Adjustment};\n\n/// Simple date adjustment defining business, settleable and holidays and rolling.\npub trait DateRoll {\n    /// Returns whether the date is part of the general working week.\n    fn is_weekday(&self, date: &NaiveDateTime) -> bool;\n\n    /// Returns whether the date is a specific holiday excluded from the regular working week.\n    fn is_holiday(&self, date: &NaiveDateTime) -> bool;\n\n    /// Returns whether the date is valid relative to an associated settlement calendar.\n    ///\n    /// If the holiday calendar object has no associated settlement calendar this should return `true`\n    /// for any date.\n    fn is_settlement(&self, date: &NaiveDateTime) -> bool;\n\n    /// Returns whether the date is a business day, i.e. part of the working week and not a holiday.\n    fn is_bus_day(&self, date: &NaiveDateTime) -> bool {\n        self.is_weekday(date) && !self.is_holiday(date)\n    }\n\n    /// Returns whether the date is not a business day, i.e. either not in working week or a specific holiday.\n    fn is_non_bus_day(&self, date: &NaiveDateTime) -> bool {\n        !self.is_bus_day(date)\n    }\n\n    /// Return the `date`, if a business day, or get the next business date after `date`.\n    fn roll_forward_bus_day(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let mut new_date = *date;\n        while !self.is_bus_day(&new_date) {\n            new_date = new_date + Days::new(1);\n        }\n        new_date\n    }\n\n    /// Return the `date`, if a business day, or get the business day preceding `date`.\n    fn roll_backward_bus_day(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let mut new_date = *date;\n        while !self.is_bus_day(&new_date) {\n            new_date = new_date - Days::new(1);\n        }\n        new_date\n    }\n\n    /// Return the `date`, if a business day, or get the proceeding business date, without rolling\n    /// into a new month.\n    fn roll_mod_forward_bus_day(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let new_date = self.roll_forward_bus_day(date);\n        if new_date.month() != date.month() {\n            self.roll_backward_bus_day(date)\n        } else {\n            new_date\n        }\n    }\n\n    /// Return the `date`, if a business day, or get the proceeding business date, without rolling\n    /// into a new month.\n    fn roll_mod_backward_bus_day(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let new_date = self.roll_backward_bus_day(date);\n        if new_date.month() != date.month() {\n            self.roll_forward_bus_day(date)\n        } else {\n            new_date\n        }\n    }\n\n    /// Return the date, if a business day that can be settled, or the proceeding date that is such.\n    ///\n    /// If the calendar has no associated settlement calendar this is identical to `roll_forward_bus_day`.\n    fn roll_forward_settled_bus_day(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let mut new_date = self.roll_forward_bus_day(date);\n        while !self.is_settlement(&new_date) {\n            new_date = self.roll_forward_bus_day(&(new_date + Days::new(1)));\n        }\n        new_date\n    }\n\n    /// Return the date, if a business day that can be settled, or the preceding date that is such.\n    ///\n    /// If the calendar has no associated settlement calendar this is identical to `roll_backward_bus_day`.\n    fn roll_backward_settled_bus_day(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let mut new_date = self.roll_backward_bus_day(date);\n        while !self.is_settlement(&new_date) {\n            new_date = self.roll_backward_bus_day(&(new_date - Days::new(1)));\n        }\n        new_date\n    }\n\n    /// Return the `date`, if a business day that can be settled, or get the proceeding\n    /// such date, without rolling into a new month.\n    fn roll_forward_mod_settled_bus_day(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let new_date = self.roll_forward_settled_bus_day(date);\n        if new_date.month() != date.month() {\n            self.roll_backward_settled_bus_day(date)\n        } else {\n            new_date\n        }\n    }\n\n    /// Return the `date`, if a business day that can be settled, or get the preceding such date, without rolling\n    /// into a new month.\n    fn roll_backward_mod_settled_bus_day(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let new_date = self.roll_backward_settled_bus_day(date);\n        if new_date.month() != date.month() {\n            self.roll_forward_settled_bus_day(date)\n        } else {\n            new_date\n        }\n    }\n\n    /// Adjust a date by a number of business days, under lag rules.\n    ///\n    /// *Note*: if the number of business days is **zero** a non-business day will be rolled\n    /// **forwards**.\n    ///\n    /// *Note*: `settlement` enforcement is handled post date determination. If the number of\n    /// business `days` is zero or greater the date is rolled forwards to the nearest settleable\n    /// day if not already one.\n    /// If the number of business `days` is less than zero then the date is rolled backwards\n    /// to the nearest settleable date.\n    ///\n    /// *Note*: if the given `date` is a non-business date adding or subtracting 1 business\n    /// day is equivalent to the rolling forwards or backwards, respectively.\n    fn lag_bus_days(&self, date: &NaiveDateTime, days: i32, settlement: bool) -> NaiveDateTime {\n        if self.is_bus_day(date) {\n            return self.add_bus_days(date, days, settlement).unwrap();\n        }\n        match days.cmp(&0_i32) {\n            Ordering::Equal => self\n                .add_bus_days(&self.roll_forward_bus_day(date), 0, settlement)\n                .unwrap(),\n            Ordering::Less => self\n                .add_bus_days(&self.roll_backward_bus_day(date), days + 1, settlement)\n                .unwrap(),\n            Ordering::Greater => self\n                .add_bus_days(&self.roll_forward_bus_day(date), days - 1, settlement)\n                .unwrap(),\n        }\n    }\n\n    /// Add a given number of calendar days to a `date` with the result adjusted to a business day that may or may not\n    /// allow `settlement`.\n    fn add_cal_days(&self, date: &NaiveDateTime, days: i32, adjuster: &Adjuster) -> NaiveDateTime\n    where\n        Self: Sized,\n    {\n        let new_date = if days < 0 {\n            *date - Days::new(u64::try_from(-days).unwrap())\n        } else {\n            *date + Days::new(u64::try_from(days).unwrap())\n        };\n        adjuster.adjust(&new_date, self)\n    }\n\n    /// Add a given number of business days to a `date` with the result adjusted to a business day that may or may\n    /// not allow `settlement`.\n    ///\n    /// *Note*: When adding a positive number of business days the only sensible modifier is\n    /// `Modifier::F` and when subtracting business days it is `Modifier::P`.\n    fn add_bus_days(\n        &self,\n        date: &NaiveDateTime,\n        days: i32,\n        settlement: bool,\n    ) -> Result<NaiveDateTime, PyErr> {\n        if self.is_non_bus_day(date) {\n            return Err(PyValueError::new_err(\n                \"Cannot add business days to an input `date` that is not a business day.\",\n            ));\n        }\n        let mut new_date = *date;\n        let mut counter: i32 = 0;\n        if days < 0 {\n            // then we subtract business days\n            while counter > days {\n                new_date = self.roll_backward_bus_day(&(new_date - Days::new(1)));\n                counter -= 1;\n            }\n        } else {\n            // add business days\n            while counter < days {\n                new_date = self.roll_forward_bus_day(&(new_date + Days::new(1)));\n                counter += 1;\n            }\n        }\n\n        if !settlement {\n            Ok(new_date)\n        } else if days < 0 {\n            Ok(self.roll_backward_settled_bus_day(&new_date))\n        } else {\n            Ok(self.roll_forward_settled_bus_day(&new_date))\n        }\n    }\n\n    /// Return a vector of business dates between a start and end, inclusive.\n    fn bus_date_range(\n        &self,\n        start: &NaiveDateTime,\n        end: &NaiveDateTime,\n    ) -> Result<Vec<NaiveDateTime>, PyErr> {\n        if self.is_non_bus_day(start) || self.is_non_bus_day(end) {\n            return Err(PyValueError::new_err(\"`start` and `end` for a calendar `bus_date_range` must both be valid business days\"));\n        }\n        let mut vec = Vec::new();\n        let mut sample_date = *start;\n        while sample_date <= *end {\n            vec.push(sample_date);\n            sample_date = self.add_bus_days(&sample_date, 1, false)?;\n        }\n        Ok(vec)\n    }\n\n    /// Return a vector of calendar dates between a start and end, inclusive\n    fn cal_date_range(\n        &self,\n        start: &NaiveDateTime,\n        end: &NaiveDateTime,\n    ) -> Result<Vec<NaiveDateTime>, PyErr> {\n        let mut vec = Vec::new();\n        let mut sample_date = *start;\n        while sample_date <= *end {\n            vec.push(sample_date);\n            sample_date = sample_date + Days::new(1);\n        }\n        Ok(vec)\n    }\n\n    /// Print a representation of the month of the object.\n    fn print_month(&self, year: i32, month: u8) -> String {\n        let _map: Vec<String> = vec![\n            format!(\"        January {}\\n\", year),\n            format!(\"       February {}\\n\", year),\n            format!(\"          March {}\\n\", year),\n            format!(\"          April {}\\n\", year),\n            format!(\"            May {}\\n\", year),\n            format!(\"           June {}\\n\", year),\n            format!(\"           July {}\\n\", year),\n            format!(\"         August {}\\n\", year),\n            format!(\"      September {}\\n\", year),\n            format!(\"        October {}\\n\", year),\n            format!(\"       November {}\\n\", year),\n            format!(\"       December {}\\n\", year),\n        ];\n        let mut output = _map[(month - 1) as usize].clone();\n        output += \"Su Mo Tu We Th Fr Sa\\n\";\n\n        let month_obj = Month::try_from(month).unwrap();\n        let days: u8 = month_obj.num_days(year).unwrap();\n        let weekday = NaiveDate::from_ymd_opt(year, month.into(), 1)\n            .unwrap()\n            .weekday()\n            .num_days_from_monday();\n        let idx_start: u32 = (weekday + 1) % 7;\n\n        let mut arr: [String; 42] = std::array::from_fn(|_| String::from(\"  \"));\n        for i in 0..days {\n            let date = NaiveDate::from_ymd_opt(year, month.into(), (i + 1).into())\n                .expect(\"`year`, `month` `day` are invalid.\")\n                .and_hms_opt(0, 0, 0)\n                .unwrap();\n            let s: String = {\n                if self.is_bus_day(&date) && self.is_settlement(&date) {\n                    format!(\"{:>2}\", i + 1)\n                } else if self.is_bus_day(&date) && !self.is_settlement(&date) {\n                    \" X\".to_string()\n                } else if !self.is_bus_day(&date)\n                    && matches!(date.weekday(), Weekday::Sat | Weekday::Sun)\n                {\n                    \" .\".to_string()\n                } else {\n                    \" *\".to_string()\n                }\n            };\n            let index: u32 = i as u32 + idx_start;\n            arr[index as usize] = s;\n        }\n\n        for row in 0..6 {\n            output += &format!(\n                \"{} {} {} {} {} {} {}\\n\",\n                &arr[row * 7],\n                &arr[row * 7 + 1],\n                &arr[row * 7 + 2],\n                &arr[row * 7 + 3],\n                &arr[row * 7 + 4],\n                &arr[row * 7 + 5],\n                &arr[row * 7 + 6]\n            );\n        }\n        output\n    }\n\n    /// Print a representation of a year of the object.\n    fn print_year(&self, year: i32) -> String {\n        let mut data: Vec<Vec<String>> = vec![];\n        for i in 1..13 {\n            data.push(\n                self.print_month(year, i)\n                    .lines()\n                    .map(|s| s.to_string())\n                    .collect(),\n            );\n        }\n        let mut output = \"\\n\".to_string();\n        for i in 0..8 {\n            output += &format!(\n                \"{}   {}   {}   {}\\n\",\n                data[0][i], data[3][i], data[6][i], data[9][i]\n            );\n        }\n        for i in 0..8 {\n            output += &format!(\n                \"{}   {}   {}   {}\\n\",\n                data[1][i], data[4][i], data[7][i], data[10][i]\n            );\n        }\n        for i in 0..8 {\n            output += &format!(\n                \"{}   {}   {}   {}\\n\",\n                data[2][i], data[5][i], data[8][i], data[11][i]\n            );\n        }\n        output += \"Legend:\\n\";\n        output += \"'1-31': Settleable business day         'X': Non-settleable business day\\n\";\n        output += \"   '.': Non-business weekend            '*': Non-business day\\n\";\n        output\n    }\n\n    /// Compare two calendars and highlight differences.\n    fn print_compare<T: DateRoll>(&self, comparator: &T, year: i32) -> String {\n        let str1: Vec<String> = self\n            .print_year(year)\n            .lines()\n            .map(|s| s.to_string())\n            .collect();\n        let str2: Vec<String> = comparator\n            .print_year(year)\n            .lines()\n            .map(|s| s.to_string())\n            .collect();\n\n        let header_row: Vec<usize> = vec![0, 1, 2, 9, 10, 17, 18];\n        let mut output = \"\\n\".to_string();\n        for i in 1..25 {\n            if header_row.contains(&i) {\n                output += &str1[i];\n            } else {\n                let row_data: Vec<(char, char)> = str1[i].chars().zip(str2[i].chars()).collect();\n                for m in 0..4 {\n                    let m_ = m * 23;\n                    if m > 0 {\n                        output += \"  \";\n                    }\n                    for c in 0..7 {\n                        let c_ = c * 3 + m_;\n                        if c_ > 89 {\n                            continue;\n                        }\n                        if row_data[c_].0 == row_data[c_].1\n                            && row_data[c_ + 1].0 == row_data[c_ + 1].1\n                        {\n                            if row_data[c_].0 == ' ' && row_data[c_ + 1].0 == ' ' {\n                                output += \"   \";\n                            } else {\n                                output += \" _ \";\n                            }\n                        } else {\n                            output += \"[] \";\n                        }\n                    }\n                }\n            }\n            output += &\"\\n\";\n        }\n        return output;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::{ndt, Cal, CalendarAdjustment, UnionCal};\n\n    fn fixture_hol_cal() -> Cal {\n        let hols = vec![ndt(2015, 9, 5), ndt(2015, 9, 7)]; // Saturday and Monday\n        Cal::new(hols, vec![5, 6])\n    }\n\n    #[test]\n    fn test_roll_forward_bus_day() {\n        let cal = fixture_hol_cal();\n        let hol =\n            NaiveDateTime::parse_from_str(\"2015-09-07 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let next = cal.roll_forward_bus_day(&hol);\n        assert_eq!(\n            next,\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n\n        let sat =\n            NaiveDateTime::parse_from_str(\"2015-09-05 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let next = cal.roll_forward_bus_day(&sat);\n        assert_eq!(\n            next,\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n\n        let fri =\n            NaiveDateTime::parse_from_str(\"2015-09-04 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let next = cal.roll_forward_bus_day(&fri);\n        assert_eq!(\n            next,\n            NaiveDateTime::parse_from_str(\"2015-09-04 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        )\n    }\n\n    #[test]\n    fn test_roll_backward_bus_day() {\n        let cal = fixture_hol_cal();\n        let hol =\n            NaiveDateTime::parse_from_str(\"2015-09-07 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let prev = cal.roll_backward_bus_day(&hol);\n        assert_eq!(\n            prev,\n            NaiveDateTime::parse_from_str(\"2015-09-04 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n\n        let fri =\n            NaiveDateTime::parse_from_str(\"2015-09-04 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let next = cal.roll_backward_bus_day(&fri);\n        assert_eq!(\n            next,\n            NaiveDateTime::parse_from_str(\"2015-09-04 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        )\n    }\n\n    #[test]\n    fn test_is_business_day() {\n        let cal = fixture_hol_cal();\n        let hol =\n            NaiveDateTime::parse_from_str(\"2015-09-07 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let no_hol =\n            NaiveDateTime::parse_from_str(\"2015-09-10 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let saturday =\n            NaiveDateTime::parse_from_str(\"2024-01-06 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        assert!(!cal.is_bus_day(&hol)); // Monday in Hol list\n        assert!(cal.is_bus_day(&no_hol)); //Thursday\n        assert!(!cal.is_bus_day(&saturday)); // Saturday\n    }\n\n    #[test]\n    fn test_is_non_business_day() {\n        let cal = fixture_hol_cal();\n        let hol =\n            NaiveDateTime::parse_from_str(\"2015-09-07 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let no_hol =\n            NaiveDateTime::parse_from_str(\"2015-09-10 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let saturday =\n            NaiveDateTime::parse_from_str(\"2024-01-06 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        assert!(cal.is_non_bus_day(&hol)); // Monday in Hol list\n        assert!(!cal.is_non_bus_day(&no_hol)); //Thursday\n        assert!(cal.is_non_bus_day(&saturday)); // Saturday\n    }\n\n    #[test]\n    fn test_lag_bus_days() {\n        let cal = fixture_hol_cal();\n        let result = cal.lag_bus_days(&ndt(2015, 9, 7), 1, true);\n        assert_eq!(result, ndt(2015, 9, 8));\n\n        let result = cal.lag_bus_days(&ndt(2025, 2, 15), -1, true);\n        assert_eq!(result, ndt(2025, 2, 14));\n\n        let result = cal.lag_bus_days(&ndt(2015, 9, 7), 0, true);\n        assert_eq!(result, ndt(2015, 9, 8))\n    }\n\n    #[test]\n    fn test_add_days() {\n        let hols = vec![\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap(),\n            NaiveDateTime::parse_from_str(\"2015-09-10 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap(),\n        ];\n        let settle =\n            vec![\n                NaiveDateTime::parse_from_str(\"2015-09-11 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap(),\n            ];\n        let hcal = Cal::new(hols, vec![5, 6]);\n        let scal = Cal::new(settle, vec![5, 6]);\n        let cal = UnionCal::new(vec![hcal], vec![scal].into());\n\n        // without settlement constraint 11th is a valid forward roll date\n        let tue =\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let next = cal.add_cal_days(&tue, 2, &Adjuster::Following {});\n        assert_eq!(\n            next,\n            NaiveDateTime::parse_from_str(\"2015-09-11 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n\n        // with settlement constraint 11th is invalid. Pushed to 14th over weekend.-\n        let tue =\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let next = cal.add_cal_days(&tue, 2, &Adjuster::FollowingSettle {});\n        assert_eq!(\n            next,\n            NaiveDateTime::parse_from_str(\"2015-09-14 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n\n        // without settlement constraint 11th is a valid previous roll date\n        let tue =\n            NaiveDateTime::parse_from_str(\"2015-09-15 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let prev = cal.add_cal_days(&tue, -2, &Adjuster::Previous {});\n        assert_eq!(\n            prev,\n            NaiveDateTime::parse_from_str(\"2015-09-11 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n\n        // with settlement constraint 11th is invalid. Pushed to 9th over holiday.\n        let tue =\n            NaiveDateTime::parse_from_str(\"2015-09-15 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let prev = cal.add_cal_days(&tue, -2, &Adjuster::PreviousSettle {});\n        assert_eq!(\n            prev,\n            NaiveDateTime::parse_from_str(\"2015-09-09 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n    }\n\n    #[test]\n    fn test_add_bus_days() {\n        let hols = vec![ndt(2015, 9, 8), ndt(2015, 9, 10)];\n        let settle = vec![ndt(2015, 9, 11)];\n\n        let hcal = Cal::new(hols, vec![5, 6]);\n        let scal = Cal::new(settle, vec![5, 6]);\n        let cal = UnionCal::new(vec![hcal], vec![scal].into());\n\n        // without settlement constraint 11th is a valid forward roll date\n        let mon = ndt(2015, 9, 7);\n        let next = cal.add_bus_days(&mon, 2, false).unwrap();\n        assert_eq!(next, ndt(2015, 9, 11));\n\n        // with settlement constraint 11th is invalid. Pushed to 14th over weekend.-\n        let next = cal.add_bus_days(&mon, 2, true).unwrap();\n        assert_eq!(next, ndt(2015, 9, 14));\n\n        // without settlement constraint 11th is a valid previous roll date\n        let tue = ndt(2015, 9, 15);\n        let prev = cal.add_bus_days(&tue, -2, false).unwrap();\n        assert_eq!(prev, ndt(2015, 9, 11));\n\n        // with settlement constraint 11th is invalid. Pushed to 9th over holiday.\n        let prev = cal.add_bus_days(&tue, -2, true).unwrap();\n        assert_eq!(prev, ndt(2015, 9, 9));\n    }\n\n    #[test]\n    fn test_add_bus_days_error() {\n        let cal = fixture_hol_cal();\n        match cal.add_bus_days(&ndt(2015, 9, 7), 3, true) {\n            Ok(_) => assert!(false),\n            Err(_) => assert!(true),\n        }\n    }\n\n    #[test]\n    fn test_add_bus_days_with_settlement() {\n        let cal = Cal::new(vec![ndt(2024, 6, 5)], vec![5, 6]);\n        let settle = Cal::new(vec![ndt(2024, 6, 4), ndt(2024, 6, 6)], vec![5, 6]);\n        let union = UnionCal::new(vec![cal], Some(vec![settle]));\n\n        let result = union.add_bus_days(&ndt(2024, 6, 4), 1, false).unwrap();\n        assert_eq!(result, ndt(2024, 6, 6)); //\n        let result = union.add_bus_days(&ndt(2024, 6, 4), 1, true).unwrap();\n        assert_eq!(result, ndt(2024, 6, 7)); //\n\n        let result = union.add_bus_days(&ndt(2024, 6, 6), -1, false).unwrap();\n        assert_eq!(result, ndt(2024, 6, 4)); //\n        let result = union.add_bus_days(&ndt(2024, 6, 6), -1, true).unwrap();\n        assert_eq!(result, ndt(2024, 6, 3)); //\n    }\n\n    #[test]\n    fn test_rolls() {\n        let cal = fixture_hol_cal();\n        let udates = vec![\n            ndt(2015, 9, 4),\n            ndt(2015, 9, 5),\n            ndt(2015, 9, 6),\n            ndt(2015, 9, 7),\n        ];\n        let result = cal.adjusts(&udates, &Adjuster::Following {});\n        assert_eq!(\n            result,\n            vec![\n                ndt(2015, 9, 4),\n                ndt(2015, 9, 8),\n                ndt(2015, 9, 8),\n                ndt(2015, 9, 8)\n            ]\n        );\n    }\n\n    #[test]\n    fn test_lag_bus_days_zero_with_settlement() {\n        // Test ModifiedPrevious and ModifiedPreviousSettle from the book diagram\n        let cal = Cal::new(vec![ndt(2000, 6, 27)], vec![]);\n        let settle = Cal::new(vec![ndt(2000, 6, 26), ndt(2000, 6, 28)], vec![]);\n        let uni = UnionCal::new(vec![cal], Some(vec![settle]));\n\n        // adding zero bus days not settleable yields 28th June\n        assert_eq!(\n            ndt(2000, 6, 28),\n            uni.lag_bus_days(&ndt(2000, 6, 27), 0, false)\n        );\n\n        // adding zero bus days settleable yields 29th June\n        assert_eq!(\n            ndt(2000, 6, 29),\n            uni.lag_bus_days(&ndt(2000, 6, 27), 0, true)\n        );\n\n        // adding zero bus days not settleable yields 28th June\n        assert_eq!(\n            ndt(2000, 6, 28),\n            uni.lag_bus_days(&ndt(2000, 6, 28), 0, false)\n        );\n\n        // adding zero bus days settleable yields 29th June\n        assert_eq!(\n            ndt(2000, 6, 29),\n            uni.lag_bus_days(&ndt(2000, 6, 28), 0, true)\n        );\n    }\n\n    #[test]\n    fn test_print_month() {\n        let cal = Cal::new(vec![ndt(2026, 1, 1), ndt(2026, 1, 19)], vec![5, 6]);\n        let result = cal.print_month(2026, 1);\n        let raw_output = r#\"        January 2026\nSu Mo Tu We Th Fr Sa\n             *  2  .\n .  5  6  7  8  9  .\n . 12 13 14 15 16  .\n .  * 20 21 22 23  .\n . 26 27 28 29 30  .\n$$$$$$$$$$$$$$$$$$$$\n\"#;\n        let expected = raw_output.replace(\"$\", \" \");\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_print_year() {\n        let cal = Cal::new(vec![ndt(2026, 1, 1), ndt(2026, 1, 19)], vec![5, 6]);\n        let result = cal.print_year(2026);\n        let raw_output = r#\"\n        January 2026             April 2026              July 2026           October 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n             *  2  .             1  2  3  .             1  2  3  .                1  2  .\n .  5  6  7  8  9  .    .  6  7  8  9 10  .    .  6  7  8  9 10  .    .  5  6  7  8  9  .\n . 12 13 14 15 16  .    . 13 14 15 16 17  .    . 13 14 15 16 17  .    . 12 13 14 15 16  .\n .  * 20 21 22 23  .    . 20 21 22 23 24  .    . 20 21 22 23 24  .    . 19 20 21 22 23  .\n . 26 27 28 29 30  .    . 27 28 29 30          . 27 28 29 30 31       . 26 27 28 29 30  .\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n       February 2026               May 2026            August 2026          November 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n .  2  3  4  5  6  .                   1  .                      .    .  2  3  4  5  6  .\n .  9 10 11 12 13  .    .  4  5  6  7  8  .    .  3  4  5  6  7  .    .  9 10 11 12 13  .\n . 16 17 18 19 20  .    . 11 12 13 14 15  .    . 10 11 12 13 14  .    . 16 17 18 19 20  .\n . 23 24 25 26 27  .    . 18 19 20 21 22  .    . 17 18 19 20 21  .    . 23 24 25 26 27  .\n                        . 25 26 27 28 29  .    . 24 25 26 27 28  .    . 30$$$$$$$$$$$$$$$\n                        .                      . 31$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n          March 2026              June 2026         September 2026          December 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n .  2  3  4  5  6  .       1  2  3  4  5  .          1  2  3  4  .          1  2  3  4  .\n .  9 10 11 12 13  .    .  8  9 10 11 12  .    .  7  8  9 10 11  .    .  7  8  9 10 11  .\n . 16 17 18 19 20  .    . 15 16 17 18 19  .    . 14 15 16 17 18  .    . 14 15 16 17 18  .\n . 23 24 25 26 27  .    . 22 23 24 25 26  .    . 21 22 23 24 25  .    . 21 22 23 24 25  .\n . 30 31                . 29 30                . 28 29 30             . 28 29 30 31$$$$$$\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\nLegend:\n'1-31': Settleable business day         'X': Non-settleable business day\n   '.': Non-business weekend            '*': Non-business day\n\"#;\n        let expected = raw_output.replace(\"$\", \" \");\n\n        let result_lines: Vec<&str> = result.lines().collect();\n        let expected_lines: Vec<&str> = expected.lines().collect();\n        for i in 0..result_lines.len() {\n            assert_eq!(expected_lines[i], result_lines[i]);\n        }\n    }\n\n    #[test]\n    fn test_print_compare() {\n        let cal1 = Cal::new(vec![], vec![0, 5]);\n        let cal2 = Cal::new(vec![], vec![3, 5]);\n        let result = cal1.print_compare(&cal2, 2026);\n        let raw_output = r#\"\n        January 2026             April 2026              July 2026           October 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n            []  _  _             _ []  _  _             _ []  _  _               []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []          _ []  _  _ []  _       _ []  _  _ []  _  _$\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n       February 2026               May 2026            August 2026          November 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n _ []  _  _ []  _  _                   _  _                      _    _ []  _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n                        _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []$$$$$$$$$$$$$$$$\n                        _                      _ []$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n          March 2026              June 2026         September 2026          December 2026\nSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa\n _ []  _  _ []  _  _      []  _  _ []  _  _          _  _ []  _  _          _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _    _ []  _  _ []  _  _$\n _ []  _                _ []  _                _ []  _  _             _ []  _  _ []$$$$$$$\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n\"#;\n        let expected = raw_output.replace(\"$\", \" \");\n\n        let result_lines: Vec<&str> = result.lines().collect();\n        let expected_lines: Vec<&str> = expected.lines().collect();\n        for i in 0..result_lines.len() {\n            assert_eq!(expected_lines[i], result_lines[i]);\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/calendars/manager.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::scheduling::calendars::named::{HOLIDAYS, WEEKMASKS};\nuse crate::scheduling::calendars::{Cal, CalWrapper, Calendar, NamedCal, UnionCal};\nuse pyo3::exceptions::{PyKeyError, PyValueError};\nuse pyo3::{pyclass, PyErr};\nuse std::collections::HashMap;\nuse std::sync::{Arc, LazyLock, RwLock};\n\n// A single memory allocated space to maintain the UnionCal with an associated name.\nstatic NAMED_CALENDARS: LazyLock<RwLock<HashMap<String, Arc<CalWrapper>>>> = LazyLock::new(|| {\n    let mut m = HashMap::new();\n    for (k, _) in WEEKMASKS.iter() {\n        m.insert(\n            (*k).into(),\n            Arc::new(CalWrapper::Cal(Cal::new(\n                HOLIDAYS.get(k).unwrap().to_vec(),\n                WEEKMASKS.get(k).unwrap().to_vec(),\n            ))),\n        );\n    }\n    RwLock::new(m)\n});\n\n/// A manager to add and mutate the core calendars from which [`NamedCal`] are constructed.\n#[pyclass]\npub struct CalendarManager;\n\nimpl CalendarManager {\n    /// Create an instance of the [`CalendarManager`] manager.\n    ///\n    /// This object interacts with the memory allocation for stored calendars. It returns\n    /// objects with thread safe, shared memory access to the same objects for performance.\n    pub fn new() -> Self {\n        Self {}\n    }\n\n    /// Returns *true* if the set contains a specific key.\n    pub fn contains_key(&self, key: &str) -> bool {\n        let k: String = sort_calendar_names(key);\n        let r = NAMED_CALENDARS.read().unwrap();\n        r.contains_key(&k)\n    }\n\n    /// Return a list of keys.\n    pub fn keys(&self) -> Vec<String> {\n        let r = NAMED_CALENDARS.read().unwrap();\n        r.iter().map(|(k, _)| k.to_string()).collect()\n    }\n\n    /// Add any [`Calendar`] to the calendar manager.\n    ///\n    /// Data will not be overwritten. It will error prior to that or clone existing data to a\n    /// new key.\n    pub fn add(&self, name: &str, calendar: Cal) -> Result<(), PyErr> {\n        let k: String = sort_calendar_names(name);\n        if k.chars().any(|c| c == ',' || c == '|') {\n            return Err(PyValueError::new_err(\n                \"`name` cannot contain the comma (',') or pipe ('|') characters.\\nThese are reserved to define calendar combinations (i.e. UnionCal) and only Cal objects are allowed to be populated directly to the calendar manager.\",\n            ));\n        }\n\n        let mut w = NAMED_CALENDARS.write().unwrap();\n        if w.contains_key(&k) {\n            return Err(PyKeyError::new_err(\n                \"`name` already exists in calendars.\\nCannot overwrite, first `pop` the existing calendar.\",\n            ));\n        }\n        w.insert(k, Arc::new(CalWrapper::Cal(calendar)));\n        Ok(())\n    }\n\n    /// Remove an existing [`Calendar`] from the calendar manager.\n    pub fn pop(&self, name: &str) -> Result<Calendar, PyErr> {\n        let k: String = sort_calendar_names(name);\n        let popped = remove_any_calendar(&k);\n        match popped {\n            Some(arc) => match &*arc {\n                CalWrapper::Cal(c) => {\n                    remove_all_combinations(&k);\n                    Ok(Calendar::Cal(c.clone()))\n                }\n                CalWrapper::UnionCal(c) => Ok(Calendar::UnionCal(c.clone())),\n            },\n            None => Err(PyKeyError::new_err(\"`name` does not exist in calendars.\")),\n        }\n    }\n\n    /// Return a [`NamedCal`] matching the name that is stored in the calendar manager.\n    ///\n    /// If the name as a key does not exist then an error will result.\n    pub fn get(&self, name: &str) -> Result<NamedCal, PyErr> {\n        let k: String = sort_calendar_names(name);\n        let r = NAMED_CALENDARS.read().unwrap();\n        let v = r.get(&k);\n        match v {\n            Some(arc_ref) => Ok(NamedCal {\n                name: k,\n                inner: arc_ref.clone(),\n            }),\n            None => Err(PyKeyError::new_err(\"`name` does not exist in calendars.\")),\n        }\n    }\n\n    /// Return a [`NamedCal`] matching the name that is stored in the calendar manager.\n    ///\n    /// If the name as a key does not exist but a [`UnionCal`] as a combination of [`Cal`] can\n    /// be created, the HashMap will be updated with a new entry and the relevant [`NamedCal`]\n    /// returned.\n    pub fn get_with_insert(&self, name: &str) -> Result<NamedCal, PyErr> {\n        let k: String = sort_calendar_names(name);\n        if !k.chars().any(|c| c == ',' || c == '|') {\n            // then lookup is for a single calendar, no composition necessary\n            self.get(&k)\n        } else {\n            let item = self.get(&k);\n            match item {\n                Ok(value) => Ok(value), // key is found pre-populated in HashMap\n                Err(_) => {\n                    // then the calendars might need to be composited and inserted\n                    let data = extract_individual_calendars(&k)?;\n                    let _ = insert_union_cal(\n                        &k,\n                        UnionCal {\n                            calendars: data.0,\n                            settlement_calendars: data.1,\n                        },\n                    );\n                    self.get(&k)\n                }\n            }\n        }\n    }\n}\n\n// Take an input string (potentially with comma and pipe) and convert to lower case and\n// order the specific calendar names. See test_sort_calendar_names.\nfn sort_calendar_names(name: &str) -> String {\n    let stripped: String = name.chars().filter(|c| !c.is_whitespace()).collect();\n    let parts: Vec<String> = stripped\n        .to_lowercase()\n        .split(\"|\")\n        .map(String::from)\n        .collect();\n    let mut reordered_parts: Vec<String> = Vec::new();\n    for part in parts {\n        let mut cals: Vec<String> = part.split(\",\").map(String::from).collect();\n        cals.sort();\n        reordered_parts.push(cals.join(\",\"))\n    }\n    reordered_parts.join(\"|\")\n}\n\n// Take an input string (potentially with comma and pipe) and extract the ordered list\n// of individual, expected [`Cal`] objects. `k` is expected to be cleaned (sorted, lowercase etc.)\nfn extract_individual_calendars(k: &str) -> Result<(Vec<Cal>, Option<Vec<Cal>>), PyErr> {\n    let nc = CalendarManager::new();\n    let parts: Vec<String> = k.split(\"|\").map(String::from).collect();\n    let mut container: Vec<Vec<Cal>> = Vec::new();\n    for part in &parts {\n        let cal_names: Vec<String> = part.split(\",\").map(String::from).collect();\n\n        let named_cals: Vec<NamedCal> = cal_names\n            .iter()\n            .map(|k| nc.get(k))\n            .collect::<Result<Vec<_>, _>>()?;\n\n        let cals: Vec<Cal> = named_cals\n            .iter()\n            .map(|n| match &*n.inner {\n                CalWrapper::Cal(value) => Ok(value.clone()),\n                _ => Err(PyValueError::new_err(\n                    \"Individual calendar name is not a Cal object.\",\n                )),\n            })\n            .collect::<Result<Vec<_>, _>>()?;\n\n        container.push(cals);\n    }\n    if container.len() == 1 {\n        Ok((container[0].clone(), None))\n    } else if parts.len() == 2 {\n        Ok((container[0].clone(), Some(container[1].clone())))\n    } else {\n        Err(PyValueError::new_err(\n            \"The calendar cannot be parsed. Is there more than one pipe character?\",\n        ))\n    }\n}\n\n// Insert a named calendar to the HashMap\nfn insert_union_cal(k: &str, u: UnionCal) -> Option<Arc<CalWrapper>> {\n    // returns None when inserted correctly\n    let mut w = NAMED_CALENDARS.write().unwrap();\n    w.insert(k.to_string(), Arc::new(CalWrapper::UnionCal(u)))\n}\n\n// Remove a key and return the object\nfn remove_any_calendar(k: &str) -> Option<Arc<CalWrapper>> {\n    let mut w = NAMED_CALENDARS.write().unwrap();\n    w.remove(&k.to_string())\n}\n\n// Remove all other combinations that is a UnionCal and contains the name 'k'.\nfn remove_all_combinations(k: &str) -> () {\n    let mut w = NAMED_CALENDARS.write().unwrap();\n    let keys: Vec<String> = w\n        .iter()\n        .filter(|(key, v)| key.contains(k) && is_union_cal((*v).clone()))\n        .map(|(key, _)| key.to_string())\n        .collect();\n    for key in keys.into_iter() {\n        let _ = w.remove(&key);\n    }\n}\n\nfn is_union_cal(v: Arc<CalWrapper>) -> bool {\n    match *v {\n        CalWrapper::Cal(_) => false,\n        CalWrapper::UnionCal(_) => true,\n    }\n}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_sort_calendar_names() {\n        let result = sort_calendar_names(\"tgt,NYC,  ldn|tyo, tro\");\n        assert_eq!(result, \"ldn,nyc,tgt|tro,tyo\");\n\n        let result = sort_calendar_names(\"tgt,NYC,  ldn|tyo\");\n        assert_eq!(result, \"ldn,nyc,tgt|tyo\");\n\n        let result = sort_calendar_names(\"tgt,NYC,  ldn  \");\n        assert_eq!(result, \"ldn,nyc,tgt\");\n\n        let result = sort_calendar_names(\"tgt|ldn  \");\n        assert_eq!(result, \"tgt|ldn\");\n\n        let result = sort_calendar_names(\"tgt  \");\n        assert_eq!(result, \"tgt\");\n\n        let result = sort_calendar_names(\"a2, a1 | a3 \");\n        assert_eq!(result, \"a1,a2|a3\");\n    }\n\n    #[test]\n    fn test_extract_individual_calendars() {\n        let nc = CalendarManager::new();\n        let result = extract_individual_calendars(\"ldn\").unwrap();\n        let expected = nc.get(\"ldn\").unwrap();\n        assert_eq!(result.0[0], expected);\n\n        let a1 = Cal::new(vec![], vec![1]);\n        let a2 = Cal::new(vec![], vec![2]);\n        let a3 = Cal::new(vec![], vec![3]);\n        let _ = nc.add(\"a1\", a1);\n        let _ = nc.add(\"a2\", a2);\n        let _ = nc.add(\"a3\", a3);\n\n        let result = extract_individual_calendars(\"a2, a1 | a3\").unwrap();\n        let expected = (\n            vec![Cal::new(vec![], vec![2]), Cal::new(vec![], vec![1])],\n            Some(vec![Cal::new(vec![], vec![3])]),\n        );\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn test_get_with_insert() {\n        let nc = CalendarManager::new();\n        let result = nc.get_with_insert(\"ldn\").unwrap();\n        let result2 = nc.get(\"ldn\").unwrap();\n        assert_eq!(result, result2);\n        assert!(Arc::ptr_eq(&result.inner, &result2.inner));\n    }\n\n    #[test]\n    fn test_get_with_insert_composite() {\n        let nc = CalendarManager::new();\n        let result = nc.get_with_insert(\"ldn,tgt\").unwrap();\n        let result2 = nc.get(\"ldn,tgt\").unwrap();\n        let result3 = nc.get(\"tgt,ldn\").unwrap();\n        assert_eq!(result, result2);\n        assert!(Arc::ptr_eq(&result.inner, &result2.inner));\n        assert_eq!(result, result3);\n        assert!(Arc::ptr_eq(&result.inner, &result3.inner));\n    }\n\n    #[test]\n    fn test_remove_composites_calendars() {\n        let nc = CalendarManager::new();\n\n        let a1 = Cal::new(vec![], vec![1]);\n        let a2 = Cal::new(vec![], vec![2]);\n        let a3 = Cal::new(vec![], vec![3]);\n        let _ = nc.add(\"a1\", a1);\n        let _ = nc.add(\"a2\", a2);\n        let _ = nc.add(\"a3\", a3);\n        let _ = nc.get_with_insert(\"a1,a2\");\n        let _ = nc.get_with_insert(\"a1,a3\");\n        let _ = nc.get_with_insert(\"a2,a3\");\n        let _ = nc.get_with_insert(\"a1,a2,a3\");\n\n        let _ = nc.pop(\"a1\");\n        assert!(!nc.keys().contains(&\"a1,a2\".to_string()));\n        assert!(!nc.keys().contains(&\"a1,a3\".to_string()));\n        assert!(nc.keys().contains(&\"a2,a3\".to_string()));\n        assert!(!nc.keys().contains(&\"a1,a2,a3\".to_string()));\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/calendars/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nmod adjuster;\nmod cal;\nmod calendar;\nmod dateroll;\nmod manager;\nmod named;\nmod named_cal;\nmod union_cal;\n\npub use crate::scheduling::calendars::{\n    adjuster::{Adjuster, Adjustment, CalendarAdjustment},\n    cal::Cal,\n    calendar::{ndt, Calendar},\n    dateroll::DateRoll,\n    manager::CalendarManager,\n    named_cal::NamedCal,\n    union_cal::UnionCal,\n};\n\npub(crate) use crate::scheduling::calendars::named_cal::CalWrapper;\n\nmacro_rules! impl_date_roll_partial_eq {\n    ($t1:ty, $t2:ty) => {\n        // Implement T1 == T2\n        impl PartialEq<$t2> for $t1 {\n            fn eq(&self, other: &$t2) -> bool {\n                let c = self\n                    .cal_date_range(&ndt(1970, 1, 1), &ndt(2200, 12, 31))\n                    .unwrap();\n                c.iter().all(|d| {\n                    self.is_bus_day(d) == other.is_bus_day(d)\n                        && self.is_settlement(d) == other.is_settlement(d)\n                })\n            }\n        }\n    };\n}\n\n// Usage: Just list the pairs you want to support\nimpl_date_roll_partial_eq!(Cal, UnionCal);\nimpl_date_roll_partial_eq!(Cal, NamedCal);\nimpl_date_roll_partial_eq!(UnionCal, Cal);\nimpl_date_roll_partial_eq!(UnionCal, UnionCal);\nimpl_date_roll_partial_eq!(UnionCal, NamedCal);\nimpl_date_roll_partial_eq!(NamedCal, Cal);\nimpl_date_roll_partial_eq!(NamedCal, UnionCal);\nimpl_date_roll_partial_eq!(NamedCal, NamedCal);\n"
  },
  {
    "path": "rust/scheduling/calendars/named/all.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a calendar which asserts every possible date as a business day.\n\npub const WEEKMASK: &[u8] = &[]; // all days are weekdays\n\n// pub const RULES: &[&str] = &[];\n\npub const HOLIDAYS: &[&str] = &[]; // no specific holidays\n"
  },
  {
    "path": "rust/scheduling/calendars/named/bjs.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Chinese Interbank business day calendar,\n\npub const WEEKMASK: &[u8] = &[]; // Saturday and Sunday weekend are added specifically as holidays\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Jan 2 (New Year)\",\n//     \"Jan 3 (New Year)\",\n//     \"Jan 2nd Mon (Coming-of-Age)\",\n//     \"Feb 11: Sun->Mon (Foundation)\",\n//     \"Feb 23: Sun->Mon (Emperor Naruhito Birthday est. 2020)\",\n//     \"Mar 20/21: Sun->Mon (Vernal Equinox)\",\n//     \"Apr 29: Sun->Mon (Showa)\",\n//     \"May 3: Sun->Mon (Constitution)\",\n//     \"May 4: Sun->Mon->Tue (Greenery)\",\n//     \"May 5: Sun->Mon->Tue->Wed (Children)\",\n//     \"Jul 3rd Mon (Marine)\",\n//     \"Aug 11: Sun->Mon (Mountain est. 2016)\",\n//     \"Sep 3rd Mon (Respect Aged)\",\n//     \"Sep 22/23: Sun->Mon (Autumn Equinox)\",\n//     \"Oct 2nd Mon (Sports)\",\n//     \"Nov 3: Sun->Mon (Culture)\",\n//     \"Nov 23: Sun->Mon (Labor Thanksgiving)\",\n//     \"Dec 23: Sun->Mon (Emperor Akihito Birthday end. 2019)\",\n//     \"Dec 31 (New Year)\",\n//     \"Note: 2020 Olympics adjustments.\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-03 00:00:00\",\n    \"1970-01-04 00:00:00\",\n    \"1970-01-10 00:00:00\",\n    \"1970-01-11 00:00:00\",\n    \"1970-01-17 00:00:00\",\n    \"1970-01-18 00:00:00\",\n    \"1970-01-24 00:00:00\",\n    \"1970-01-25 00:00:00\",\n    \"1970-01-31 00:00:00\",\n    \"1970-02-01 00:00:00\",\n    \"1970-02-07 00:00:00\",\n    \"1970-02-08 00:00:00\",\n    \"1970-02-14 00:00:00\",\n    \"1970-02-15 00:00:00\",\n    \"1970-02-21 00:00:00\",\n    \"1970-02-22 00:00:00\",\n    \"1970-02-28 00:00:00\",\n    \"1970-03-01 00:00:00\",\n    \"1970-03-07 00:00:00\",\n    \"1970-03-08 00:00:00\",\n    \"1970-03-14 00:00:00\",\n    \"1970-03-15 00:00:00\",\n    \"1970-03-21 00:00:00\",\n    \"1970-03-22 00:00:00\",\n    \"1970-03-28 00:00:00\",\n    \"1970-03-29 00:00:00\",\n    \"1970-04-04 00:00:00\",\n    \"1970-04-05 00:00:00\",\n    \"1970-04-11 00:00:00\",\n    \"1970-04-12 00:00:00\",\n    \"1970-04-18 00:00:00\",\n    \"1970-04-19 00:00:00\",\n    \"1970-04-25 00:00:00\",\n    \"1970-04-26 00:00:00\",\n    \"1970-05-02 00:00:00\",\n    \"1970-05-03 00:00:00\",\n    \"1970-05-09 00:00:00\",\n    \"1970-05-10 00:00:00\",\n    \"1970-05-16 00:00:00\",\n    \"1970-05-17 00:00:00\",\n    \"1970-05-23 00:00:00\",\n    \"1970-05-24 00:00:00\",\n    \"1970-05-30 00:00:00\",\n    \"1970-05-31 00:00:00\",\n    \"1970-06-06 00:00:00\",\n    \"1970-06-07 00:00:00\",\n    \"1970-06-13 00:00:00\",\n    \"1970-06-14 00:00:00\",\n    \"1970-06-20 00:00:00\",\n    \"1970-06-21 00:00:00\",\n    \"1970-06-27 00:00:00\",\n    \"1970-06-28 00:00:00\",\n    \"1970-07-04 00:00:00\",\n    \"1970-07-05 00:00:00\",\n    \"1970-07-11 00:00:00\",\n    \"1970-07-12 00:00:00\",\n    \"1970-07-18 00:00:00\",\n    \"1970-07-19 00:00:00\",\n    \"1970-07-25 00:00:00\",\n    \"1970-07-26 00:00:00\",\n    \"1970-08-01 00:00:00\",\n    \"1970-08-02 00:00:00\",\n    \"1970-08-08 00:00:00\",\n    \"1970-08-09 00:00:00\",\n    \"1970-08-15 00:00:00\",\n    \"1970-08-16 00:00:00\",\n    \"1970-08-22 00:00:00\",\n    \"1970-08-23 00:00:00\",\n    \"1970-08-29 00:00:00\",\n    \"1970-08-30 00:00:00\",\n    \"1970-09-05 00:00:00\",\n    \"1970-09-06 00:00:00\",\n    \"1970-09-12 00:00:00\",\n    \"1970-09-13 00:00:00\",\n    \"1970-09-19 00:00:00\",\n    \"1970-09-20 00:00:00\",\n    \"1970-09-26 00:00:00\",\n    \"1970-09-27 00:00:00\",\n    \"1970-10-03 00:00:00\",\n    \"1970-10-04 00:00:00\",\n    \"1970-10-10 00:00:00\",\n    \"1970-10-11 00:00:00\",\n    \"1970-10-17 00:00:00\",\n    \"1970-10-18 00:00:00\",\n    \"1970-10-24 00:00:00\",\n    \"1970-10-25 00:00:00\",\n    \"1970-10-31 00:00:00\",\n    \"1970-11-01 00:00:00\",\n    \"1970-11-07 00:00:00\",\n    \"1970-11-08 00:00:00\",\n    \"1970-11-14 00:00:00\",\n    \"1970-11-15 00:00:00\",\n    \"1970-11-21 00:00:00\",\n    \"1970-11-22 00:00:00\",\n    \"1970-11-28 00:00:00\",\n    \"1970-11-29 00:00:00\",\n    \"1970-12-05 00:00:00\",\n    \"1970-12-06 00:00:00\",\n    \"1970-12-12 00:00:00\",\n    \"1970-12-13 00:00:00\",\n    \"1970-12-19 00:00:00\",\n    \"1970-12-20 00:00:00\",\n    \"1970-12-26 00:00:00\",\n    \"1970-12-27 00:00:00\",\n    \"1971-01-02 00:00:00\",\n    \"1971-01-03 00:00:00\",\n    \"1971-01-09 00:00:00\",\n    \"1971-01-10 00:00:00\",\n    \"1971-01-16 00:00:00\",\n    \"1971-01-17 00:00:00\",\n    \"1971-01-23 00:00:00\",\n    \"1971-01-24 00:00:00\",\n    \"1971-01-30 00:00:00\",\n    \"1971-01-31 00:00:00\",\n    \"1971-02-06 00:00:00\",\n    \"1971-02-07 00:00:00\",\n    \"1971-02-13 00:00:00\",\n    \"1971-02-14 00:00:00\",\n    \"1971-02-20 00:00:00\",\n    \"1971-02-21 00:00:00\",\n    \"1971-02-27 00:00:00\",\n    \"1971-02-28 00:00:00\",\n    \"1971-03-06 00:00:00\",\n    \"1971-03-07 00:00:00\",\n    \"1971-03-13 00:00:00\",\n    \"1971-03-14 00:00:00\",\n    \"1971-03-20 00:00:00\",\n    \"1971-03-21 00:00:00\",\n    \"1971-03-27 00:00:00\",\n    \"1971-03-28 00:00:00\",\n    \"1971-04-03 00:00:00\",\n    \"1971-04-04 00:00:00\",\n    \"1971-04-10 00:00:00\",\n    \"1971-04-11 00:00:00\",\n    \"1971-04-17 00:00:00\",\n    \"1971-04-18 00:00:00\",\n    \"1971-04-24 00:00:00\",\n    \"1971-04-25 00:00:00\",\n    \"1971-05-01 00:00:00\",\n    \"1971-05-02 00:00:00\",\n    \"1971-05-08 00:00:00\",\n    \"1971-05-09 00:00:00\",\n    \"1971-05-15 00:00:00\",\n    \"1971-05-16 00:00:00\",\n    \"1971-05-22 00:00:00\",\n    \"1971-05-23 00:00:00\",\n    \"1971-05-29 00:00:00\",\n    \"1971-05-30 00:00:00\",\n    \"1971-06-05 00:00:00\",\n    \"1971-06-06 00:00:00\",\n    \"1971-06-12 00:00:00\",\n    \"1971-06-13 00:00:00\",\n    \"1971-06-19 00:00:00\",\n    \"1971-06-20 00:00:00\",\n    \"1971-06-26 00:00:00\",\n    \"1971-06-27 00:00:00\",\n    \"1971-07-03 00:00:00\",\n    \"1971-07-04 00:00:00\",\n    \"1971-07-10 00:00:00\",\n    \"1971-07-11 00:00:00\",\n    \"1971-07-17 00:00:00\",\n    \"1971-07-18 00:00:00\",\n    \"1971-07-24 00:00:00\",\n    \"1971-07-25 00:00:00\",\n    \"1971-07-31 00:00:00\",\n    \"1971-08-01 00:00:00\",\n    \"1971-08-07 00:00:00\",\n    \"1971-08-08 00:00:00\",\n    \"1971-08-14 00:00:00\",\n    \"1971-08-15 00:00:00\",\n    \"1971-08-21 00:00:00\",\n    \"1971-08-22 00:00:00\",\n    \"1971-08-28 00:00:00\",\n    \"1971-08-29 00:00:00\",\n    \"1971-09-04 00:00:00\",\n    \"1971-09-05 00:00:00\",\n    \"1971-09-11 00:00:00\",\n    \"1971-09-12 00:00:00\",\n    \"1971-09-18 00:00:00\",\n    \"1971-09-19 00:00:00\",\n    \"1971-09-25 00:00:00\",\n    \"1971-09-26 00:00:00\",\n    \"1971-10-02 00:00:00\",\n    \"1971-10-03 00:00:00\",\n    \"1971-10-09 00:00:00\",\n    \"1971-10-10 00:00:00\",\n    \"1971-10-16 00:00:00\",\n    \"1971-10-17 00:00:00\",\n    \"1971-10-23 00:00:00\",\n    \"1971-10-24 00:00:00\",\n    \"1971-10-30 00:00:00\",\n    \"1971-10-31 00:00:00\",\n    \"1971-11-06 00:00:00\",\n    \"1971-11-07 00:00:00\",\n    \"1971-11-13 00:00:00\",\n    \"1971-11-14 00:00:00\",\n    \"1971-11-20 00:00:00\",\n    \"1971-11-21 00:00:00\",\n    \"1971-11-27 00:00:00\",\n    \"1971-11-28 00:00:00\",\n    \"1971-12-04 00:00:00\",\n    \"1971-12-05 00:00:00\",\n    \"1971-12-11 00:00:00\",\n    \"1971-12-12 00:00:00\",\n    \"1971-12-18 00:00:00\",\n    \"1971-12-19 00:00:00\",\n    \"1971-12-25 00:00:00\",\n    \"1971-12-26 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-01-02 00:00:00\",\n    \"1972-01-08 00:00:00\",\n    \"1972-01-09 00:00:00\",\n    \"1972-01-15 00:00:00\",\n    \"1972-01-16 00:00:00\",\n    \"1972-01-22 00:00:00\",\n    \"1972-01-23 00:00:00\",\n    \"1972-01-29 00:00:00\",\n    \"1972-01-30 00:00:00\",\n    \"1972-02-05 00:00:00\",\n    \"1972-02-06 00:00:00\",\n    \"1972-02-12 00:00:00\",\n    \"1972-02-13 00:00:00\",\n    \"1972-02-19 00:00:00\",\n    \"1972-02-20 00:00:00\",\n    \"1972-02-26 00:00:00\",\n    \"1972-02-27 00:00:00\",\n    \"1972-03-04 00:00:00\",\n    \"1972-03-05 00:00:00\",\n    \"1972-03-11 00:00:00\",\n    \"1972-03-12 00:00:00\",\n    \"1972-03-18 00:00:00\",\n    \"1972-03-19 00:00:00\",\n    \"1972-03-25 00:00:00\",\n    \"1972-03-26 00:00:00\",\n    \"1972-04-01 00:00:00\",\n    \"1972-04-02 00:00:00\",\n    \"1972-04-08 00:00:00\",\n    \"1972-04-09 00:00:00\",\n    \"1972-04-15 00:00:00\",\n    \"1972-04-16 00:00:00\",\n    \"1972-04-22 00:00:00\",\n    \"1972-04-23 00:00:00\",\n    \"1972-04-29 00:00:00\",\n    \"1972-04-30 00:00:00\",\n    \"1972-05-06 00:00:00\",\n    \"1972-05-07 00:00:00\",\n    \"1972-05-13 00:00:00\",\n    \"1972-05-14 00:00:00\",\n    \"1972-05-20 00:00:00\",\n    \"1972-05-21 00:00:00\",\n    \"1972-05-27 00:00:00\",\n    \"1972-05-28 00:00:00\",\n    \"1972-06-03 00:00:00\",\n    \"1972-06-04 00:00:00\",\n    \"1972-06-10 00:00:00\",\n    \"1972-06-11 00:00:00\",\n    \"1972-06-17 00:00:00\",\n    \"1972-06-18 00:00:00\",\n    \"1972-06-24 00:00:00\",\n    \"1972-06-25 00:00:00\",\n    \"1972-07-01 00:00:00\",\n    \"1972-07-02 00:00:00\",\n    \"1972-07-08 00:00:00\",\n    \"1972-07-09 00:00:00\",\n    \"1972-07-15 00:00:00\",\n    \"1972-07-16 00:00:00\",\n    \"1972-07-22 00:00:00\",\n    \"1972-07-23 00:00:00\",\n    \"1972-07-29 00:00:00\",\n    \"1972-07-30 00:00:00\",\n    \"1972-08-05 00:00:00\",\n    \"1972-08-06 00:00:00\",\n    \"1972-08-12 00:00:00\",\n    \"1972-08-13 00:00:00\",\n    \"1972-08-19 00:00:00\",\n    \"1972-08-20 00:00:00\",\n    \"1972-08-26 00:00:00\",\n    \"1972-08-27 00:00:00\",\n    \"1972-09-02 00:00:00\",\n    \"1972-09-03 00:00:00\",\n    \"1972-09-09 00:00:00\",\n    \"1972-09-10 00:00:00\",\n    \"1972-09-16 00:00:00\",\n    \"1972-09-17 00:00:00\",\n    \"1972-09-23 00:00:00\",\n    \"1972-09-24 00:00:00\",\n    \"1972-09-30 00:00:00\",\n    \"1972-10-01 00:00:00\",\n    \"1972-10-07 00:00:00\",\n    \"1972-10-08 00:00:00\",\n    \"1972-10-14 00:00:00\",\n    \"1972-10-15 00:00:00\",\n    \"1972-10-21 00:00:00\",\n    \"1972-10-22 00:00:00\",\n    \"1972-10-28 00:00:00\",\n    \"1972-10-29 00:00:00\",\n    \"1972-11-04 00:00:00\",\n    \"1972-11-05 00:00:00\",\n    \"1972-11-11 00:00:00\",\n    \"1972-11-12 00:00:00\",\n    \"1972-11-18 00:00:00\",\n    \"1972-11-19 00:00:00\",\n    \"1972-11-25 00:00:00\",\n    \"1972-11-26 00:00:00\",\n    \"1972-12-02 00:00:00\",\n    \"1972-12-03 00:00:00\",\n    \"1972-12-09 00:00:00\",\n    \"1972-12-10 00:00:00\",\n    \"1972-12-16 00:00:00\",\n    \"1972-12-17 00:00:00\",\n    \"1972-12-23 00:00:00\",\n    \"1972-12-24 00:00:00\",\n    \"1972-12-30 00:00:00\",\n    \"1972-12-31 00:00:00\",\n    \"1973-01-06 00:00:00\",\n    \"1973-01-07 00:00:00\",\n    \"1973-01-13 00:00:00\",\n    \"1973-01-14 00:00:00\",\n    \"1973-01-20 00:00:00\",\n    \"1973-01-21 00:00:00\",\n    \"1973-01-27 00:00:00\",\n    \"1973-01-28 00:00:00\",\n    \"1973-02-03 00:00:00\",\n    \"1973-02-04 00:00:00\",\n    \"1973-02-10 00:00:00\",\n    \"1973-02-11 00:00:00\",\n    \"1973-02-17 00:00:00\",\n    \"1973-02-18 00:00:00\",\n    \"1973-02-24 00:00:00\",\n    \"1973-02-25 00:00:00\",\n    \"1973-03-03 00:00:00\",\n    \"1973-03-04 00:00:00\",\n    \"1973-03-10 00:00:00\",\n    \"1973-03-11 00:00:00\",\n    \"1973-03-17 00:00:00\",\n    \"1973-03-18 00:00:00\",\n    \"1973-03-24 00:00:00\",\n    \"1973-03-25 00:00:00\",\n    \"1973-03-31 00:00:00\",\n    \"1973-04-01 00:00:00\",\n    \"1973-04-07 00:00:00\",\n    \"1973-04-08 00:00:00\",\n    \"1973-04-14 00:00:00\",\n    \"1973-04-15 00:00:00\",\n    \"1973-04-21 00:00:00\",\n    \"1973-04-22 00:00:00\",\n    \"1973-04-28 00:00:00\",\n    \"1973-04-29 00:00:00\",\n    \"1973-05-05 00:00:00\",\n    \"1973-05-06 00:00:00\",\n    \"1973-05-12 00:00:00\",\n    \"1973-05-13 00:00:00\",\n    \"1973-05-19 00:00:00\",\n    \"1973-05-20 00:00:00\",\n    \"1973-05-26 00:00:00\",\n    \"1973-05-27 00:00:00\",\n    \"1973-06-02 00:00:00\",\n    \"1973-06-03 00:00:00\",\n    \"1973-06-09 00:00:00\",\n    \"1973-06-10 00:00:00\",\n    \"1973-06-16 00:00:00\",\n    \"1973-06-17 00:00:00\",\n    \"1973-06-23 00:00:00\",\n    \"1973-06-24 00:00:00\",\n    \"1973-06-30 00:00:00\",\n    \"1973-07-01 00:00:00\",\n    \"1973-07-07 00:00:00\",\n    \"1973-07-08 00:00:00\",\n    \"1973-07-14 00:00:00\",\n    \"1973-07-15 00:00:00\",\n    \"1973-07-21 00:00:00\",\n    \"1973-07-22 00:00:00\",\n    \"1973-07-28 00:00:00\",\n    \"1973-07-29 00:00:00\",\n    \"1973-08-04 00:00:00\",\n    \"1973-08-05 00:00:00\",\n    \"1973-08-11 00:00:00\",\n    \"1973-08-12 00:00:00\",\n    \"1973-08-18 00:00:00\",\n    \"1973-08-19 00:00:00\",\n    \"1973-08-25 00:00:00\",\n    \"1973-08-26 00:00:00\",\n    \"1973-09-01 00:00:00\",\n    \"1973-09-02 00:00:00\",\n    \"1973-09-08 00:00:00\",\n    \"1973-09-09 00:00:00\",\n    \"1973-09-15 00:00:00\",\n    \"1973-09-16 00:00:00\",\n    \"1973-09-22 00:00:00\",\n    \"1973-09-23 00:00:00\",\n    \"1973-09-29 00:00:00\",\n    \"1973-09-30 00:00:00\",\n    \"1973-10-06 00:00:00\",\n    \"1973-10-07 00:00:00\",\n    \"1973-10-13 00:00:00\",\n    \"1973-10-14 00:00:00\",\n    \"1973-10-20 00:00:00\",\n    \"1973-10-21 00:00:00\",\n    \"1973-10-27 00:00:00\",\n    \"1973-10-28 00:00:00\",\n    \"1973-11-03 00:00:00\",\n    \"1973-11-04 00:00:00\",\n    \"1973-11-10 00:00:00\",\n    \"1973-11-11 00:00:00\",\n    \"1973-11-17 00:00:00\",\n    \"1973-11-18 00:00:00\",\n    \"1973-11-24 00:00:00\",\n    \"1973-11-25 00:00:00\",\n    \"1973-12-01 00:00:00\",\n    \"1973-12-02 00:00:00\",\n    \"1973-12-08 00:00:00\",\n    \"1973-12-09 00:00:00\",\n    \"1973-12-15 00:00:00\",\n    \"1973-12-16 00:00:00\",\n    \"1973-12-22 00:00:00\",\n    \"1973-12-23 00:00:00\",\n    \"1973-12-29 00:00:00\",\n    \"1973-12-30 00:00:00\",\n    \"1974-01-05 00:00:00\",\n    \"1974-01-06 00:00:00\",\n    \"1974-01-12 00:00:00\",\n    \"1974-01-13 00:00:00\",\n    \"1974-01-19 00:00:00\",\n    \"1974-01-20 00:00:00\",\n    \"1974-01-26 00:00:00\",\n    \"1974-01-27 00:00:00\",\n    \"1974-02-02 00:00:00\",\n    \"1974-02-03 00:00:00\",\n    \"1974-02-09 00:00:00\",\n    \"1974-02-10 00:00:00\",\n    \"1974-02-16 00:00:00\",\n    \"1974-02-17 00:00:00\",\n    \"1974-02-23 00:00:00\",\n    \"1974-02-24 00:00:00\",\n    \"1974-03-02 00:00:00\",\n    \"1974-03-03 00:00:00\",\n    \"1974-03-09 00:00:00\",\n    \"1974-03-10 00:00:00\",\n    \"1974-03-16 00:00:00\",\n    \"1974-03-17 00:00:00\",\n    \"1974-03-23 00:00:00\",\n    \"1974-03-24 00:00:00\",\n    \"1974-03-30 00:00:00\",\n    \"1974-03-31 00:00:00\",\n    \"1974-04-06 00:00:00\",\n    \"1974-04-07 00:00:00\",\n    \"1974-04-13 00:00:00\",\n    \"1974-04-14 00:00:00\",\n    \"1974-04-20 00:00:00\",\n    \"1974-04-21 00:00:00\",\n    \"1974-04-27 00:00:00\",\n    \"1974-04-28 00:00:00\",\n    \"1974-05-04 00:00:00\",\n    \"1974-05-05 00:00:00\",\n    \"1974-05-11 00:00:00\",\n    \"1974-05-12 00:00:00\",\n    \"1974-05-18 00:00:00\",\n    \"1974-05-19 00:00:00\",\n    \"1974-05-25 00:00:00\",\n    \"1974-05-26 00:00:00\",\n    \"1974-06-01 00:00:00\",\n    \"1974-06-02 00:00:00\",\n    \"1974-06-08 00:00:00\",\n    \"1974-06-09 00:00:00\",\n    \"1974-06-15 00:00:00\",\n    \"1974-06-16 00:00:00\",\n    \"1974-06-22 00:00:00\",\n    \"1974-06-23 00:00:00\",\n    \"1974-06-29 00:00:00\",\n    \"1974-06-30 00:00:00\",\n    \"1974-07-06 00:00:00\",\n    \"1974-07-07 00:00:00\",\n    \"1974-07-13 00:00:00\",\n    \"1974-07-14 00:00:00\",\n    \"1974-07-20 00:00:00\",\n    \"1974-07-21 00:00:00\",\n    \"1974-07-27 00:00:00\",\n    \"1974-07-28 00:00:00\",\n    \"1974-08-03 00:00:00\",\n    \"1974-08-04 00:00:00\",\n    \"1974-08-10 00:00:00\",\n    \"1974-08-11 00:00:00\",\n    \"1974-08-17 00:00:00\",\n    \"1974-08-18 00:00:00\",\n    \"1974-08-24 00:00:00\",\n    \"1974-08-25 00:00:00\",\n    \"1974-08-31 00:00:00\",\n    \"1974-09-01 00:00:00\",\n    \"1974-09-07 00:00:00\",\n    \"1974-09-08 00:00:00\",\n    \"1974-09-14 00:00:00\",\n    \"1974-09-15 00:00:00\",\n    \"1974-09-21 00:00:00\",\n    \"1974-09-22 00:00:00\",\n    \"1974-09-28 00:00:00\",\n    \"1974-09-29 00:00:00\",\n    \"1974-10-05 00:00:00\",\n    \"1974-10-06 00:00:00\",\n    \"1974-10-12 00:00:00\",\n    \"1974-10-13 00:00:00\",\n    \"1974-10-19 00:00:00\",\n    \"1974-10-20 00:00:00\",\n    \"1974-10-26 00:00:00\",\n    \"1974-10-27 00:00:00\",\n    \"1974-11-02 00:00:00\",\n    \"1974-11-03 00:00:00\",\n    \"1974-11-09 00:00:00\",\n    \"1974-11-10 00:00:00\",\n    \"1974-11-16 00:00:00\",\n    \"1974-11-17 00:00:00\",\n    \"1974-11-23 00:00:00\",\n    \"1974-11-24 00:00:00\",\n    \"1974-11-30 00:00:00\",\n    \"1974-12-01 00:00:00\",\n    \"1974-12-07 00:00:00\",\n    \"1974-12-08 00:00:00\",\n    \"1974-12-14 00:00:00\",\n    \"1974-12-15 00:00:00\",\n    \"1974-12-21 00:00:00\",\n    \"1974-12-22 00:00:00\",\n    \"1974-12-28 00:00:00\",\n    \"1974-12-29 00:00:00\",\n    \"1975-01-04 00:00:00\",\n    \"1975-01-05 00:00:00\",\n    \"1975-01-11 00:00:00\",\n    \"1975-01-12 00:00:00\",\n    \"1975-01-18 00:00:00\",\n    \"1975-01-19 00:00:00\",\n    \"1975-01-25 00:00:00\",\n    \"1975-01-26 00:00:00\",\n    \"1975-02-01 00:00:00\",\n    \"1975-02-02 00:00:00\",\n    \"1975-02-08 00:00:00\",\n    \"1975-02-09 00:00:00\",\n    \"1975-02-15 00:00:00\",\n    \"1975-02-16 00:00:00\",\n    \"1975-02-22 00:00:00\",\n    \"1975-02-23 00:00:00\",\n    \"1975-03-01 00:00:00\",\n    \"1975-03-02 00:00:00\",\n    \"1975-03-08 00:00:00\",\n    \"1975-03-09 00:00:00\",\n    \"1975-03-15 00:00:00\",\n    \"1975-03-16 00:00:00\",\n    \"1975-03-22 00:00:00\",\n    \"1975-03-23 00:00:00\",\n    \"1975-03-29 00:00:00\",\n    \"1975-03-30 00:00:00\",\n    \"1975-04-05 00:00:00\",\n    \"1975-04-06 00:00:00\",\n    \"1975-04-12 00:00:00\",\n    \"1975-04-13 00:00:00\",\n    \"1975-04-19 00:00:00\",\n    \"1975-04-20 00:00:00\",\n    \"1975-04-26 00:00:00\",\n    \"1975-04-27 00:00:00\",\n    \"1975-05-03 00:00:00\",\n    \"1975-05-04 00:00:00\",\n    \"1975-05-10 00:00:00\",\n    \"1975-05-11 00:00:00\",\n    \"1975-05-17 00:00:00\",\n    \"1975-05-18 00:00:00\",\n    \"1975-05-24 00:00:00\",\n    \"1975-05-25 00:00:00\",\n    \"1975-05-31 00:00:00\",\n    \"1975-06-01 00:00:00\",\n    \"1975-06-07 00:00:00\",\n    \"1975-06-08 00:00:00\",\n    \"1975-06-14 00:00:00\",\n    \"1975-06-15 00:00:00\",\n    \"1975-06-21 00:00:00\",\n    \"1975-06-22 00:00:00\",\n    \"1975-06-28 00:00:00\",\n    \"1975-06-29 00:00:00\",\n    \"1975-07-05 00:00:00\",\n    \"1975-07-06 00:00:00\",\n    \"1975-07-12 00:00:00\",\n    \"1975-07-13 00:00:00\",\n    \"1975-07-19 00:00:00\",\n    \"1975-07-20 00:00:00\",\n    \"1975-07-26 00:00:00\",\n    \"1975-07-27 00:00:00\",\n    \"1975-08-02 00:00:00\",\n    \"1975-08-03 00:00:00\",\n    \"1975-08-09 00:00:00\",\n    \"1975-08-10 00:00:00\",\n    \"1975-08-16 00:00:00\",\n    \"1975-08-17 00:00:00\",\n    \"1975-08-23 00:00:00\",\n    \"1975-08-24 00:00:00\",\n    \"1975-08-30 00:00:00\",\n    \"1975-08-31 00:00:00\",\n    \"1975-09-06 00:00:00\",\n    \"1975-09-07 00:00:00\",\n    \"1975-09-13 00:00:00\",\n    \"1975-09-14 00:00:00\",\n    \"1975-09-20 00:00:00\",\n    \"1975-09-21 00:00:00\",\n    \"1975-09-27 00:00:00\",\n    \"1975-09-28 00:00:00\",\n    \"1975-10-04 00:00:00\",\n    \"1975-10-05 00:00:00\",\n    \"1975-10-11 00:00:00\",\n    \"1975-10-12 00:00:00\",\n    \"1975-10-18 00:00:00\",\n    \"1975-10-19 00:00:00\",\n    \"1975-10-25 00:00:00\",\n    \"1975-10-26 00:00:00\",\n    \"1975-11-01 00:00:00\",\n    \"1975-11-02 00:00:00\",\n    \"1975-11-08 00:00:00\",\n    \"1975-11-09 00:00:00\",\n    \"1975-11-15 00:00:00\",\n    \"1975-11-16 00:00:00\",\n    \"1975-11-22 00:00:00\",\n    \"1975-11-23 00:00:00\",\n    \"1975-11-29 00:00:00\",\n    \"1975-11-30 00:00:00\",\n    \"1975-12-06 00:00:00\",\n    \"1975-12-07 00:00:00\",\n    \"1975-12-13 00:00:00\",\n    \"1975-12-14 00:00:00\",\n    \"1975-12-20 00:00:00\",\n    \"1975-12-21 00:00:00\",\n    \"1975-12-27 00:00:00\",\n    \"1975-12-28 00:00:00\",\n    \"1976-01-03 00:00:00\",\n    \"1976-01-04 00:00:00\",\n    \"1976-01-10 00:00:00\",\n    \"1976-01-11 00:00:00\",\n    \"1976-01-17 00:00:00\",\n    \"1976-01-18 00:00:00\",\n    \"1976-01-24 00:00:00\",\n    \"1976-01-25 00:00:00\",\n    \"1976-01-31 00:00:00\",\n    \"1976-02-01 00:00:00\",\n    \"1976-02-07 00:00:00\",\n    \"1976-02-08 00:00:00\",\n    \"1976-02-14 00:00:00\",\n    \"1976-02-15 00:00:00\",\n    \"1976-02-21 00:00:00\",\n    \"1976-02-22 00:00:00\",\n    \"1976-02-28 00:00:00\",\n    \"1976-02-29 00:00:00\",\n    \"1976-03-06 00:00:00\",\n    \"1976-03-07 00:00:00\",\n    \"1976-03-13 00:00:00\",\n    \"1976-03-14 00:00:00\",\n    \"1976-03-20 00:00:00\",\n    \"1976-03-21 00:00:00\",\n    \"1976-03-27 00:00:00\",\n    \"1976-03-28 00:00:00\",\n    \"1976-04-03 00:00:00\",\n    \"1976-04-04 00:00:00\",\n    \"1976-04-10 00:00:00\",\n    \"1976-04-11 00:00:00\",\n    \"1976-04-17 00:00:00\",\n    \"1976-04-18 00:00:00\",\n    \"1976-04-24 00:00:00\",\n    \"1976-04-25 00:00:00\",\n    \"1976-05-01 00:00:00\",\n    \"1976-05-02 00:00:00\",\n    \"1976-05-08 00:00:00\",\n    \"1976-05-09 00:00:00\",\n    \"1976-05-15 00:00:00\",\n    \"1976-05-16 00:00:00\",\n    \"1976-05-22 00:00:00\",\n    \"1976-05-23 00:00:00\",\n    \"1976-05-29 00:00:00\",\n    \"1976-05-30 00:00:00\",\n    \"1976-06-05 00:00:00\",\n    \"1976-06-06 00:00:00\",\n    \"1976-06-12 00:00:00\",\n    \"1976-06-13 00:00:00\",\n    \"1976-06-19 00:00:00\",\n    \"1976-06-20 00:00:00\",\n    \"1976-06-26 00:00:00\",\n    \"1976-06-27 00:00:00\",\n    \"1976-07-03 00:00:00\",\n    \"1976-07-04 00:00:00\",\n    \"1976-07-10 00:00:00\",\n    \"1976-07-11 00:00:00\",\n    \"1976-07-17 00:00:00\",\n    \"1976-07-18 00:00:00\",\n    \"1976-07-24 00:00:00\",\n    \"1976-07-25 00:00:00\",\n    \"1976-07-31 00:00:00\",\n    \"1976-08-01 00:00:00\",\n    \"1976-08-07 00:00:00\",\n    \"1976-08-08 00:00:00\",\n    \"1976-08-14 00:00:00\",\n    \"1976-08-15 00:00:00\",\n    \"1976-08-21 00:00:00\",\n    \"1976-08-22 00:00:00\",\n    \"1976-08-28 00:00:00\",\n    \"1976-08-29 00:00:00\",\n    \"1976-09-04 00:00:00\",\n    \"1976-09-05 00:00:00\",\n    \"1976-09-11 00:00:00\",\n    \"1976-09-12 00:00:00\",\n    \"1976-09-18 00:00:00\",\n    \"1976-09-19 00:00:00\",\n    \"1976-09-25 00:00:00\",\n    \"1976-09-26 00:00:00\",\n    \"1976-10-02 00:00:00\",\n    \"1976-10-03 00:00:00\",\n    \"1976-10-09 00:00:00\",\n    \"1976-10-10 00:00:00\",\n    \"1976-10-16 00:00:00\",\n    \"1976-10-17 00:00:00\",\n    \"1976-10-23 00:00:00\",\n    \"1976-10-24 00:00:00\",\n    \"1976-10-30 00:00:00\",\n    \"1976-10-31 00:00:00\",\n    \"1976-11-06 00:00:00\",\n    \"1976-11-07 00:00:00\",\n    \"1976-11-13 00:00:00\",\n    \"1976-11-14 00:00:00\",\n    \"1976-11-20 00:00:00\",\n    \"1976-11-21 00:00:00\",\n    \"1976-11-27 00:00:00\",\n    \"1976-11-28 00:00:00\",\n    \"1976-12-04 00:00:00\",\n    \"1976-12-05 00:00:00\",\n    \"1976-12-11 00:00:00\",\n    \"1976-12-12 00:00:00\",\n    \"1976-12-18 00:00:00\",\n    \"1976-12-19 00:00:00\",\n    \"1976-12-25 00:00:00\",\n    \"1976-12-26 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-01-02 00:00:00\",\n    \"1977-01-08 00:00:00\",\n    \"1977-01-09 00:00:00\",\n    \"1977-01-15 00:00:00\",\n    \"1977-01-16 00:00:00\",\n    \"1977-01-22 00:00:00\",\n    \"1977-01-23 00:00:00\",\n    \"1977-01-29 00:00:00\",\n    \"1977-01-30 00:00:00\",\n    \"1977-02-05 00:00:00\",\n    \"1977-02-06 00:00:00\",\n    \"1977-02-12 00:00:00\",\n    \"1977-02-13 00:00:00\",\n    \"1977-02-19 00:00:00\",\n    \"1977-02-20 00:00:00\",\n    \"1977-02-26 00:00:00\",\n    \"1977-02-27 00:00:00\",\n    \"1977-03-05 00:00:00\",\n    \"1977-03-06 00:00:00\",\n    \"1977-03-12 00:00:00\",\n    \"1977-03-13 00:00:00\",\n    \"1977-03-19 00:00:00\",\n    \"1977-03-20 00:00:00\",\n    \"1977-03-26 00:00:00\",\n    \"1977-03-27 00:00:00\",\n    \"1977-04-02 00:00:00\",\n    \"1977-04-03 00:00:00\",\n    \"1977-04-09 00:00:00\",\n    \"1977-04-10 00:00:00\",\n    \"1977-04-16 00:00:00\",\n    \"1977-04-17 00:00:00\",\n    \"1977-04-23 00:00:00\",\n    \"1977-04-24 00:00:00\",\n    \"1977-04-30 00:00:00\",\n    \"1977-05-01 00:00:00\",\n    \"1977-05-07 00:00:00\",\n    \"1977-05-08 00:00:00\",\n    \"1977-05-14 00:00:00\",\n    \"1977-05-15 00:00:00\",\n    \"1977-05-21 00:00:00\",\n    \"1977-05-22 00:00:00\",\n    \"1977-05-28 00:00:00\",\n    \"1977-05-29 00:00:00\",\n    \"1977-06-04 00:00:00\",\n    \"1977-06-05 00:00:00\",\n    \"1977-06-11 00:00:00\",\n    \"1977-06-12 00:00:00\",\n    \"1977-06-18 00:00:00\",\n    \"1977-06-19 00:00:00\",\n    \"1977-06-25 00:00:00\",\n    \"1977-06-26 00:00:00\",\n    \"1977-07-02 00:00:00\",\n    \"1977-07-03 00:00:00\",\n    \"1977-07-09 00:00:00\",\n    \"1977-07-10 00:00:00\",\n    \"1977-07-16 00:00:00\",\n    \"1977-07-17 00:00:00\",\n    \"1977-07-23 00:00:00\",\n    \"1977-07-24 00:00:00\",\n    \"1977-07-30 00:00:00\",\n    \"1977-07-31 00:00:00\",\n    \"1977-08-06 00:00:00\",\n    \"1977-08-07 00:00:00\",\n    \"1977-08-13 00:00:00\",\n    \"1977-08-14 00:00:00\",\n    \"1977-08-20 00:00:00\",\n    \"1977-08-21 00:00:00\",\n    \"1977-08-27 00:00:00\",\n    \"1977-08-28 00:00:00\",\n    \"1977-09-03 00:00:00\",\n    \"1977-09-04 00:00:00\",\n    \"1977-09-10 00:00:00\",\n    \"1977-09-11 00:00:00\",\n    \"1977-09-17 00:00:00\",\n    \"1977-09-18 00:00:00\",\n    \"1977-09-24 00:00:00\",\n    \"1977-09-25 00:00:00\",\n    \"1977-10-01 00:00:00\",\n    \"1977-10-02 00:00:00\",\n    \"1977-10-08 00:00:00\",\n    \"1977-10-09 00:00:00\",\n    \"1977-10-15 00:00:00\",\n    \"1977-10-16 00:00:00\",\n    \"1977-10-22 00:00:00\",\n    \"1977-10-23 00:00:00\",\n    \"1977-10-29 00:00:00\",\n    \"1977-10-30 00:00:00\",\n    \"1977-11-05 00:00:00\",\n    \"1977-11-06 00:00:00\",\n    \"1977-11-12 00:00:00\",\n    \"1977-11-13 00:00:00\",\n    \"1977-11-19 00:00:00\",\n    \"1977-11-20 00:00:00\",\n    \"1977-11-26 00:00:00\",\n    \"1977-11-27 00:00:00\",\n    \"1977-12-03 00:00:00\",\n    \"1977-12-04 00:00:00\",\n    \"1977-12-10 00:00:00\",\n    \"1977-12-11 00:00:00\",\n    \"1977-12-17 00:00:00\",\n    \"1977-12-18 00:00:00\",\n    \"1977-12-24 00:00:00\",\n    \"1977-12-25 00:00:00\",\n    \"1977-12-31 00:00:00\",\n    \"1978-01-01 00:00:00\",\n    \"1978-01-07 00:00:00\",\n    \"1978-01-08 00:00:00\",\n    \"1978-01-14 00:00:00\",\n    \"1978-01-15 00:00:00\",\n    \"1978-01-21 00:00:00\",\n    \"1978-01-22 00:00:00\",\n    \"1978-01-28 00:00:00\",\n    \"1978-01-29 00:00:00\",\n    \"1978-02-04 00:00:00\",\n    \"1978-02-05 00:00:00\",\n    \"1978-02-11 00:00:00\",\n    \"1978-02-12 00:00:00\",\n    \"1978-02-18 00:00:00\",\n    \"1978-02-19 00:00:00\",\n    \"1978-02-25 00:00:00\",\n    \"1978-02-26 00:00:00\",\n    \"1978-03-04 00:00:00\",\n    \"1978-03-05 00:00:00\",\n    \"1978-03-11 00:00:00\",\n    \"1978-03-12 00:00:00\",\n    \"1978-03-18 00:00:00\",\n    \"1978-03-19 00:00:00\",\n    \"1978-03-25 00:00:00\",\n    \"1978-03-26 00:00:00\",\n    \"1978-04-01 00:00:00\",\n    \"1978-04-02 00:00:00\",\n    \"1978-04-08 00:00:00\",\n    \"1978-04-09 00:00:00\",\n    \"1978-04-15 00:00:00\",\n    \"1978-04-16 00:00:00\",\n    \"1978-04-22 00:00:00\",\n    \"1978-04-23 00:00:00\",\n    \"1978-04-29 00:00:00\",\n    \"1978-04-30 00:00:00\",\n    \"1978-05-06 00:00:00\",\n    \"1978-05-07 00:00:00\",\n    \"1978-05-13 00:00:00\",\n    \"1978-05-14 00:00:00\",\n    \"1978-05-20 00:00:00\",\n    \"1978-05-21 00:00:00\",\n    \"1978-05-27 00:00:00\",\n    \"1978-05-28 00:00:00\",\n    \"1978-06-03 00:00:00\",\n    \"1978-06-04 00:00:00\",\n    \"1978-06-10 00:00:00\",\n    \"1978-06-11 00:00:00\",\n    \"1978-06-17 00:00:00\",\n    \"1978-06-18 00:00:00\",\n    \"1978-06-24 00:00:00\",\n    \"1978-06-25 00:00:00\",\n    \"1978-07-01 00:00:00\",\n    \"1978-07-02 00:00:00\",\n    \"1978-07-08 00:00:00\",\n    \"1978-07-09 00:00:00\",\n    \"1978-07-15 00:00:00\",\n    \"1978-07-16 00:00:00\",\n    \"1978-07-22 00:00:00\",\n    \"1978-07-23 00:00:00\",\n    \"1978-07-29 00:00:00\",\n    \"1978-07-30 00:00:00\",\n    \"1978-08-05 00:00:00\",\n    \"1978-08-06 00:00:00\",\n    \"1978-08-12 00:00:00\",\n    \"1978-08-13 00:00:00\",\n    \"1978-08-19 00:00:00\",\n    \"1978-08-20 00:00:00\",\n    \"1978-08-26 00:00:00\",\n    \"1978-08-27 00:00:00\",\n    \"1978-09-02 00:00:00\",\n    \"1978-09-03 00:00:00\",\n    \"1978-09-09 00:00:00\",\n    \"1978-09-10 00:00:00\",\n    \"1978-09-16 00:00:00\",\n    \"1978-09-17 00:00:00\",\n    \"1978-09-23 00:00:00\",\n    \"1978-09-24 00:00:00\",\n    \"1978-09-30 00:00:00\",\n    \"1978-10-01 00:00:00\",\n    \"1978-10-07 00:00:00\",\n    \"1978-10-08 00:00:00\",\n    \"1978-10-14 00:00:00\",\n    \"1978-10-15 00:00:00\",\n    \"1978-10-21 00:00:00\",\n    \"1978-10-22 00:00:00\",\n    \"1978-10-28 00:00:00\",\n    \"1978-10-29 00:00:00\",\n    \"1978-11-04 00:00:00\",\n    \"1978-11-05 00:00:00\",\n    \"1978-11-11 00:00:00\",\n    \"1978-11-12 00:00:00\",\n    \"1978-11-18 00:00:00\",\n    \"1978-11-19 00:00:00\",\n    \"1978-11-25 00:00:00\",\n    \"1978-11-26 00:00:00\",\n    \"1978-12-02 00:00:00\",\n    \"1978-12-03 00:00:00\",\n    \"1978-12-09 00:00:00\",\n    \"1978-12-10 00:00:00\",\n    \"1978-12-16 00:00:00\",\n    \"1978-12-17 00:00:00\",\n    \"1978-12-23 00:00:00\",\n    \"1978-12-24 00:00:00\",\n    \"1978-12-30 00:00:00\",\n    \"1978-12-31 00:00:00\",\n    \"1979-01-06 00:00:00\",\n    \"1979-01-07 00:00:00\",\n    \"1979-01-13 00:00:00\",\n    \"1979-01-14 00:00:00\",\n    \"1979-01-20 00:00:00\",\n    \"1979-01-21 00:00:00\",\n    \"1979-01-27 00:00:00\",\n    \"1979-01-28 00:00:00\",\n    \"1979-02-03 00:00:00\",\n    \"1979-02-04 00:00:00\",\n    \"1979-02-10 00:00:00\",\n    \"1979-02-11 00:00:00\",\n    \"1979-02-17 00:00:00\",\n    \"1979-02-18 00:00:00\",\n    \"1979-02-24 00:00:00\",\n    \"1979-02-25 00:00:00\",\n    \"1979-03-03 00:00:00\",\n    \"1979-03-04 00:00:00\",\n    \"1979-03-10 00:00:00\",\n    \"1979-03-11 00:00:00\",\n    \"1979-03-17 00:00:00\",\n    \"1979-03-18 00:00:00\",\n    \"1979-03-24 00:00:00\",\n    \"1979-03-25 00:00:00\",\n    \"1979-03-31 00:00:00\",\n    \"1979-04-01 00:00:00\",\n    \"1979-04-07 00:00:00\",\n    \"1979-04-08 00:00:00\",\n    \"1979-04-14 00:00:00\",\n    \"1979-04-15 00:00:00\",\n    \"1979-04-21 00:00:00\",\n    \"1979-04-22 00:00:00\",\n    \"1979-04-28 00:00:00\",\n    \"1979-04-29 00:00:00\",\n    \"1979-05-05 00:00:00\",\n    \"1979-05-06 00:00:00\",\n    \"1979-05-12 00:00:00\",\n    \"1979-05-13 00:00:00\",\n    \"1979-05-19 00:00:00\",\n    \"1979-05-20 00:00:00\",\n    \"1979-05-26 00:00:00\",\n    \"1979-05-27 00:00:00\",\n    \"1979-06-02 00:00:00\",\n    \"1979-06-03 00:00:00\",\n    \"1979-06-09 00:00:00\",\n    \"1979-06-10 00:00:00\",\n    \"1979-06-16 00:00:00\",\n    \"1979-06-17 00:00:00\",\n    \"1979-06-23 00:00:00\",\n    \"1979-06-24 00:00:00\",\n    \"1979-06-30 00:00:00\",\n    \"1979-07-01 00:00:00\",\n    \"1979-07-07 00:00:00\",\n    \"1979-07-08 00:00:00\",\n    \"1979-07-14 00:00:00\",\n    \"1979-07-15 00:00:00\",\n    \"1979-07-21 00:00:00\",\n    \"1979-07-22 00:00:00\",\n    \"1979-07-28 00:00:00\",\n    \"1979-07-29 00:00:00\",\n    \"1979-08-04 00:00:00\",\n    \"1979-08-05 00:00:00\",\n    \"1979-08-11 00:00:00\",\n    \"1979-08-12 00:00:00\",\n    \"1979-08-18 00:00:00\",\n    \"1979-08-19 00:00:00\",\n    \"1979-08-25 00:00:00\",\n    \"1979-08-26 00:00:00\",\n    \"1979-09-01 00:00:00\",\n    \"1979-09-02 00:00:00\",\n    \"1979-09-08 00:00:00\",\n    \"1979-09-09 00:00:00\",\n    \"1979-09-15 00:00:00\",\n    \"1979-09-16 00:00:00\",\n    \"1979-09-22 00:00:00\",\n    \"1979-09-23 00:00:00\",\n    \"1979-09-29 00:00:00\",\n    \"1979-09-30 00:00:00\",\n    \"1979-10-06 00:00:00\",\n    \"1979-10-07 00:00:00\",\n    \"1979-10-13 00:00:00\",\n    \"1979-10-14 00:00:00\",\n    \"1979-10-20 00:00:00\",\n    \"1979-10-21 00:00:00\",\n    \"1979-10-27 00:00:00\",\n    \"1979-10-28 00:00:00\",\n    \"1979-11-03 00:00:00\",\n    \"1979-11-04 00:00:00\",\n    \"1979-11-10 00:00:00\",\n    \"1979-11-11 00:00:00\",\n    \"1979-11-17 00:00:00\",\n    \"1979-11-18 00:00:00\",\n    \"1979-11-24 00:00:00\",\n    \"1979-11-25 00:00:00\",\n    \"1979-12-01 00:00:00\",\n    \"1979-12-02 00:00:00\",\n    \"1979-12-08 00:00:00\",\n    \"1979-12-09 00:00:00\",\n    \"1979-12-15 00:00:00\",\n    \"1979-12-16 00:00:00\",\n    \"1979-12-22 00:00:00\",\n    \"1979-12-23 00:00:00\",\n    \"1979-12-29 00:00:00\",\n    \"1979-12-30 00:00:00\",\n    \"1980-01-05 00:00:00\",\n    \"1980-01-06 00:00:00\",\n    \"1980-01-12 00:00:00\",\n    \"1980-01-13 00:00:00\",\n    \"1980-01-19 00:00:00\",\n    \"1980-01-20 00:00:00\",\n    \"1980-01-26 00:00:00\",\n    \"1980-01-27 00:00:00\",\n    \"1980-02-02 00:00:00\",\n    \"1980-02-03 00:00:00\",\n    \"1980-02-09 00:00:00\",\n    \"1980-02-10 00:00:00\",\n    \"1980-02-16 00:00:00\",\n    \"1980-02-17 00:00:00\",\n    \"1980-02-23 00:00:00\",\n    \"1980-02-24 00:00:00\",\n    \"1980-03-01 00:00:00\",\n    \"1980-03-02 00:00:00\",\n    \"1980-03-08 00:00:00\",\n    \"1980-03-09 00:00:00\",\n    \"1980-03-15 00:00:00\",\n    \"1980-03-16 00:00:00\",\n    \"1980-03-22 00:00:00\",\n    \"1980-03-23 00:00:00\",\n    \"1980-03-29 00:00:00\",\n    \"1980-03-30 00:00:00\",\n    \"1980-04-05 00:00:00\",\n    \"1980-04-06 00:00:00\",\n    \"1980-04-12 00:00:00\",\n    \"1980-04-13 00:00:00\",\n    \"1980-04-19 00:00:00\",\n    \"1980-04-20 00:00:00\",\n    \"1980-04-26 00:00:00\",\n    \"1980-04-27 00:00:00\",\n    \"1980-05-03 00:00:00\",\n    \"1980-05-04 00:00:00\",\n    \"1980-05-10 00:00:00\",\n    \"1980-05-11 00:00:00\",\n    \"1980-05-17 00:00:00\",\n    \"1980-05-18 00:00:00\",\n    \"1980-05-24 00:00:00\",\n    \"1980-05-25 00:00:00\",\n    \"1980-05-31 00:00:00\",\n    \"1980-06-01 00:00:00\",\n    \"1980-06-07 00:00:00\",\n    \"1980-06-08 00:00:00\",\n    \"1980-06-14 00:00:00\",\n    \"1980-06-15 00:00:00\",\n    \"1980-06-21 00:00:00\",\n    \"1980-06-22 00:00:00\",\n    \"1980-06-28 00:00:00\",\n    \"1980-06-29 00:00:00\",\n    \"1980-07-05 00:00:00\",\n    \"1980-07-06 00:00:00\",\n    \"1980-07-12 00:00:00\",\n    \"1980-07-13 00:00:00\",\n    \"1980-07-19 00:00:00\",\n    \"1980-07-20 00:00:00\",\n    \"1980-07-26 00:00:00\",\n    \"1980-07-27 00:00:00\",\n    \"1980-08-02 00:00:00\",\n    \"1980-08-03 00:00:00\",\n    \"1980-08-09 00:00:00\",\n    \"1980-08-10 00:00:00\",\n    \"1980-08-16 00:00:00\",\n    \"1980-08-17 00:00:00\",\n    \"1980-08-23 00:00:00\",\n    \"1980-08-24 00:00:00\",\n    \"1980-08-30 00:00:00\",\n    \"1980-08-31 00:00:00\",\n    \"1980-09-06 00:00:00\",\n    \"1980-09-07 00:00:00\",\n    \"1980-09-13 00:00:00\",\n    \"1980-09-14 00:00:00\",\n    \"1980-09-20 00:00:00\",\n    \"1980-09-21 00:00:00\",\n    \"1980-09-27 00:00:00\",\n    \"1980-09-28 00:00:00\",\n    \"1980-10-04 00:00:00\",\n    \"1980-10-05 00:00:00\",\n    \"1980-10-11 00:00:00\",\n    \"1980-10-12 00:00:00\",\n    \"1980-10-18 00:00:00\",\n    \"1980-10-19 00:00:00\",\n    \"1980-10-25 00:00:00\",\n    \"1980-10-26 00:00:00\",\n    \"1980-11-01 00:00:00\",\n    \"1980-11-02 00:00:00\",\n    \"1980-11-08 00:00:00\",\n    \"1980-11-09 00:00:00\",\n    \"1980-11-15 00:00:00\",\n    \"1980-11-16 00:00:00\",\n    \"1980-11-22 00:00:00\",\n    \"1980-11-23 00:00:00\",\n    \"1980-11-29 00:00:00\",\n    \"1980-11-30 00:00:00\",\n    \"1980-12-06 00:00:00\",\n    \"1980-12-07 00:00:00\",\n    \"1980-12-13 00:00:00\",\n    \"1980-12-14 00:00:00\",\n    \"1980-12-20 00:00:00\",\n    \"1980-12-21 00:00:00\",\n    \"1980-12-27 00:00:00\",\n    \"1980-12-28 00:00:00\",\n    \"1981-01-03 00:00:00\",\n    \"1981-01-04 00:00:00\",\n    \"1981-01-10 00:00:00\",\n    \"1981-01-11 00:00:00\",\n    \"1981-01-17 00:00:00\",\n    \"1981-01-18 00:00:00\",\n    \"1981-01-24 00:00:00\",\n    \"1981-01-25 00:00:00\",\n    \"1981-01-31 00:00:00\",\n    \"1981-02-01 00:00:00\",\n    \"1981-02-07 00:00:00\",\n    \"1981-02-08 00:00:00\",\n    \"1981-02-14 00:00:00\",\n    \"1981-02-15 00:00:00\",\n    \"1981-02-21 00:00:00\",\n    \"1981-02-22 00:00:00\",\n    \"1981-02-28 00:00:00\",\n    \"1981-03-01 00:00:00\",\n    \"1981-03-07 00:00:00\",\n    \"1981-03-08 00:00:00\",\n    \"1981-03-14 00:00:00\",\n    \"1981-03-15 00:00:00\",\n    \"1981-03-21 00:00:00\",\n    \"1981-03-22 00:00:00\",\n    \"1981-03-28 00:00:00\",\n    \"1981-03-29 00:00:00\",\n    \"1981-04-04 00:00:00\",\n    \"1981-04-05 00:00:00\",\n    \"1981-04-11 00:00:00\",\n    \"1981-04-12 00:00:00\",\n    \"1981-04-18 00:00:00\",\n    \"1981-04-19 00:00:00\",\n    \"1981-04-25 00:00:00\",\n    \"1981-04-26 00:00:00\",\n    \"1981-05-02 00:00:00\",\n    \"1981-05-03 00:00:00\",\n    \"1981-05-09 00:00:00\",\n    \"1981-05-10 00:00:00\",\n    \"1981-05-16 00:00:00\",\n    \"1981-05-17 00:00:00\",\n    \"1981-05-23 00:00:00\",\n    \"1981-05-24 00:00:00\",\n    \"1981-05-30 00:00:00\",\n    \"1981-05-31 00:00:00\",\n    \"1981-06-06 00:00:00\",\n    \"1981-06-07 00:00:00\",\n    \"1981-06-13 00:00:00\",\n    \"1981-06-14 00:00:00\",\n    \"1981-06-20 00:00:00\",\n    \"1981-06-21 00:00:00\",\n    \"1981-06-27 00:00:00\",\n    \"1981-06-28 00:00:00\",\n    \"1981-07-04 00:00:00\",\n    \"1981-07-05 00:00:00\",\n    \"1981-07-11 00:00:00\",\n    \"1981-07-12 00:00:00\",\n    \"1981-07-18 00:00:00\",\n    \"1981-07-19 00:00:00\",\n    \"1981-07-25 00:00:00\",\n    \"1981-07-26 00:00:00\",\n    \"1981-08-01 00:00:00\",\n    \"1981-08-02 00:00:00\",\n    \"1981-08-08 00:00:00\",\n    \"1981-08-09 00:00:00\",\n    \"1981-08-15 00:00:00\",\n    \"1981-08-16 00:00:00\",\n    \"1981-08-22 00:00:00\",\n    \"1981-08-23 00:00:00\",\n    \"1981-08-29 00:00:00\",\n    \"1981-08-30 00:00:00\",\n    \"1981-09-05 00:00:00\",\n    \"1981-09-06 00:00:00\",\n    \"1981-09-12 00:00:00\",\n    \"1981-09-13 00:00:00\",\n    \"1981-09-19 00:00:00\",\n    \"1981-09-20 00:00:00\",\n    \"1981-09-26 00:00:00\",\n    \"1981-09-27 00:00:00\",\n    \"1981-10-03 00:00:00\",\n    \"1981-10-04 00:00:00\",\n    \"1981-10-10 00:00:00\",\n    \"1981-10-11 00:00:00\",\n    \"1981-10-17 00:00:00\",\n    \"1981-10-18 00:00:00\",\n    \"1981-10-24 00:00:00\",\n    \"1981-10-25 00:00:00\",\n    \"1981-10-31 00:00:00\",\n    \"1981-11-01 00:00:00\",\n    \"1981-11-07 00:00:00\",\n    \"1981-11-08 00:00:00\",\n    \"1981-11-14 00:00:00\",\n    \"1981-11-15 00:00:00\",\n    \"1981-11-21 00:00:00\",\n    \"1981-11-22 00:00:00\",\n    \"1981-11-28 00:00:00\",\n    \"1981-11-29 00:00:00\",\n    \"1981-12-05 00:00:00\",\n    \"1981-12-06 00:00:00\",\n    \"1981-12-12 00:00:00\",\n    \"1981-12-13 00:00:00\",\n    \"1981-12-19 00:00:00\",\n    \"1981-12-20 00:00:00\",\n    \"1981-12-26 00:00:00\",\n    \"1981-12-27 00:00:00\",\n    \"1982-01-02 00:00:00\",\n    \"1982-01-03 00:00:00\",\n    \"1982-01-09 00:00:00\",\n    \"1982-01-10 00:00:00\",\n    \"1982-01-16 00:00:00\",\n    \"1982-01-17 00:00:00\",\n    \"1982-01-23 00:00:00\",\n    \"1982-01-24 00:00:00\",\n    \"1982-01-30 00:00:00\",\n    \"1982-01-31 00:00:00\",\n    \"1982-02-06 00:00:00\",\n    \"1982-02-07 00:00:00\",\n    \"1982-02-13 00:00:00\",\n    \"1982-02-14 00:00:00\",\n    \"1982-02-20 00:00:00\",\n    \"1982-02-21 00:00:00\",\n    \"1982-02-27 00:00:00\",\n    \"1982-02-28 00:00:00\",\n    \"1982-03-06 00:00:00\",\n    \"1982-03-07 00:00:00\",\n    \"1982-03-13 00:00:00\",\n    \"1982-03-14 00:00:00\",\n    \"1982-03-20 00:00:00\",\n    \"1982-03-21 00:00:00\",\n    \"1982-03-27 00:00:00\",\n    \"1982-03-28 00:00:00\",\n    \"1982-04-03 00:00:00\",\n    \"1982-04-04 00:00:00\",\n    \"1982-04-10 00:00:00\",\n    \"1982-04-11 00:00:00\",\n    \"1982-04-17 00:00:00\",\n    \"1982-04-18 00:00:00\",\n    \"1982-04-24 00:00:00\",\n    \"1982-04-25 00:00:00\",\n    \"1982-05-01 00:00:00\",\n    \"1982-05-02 00:00:00\",\n    \"1982-05-08 00:00:00\",\n    \"1982-05-09 00:00:00\",\n    \"1982-05-15 00:00:00\",\n    \"1982-05-16 00:00:00\",\n    \"1982-05-22 00:00:00\",\n    \"1982-05-23 00:00:00\",\n    \"1982-05-29 00:00:00\",\n    \"1982-05-30 00:00:00\",\n    \"1982-06-05 00:00:00\",\n    \"1982-06-06 00:00:00\",\n    \"1982-06-12 00:00:00\",\n    \"1982-06-13 00:00:00\",\n    \"1982-06-19 00:00:00\",\n    \"1982-06-20 00:00:00\",\n    \"1982-06-26 00:00:00\",\n    \"1982-06-27 00:00:00\",\n    \"1982-07-03 00:00:00\",\n    \"1982-07-04 00:00:00\",\n    \"1982-07-10 00:00:00\",\n    \"1982-07-11 00:00:00\",\n    \"1982-07-17 00:00:00\",\n    \"1982-07-18 00:00:00\",\n    \"1982-07-24 00:00:00\",\n    \"1982-07-25 00:00:00\",\n    \"1982-07-31 00:00:00\",\n    \"1982-08-01 00:00:00\",\n    \"1982-08-07 00:00:00\",\n    \"1982-08-08 00:00:00\",\n    \"1982-08-14 00:00:00\",\n    \"1982-08-15 00:00:00\",\n    \"1982-08-21 00:00:00\",\n    \"1982-08-22 00:00:00\",\n    \"1982-08-28 00:00:00\",\n    \"1982-08-29 00:00:00\",\n    \"1982-09-04 00:00:00\",\n    \"1982-09-05 00:00:00\",\n    \"1982-09-11 00:00:00\",\n    \"1982-09-12 00:00:00\",\n    \"1982-09-18 00:00:00\",\n    \"1982-09-19 00:00:00\",\n    \"1982-09-25 00:00:00\",\n    \"1982-09-26 00:00:00\",\n    \"1982-10-02 00:00:00\",\n    \"1982-10-03 00:00:00\",\n    \"1982-10-09 00:00:00\",\n    \"1982-10-10 00:00:00\",\n    \"1982-10-16 00:00:00\",\n    \"1982-10-17 00:00:00\",\n    \"1982-10-23 00:00:00\",\n    \"1982-10-24 00:00:00\",\n    \"1982-10-30 00:00:00\",\n    \"1982-10-31 00:00:00\",\n    \"1982-11-06 00:00:00\",\n    \"1982-11-07 00:00:00\",\n    \"1982-11-13 00:00:00\",\n    \"1982-11-14 00:00:00\",\n    \"1982-11-20 00:00:00\",\n    \"1982-11-21 00:00:00\",\n    \"1982-11-27 00:00:00\",\n    \"1982-11-28 00:00:00\",\n    \"1982-12-04 00:00:00\",\n    \"1982-12-05 00:00:00\",\n    \"1982-12-11 00:00:00\",\n    \"1982-12-12 00:00:00\",\n    \"1982-12-18 00:00:00\",\n    \"1982-12-19 00:00:00\",\n    \"1982-12-25 00:00:00\",\n    \"1982-12-26 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-01-02 00:00:00\",\n    \"1983-01-08 00:00:00\",\n    \"1983-01-09 00:00:00\",\n    \"1983-01-15 00:00:00\",\n    \"1983-01-16 00:00:00\",\n    \"1983-01-22 00:00:00\",\n    \"1983-01-23 00:00:00\",\n    \"1983-01-29 00:00:00\",\n    \"1983-01-30 00:00:00\",\n    \"1983-02-05 00:00:00\",\n    \"1983-02-06 00:00:00\",\n    \"1983-02-12 00:00:00\",\n    \"1983-02-13 00:00:00\",\n    \"1983-02-19 00:00:00\",\n    \"1983-02-20 00:00:00\",\n    \"1983-02-26 00:00:00\",\n    \"1983-02-27 00:00:00\",\n    \"1983-03-05 00:00:00\",\n    \"1983-03-06 00:00:00\",\n    \"1983-03-12 00:00:00\",\n    \"1983-03-13 00:00:00\",\n    \"1983-03-19 00:00:00\",\n    \"1983-03-20 00:00:00\",\n    \"1983-03-26 00:00:00\",\n    \"1983-03-27 00:00:00\",\n    \"1983-04-02 00:00:00\",\n    \"1983-04-03 00:00:00\",\n    \"1983-04-09 00:00:00\",\n    \"1983-04-10 00:00:00\",\n    \"1983-04-16 00:00:00\",\n    \"1983-04-17 00:00:00\",\n    \"1983-04-23 00:00:00\",\n    \"1983-04-24 00:00:00\",\n    \"1983-04-30 00:00:00\",\n    \"1983-05-01 00:00:00\",\n    \"1983-05-07 00:00:00\",\n    \"1983-05-08 00:00:00\",\n    \"1983-05-14 00:00:00\",\n    \"1983-05-15 00:00:00\",\n    \"1983-05-21 00:00:00\",\n    \"1983-05-22 00:00:00\",\n    \"1983-05-28 00:00:00\",\n    \"1983-05-29 00:00:00\",\n    \"1983-06-04 00:00:00\",\n    \"1983-06-05 00:00:00\",\n    \"1983-06-11 00:00:00\",\n    \"1983-06-12 00:00:00\",\n    \"1983-06-18 00:00:00\",\n    \"1983-06-19 00:00:00\",\n    \"1983-06-25 00:00:00\",\n    \"1983-06-26 00:00:00\",\n    \"1983-07-02 00:00:00\",\n    \"1983-07-03 00:00:00\",\n    \"1983-07-09 00:00:00\",\n    \"1983-07-10 00:00:00\",\n    \"1983-07-16 00:00:00\",\n    \"1983-07-17 00:00:00\",\n    \"1983-07-23 00:00:00\",\n    \"1983-07-24 00:00:00\",\n    \"1983-07-30 00:00:00\",\n    \"1983-07-31 00:00:00\",\n    \"1983-08-06 00:00:00\",\n    \"1983-08-07 00:00:00\",\n    \"1983-08-13 00:00:00\",\n    \"1983-08-14 00:00:00\",\n    \"1983-08-20 00:00:00\",\n    \"1983-08-21 00:00:00\",\n    \"1983-08-27 00:00:00\",\n    \"1983-08-28 00:00:00\",\n    \"1983-09-03 00:00:00\",\n    \"1983-09-04 00:00:00\",\n    \"1983-09-10 00:00:00\",\n    \"1983-09-11 00:00:00\",\n    \"1983-09-17 00:00:00\",\n    \"1983-09-18 00:00:00\",\n    \"1983-09-24 00:00:00\",\n    \"1983-09-25 00:00:00\",\n    \"1983-10-01 00:00:00\",\n    \"1983-10-02 00:00:00\",\n    \"1983-10-08 00:00:00\",\n    \"1983-10-09 00:00:00\",\n    \"1983-10-15 00:00:00\",\n    \"1983-10-16 00:00:00\",\n    \"1983-10-22 00:00:00\",\n    \"1983-10-23 00:00:00\",\n    \"1983-10-29 00:00:00\",\n    \"1983-10-30 00:00:00\",\n    \"1983-11-05 00:00:00\",\n    \"1983-11-06 00:00:00\",\n    \"1983-11-12 00:00:00\",\n    \"1983-11-13 00:00:00\",\n    \"1983-11-19 00:00:00\",\n    \"1983-11-20 00:00:00\",\n    \"1983-11-26 00:00:00\",\n    \"1983-11-27 00:00:00\",\n    \"1983-12-03 00:00:00\",\n    \"1983-12-04 00:00:00\",\n    \"1983-12-10 00:00:00\",\n    \"1983-12-11 00:00:00\",\n    \"1983-12-17 00:00:00\",\n    \"1983-12-18 00:00:00\",\n    \"1983-12-24 00:00:00\",\n    \"1983-12-25 00:00:00\",\n    \"1983-12-31 00:00:00\",\n    \"1984-01-01 00:00:00\",\n    \"1984-01-07 00:00:00\",\n    \"1984-01-08 00:00:00\",\n    \"1984-01-14 00:00:00\",\n    \"1984-01-15 00:00:00\",\n    \"1984-01-21 00:00:00\",\n    \"1984-01-22 00:00:00\",\n    \"1984-01-28 00:00:00\",\n    \"1984-01-29 00:00:00\",\n    \"1984-02-04 00:00:00\",\n    \"1984-02-05 00:00:00\",\n    \"1984-02-11 00:00:00\",\n    \"1984-02-12 00:00:00\",\n    \"1984-02-18 00:00:00\",\n    \"1984-02-19 00:00:00\",\n    \"1984-02-25 00:00:00\",\n    \"1984-02-26 00:00:00\",\n    \"1984-03-03 00:00:00\",\n    \"1984-03-04 00:00:00\",\n    \"1984-03-10 00:00:00\",\n    \"1984-03-11 00:00:00\",\n    \"1984-03-17 00:00:00\",\n    \"1984-03-18 00:00:00\",\n    \"1984-03-24 00:00:00\",\n    \"1984-03-25 00:00:00\",\n    \"1984-03-31 00:00:00\",\n    \"1984-04-01 00:00:00\",\n    \"1984-04-07 00:00:00\",\n    \"1984-04-08 00:00:00\",\n    \"1984-04-14 00:00:00\",\n    \"1984-04-15 00:00:00\",\n    \"1984-04-21 00:00:00\",\n    \"1984-04-22 00:00:00\",\n    \"1984-04-28 00:00:00\",\n    \"1984-04-29 00:00:00\",\n    \"1984-05-05 00:00:00\",\n    \"1984-05-06 00:00:00\",\n    \"1984-05-12 00:00:00\",\n    \"1984-05-13 00:00:00\",\n    \"1984-05-19 00:00:00\",\n    \"1984-05-20 00:00:00\",\n    \"1984-05-26 00:00:00\",\n    \"1984-05-27 00:00:00\",\n    \"1984-06-02 00:00:00\",\n    \"1984-06-03 00:00:00\",\n    \"1984-06-09 00:00:00\",\n    \"1984-06-10 00:00:00\",\n    \"1984-06-16 00:00:00\",\n    \"1984-06-17 00:00:00\",\n    \"1984-06-23 00:00:00\",\n    \"1984-06-24 00:00:00\",\n    \"1984-06-30 00:00:00\",\n    \"1984-07-01 00:00:00\",\n    \"1984-07-07 00:00:00\",\n    \"1984-07-08 00:00:00\",\n    \"1984-07-14 00:00:00\",\n    \"1984-07-15 00:00:00\",\n    \"1984-07-21 00:00:00\",\n    \"1984-07-22 00:00:00\",\n    \"1984-07-28 00:00:00\",\n    \"1984-07-29 00:00:00\",\n    \"1984-08-04 00:00:00\",\n    \"1984-08-05 00:00:00\",\n    \"1984-08-11 00:00:00\",\n    \"1984-08-12 00:00:00\",\n    \"1984-08-18 00:00:00\",\n    \"1984-08-19 00:00:00\",\n    \"1984-08-25 00:00:00\",\n    \"1984-08-26 00:00:00\",\n    \"1984-09-01 00:00:00\",\n    \"1984-09-02 00:00:00\",\n    \"1984-09-08 00:00:00\",\n    \"1984-09-09 00:00:00\",\n    \"1984-09-15 00:00:00\",\n    \"1984-09-16 00:00:00\",\n    \"1984-09-22 00:00:00\",\n    \"1984-09-23 00:00:00\",\n    \"1984-09-29 00:00:00\",\n    \"1984-09-30 00:00:00\",\n    \"1984-10-06 00:00:00\",\n    \"1984-10-07 00:00:00\",\n    \"1984-10-13 00:00:00\",\n    \"1984-10-14 00:00:00\",\n    \"1984-10-20 00:00:00\",\n    \"1984-10-21 00:00:00\",\n    \"1984-10-27 00:00:00\",\n    \"1984-10-28 00:00:00\",\n    \"1984-11-03 00:00:00\",\n    \"1984-11-04 00:00:00\",\n    \"1984-11-10 00:00:00\",\n    \"1984-11-11 00:00:00\",\n    \"1984-11-17 00:00:00\",\n    \"1984-11-18 00:00:00\",\n    \"1984-11-24 00:00:00\",\n    \"1984-11-25 00:00:00\",\n    \"1984-12-01 00:00:00\",\n    \"1984-12-02 00:00:00\",\n    \"1984-12-08 00:00:00\",\n    \"1984-12-09 00:00:00\",\n    \"1984-12-15 00:00:00\",\n    \"1984-12-16 00:00:00\",\n    \"1984-12-22 00:00:00\",\n    \"1984-12-23 00:00:00\",\n    \"1984-12-29 00:00:00\",\n    \"1984-12-30 00:00:00\",\n    \"1985-01-05 00:00:00\",\n    \"1985-01-06 00:00:00\",\n    \"1985-01-12 00:00:00\",\n    \"1985-01-13 00:00:00\",\n    \"1985-01-19 00:00:00\",\n    \"1985-01-20 00:00:00\",\n    \"1985-01-26 00:00:00\",\n    \"1985-01-27 00:00:00\",\n    \"1985-02-02 00:00:00\",\n    \"1985-02-03 00:00:00\",\n    \"1985-02-09 00:00:00\",\n    \"1985-02-10 00:00:00\",\n    \"1985-02-16 00:00:00\",\n    \"1985-02-17 00:00:00\",\n    \"1985-02-23 00:00:00\",\n    \"1985-02-24 00:00:00\",\n    \"1985-03-02 00:00:00\",\n    \"1985-03-03 00:00:00\",\n    \"1985-03-09 00:00:00\",\n    \"1985-03-10 00:00:00\",\n    \"1985-03-16 00:00:00\",\n    \"1985-03-17 00:00:00\",\n    \"1985-03-23 00:00:00\",\n    \"1985-03-24 00:00:00\",\n    \"1985-03-30 00:00:00\",\n    \"1985-03-31 00:00:00\",\n    \"1985-04-06 00:00:00\",\n    \"1985-04-07 00:00:00\",\n    \"1985-04-13 00:00:00\",\n    \"1985-04-14 00:00:00\",\n    \"1985-04-20 00:00:00\",\n    \"1985-04-21 00:00:00\",\n    \"1985-04-27 00:00:00\",\n    \"1985-04-28 00:00:00\",\n    \"1985-05-04 00:00:00\",\n    \"1985-05-05 00:00:00\",\n    \"1985-05-11 00:00:00\",\n    \"1985-05-12 00:00:00\",\n    \"1985-05-18 00:00:00\",\n    \"1985-05-19 00:00:00\",\n    \"1985-05-25 00:00:00\",\n    \"1985-05-26 00:00:00\",\n    \"1985-06-01 00:00:00\",\n    \"1985-06-02 00:00:00\",\n    \"1985-06-08 00:00:00\",\n    \"1985-06-09 00:00:00\",\n    \"1985-06-15 00:00:00\",\n    \"1985-06-16 00:00:00\",\n    \"1985-06-22 00:00:00\",\n    \"1985-06-23 00:00:00\",\n    \"1985-06-29 00:00:00\",\n    \"1985-06-30 00:00:00\",\n    \"1985-07-06 00:00:00\",\n    \"1985-07-07 00:00:00\",\n    \"1985-07-13 00:00:00\",\n    \"1985-07-14 00:00:00\",\n    \"1985-07-20 00:00:00\",\n    \"1985-07-21 00:00:00\",\n    \"1985-07-27 00:00:00\",\n    \"1985-07-28 00:00:00\",\n    \"1985-08-03 00:00:00\",\n    \"1985-08-04 00:00:00\",\n    \"1985-08-10 00:00:00\",\n    \"1985-08-11 00:00:00\",\n    \"1985-08-17 00:00:00\",\n    \"1985-08-18 00:00:00\",\n    \"1985-08-24 00:00:00\",\n    \"1985-08-25 00:00:00\",\n    \"1985-08-31 00:00:00\",\n    \"1985-09-01 00:00:00\",\n    \"1985-09-07 00:00:00\",\n    \"1985-09-08 00:00:00\",\n    \"1985-09-14 00:00:00\",\n    \"1985-09-15 00:00:00\",\n    \"1985-09-21 00:00:00\",\n    \"1985-09-22 00:00:00\",\n    \"1985-09-28 00:00:00\",\n    \"1985-09-29 00:00:00\",\n    \"1985-10-05 00:00:00\",\n    \"1985-10-06 00:00:00\",\n    \"1985-10-12 00:00:00\",\n    \"1985-10-13 00:00:00\",\n    \"1985-10-19 00:00:00\",\n    \"1985-10-20 00:00:00\",\n    \"1985-10-26 00:00:00\",\n    \"1985-10-27 00:00:00\",\n    \"1985-11-02 00:00:00\",\n    \"1985-11-03 00:00:00\",\n    \"1985-11-09 00:00:00\",\n    \"1985-11-10 00:00:00\",\n    \"1985-11-16 00:00:00\",\n    \"1985-11-17 00:00:00\",\n    \"1985-11-23 00:00:00\",\n    \"1985-11-24 00:00:00\",\n    \"1985-11-30 00:00:00\",\n    \"1985-12-01 00:00:00\",\n    \"1985-12-07 00:00:00\",\n    \"1985-12-08 00:00:00\",\n    \"1985-12-14 00:00:00\",\n    \"1985-12-15 00:00:00\",\n    \"1985-12-21 00:00:00\",\n    \"1985-12-22 00:00:00\",\n    \"1985-12-28 00:00:00\",\n    \"1985-12-29 00:00:00\",\n    \"1986-01-04 00:00:00\",\n    \"1986-01-05 00:00:00\",\n    \"1986-01-11 00:00:00\",\n    \"1986-01-12 00:00:00\",\n    \"1986-01-18 00:00:00\",\n    \"1986-01-19 00:00:00\",\n    \"1986-01-25 00:00:00\",\n    \"1986-01-26 00:00:00\",\n    \"1986-02-01 00:00:00\",\n    \"1986-02-02 00:00:00\",\n    \"1986-02-08 00:00:00\",\n    \"1986-02-09 00:00:00\",\n    \"1986-02-15 00:00:00\",\n    \"1986-02-16 00:00:00\",\n    \"1986-02-22 00:00:00\",\n    \"1986-02-23 00:00:00\",\n    \"1986-03-01 00:00:00\",\n    \"1986-03-02 00:00:00\",\n    \"1986-03-08 00:00:00\",\n    \"1986-03-09 00:00:00\",\n    \"1986-03-15 00:00:00\",\n    \"1986-03-16 00:00:00\",\n    \"1986-03-22 00:00:00\",\n    \"1986-03-23 00:00:00\",\n    \"1986-03-29 00:00:00\",\n    \"1986-03-30 00:00:00\",\n    \"1986-04-05 00:00:00\",\n    \"1986-04-06 00:00:00\",\n    \"1986-04-12 00:00:00\",\n    \"1986-04-13 00:00:00\",\n    \"1986-04-19 00:00:00\",\n    \"1986-04-20 00:00:00\",\n    \"1986-04-26 00:00:00\",\n    \"1986-04-27 00:00:00\",\n    \"1986-05-03 00:00:00\",\n    \"1986-05-04 00:00:00\",\n    \"1986-05-10 00:00:00\",\n    \"1986-05-11 00:00:00\",\n    \"1986-05-17 00:00:00\",\n    \"1986-05-18 00:00:00\",\n    \"1986-05-24 00:00:00\",\n    \"1986-05-25 00:00:00\",\n    \"1986-05-31 00:00:00\",\n    \"1986-06-01 00:00:00\",\n    \"1986-06-07 00:00:00\",\n    \"1986-06-08 00:00:00\",\n    \"1986-06-14 00:00:00\",\n    \"1986-06-15 00:00:00\",\n    \"1986-06-21 00:00:00\",\n    \"1986-06-22 00:00:00\",\n    \"1986-06-28 00:00:00\",\n    \"1986-06-29 00:00:00\",\n    \"1986-07-05 00:00:00\",\n    \"1986-07-06 00:00:00\",\n    \"1986-07-12 00:00:00\",\n    \"1986-07-13 00:00:00\",\n    \"1986-07-19 00:00:00\",\n    \"1986-07-20 00:00:00\",\n    \"1986-07-26 00:00:00\",\n    \"1986-07-27 00:00:00\",\n    \"1986-08-02 00:00:00\",\n    \"1986-08-03 00:00:00\",\n    \"1986-08-09 00:00:00\",\n    \"1986-08-10 00:00:00\",\n    \"1986-08-16 00:00:00\",\n    \"1986-08-17 00:00:00\",\n    \"1986-08-23 00:00:00\",\n    \"1986-08-24 00:00:00\",\n    \"1986-08-30 00:00:00\",\n    \"1986-08-31 00:00:00\",\n    \"1986-09-06 00:00:00\",\n    \"1986-09-07 00:00:00\",\n    \"1986-09-13 00:00:00\",\n    \"1986-09-14 00:00:00\",\n    \"1986-09-20 00:00:00\",\n    \"1986-09-21 00:00:00\",\n    \"1986-09-27 00:00:00\",\n    \"1986-09-28 00:00:00\",\n    \"1986-10-04 00:00:00\",\n    \"1986-10-05 00:00:00\",\n    \"1986-10-11 00:00:00\",\n    \"1986-10-12 00:00:00\",\n    \"1986-10-18 00:00:00\",\n    \"1986-10-19 00:00:00\",\n    \"1986-10-25 00:00:00\",\n    \"1986-10-26 00:00:00\",\n    \"1986-11-01 00:00:00\",\n    \"1986-11-02 00:00:00\",\n    \"1986-11-08 00:00:00\",\n    \"1986-11-09 00:00:00\",\n    \"1986-11-15 00:00:00\",\n    \"1986-11-16 00:00:00\",\n    \"1986-11-22 00:00:00\",\n    \"1986-11-23 00:00:00\",\n    \"1986-11-29 00:00:00\",\n    \"1986-11-30 00:00:00\",\n    \"1986-12-06 00:00:00\",\n    \"1986-12-07 00:00:00\",\n    \"1986-12-13 00:00:00\",\n    \"1986-12-14 00:00:00\",\n    \"1986-12-20 00:00:00\",\n    \"1986-12-21 00:00:00\",\n    \"1986-12-27 00:00:00\",\n    \"1986-12-28 00:00:00\",\n    \"1987-01-03 00:00:00\",\n    \"1987-01-04 00:00:00\",\n    \"1987-01-10 00:00:00\",\n    \"1987-01-11 00:00:00\",\n    \"1987-01-17 00:00:00\",\n    \"1987-01-18 00:00:00\",\n    \"1987-01-24 00:00:00\",\n    \"1987-01-25 00:00:00\",\n    \"1987-01-31 00:00:00\",\n    \"1987-02-01 00:00:00\",\n    \"1987-02-07 00:00:00\",\n    \"1987-02-08 00:00:00\",\n    \"1987-02-14 00:00:00\",\n    \"1987-02-15 00:00:00\",\n    \"1987-02-21 00:00:00\",\n    \"1987-02-22 00:00:00\",\n    \"1987-02-28 00:00:00\",\n    \"1987-03-01 00:00:00\",\n    \"1987-03-07 00:00:00\",\n    \"1987-03-08 00:00:00\",\n    \"1987-03-14 00:00:00\",\n    \"1987-03-15 00:00:00\",\n    \"1987-03-21 00:00:00\",\n    \"1987-03-22 00:00:00\",\n    \"1987-03-28 00:00:00\",\n    \"1987-03-29 00:00:00\",\n    \"1987-04-04 00:00:00\",\n    \"1987-04-05 00:00:00\",\n    \"1987-04-11 00:00:00\",\n    \"1987-04-12 00:00:00\",\n    \"1987-04-18 00:00:00\",\n    \"1987-04-19 00:00:00\",\n    \"1987-04-25 00:00:00\",\n    \"1987-04-26 00:00:00\",\n    \"1987-05-02 00:00:00\",\n    \"1987-05-03 00:00:00\",\n    \"1987-05-09 00:00:00\",\n    \"1987-05-10 00:00:00\",\n    \"1987-05-16 00:00:00\",\n    \"1987-05-17 00:00:00\",\n    \"1987-05-23 00:00:00\",\n    \"1987-05-24 00:00:00\",\n    \"1987-05-30 00:00:00\",\n    \"1987-05-31 00:00:00\",\n    \"1987-06-06 00:00:00\",\n    \"1987-06-07 00:00:00\",\n    \"1987-06-13 00:00:00\",\n    \"1987-06-14 00:00:00\",\n    \"1987-06-20 00:00:00\",\n    \"1987-06-21 00:00:00\",\n    \"1987-06-27 00:00:00\",\n    \"1987-06-28 00:00:00\",\n    \"1987-07-04 00:00:00\",\n    \"1987-07-05 00:00:00\",\n    \"1987-07-11 00:00:00\",\n    \"1987-07-12 00:00:00\",\n    \"1987-07-18 00:00:00\",\n    \"1987-07-19 00:00:00\",\n    \"1987-07-25 00:00:00\",\n    \"1987-07-26 00:00:00\",\n    \"1987-08-01 00:00:00\",\n    \"1987-08-02 00:00:00\",\n    \"1987-08-08 00:00:00\",\n    \"1987-08-09 00:00:00\",\n    \"1987-08-15 00:00:00\",\n    \"1987-08-16 00:00:00\",\n    \"1987-08-22 00:00:00\",\n    \"1987-08-23 00:00:00\",\n    \"1987-08-29 00:00:00\",\n    \"1987-08-30 00:00:00\",\n    \"1987-09-05 00:00:00\",\n    \"1987-09-06 00:00:00\",\n    \"1987-09-12 00:00:00\",\n    \"1987-09-13 00:00:00\",\n    \"1987-09-19 00:00:00\",\n    \"1987-09-20 00:00:00\",\n    \"1987-09-26 00:00:00\",\n    \"1987-09-27 00:00:00\",\n    \"1987-10-03 00:00:00\",\n    \"1987-10-04 00:00:00\",\n    \"1987-10-10 00:00:00\",\n    \"1987-10-11 00:00:00\",\n    \"1987-10-17 00:00:00\",\n    \"1987-10-18 00:00:00\",\n    \"1987-10-24 00:00:00\",\n    \"1987-10-25 00:00:00\",\n    \"1987-10-31 00:00:00\",\n    \"1987-11-01 00:00:00\",\n    \"1987-11-07 00:00:00\",\n    \"1987-11-08 00:00:00\",\n    \"1987-11-14 00:00:00\",\n    \"1987-11-15 00:00:00\",\n    \"1987-11-21 00:00:00\",\n    \"1987-11-22 00:00:00\",\n    \"1987-11-28 00:00:00\",\n    \"1987-11-29 00:00:00\",\n    \"1987-12-05 00:00:00\",\n    \"1987-12-06 00:00:00\",\n    \"1987-12-12 00:00:00\",\n    \"1987-12-13 00:00:00\",\n    \"1987-12-19 00:00:00\",\n    \"1987-12-20 00:00:00\",\n    \"1987-12-26 00:00:00\",\n    \"1987-12-27 00:00:00\",\n    \"1988-01-02 00:00:00\",\n    \"1988-01-03 00:00:00\",\n    \"1988-01-09 00:00:00\",\n    \"1988-01-10 00:00:00\",\n    \"1988-01-16 00:00:00\",\n    \"1988-01-17 00:00:00\",\n    \"1988-01-23 00:00:00\",\n    \"1988-01-24 00:00:00\",\n    \"1988-01-30 00:00:00\",\n    \"1988-01-31 00:00:00\",\n    \"1988-02-06 00:00:00\",\n    \"1988-02-07 00:00:00\",\n    \"1988-02-13 00:00:00\",\n    \"1988-02-14 00:00:00\",\n    \"1988-02-20 00:00:00\",\n    \"1988-02-21 00:00:00\",\n    \"1988-02-27 00:00:00\",\n    \"1988-02-28 00:00:00\",\n    \"1988-03-05 00:00:00\",\n    \"1988-03-06 00:00:00\",\n    \"1988-03-12 00:00:00\",\n    \"1988-03-13 00:00:00\",\n    \"1988-03-19 00:00:00\",\n    \"1988-03-20 00:00:00\",\n    \"1988-03-26 00:00:00\",\n    \"1988-03-27 00:00:00\",\n    \"1988-04-02 00:00:00\",\n    \"1988-04-03 00:00:00\",\n    \"1988-04-09 00:00:00\",\n    \"1988-04-10 00:00:00\",\n    \"1988-04-16 00:00:00\",\n    \"1988-04-17 00:00:00\",\n    \"1988-04-23 00:00:00\",\n    \"1988-04-24 00:00:00\",\n    \"1988-04-30 00:00:00\",\n    \"1988-05-01 00:00:00\",\n    \"1988-05-07 00:00:00\",\n    \"1988-05-08 00:00:00\",\n    \"1988-05-14 00:00:00\",\n    \"1988-05-15 00:00:00\",\n    \"1988-05-21 00:00:00\",\n    \"1988-05-22 00:00:00\",\n    \"1988-05-28 00:00:00\",\n    \"1988-05-29 00:00:00\",\n    \"1988-06-04 00:00:00\",\n    \"1988-06-05 00:00:00\",\n    \"1988-06-11 00:00:00\",\n    \"1988-06-12 00:00:00\",\n    \"1988-06-18 00:00:00\",\n    \"1988-06-19 00:00:00\",\n    \"1988-06-25 00:00:00\",\n    \"1988-06-26 00:00:00\",\n    \"1988-07-02 00:00:00\",\n    \"1988-07-03 00:00:00\",\n    \"1988-07-09 00:00:00\",\n    \"1988-07-10 00:00:00\",\n    \"1988-07-16 00:00:00\",\n    \"1988-07-17 00:00:00\",\n    \"1988-07-23 00:00:00\",\n    \"1988-07-24 00:00:00\",\n    \"1988-07-30 00:00:00\",\n    \"1988-07-31 00:00:00\",\n    \"1988-08-06 00:00:00\",\n    \"1988-08-07 00:00:00\",\n    \"1988-08-13 00:00:00\",\n    \"1988-08-14 00:00:00\",\n    \"1988-08-20 00:00:00\",\n    \"1988-08-21 00:00:00\",\n    \"1988-08-27 00:00:00\",\n    \"1988-08-28 00:00:00\",\n    \"1988-09-03 00:00:00\",\n    \"1988-09-04 00:00:00\",\n    \"1988-09-10 00:00:00\",\n    \"1988-09-11 00:00:00\",\n    \"1988-09-17 00:00:00\",\n    \"1988-09-18 00:00:00\",\n    \"1988-09-24 00:00:00\",\n    \"1988-09-25 00:00:00\",\n    \"1988-10-01 00:00:00\",\n    \"1988-10-02 00:00:00\",\n    \"1988-10-08 00:00:00\",\n    \"1988-10-09 00:00:00\",\n    \"1988-10-15 00:00:00\",\n    \"1988-10-16 00:00:00\",\n    \"1988-10-22 00:00:00\",\n    \"1988-10-23 00:00:00\",\n    \"1988-10-29 00:00:00\",\n    \"1988-10-30 00:00:00\",\n    \"1988-11-05 00:00:00\",\n    \"1988-11-06 00:00:00\",\n    \"1988-11-12 00:00:00\",\n    \"1988-11-13 00:00:00\",\n    \"1988-11-19 00:00:00\",\n    \"1988-11-20 00:00:00\",\n    \"1988-11-26 00:00:00\",\n    \"1988-11-27 00:00:00\",\n    \"1988-12-03 00:00:00\",\n    \"1988-12-04 00:00:00\",\n    \"1988-12-10 00:00:00\",\n    \"1988-12-11 00:00:00\",\n    \"1988-12-17 00:00:00\",\n    \"1988-12-18 00:00:00\",\n    \"1988-12-24 00:00:00\",\n    \"1988-12-25 00:00:00\",\n    \"1988-12-31 00:00:00\",\n    \"1989-01-01 00:00:00\",\n    \"1989-01-07 00:00:00\",\n    \"1989-01-08 00:00:00\",\n    \"1989-01-14 00:00:00\",\n    \"1989-01-15 00:00:00\",\n    \"1989-01-21 00:00:00\",\n    \"1989-01-22 00:00:00\",\n    \"1989-01-28 00:00:00\",\n    \"1989-01-29 00:00:00\",\n    \"1989-02-04 00:00:00\",\n    \"1989-02-05 00:00:00\",\n    \"1989-02-11 00:00:00\",\n    \"1989-02-12 00:00:00\",\n    \"1989-02-18 00:00:00\",\n    \"1989-02-19 00:00:00\",\n    \"1989-02-25 00:00:00\",\n    \"1989-02-26 00:00:00\",\n    \"1989-03-04 00:00:00\",\n    \"1989-03-05 00:00:00\",\n    \"1989-03-11 00:00:00\",\n    \"1989-03-12 00:00:00\",\n    \"1989-03-18 00:00:00\",\n    \"1989-03-19 00:00:00\",\n    \"1989-03-25 00:00:00\",\n    \"1989-03-26 00:00:00\",\n    \"1989-04-01 00:00:00\",\n    \"1989-04-02 00:00:00\",\n    \"1989-04-08 00:00:00\",\n    \"1989-04-09 00:00:00\",\n    \"1989-04-15 00:00:00\",\n    \"1989-04-16 00:00:00\",\n    \"1989-04-22 00:00:00\",\n    \"1989-04-23 00:00:00\",\n    \"1989-04-29 00:00:00\",\n    \"1989-04-30 00:00:00\",\n    \"1989-05-06 00:00:00\",\n    \"1989-05-07 00:00:00\",\n    \"1989-05-13 00:00:00\",\n    \"1989-05-14 00:00:00\",\n    \"1989-05-20 00:00:00\",\n    \"1989-05-21 00:00:00\",\n    \"1989-05-27 00:00:00\",\n    \"1989-05-28 00:00:00\",\n    \"1989-06-03 00:00:00\",\n    \"1989-06-04 00:00:00\",\n    \"1989-06-10 00:00:00\",\n    \"1989-06-11 00:00:00\",\n    \"1989-06-17 00:00:00\",\n    \"1989-06-18 00:00:00\",\n    \"1989-06-24 00:00:00\",\n    \"1989-06-25 00:00:00\",\n    \"1989-07-01 00:00:00\",\n    \"1989-07-02 00:00:00\",\n    \"1989-07-08 00:00:00\",\n    \"1989-07-09 00:00:00\",\n    \"1989-07-15 00:00:00\",\n    \"1989-07-16 00:00:00\",\n    \"1989-07-22 00:00:00\",\n    \"1989-07-23 00:00:00\",\n    \"1989-07-29 00:00:00\",\n    \"1989-07-30 00:00:00\",\n    \"1989-08-05 00:00:00\",\n    \"1989-08-06 00:00:00\",\n    \"1989-08-12 00:00:00\",\n    \"1989-08-13 00:00:00\",\n    \"1989-08-19 00:00:00\",\n    \"1989-08-20 00:00:00\",\n    \"1989-08-26 00:00:00\",\n    \"1989-08-27 00:00:00\",\n    \"1989-09-02 00:00:00\",\n    \"1989-09-03 00:00:00\",\n    \"1989-09-09 00:00:00\",\n    \"1989-09-10 00:00:00\",\n    \"1989-09-16 00:00:00\",\n    \"1989-09-17 00:00:00\",\n    \"1989-09-23 00:00:00\",\n    \"1989-09-24 00:00:00\",\n    \"1989-09-30 00:00:00\",\n    \"1989-10-01 00:00:00\",\n    \"1989-10-07 00:00:00\",\n    \"1989-10-08 00:00:00\",\n    \"1989-10-14 00:00:00\",\n    \"1989-10-15 00:00:00\",\n    \"1989-10-21 00:00:00\",\n    \"1989-10-22 00:00:00\",\n    \"1989-10-28 00:00:00\",\n    \"1989-10-29 00:00:00\",\n    \"1989-11-04 00:00:00\",\n    \"1989-11-05 00:00:00\",\n    \"1989-11-11 00:00:00\",\n    \"1989-11-12 00:00:00\",\n    \"1989-11-18 00:00:00\",\n    \"1989-11-19 00:00:00\",\n    \"1989-11-25 00:00:00\",\n    \"1989-11-26 00:00:00\",\n    \"1989-12-02 00:00:00\",\n    \"1989-12-03 00:00:00\",\n    \"1989-12-09 00:00:00\",\n    \"1989-12-10 00:00:00\",\n    \"1989-12-16 00:00:00\",\n    \"1989-12-17 00:00:00\",\n    \"1989-12-23 00:00:00\",\n    \"1989-12-24 00:00:00\",\n    \"1989-12-30 00:00:00\",\n    \"1989-12-31 00:00:00\",\n    \"1990-01-06 00:00:00\",\n    \"1990-01-07 00:00:00\",\n    \"1990-01-13 00:00:00\",\n    \"1990-01-14 00:00:00\",\n    \"1990-01-20 00:00:00\",\n    \"1990-01-21 00:00:00\",\n    \"1990-01-27 00:00:00\",\n    \"1990-01-28 00:00:00\",\n    \"1990-02-03 00:00:00\",\n    \"1990-02-04 00:00:00\",\n    \"1990-02-10 00:00:00\",\n    \"1990-02-11 00:00:00\",\n    \"1990-02-17 00:00:00\",\n    \"1990-02-18 00:00:00\",\n    \"1990-02-24 00:00:00\",\n    \"1990-02-25 00:00:00\",\n    \"1990-03-03 00:00:00\",\n    \"1990-03-04 00:00:00\",\n    \"1990-03-10 00:00:00\",\n    \"1990-03-11 00:00:00\",\n    \"1990-03-17 00:00:00\",\n    \"1990-03-18 00:00:00\",\n    \"1990-03-24 00:00:00\",\n    \"1990-03-25 00:00:00\",\n    \"1990-03-31 00:00:00\",\n    \"1990-04-01 00:00:00\",\n    \"1990-04-07 00:00:00\",\n    \"1990-04-08 00:00:00\",\n    \"1990-04-14 00:00:00\",\n    \"1990-04-15 00:00:00\",\n    \"1990-04-21 00:00:00\",\n    \"1990-04-22 00:00:00\",\n    \"1990-04-28 00:00:00\",\n    \"1990-04-29 00:00:00\",\n    \"1990-05-05 00:00:00\",\n    \"1990-05-06 00:00:00\",\n    \"1990-05-12 00:00:00\",\n    \"1990-05-13 00:00:00\",\n    \"1990-05-19 00:00:00\",\n    \"1990-05-20 00:00:00\",\n    \"1990-05-26 00:00:00\",\n    \"1990-05-27 00:00:00\",\n    \"1990-06-02 00:00:00\",\n    \"1990-06-03 00:00:00\",\n    \"1990-06-09 00:00:00\",\n    \"1990-06-10 00:00:00\",\n    \"1990-06-16 00:00:00\",\n    \"1990-06-17 00:00:00\",\n    \"1990-06-23 00:00:00\",\n    \"1990-06-24 00:00:00\",\n    \"1990-06-30 00:00:00\",\n    \"1990-07-01 00:00:00\",\n    \"1990-07-07 00:00:00\",\n    \"1990-07-08 00:00:00\",\n    \"1990-07-14 00:00:00\",\n    \"1990-07-15 00:00:00\",\n    \"1990-07-21 00:00:00\",\n    \"1990-07-22 00:00:00\",\n    \"1990-07-28 00:00:00\",\n    \"1990-07-29 00:00:00\",\n    \"1990-08-04 00:00:00\",\n    \"1990-08-05 00:00:00\",\n    \"1990-08-11 00:00:00\",\n    \"1990-08-12 00:00:00\",\n    \"1990-08-18 00:00:00\",\n    \"1990-08-19 00:00:00\",\n    \"1990-08-25 00:00:00\",\n    \"1990-08-26 00:00:00\",\n    \"1990-09-01 00:00:00\",\n    \"1990-09-02 00:00:00\",\n    \"1990-09-08 00:00:00\",\n    \"1990-09-09 00:00:00\",\n    \"1990-09-15 00:00:00\",\n    \"1990-09-16 00:00:00\",\n    \"1990-09-22 00:00:00\",\n    \"1990-09-23 00:00:00\",\n    \"1990-09-29 00:00:00\",\n    \"1990-09-30 00:00:00\",\n    \"1990-10-06 00:00:00\",\n    \"1990-10-07 00:00:00\",\n    \"1990-10-13 00:00:00\",\n    \"1990-10-14 00:00:00\",\n    \"1990-10-20 00:00:00\",\n    \"1990-10-21 00:00:00\",\n    \"1990-10-27 00:00:00\",\n    \"1990-10-28 00:00:00\",\n    \"1990-11-03 00:00:00\",\n    \"1990-11-04 00:00:00\",\n    \"1990-11-10 00:00:00\",\n    \"1990-11-11 00:00:00\",\n    \"1990-11-17 00:00:00\",\n    \"1990-11-18 00:00:00\",\n    \"1990-11-24 00:00:00\",\n    \"1990-11-25 00:00:00\",\n    \"1990-12-01 00:00:00\",\n    \"1990-12-02 00:00:00\",\n    \"1990-12-08 00:00:00\",\n    \"1990-12-09 00:00:00\",\n    \"1990-12-15 00:00:00\",\n    \"1990-12-16 00:00:00\",\n    \"1990-12-22 00:00:00\",\n    \"1990-12-23 00:00:00\",\n    \"1990-12-29 00:00:00\",\n    \"1990-12-30 00:00:00\",\n    \"1991-01-05 00:00:00\",\n    \"1991-01-06 00:00:00\",\n    \"1991-01-12 00:00:00\",\n    \"1991-01-13 00:00:00\",\n    \"1991-01-19 00:00:00\",\n    \"1991-01-20 00:00:00\",\n    \"1991-01-26 00:00:00\",\n    \"1991-01-27 00:00:00\",\n    \"1991-02-02 00:00:00\",\n    \"1991-02-03 00:00:00\",\n    \"1991-02-09 00:00:00\",\n    \"1991-02-10 00:00:00\",\n    \"1991-02-16 00:00:00\",\n    \"1991-02-17 00:00:00\",\n    \"1991-02-23 00:00:00\",\n    \"1991-02-24 00:00:00\",\n    \"1991-03-02 00:00:00\",\n    \"1991-03-03 00:00:00\",\n    \"1991-03-09 00:00:00\",\n    \"1991-03-10 00:00:00\",\n    \"1991-03-16 00:00:00\",\n    \"1991-03-17 00:00:00\",\n    \"1991-03-23 00:00:00\",\n    \"1991-03-24 00:00:00\",\n    \"1991-03-30 00:00:00\",\n    \"1991-03-31 00:00:00\",\n    \"1991-04-06 00:00:00\",\n    \"1991-04-07 00:00:00\",\n    \"1991-04-13 00:00:00\",\n    \"1991-04-14 00:00:00\",\n    \"1991-04-20 00:00:00\",\n    \"1991-04-21 00:00:00\",\n    \"1991-04-27 00:00:00\",\n    \"1991-04-28 00:00:00\",\n    \"1991-05-04 00:00:00\",\n    \"1991-05-05 00:00:00\",\n    \"1991-05-11 00:00:00\",\n    \"1991-05-12 00:00:00\",\n    \"1991-05-18 00:00:00\",\n    \"1991-05-19 00:00:00\",\n    \"1991-05-25 00:00:00\",\n    \"1991-05-26 00:00:00\",\n    \"1991-06-01 00:00:00\",\n    \"1991-06-02 00:00:00\",\n    \"1991-06-08 00:00:00\",\n    \"1991-06-09 00:00:00\",\n    \"1991-06-15 00:00:00\",\n    \"1991-06-16 00:00:00\",\n    \"1991-06-22 00:00:00\",\n    \"1991-06-23 00:00:00\",\n    \"1991-06-29 00:00:00\",\n    \"1991-06-30 00:00:00\",\n    \"1991-07-06 00:00:00\",\n    \"1991-07-07 00:00:00\",\n    \"1991-07-13 00:00:00\",\n    \"1991-07-14 00:00:00\",\n    \"1991-07-20 00:00:00\",\n    \"1991-07-21 00:00:00\",\n    \"1991-07-27 00:00:00\",\n    \"1991-07-28 00:00:00\",\n    \"1991-08-03 00:00:00\",\n    \"1991-08-04 00:00:00\",\n    \"1991-08-10 00:00:00\",\n    \"1991-08-11 00:00:00\",\n    \"1991-08-17 00:00:00\",\n    \"1991-08-18 00:00:00\",\n    \"1991-08-24 00:00:00\",\n    \"1991-08-25 00:00:00\",\n    \"1991-08-31 00:00:00\",\n    \"1991-09-01 00:00:00\",\n    \"1991-09-07 00:00:00\",\n    \"1991-09-08 00:00:00\",\n    \"1991-09-14 00:00:00\",\n    \"1991-09-15 00:00:00\",\n    \"1991-09-21 00:00:00\",\n    \"1991-09-22 00:00:00\",\n    \"1991-09-28 00:00:00\",\n    \"1991-09-29 00:00:00\",\n    \"1991-10-05 00:00:00\",\n    \"1991-10-06 00:00:00\",\n    \"1991-10-12 00:00:00\",\n    \"1991-10-13 00:00:00\",\n    \"1991-10-19 00:00:00\",\n    \"1991-10-20 00:00:00\",\n    \"1991-10-26 00:00:00\",\n    \"1991-10-27 00:00:00\",\n    \"1991-11-02 00:00:00\",\n    \"1991-11-03 00:00:00\",\n    \"1991-11-09 00:00:00\",\n    \"1991-11-10 00:00:00\",\n    \"1991-11-16 00:00:00\",\n    \"1991-11-17 00:00:00\",\n    \"1991-11-23 00:00:00\",\n    \"1991-11-24 00:00:00\",\n    \"1991-11-30 00:00:00\",\n    \"1991-12-01 00:00:00\",\n    \"1991-12-07 00:00:00\",\n    \"1991-12-08 00:00:00\",\n    \"1991-12-14 00:00:00\",\n    \"1991-12-15 00:00:00\",\n    \"1991-12-21 00:00:00\",\n    \"1991-12-22 00:00:00\",\n    \"1991-12-28 00:00:00\",\n    \"1991-12-29 00:00:00\",\n    \"1992-01-04 00:00:00\",\n    \"1992-01-05 00:00:00\",\n    \"1992-01-11 00:00:00\",\n    \"1992-01-12 00:00:00\",\n    \"1992-01-18 00:00:00\",\n    \"1992-01-19 00:00:00\",\n    \"1992-01-25 00:00:00\",\n    \"1992-01-26 00:00:00\",\n    \"1992-02-01 00:00:00\",\n    \"1992-02-02 00:00:00\",\n    \"1992-02-08 00:00:00\",\n    \"1992-02-09 00:00:00\",\n    \"1992-02-15 00:00:00\",\n    \"1992-02-16 00:00:00\",\n    \"1992-02-22 00:00:00\",\n    \"1992-02-23 00:00:00\",\n    \"1992-02-29 00:00:00\",\n    \"1992-03-01 00:00:00\",\n    \"1992-03-07 00:00:00\",\n    \"1992-03-08 00:00:00\",\n    \"1992-03-14 00:00:00\",\n    \"1992-03-15 00:00:00\",\n    \"1992-03-21 00:00:00\",\n    \"1992-03-22 00:00:00\",\n    \"1992-03-28 00:00:00\",\n    \"1992-03-29 00:00:00\",\n    \"1992-04-04 00:00:00\",\n    \"1992-04-05 00:00:00\",\n    \"1992-04-11 00:00:00\",\n    \"1992-04-12 00:00:00\",\n    \"1992-04-18 00:00:00\",\n    \"1992-04-19 00:00:00\",\n    \"1992-04-25 00:00:00\",\n    \"1992-04-26 00:00:00\",\n    \"1992-05-02 00:00:00\",\n    \"1992-05-03 00:00:00\",\n    \"1992-05-09 00:00:00\",\n    \"1992-05-10 00:00:00\",\n    \"1992-05-16 00:00:00\",\n    \"1992-05-17 00:00:00\",\n    \"1992-05-23 00:00:00\",\n    \"1992-05-24 00:00:00\",\n    \"1992-05-30 00:00:00\",\n    \"1992-05-31 00:00:00\",\n    \"1992-06-06 00:00:00\",\n    \"1992-06-07 00:00:00\",\n    \"1992-06-13 00:00:00\",\n    \"1992-06-14 00:00:00\",\n    \"1992-06-20 00:00:00\",\n    \"1992-06-21 00:00:00\",\n    \"1992-06-27 00:00:00\",\n    \"1992-06-28 00:00:00\",\n    \"1992-07-04 00:00:00\",\n    \"1992-07-05 00:00:00\",\n    \"1992-07-11 00:00:00\",\n    \"1992-07-12 00:00:00\",\n    \"1992-07-18 00:00:00\",\n    \"1992-07-19 00:00:00\",\n    \"1992-07-25 00:00:00\",\n    \"1992-07-26 00:00:00\",\n    \"1992-08-01 00:00:00\",\n    \"1992-08-02 00:00:00\",\n    \"1992-08-08 00:00:00\",\n    \"1992-08-09 00:00:00\",\n    \"1992-08-15 00:00:00\",\n    \"1992-08-16 00:00:00\",\n    \"1992-08-22 00:00:00\",\n    \"1992-08-23 00:00:00\",\n    \"1992-08-29 00:00:00\",\n    \"1992-08-30 00:00:00\",\n    \"1992-09-05 00:00:00\",\n    \"1992-09-06 00:00:00\",\n    \"1992-09-12 00:00:00\",\n    \"1992-09-13 00:00:00\",\n    \"1992-09-19 00:00:00\",\n    \"1992-09-20 00:00:00\",\n    \"1992-09-26 00:00:00\",\n    \"1992-09-27 00:00:00\",\n    \"1992-10-03 00:00:00\",\n    \"1992-10-04 00:00:00\",\n    \"1992-10-10 00:00:00\",\n    \"1992-10-11 00:00:00\",\n    \"1992-10-17 00:00:00\",\n    \"1992-10-18 00:00:00\",\n    \"1992-10-24 00:00:00\",\n    \"1992-10-25 00:00:00\",\n    \"1992-10-31 00:00:00\",\n    \"1992-11-01 00:00:00\",\n    \"1992-11-07 00:00:00\",\n    \"1992-11-08 00:00:00\",\n    \"1992-11-14 00:00:00\",\n    \"1992-11-15 00:00:00\",\n    \"1992-11-21 00:00:00\",\n    \"1992-11-22 00:00:00\",\n    \"1992-11-28 00:00:00\",\n    \"1992-11-29 00:00:00\",\n    \"1992-12-05 00:00:00\",\n    \"1992-12-06 00:00:00\",\n    \"1992-12-12 00:00:00\",\n    \"1992-12-13 00:00:00\",\n    \"1992-12-19 00:00:00\",\n    \"1992-12-20 00:00:00\",\n    \"1992-12-26 00:00:00\",\n    \"1992-12-27 00:00:00\",\n    \"1993-01-02 00:00:00\",\n    \"1993-01-03 00:00:00\",\n    \"1993-01-09 00:00:00\",\n    \"1993-01-10 00:00:00\",\n    \"1993-01-16 00:00:00\",\n    \"1993-01-17 00:00:00\",\n    \"1993-01-23 00:00:00\",\n    \"1993-01-24 00:00:00\",\n    \"1993-01-30 00:00:00\",\n    \"1993-01-31 00:00:00\",\n    \"1993-02-06 00:00:00\",\n    \"1993-02-07 00:00:00\",\n    \"1993-02-13 00:00:00\",\n    \"1993-02-14 00:00:00\",\n    \"1993-02-20 00:00:00\",\n    \"1993-02-21 00:00:00\",\n    \"1993-02-27 00:00:00\",\n    \"1993-02-28 00:00:00\",\n    \"1993-03-06 00:00:00\",\n    \"1993-03-07 00:00:00\",\n    \"1993-03-13 00:00:00\",\n    \"1993-03-14 00:00:00\",\n    \"1993-03-20 00:00:00\",\n    \"1993-03-21 00:00:00\",\n    \"1993-03-27 00:00:00\",\n    \"1993-03-28 00:00:00\",\n    \"1993-04-03 00:00:00\",\n    \"1993-04-04 00:00:00\",\n    \"1993-04-10 00:00:00\",\n    \"1993-04-11 00:00:00\",\n    \"1993-04-17 00:00:00\",\n    \"1993-04-18 00:00:00\",\n    \"1993-04-24 00:00:00\",\n    \"1993-04-25 00:00:00\",\n    \"1993-05-01 00:00:00\",\n    \"1993-05-02 00:00:00\",\n    \"1993-05-08 00:00:00\",\n    \"1993-05-09 00:00:00\",\n    \"1993-05-15 00:00:00\",\n    \"1993-05-16 00:00:00\",\n    \"1993-05-22 00:00:00\",\n    \"1993-05-23 00:00:00\",\n    \"1993-05-29 00:00:00\",\n    \"1993-05-30 00:00:00\",\n    \"1993-06-05 00:00:00\",\n    \"1993-06-06 00:00:00\",\n    \"1993-06-12 00:00:00\",\n    \"1993-06-13 00:00:00\",\n    \"1993-06-19 00:00:00\",\n    \"1993-06-20 00:00:00\",\n    \"1993-06-26 00:00:00\",\n    \"1993-06-27 00:00:00\",\n    \"1993-07-03 00:00:00\",\n    \"1993-07-04 00:00:00\",\n    \"1993-07-10 00:00:00\",\n    \"1993-07-11 00:00:00\",\n    \"1993-07-17 00:00:00\",\n    \"1993-07-18 00:00:00\",\n    \"1993-07-24 00:00:00\",\n    \"1993-07-25 00:00:00\",\n    \"1993-07-31 00:00:00\",\n    \"1993-08-01 00:00:00\",\n    \"1993-08-07 00:00:00\",\n    \"1993-08-08 00:00:00\",\n    \"1993-08-14 00:00:00\",\n    \"1993-08-15 00:00:00\",\n    \"1993-08-21 00:00:00\",\n    \"1993-08-22 00:00:00\",\n    \"1993-08-28 00:00:00\",\n    \"1993-08-29 00:00:00\",\n    \"1993-09-04 00:00:00\",\n    \"1993-09-05 00:00:00\",\n    \"1993-09-11 00:00:00\",\n    \"1993-09-12 00:00:00\",\n    \"1993-09-18 00:00:00\",\n    \"1993-09-19 00:00:00\",\n    \"1993-09-25 00:00:00\",\n    \"1993-09-26 00:00:00\",\n    \"1993-10-02 00:00:00\",\n    \"1993-10-03 00:00:00\",\n    \"1993-10-09 00:00:00\",\n    \"1993-10-10 00:00:00\",\n    \"1993-10-16 00:00:00\",\n    \"1993-10-17 00:00:00\",\n    \"1993-10-23 00:00:00\",\n    \"1993-10-24 00:00:00\",\n    \"1993-10-30 00:00:00\",\n    \"1993-10-31 00:00:00\",\n    \"1993-11-06 00:00:00\",\n    \"1993-11-07 00:00:00\",\n    \"1993-11-13 00:00:00\",\n    \"1993-11-14 00:00:00\",\n    \"1993-11-20 00:00:00\",\n    \"1993-11-21 00:00:00\",\n    \"1993-11-27 00:00:00\",\n    \"1993-11-28 00:00:00\",\n    \"1993-12-04 00:00:00\",\n    \"1993-12-05 00:00:00\",\n    \"1993-12-11 00:00:00\",\n    \"1993-12-12 00:00:00\",\n    \"1993-12-18 00:00:00\",\n    \"1993-12-19 00:00:00\",\n    \"1993-12-25 00:00:00\",\n    \"1993-12-26 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-01-02 00:00:00\",\n    \"1994-01-08 00:00:00\",\n    \"1994-01-09 00:00:00\",\n    \"1994-01-15 00:00:00\",\n    \"1994-01-16 00:00:00\",\n    \"1994-01-22 00:00:00\",\n    \"1994-01-23 00:00:00\",\n    \"1994-01-29 00:00:00\",\n    \"1994-01-30 00:00:00\",\n    \"1994-02-05 00:00:00\",\n    \"1994-02-06 00:00:00\",\n    \"1994-02-12 00:00:00\",\n    \"1994-02-13 00:00:00\",\n    \"1994-02-19 00:00:00\",\n    \"1994-02-20 00:00:00\",\n    \"1994-02-26 00:00:00\",\n    \"1994-02-27 00:00:00\",\n    \"1994-03-05 00:00:00\",\n    \"1994-03-06 00:00:00\",\n    \"1994-03-12 00:00:00\",\n    \"1994-03-13 00:00:00\",\n    \"1994-03-19 00:00:00\",\n    \"1994-03-20 00:00:00\",\n    \"1994-03-26 00:00:00\",\n    \"1994-03-27 00:00:00\",\n    \"1994-04-02 00:00:00\",\n    \"1994-04-03 00:00:00\",\n    \"1994-04-09 00:00:00\",\n    \"1994-04-10 00:00:00\",\n    \"1994-04-16 00:00:00\",\n    \"1994-04-17 00:00:00\",\n    \"1994-04-23 00:00:00\",\n    \"1994-04-24 00:00:00\",\n    \"1994-04-30 00:00:00\",\n    \"1994-05-01 00:00:00\",\n    \"1994-05-07 00:00:00\",\n    \"1994-05-08 00:00:00\",\n    \"1994-05-14 00:00:00\",\n    \"1994-05-15 00:00:00\",\n    \"1994-05-21 00:00:00\",\n    \"1994-05-22 00:00:00\",\n    \"1994-05-28 00:00:00\",\n    \"1994-05-29 00:00:00\",\n    \"1994-06-04 00:00:00\",\n    \"1994-06-05 00:00:00\",\n    \"1994-06-11 00:00:00\",\n    \"1994-06-12 00:00:00\",\n    \"1994-06-18 00:00:00\",\n    \"1994-06-19 00:00:00\",\n    \"1994-06-25 00:00:00\",\n    \"1994-06-26 00:00:00\",\n    \"1994-07-02 00:00:00\",\n    \"1994-07-03 00:00:00\",\n    \"1994-07-09 00:00:00\",\n    \"1994-07-10 00:00:00\",\n    \"1994-07-16 00:00:00\",\n    \"1994-07-17 00:00:00\",\n    \"1994-07-23 00:00:00\",\n    \"1994-07-24 00:00:00\",\n    \"1994-07-30 00:00:00\",\n    \"1994-07-31 00:00:00\",\n    \"1994-08-06 00:00:00\",\n    \"1994-08-07 00:00:00\",\n    \"1994-08-13 00:00:00\",\n    \"1994-08-14 00:00:00\",\n    \"1994-08-20 00:00:00\",\n    \"1994-08-21 00:00:00\",\n    \"1994-08-27 00:00:00\",\n    \"1994-08-28 00:00:00\",\n    \"1994-09-03 00:00:00\",\n    \"1994-09-04 00:00:00\",\n    \"1994-09-10 00:00:00\",\n    \"1994-09-11 00:00:00\",\n    \"1994-09-17 00:00:00\",\n    \"1994-09-18 00:00:00\",\n    \"1994-09-24 00:00:00\",\n    \"1994-09-25 00:00:00\",\n    \"1994-10-01 00:00:00\",\n    \"1994-10-02 00:00:00\",\n    \"1994-10-08 00:00:00\",\n    \"1994-10-09 00:00:00\",\n    \"1994-10-15 00:00:00\",\n    \"1994-10-16 00:00:00\",\n    \"1994-10-22 00:00:00\",\n    \"1994-10-23 00:00:00\",\n    \"1994-10-29 00:00:00\",\n    \"1994-10-30 00:00:00\",\n    \"1994-11-05 00:00:00\",\n    \"1994-11-06 00:00:00\",\n    \"1994-11-12 00:00:00\",\n    \"1994-11-13 00:00:00\",\n    \"1994-11-19 00:00:00\",\n    \"1994-11-20 00:00:00\",\n    \"1994-11-26 00:00:00\",\n    \"1994-11-27 00:00:00\",\n    \"1994-12-03 00:00:00\",\n    \"1994-12-04 00:00:00\",\n    \"1994-12-10 00:00:00\",\n    \"1994-12-11 00:00:00\",\n    \"1994-12-17 00:00:00\",\n    \"1994-12-18 00:00:00\",\n    \"1994-12-24 00:00:00\",\n    \"1994-12-25 00:00:00\",\n    \"1994-12-31 00:00:00\",\n    \"1995-01-01 00:00:00\",\n    \"1995-01-07 00:00:00\",\n    \"1995-01-08 00:00:00\",\n    \"1995-01-14 00:00:00\",\n    \"1995-01-15 00:00:00\",\n    \"1995-01-21 00:00:00\",\n    \"1995-01-22 00:00:00\",\n    \"1995-01-28 00:00:00\",\n    \"1995-01-29 00:00:00\",\n    \"1995-02-04 00:00:00\",\n    \"1995-02-05 00:00:00\",\n    \"1995-02-11 00:00:00\",\n    \"1995-02-12 00:00:00\",\n    \"1995-02-18 00:00:00\",\n    \"1995-02-19 00:00:00\",\n    \"1995-02-25 00:00:00\",\n    \"1995-02-26 00:00:00\",\n    \"1995-03-04 00:00:00\",\n    \"1995-03-05 00:00:00\",\n    \"1995-03-11 00:00:00\",\n    \"1995-03-12 00:00:00\",\n    \"1995-03-18 00:00:00\",\n    \"1995-03-19 00:00:00\",\n    \"1995-03-25 00:00:00\",\n    \"1995-03-26 00:00:00\",\n    \"1995-04-01 00:00:00\",\n    \"1995-04-02 00:00:00\",\n    \"1995-04-08 00:00:00\",\n    \"1995-04-09 00:00:00\",\n    \"1995-04-15 00:00:00\",\n    \"1995-04-16 00:00:00\",\n    \"1995-04-22 00:00:00\",\n    \"1995-04-23 00:00:00\",\n    \"1995-04-29 00:00:00\",\n    \"1995-04-30 00:00:00\",\n    \"1995-05-06 00:00:00\",\n    \"1995-05-07 00:00:00\",\n    \"1995-05-13 00:00:00\",\n    \"1995-05-14 00:00:00\",\n    \"1995-05-20 00:00:00\",\n    \"1995-05-21 00:00:00\",\n    \"1995-05-27 00:00:00\",\n    \"1995-05-28 00:00:00\",\n    \"1995-06-03 00:00:00\",\n    \"1995-06-04 00:00:00\",\n    \"1995-06-10 00:00:00\",\n    \"1995-06-11 00:00:00\",\n    \"1995-06-17 00:00:00\",\n    \"1995-06-18 00:00:00\",\n    \"1995-06-24 00:00:00\",\n    \"1995-06-25 00:00:00\",\n    \"1995-07-01 00:00:00\",\n    \"1995-07-02 00:00:00\",\n    \"1995-07-08 00:00:00\",\n    \"1995-07-09 00:00:00\",\n    \"1995-07-15 00:00:00\",\n    \"1995-07-16 00:00:00\",\n    \"1995-07-22 00:00:00\",\n    \"1995-07-23 00:00:00\",\n    \"1995-07-29 00:00:00\",\n    \"1995-07-30 00:00:00\",\n    \"1995-08-05 00:00:00\",\n    \"1995-08-06 00:00:00\",\n    \"1995-08-12 00:00:00\",\n    \"1995-08-13 00:00:00\",\n    \"1995-08-19 00:00:00\",\n    \"1995-08-20 00:00:00\",\n    \"1995-08-26 00:00:00\",\n    \"1995-08-27 00:00:00\",\n    \"1995-09-02 00:00:00\",\n    \"1995-09-03 00:00:00\",\n    \"1995-09-09 00:00:00\",\n    \"1995-09-10 00:00:00\",\n    \"1995-09-16 00:00:00\",\n    \"1995-09-17 00:00:00\",\n    \"1995-09-23 00:00:00\",\n    \"1995-09-24 00:00:00\",\n    \"1995-09-30 00:00:00\",\n    \"1995-10-01 00:00:00\",\n    \"1995-10-07 00:00:00\",\n    \"1995-10-08 00:00:00\",\n    \"1995-10-14 00:00:00\",\n    \"1995-10-15 00:00:00\",\n    \"1995-10-21 00:00:00\",\n    \"1995-10-22 00:00:00\",\n    \"1995-10-28 00:00:00\",\n    \"1995-10-29 00:00:00\",\n    \"1995-11-04 00:00:00\",\n    \"1995-11-05 00:00:00\",\n    \"1995-11-11 00:00:00\",\n    \"1995-11-12 00:00:00\",\n    \"1995-11-18 00:00:00\",\n    \"1995-11-19 00:00:00\",\n    \"1995-11-25 00:00:00\",\n    \"1995-11-26 00:00:00\",\n    \"1995-12-02 00:00:00\",\n    \"1995-12-03 00:00:00\",\n    \"1995-12-09 00:00:00\",\n    \"1995-12-10 00:00:00\",\n    \"1995-12-16 00:00:00\",\n    \"1995-12-17 00:00:00\",\n    \"1995-12-23 00:00:00\",\n    \"1995-12-24 00:00:00\",\n    \"1995-12-30 00:00:00\",\n    \"1995-12-31 00:00:00\",\n    \"1996-01-06 00:00:00\",\n    \"1996-01-07 00:00:00\",\n    \"1996-01-13 00:00:00\",\n    \"1996-01-14 00:00:00\",\n    \"1996-01-20 00:00:00\",\n    \"1996-01-21 00:00:00\",\n    \"1996-01-27 00:00:00\",\n    \"1996-01-28 00:00:00\",\n    \"1996-02-03 00:00:00\",\n    \"1996-02-04 00:00:00\",\n    \"1996-02-10 00:00:00\",\n    \"1996-02-11 00:00:00\",\n    \"1996-02-17 00:00:00\",\n    \"1996-02-18 00:00:00\",\n    \"1996-02-24 00:00:00\",\n    \"1996-02-25 00:00:00\",\n    \"1996-03-02 00:00:00\",\n    \"1996-03-03 00:00:00\",\n    \"1996-03-09 00:00:00\",\n    \"1996-03-10 00:00:00\",\n    \"1996-03-16 00:00:00\",\n    \"1996-03-17 00:00:00\",\n    \"1996-03-23 00:00:00\",\n    \"1996-03-24 00:00:00\",\n    \"1996-03-30 00:00:00\",\n    \"1996-03-31 00:00:00\",\n    \"1996-04-06 00:00:00\",\n    \"1996-04-07 00:00:00\",\n    \"1996-04-13 00:00:00\",\n    \"1996-04-14 00:00:00\",\n    \"1996-04-20 00:00:00\",\n    \"1996-04-21 00:00:00\",\n    \"1996-04-27 00:00:00\",\n    \"1996-04-28 00:00:00\",\n    \"1996-05-04 00:00:00\",\n    \"1996-05-05 00:00:00\",\n    \"1996-05-11 00:00:00\",\n    \"1996-05-12 00:00:00\",\n    \"1996-05-18 00:00:00\",\n    \"1996-05-19 00:00:00\",\n    \"1996-05-25 00:00:00\",\n    \"1996-05-26 00:00:00\",\n    \"1996-06-01 00:00:00\",\n    \"1996-06-02 00:00:00\",\n    \"1996-06-08 00:00:00\",\n    \"1996-06-09 00:00:00\",\n    \"1996-06-15 00:00:00\",\n    \"1996-06-16 00:00:00\",\n    \"1996-06-22 00:00:00\",\n    \"1996-06-23 00:00:00\",\n    \"1996-06-29 00:00:00\",\n    \"1996-06-30 00:00:00\",\n    \"1996-07-06 00:00:00\",\n    \"1996-07-07 00:00:00\",\n    \"1996-07-13 00:00:00\",\n    \"1996-07-14 00:00:00\",\n    \"1996-07-20 00:00:00\",\n    \"1996-07-21 00:00:00\",\n    \"1996-07-27 00:00:00\",\n    \"1996-07-28 00:00:00\",\n    \"1996-08-03 00:00:00\",\n    \"1996-08-04 00:00:00\",\n    \"1996-08-10 00:00:00\",\n    \"1996-08-11 00:00:00\",\n    \"1996-08-17 00:00:00\",\n    \"1996-08-18 00:00:00\",\n    \"1996-08-24 00:00:00\",\n    \"1996-08-25 00:00:00\",\n    \"1996-08-31 00:00:00\",\n    \"1996-09-01 00:00:00\",\n    \"1996-09-07 00:00:00\",\n    \"1996-09-08 00:00:00\",\n    \"1996-09-14 00:00:00\",\n    \"1996-09-15 00:00:00\",\n    \"1996-09-21 00:00:00\",\n    \"1996-09-22 00:00:00\",\n    \"1996-09-28 00:00:00\",\n    \"1996-09-29 00:00:00\",\n    \"1996-10-05 00:00:00\",\n    \"1996-10-06 00:00:00\",\n    \"1996-10-12 00:00:00\",\n    \"1996-10-13 00:00:00\",\n    \"1996-10-19 00:00:00\",\n    \"1996-10-20 00:00:00\",\n    \"1996-10-26 00:00:00\",\n    \"1996-10-27 00:00:00\",\n    \"1996-11-02 00:00:00\",\n    \"1996-11-03 00:00:00\",\n    \"1996-11-09 00:00:00\",\n    \"1996-11-10 00:00:00\",\n    \"1996-11-16 00:00:00\",\n    \"1996-11-17 00:00:00\",\n    \"1996-11-23 00:00:00\",\n    \"1996-11-24 00:00:00\",\n    \"1996-11-30 00:00:00\",\n    \"1996-12-01 00:00:00\",\n    \"1996-12-07 00:00:00\",\n    \"1996-12-08 00:00:00\",\n    \"1996-12-14 00:00:00\",\n    \"1996-12-15 00:00:00\",\n    \"1996-12-21 00:00:00\",\n    \"1996-12-22 00:00:00\",\n    \"1996-12-28 00:00:00\",\n    \"1996-12-29 00:00:00\",\n    \"1997-01-04 00:00:00\",\n    \"1997-01-05 00:00:00\",\n    \"1997-01-11 00:00:00\",\n    \"1997-01-12 00:00:00\",\n    \"1997-01-18 00:00:00\",\n    \"1997-01-19 00:00:00\",\n    \"1997-01-25 00:00:00\",\n    \"1997-01-26 00:00:00\",\n    \"1997-02-01 00:00:00\",\n    \"1997-02-02 00:00:00\",\n    \"1997-02-08 00:00:00\",\n    \"1997-02-09 00:00:00\",\n    \"1997-02-15 00:00:00\",\n    \"1997-02-16 00:00:00\",\n    \"1997-02-22 00:00:00\",\n    \"1997-02-23 00:00:00\",\n    \"1997-03-01 00:00:00\",\n    \"1997-03-02 00:00:00\",\n    \"1997-03-08 00:00:00\",\n    \"1997-03-09 00:00:00\",\n    \"1997-03-15 00:00:00\",\n    \"1997-03-16 00:00:00\",\n    \"1997-03-22 00:00:00\",\n    \"1997-03-23 00:00:00\",\n    \"1997-03-29 00:00:00\",\n    \"1997-03-30 00:00:00\",\n    \"1997-04-05 00:00:00\",\n    \"1997-04-06 00:00:00\",\n    \"1997-04-12 00:00:00\",\n    \"1997-04-13 00:00:00\",\n    \"1997-04-19 00:00:00\",\n    \"1997-04-20 00:00:00\",\n    \"1997-04-26 00:00:00\",\n    \"1997-04-27 00:00:00\",\n    \"1997-05-03 00:00:00\",\n    \"1997-05-04 00:00:00\",\n    \"1997-05-10 00:00:00\",\n    \"1997-05-11 00:00:00\",\n    \"1997-05-17 00:00:00\",\n    \"1997-05-18 00:00:00\",\n    \"1997-05-24 00:00:00\",\n    \"1997-05-25 00:00:00\",\n    \"1997-05-31 00:00:00\",\n    \"1997-06-01 00:00:00\",\n    \"1997-06-07 00:00:00\",\n    \"1997-06-08 00:00:00\",\n    \"1997-06-14 00:00:00\",\n    \"1997-06-15 00:00:00\",\n    \"1997-06-21 00:00:00\",\n    \"1997-06-22 00:00:00\",\n    \"1997-06-28 00:00:00\",\n    \"1997-06-29 00:00:00\",\n    \"1997-07-05 00:00:00\",\n    \"1997-07-06 00:00:00\",\n    \"1997-07-12 00:00:00\",\n    \"1997-07-13 00:00:00\",\n    \"1997-07-19 00:00:00\",\n    \"1997-07-20 00:00:00\",\n    \"1997-07-26 00:00:00\",\n    \"1997-07-27 00:00:00\",\n    \"1997-08-02 00:00:00\",\n    \"1997-08-03 00:00:00\",\n    \"1997-08-09 00:00:00\",\n    \"1997-08-10 00:00:00\",\n    \"1997-08-16 00:00:00\",\n    \"1997-08-17 00:00:00\",\n    \"1997-08-23 00:00:00\",\n    \"1997-08-24 00:00:00\",\n    \"1997-08-30 00:00:00\",\n    \"1997-08-31 00:00:00\",\n    \"1997-09-06 00:00:00\",\n    \"1997-09-07 00:00:00\",\n    \"1997-09-13 00:00:00\",\n    \"1997-09-14 00:00:00\",\n    \"1997-09-20 00:00:00\",\n    \"1997-09-21 00:00:00\",\n    \"1997-09-27 00:00:00\",\n    \"1997-09-28 00:00:00\",\n    \"1997-10-04 00:00:00\",\n    \"1997-10-05 00:00:00\",\n    \"1997-10-11 00:00:00\",\n    \"1997-10-12 00:00:00\",\n    \"1997-10-18 00:00:00\",\n    \"1997-10-19 00:00:00\",\n    \"1997-10-25 00:00:00\",\n    \"1997-10-26 00:00:00\",\n    \"1997-11-01 00:00:00\",\n    \"1997-11-02 00:00:00\",\n    \"1997-11-08 00:00:00\",\n    \"1997-11-09 00:00:00\",\n    \"1997-11-15 00:00:00\",\n    \"1997-11-16 00:00:00\",\n    \"1997-11-22 00:00:00\",\n    \"1997-11-23 00:00:00\",\n    \"1997-11-29 00:00:00\",\n    \"1997-11-30 00:00:00\",\n    \"1997-12-06 00:00:00\",\n    \"1997-12-07 00:00:00\",\n    \"1997-12-13 00:00:00\",\n    \"1997-12-14 00:00:00\",\n    \"1997-12-20 00:00:00\",\n    \"1997-12-21 00:00:00\",\n    \"1997-12-27 00:00:00\",\n    \"1997-12-28 00:00:00\",\n    \"1998-01-03 00:00:00\",\n    \"1998-01-04 00:00:00\",\n    \"1998-01-10 00:00:00\",\n    \"1998-01-11 00:00:00\",\n    \"1998-01-17 00:00:00\",\n    \"1998-01-18 00:00:00\",\n    \"1998-01-24 00:00:00\",\n    \"1998-01-25 00:00:00\",\n    \"1998-01-31 00:00:00\",\n    \"1998-02-01 00:00:00\",\n    \"1998-02-07 00:00:00\",\n    \"1998-02-08 00:00:00\",\n    \"1998-02-14 00:00:00\",\n    \"1998-02-15 00:00:00\",\n    \"1998-02-21 00:00:00\",\n    \"1998-02-22 00:00:00\",\n    \"1998-02-28 00:00:00\",\n    \"1998-03-01 00:00:00\",\n    \"1998-03-07 00:00:00\",\n    \"1998-03-08 00:00:00\",\n    \"1998-03-14 00:00:00\",\n    \"1998-03-15 00:00:00\",\n    \"1998-03-21 00:00:00\",\n    \"1998-03-22 00:00:00\",\n    \"1998-03-28 00:00:00\",\n    \"1998-03-29 00:00:00\",\n    \"1998-04-04 00:00:00\",\n    \"1998-04-05 00:00:00\",\n    \"1998-04-11 00:00:00\",\n    \"1998-04-12 00:00:00\",\n    \"1998-04-18 00:00:00\",\n    \"1998-04-19 00:00:00\",\n    \"1998-04-25 00:00:00\",\n    \"1998-04-26 00:00:00\",\n    \"1998-05-02 00:00:00\",\n    \"1998-05-03 00:00:00\",\n    \"1998-05-09 00:00:00\",\n    \"1998-05-10 00:00:00\",\n    \"1998-05-16 00:00:00\",\n    \"1998-05-17 00:00:00\",\n    \"1998-05-23 00:00:00\",\n    \"1998-05-24 00:00:00\",\n    \"1998-05-30 00:00:00\",\n    \"1998-05-31 00:00:00\",\n    \"1998-06-06 00:00:00\",\n    \"1998-06-07 00:00:00\",\n    \"1998-06-13 00:00:00\",\n    \"1998-06-14 00:00:00\",\n    \"1998-06-20 00:00:00\",\n    \"1998-06-21 00:00:00\",\n    \"1998-06-27 00:00:00\",\n    \"1998-06-28 00:00:00\",\n    \"1998-07-04 00:00:00\",\n    \"1998-07-05 00:00:00\",\n    \"1998-07-11 00:00:00\",\n    \"1998-07-12 00:00:00\",\n    \"1998-07-18 00:00:00\",\n    \"1998-07-19 00:00:00\",\n    \"1998-07-25 00:00:00\",\n    \"1998-07-26 00:00:00\",\n    \"1998-08-01 00:00:00\",\n    \"1998-08-02 00:00:00\",\n    \"1998-08-08 00:00:00\",\n    \"1998-08-09 00:00:00\",\n    \"1998-08-15 00:00:00\",\n    \"1998-08-16 00:00:00\",\n    \"1998-08-22 00:00:00\",\n    \"1998-08-23 00:00:00\",\n    \"1998-08-29 00:00:00\",\n    \"1998-08-30 00:00:00\",\n    \"1998-09-05 00:00:00\",\n    \"1998-09-06 00:00:00\",\n    \"1998-09-12 00:00:00\",\n    \"1998-09-13 00:00:00\",\n    \"1998-09-19 00:00:00\",\n    \"1998-09-20 00:00:00\",\n    \"1998-09-26 00:00:00\",\n    \"1998-09-27 00:00:00\",\n    \"1998-10-03 00:00:00\",\n    \"1998-10-04 00:00:00\",\n    \"1998-10-10 00:00:00\",\n    \"1998-10-11 00:00:00\",\n    \"1998-10-17 00:00:00\",\n    \"1998-10-18 00:00:00\",\n    \"1998-10-24 00:00:00\",\n    \"1998-10-25 00:00:00\",\n    \"1998-10-31 00:00:00\",\n    \"1998-11-01 00:00:00\",\n    \"1998-11-07 00:00:00\",\n    \"1998-11-08 00:00:00\",\n    \"1998-11-14 00:00:00\",\n    \"1998-11-15 00:00:00\",\n    \"1998-11-21 00:00:00\",\n    \"1998-11-22 00:00:00\",\n    \"1998-11-28 00:00:00\",\n    \"1998-11-29 00:00:00\",\n    \"1998-12-05 00:00:00\",\n    \"1998-12-06 00:00:00\",\n    \"1998-12-12 00:00:00\",\n    \"1998-12-13 00:00:00\",\n    \"1998-12-19 00:00:00\",\n    \"1998-12-20 00:00:00\",\n    \"1998-12-26 00:00:00\",\n    \"1998-12-27 00:00:00\",\n    \"1999-01-02 00:00:00\",\n    \"1999-01-03 00:00:00\",\n    \"1999-01-09 00:00:00\",\n    \"1999-01-10 00:00:00\",\n    \"1999-01-16 00:00:00\",\n    \"1999-01-17 00:00:00\",\n    \"1999-01-23 00:00:00\",\n    \"1999-01-24 00:00:00\",\n    \"1999-01-30 00:00:00\",\n    \"1999-01-31 00:00:00\",\n    \"1999-02-06 00:00:00\",\n    \"1999-02-07 00:00:00\",\n    \"1999-02-13 00:00:00\",\n    \"1999-02-14 00:00:00\",\n    \"1999-02-20 00:00:00\",\n    \"1999-02-21 00:00:00\",\n    \"1999-02-27 00:00:00\",\n    \"1999-02-28 00:00:00\",\n    \"1999-03-06 00:00:00\",\n    \"1999-03-07 00:00:00\",\n    \"1999-03-13 00:00:00\",\n    \"1999-03-14 00:00:00\",\n    \"1999-03-20 00:00:00\",\n    \"1999-03-21 00:00:00\",\n    \"1999-03-27 00:00:00\",\n    \"1999-03-28 00:00:00\",\n    \"1999-04-03 00:00:00\",\n    \"1999-04-04 00:00:00\",\n    \"1999-04-10 00:00:00\",\n    \"1999-04-11 00:00:00\",\n    \"1999-04-17 00:00:00\",\n    \"1999-04-18 00:00:00\",\n    \"1999-04-24 00:00:00\",\n    \"1999-04-25 00:00:00\",\n    \"1999-05-01 00:00:00\",\n    \"1999-05-02 00:00:00\",\n    \"1999-05-08 00:00:00\",\n    \"1999-05-09 00:00:00\",\n    \"1999-05-15 00:00:00\",\n    \"1999-05-16 00:00:00\",\n    \"1999-05-22 00:00:00\",\n    \"1999-05-23 00:00:00\",\n    \"1999-05-29 00:00:00\",\n    \"1999-05-30 00:00:00\",\n    \"1999-06-05 00:00:00\",\n    \"1999-06-06 00:00:00\",\n    \"1999-06-12 00:00:00\",\n    \"1999-06-13 00:00:00\",\n    \"1999-06-19 00:00:00\",\n    \"1999-06-20 00:00:00\",\n    \"1999-06-26 00:00:00\",\n    \"1999-06-27 00:00:00\",\n    \"1999-07-03 00:00:00\",\n    \"1999-07-04 00:00:00\",\n    \"1999-07-10 00:00:00\",\n    \"1999-07-11 00:00:00\",\n    \"1999-07-17 00:00:00\",\n    \"1999-07-18 00:00:00\",\n    \"1999-07-24 00:00:00\",\n    \"1999-07-25 00:00:00\",\n    \"1999-07-31 00:00:00\",\n    \"1999-08-01 00:00:00\",\n    \"1999-08-07 00:00:00\",\n    \"1999-08-08 00:00:00\",\n    \"1999-08-14 00:00:00\",\n    \"1999-08-15 00:00:00\",\n    \"1999-08-21 00:00:00\",\n    \"1999-08-22 00:00:00\",\n    \"1999-08-28 00:00:00\",\n    \"1999-08-29 00:00:00\",\n    \"1999-09-04 00:00:00\",\n    \"1999-09-05 00:00:00\",\n    \"1999-09-11 00:00:00\",\n    \"1999-09-12 00:00:00\",\n    \"1999-09-18 00:00:00\",\n    \"1999-09-19 00:00:00\",\n    \"1999-09-25 00:00:00\",\n    \"1999-09-26 00:00:00\",\n    \"1999-10-02 00:00:00\",\n    \"1999-10-03 00:00:00\",\n    \"1999-10-09 00:00:00\",\n    \"1999-10-10 00:00:00\",\n    \"1999-10-16 00:00:00\",\n    \"1999-10-17 00:00:00\",\n    \"1999-10-23 00:00:00\",\n    \"1999-10-24 00:00:00\",\n    \"1999-10-30 00:00:00\",\n    \"1999-10-31 00:00:00\",\n    \"1999-11-06 00:00:00\",\n    \"1999-11-07 00:00:00\",\n    \"1999-11-13 00:00:00\",\n    \"1999-11-14 00:00:00\",\n    \"1999-11-20 00:00:00\",\n    \"1999-11-21 00:00:00\",\n    \"1999-11-27 00:00:00\",\n    \"1999-11-28 00:00:00\",\n    \"1999-12-04 00:00:00\",\n    \"1999-12-05 00:00:00\",\n    \"1999-12-11 00:00:00\",\n    \"1999-12-12 00:00:00\",\n    \"1999-12-18 00:00:00\",\n    \"1999-12-19 00:00:00\",\n    \"1999-12-25 00:00:00\",\n    \"1999-12-26 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-01-02 00:00:00\",\n    \"2000-01-08 00:00:00\",\n    \"2000-01-09 00:00:00\",\n    \"2000-01-15 00:00:00\",\n    \"2000-01-16 00:00:00\",\n    \"2000-01-22 00:00:00\",\n    \"2000-01-23 00:00:00\",\n    \"2000-01-29 00:00:00\",\n    \"2000-01-30 00:00:00\",\n    \"2000-02-05 00:00:00\",\n    \"2000-02-06 00:00:00\",\n    \"2000-02-12 00:00:00\",\n    \"2000-02-13 00:00:00\",\n    \"2000-02-19 00:00:00\",\n    \"2000-02-20 00:00:00\",\n    \"2000-02-26 00:00:00\",\n    \"2000-02-27 00:00:00\",\n    \"2000-03-04 00:00:00\",\n    \"2000-03-05 00:00:00\",\n    \"2000-03-11 00:00:00\",\n    \"2000-03-12 00:00:00\",\n    \"2000-03-18 00:00:00\",\n    \"2000-03-19 00:00:00\",\n    \"2000-03-25 00:00:00\",\n    \"2000-03-26 00:00:00\",\n    \"2000-04-01 00:00:00\",\n    \"2000-04-02 00:00:00\",\n    \"2000-04-08 00:00:00\",\n    \"2000-04-09 00:00:00\",\n    \"2000-04-15 00:00:00\",\n    \"2000-04-16 00:00:00\",\n    \"2000-04-22 00:00:00\",\n    \"2000-04-23 00:00:00\",\n    \"2000-04-29 00:00:00\",\n    \"2000-04-30 00:00:00\",\n    \"2000-05-06 00:00:00\",\n    \"2000-05-07 00:00:00\",\n    \"2000-05-13 00:00:00\",\n    \"2000-05-14 00:00:00\",\n    \"2000-05-20 00:00:00\",\n    \"2000-05-21 00:00:00\",\n    \"2000-05-27 00:00:00\",\n    \"2000-05-28 00:00:00\",\n    \"2000-06-03 00:00:00\",\n    \"2000-06-04 00:00:00\",\n    \"2000-06-10 00:00:00\",\n    \"2000-06-11 00:00:00\",\n    \"2000-06-17 00:00:00\",\n    \"2000-06-18 00:00:00\",\n    \"2000-06-24 00:00:00\",\n    \"2000-06-25 00:00:00\",\n    \"2000-07-01 00:00:00\",\n    \"2000-07-02 00:00:00\",\n    \"2000-07-08 00:00:00\",\n    \"2000-07-09 00:00:00\",\n    \"2000-07-15 00:00:00\",\n    \"2000-07-16 00:00:00\",\n    \"2000-07-22 00:00:00\",\n    \"2000-07-23 00:00:00\",\n    \"2000-07-29 00:00:00\",\n    \"2000-07-30 00:00:00\",\n    \"2000-08-05 00:00:00\",\n    \"2000-08-06 00:00:00\",\n    \"2000-08-12 00:00:00\",\n    \"2000-08-13 00:00:00\",\n    \"2000-08-19 00:00:00\",\n    \"2000-08-20 00:00:00\",\n    \"2000-08-26 00:00:00\",\n    \"2000-08-27 00:00:00\",\n    \"2000-09-02 00:00:00\",\n    \"2000-09-03 00:00:00\",\n    \"2000-09-09 00:00:00\",\n    \"2000-09-10 00:00:00\",\n    \"2000-09-16 00:00:00\",\n    \"2000-09-17 00:00:00\",\n    \"2000-09-23 00:00:00\",\n    \"2000-09-24 00:00:00\",\n    \"2000-09-30 00:00:00\",\n    \"2000-10-01 00:00:00\",\n    \"2000-10-07 00:00:00\",\n    \"2000-10-08 00:00:00\",\n    \"2000-10-14 00:00:00\",\n    \"2000-10-15 00:00:00\",\n    \"2000-10-21 00:00:00\",\n    \"2000-10-22 00:00:00\",\n    \"2000-10-28 00:00:00\",\n    \"2000-10-29 00:00:00\",\n    \"2000-11-04 00:00:00\",\n    \"2000-11-05 00:00:00\",\n    \"2000-11-11 00:00:00\",\n    \"2000-11-12 00:00:00\",\n    \"2000-11-18 00:00:00\",\n    \"2000-11-19 00:00:00\",\n    \"2000-11-25 00:00:00\",\n    \"2000-11-26 00:00:00\",\n    \"2000-12-02 00:00:00\",\n    \"2000-12-03 00:00:00\",\n    \"2000-12-09 00:00:00\",\n    \"2000-12-10 00:00:00\",\n    \"2000-12-16 00:00:00\",\n    \"2000-12-17 00:00:00\",\n    \"2000-12-23 00:00:00\",\n    \"2000-12-24 00:00:00\",\n    \"2000-12-30 00:00:00\",\n    \"2000-12-31 00:00:00\",\n    \"2001-01-06 00:00:00\",\n    \"2001-01-07 00:00:00\",\n    \"2001-01-13 00:00:00\",\n    \"2001-01-14 00:00:00\",\n    \"2001-01-20 00:00:00\",\n    \"2001-01-21 00:00:00\",\n    \"2001-01-27 00:00:00\",\n    \"2001-01-28 00:00:00\",\n    \"2001-02-03 00:00:00\",\n    \"2001-02-04 00:00:00\",\n    \"2001-02-10 00:00:00\",\n    \"2001-02-11 00:00:00\",\n    \"2001-02-17 00:00:00\",\n    \"2001-02-18 00:00:00\",\n    \"2001-02-24 00:00:00\",\n    \"2001-02-25 00:00:00\",\n    \"2001-03-03 00:00:00\",\n    \"2001-03-04 00:00:00\",\n    \"2001-03-10 00:00:00\",\n    \"2001-03-11 00:00:00\",\n    \"2001-03-17 00:00:00\",\n    \"2001-03-18 00:00:00\",\n    \"2001-03-24 00:00:00\",\n    \"2001-03-25 00:00:00\",\n    \"2001-03-31 00:00:00\",\n    \"2001-04-01 00:00:00\",\n    \"2001-04-07 00:00:00\",\n    \"2001-04-08 00:00:00\",\n    \"2001-04-14 00:00:00\",\n    \"2001-04-15 00:00:00\",\n    \"2001-04-21 00:00:00\",\n    \"2001-04-22 00:00:00\",\n    \"2001-04-28 00:00:00\",\n    \"2001-04-29 00:00:00\",\n    \"2001-05-05 00:00:00\",\n    \"2001-05-06 00:00:00\",\n    \"2001-05-12 00:00:00\",\n    \"2001-05-13 00:00:00\",\n    \"2001-05-19 00:00:00\",\n    \"2001-05-20 00:00:00\",\n    \"2001-05-26 00:00:00\",\n    \"2001-05-27 00:00:00\",\n    \"2001-06-02 00:00:00\",\n    \"2001-06-03 00:00:00\",\n    \"2001-06-09 00:00:00\",\n    \"2001-06-10 00:00:00\",\n    \"2001-06-16 00:00:00\",\n    \"2001-06-17 00:00:00\",\n    \"2001-06-23 00:00:00\",\n    \"2001-06-24 00:00:00\",\n    \"2001-06-30 00:00:00\",\n    \"2001-07-01 00:00:00\",\n    \"2001-07-07 00:00:00\",\n    \"2001-07-08 00:00:00\",\n    \"2001-07-14 00:00:00\",\n    \"2001-07-15 00:00:00\",\n    \"2001-07-21 00:00:00\",\n    \"2001-07-22 00:00:00\",\n    \"2001-07-28 00:00:00\",\n    \"2001-07-29 00:00:00\",\n    \"2001-08-04 00:00:00\",\n    \"2001-08-05 00:00:00\",\n    \"2001-08-11 00:00:00\",\n    \"2001-08-12 00:00:00\",\n    \"2001-08-18 00:00:00\",\n    \"2001-08-19 00:00:00\",\n    \"2001-08-25 00:00:00\",\n    \"2001-08-26 00:00:00\",\n    \"2001-09-01 00:00:00\",\n    \"2001-09-02 00:00:00\",\n    \"2001-09-08 00:00:00\",\n    \"2001-09-09 00:00:00\",\n    \"2001-09-15 00:00:00\",\n    \"2001-09-16 00:00:00\",\n    \"2001-09-22 00:00:00\",\n    \"2001-09-23 00:00:00\",\n    \"2001-09-29 00:00:00\",\n    \"2001-09-30 00:00:00\",\n    \"2001-10-06 00:00:00\",\n    \"2001-10-07 00:00:00\",\n    \"2001-10-13 00:00:00\",\n    \"2001-10-14 00:00:00\",\n    \"2001-10-20 00:00:00\",\n    \"2001-10-21 00:00:00\",\n    \"2001-10-27 00:00:00\",\n    \"2001-10-28 00:00:00\",\n    \"2001-11-03 00:00:00\",\n    \"2001-11-04 00:00:00\",\n    \"2001-11-10 00:00:00\",\n    \"2001-11-11 00:00:00\",\n    \"2001-11-17 00:00:00\",\n    \"2001-11-18 00:00:00\",\n    \"2001-11-24 00:00:00\",\n    \"2001-11-25 00:00:00\",\n    \"2001-12-01 00:00:00\",\n    \"2001-12-02 00:00:00\",\n    \"2001-12-08 00:00:00\",\n    \"2001-12-09 00:00:00\",\n    \"2001-12-15 00:00:00\",\n    \"2001-12-16 00:00:00\",\n    \"2001-12-22 00:00:00\",\n    \"2001-12-23 00:00:00\",\n    \"2001-12-29 00:00:00\",\n    \"2001-12-30 00:00:00\",\n    \"2002-01-05 00:00:00\",\n    \"2002-01-06 00:00:00\",\n    \"2002-01-12 00:00:00\",\n    \"2002-01-13 00:00:00\",\n    \"2002-01-19 00:00:00\",\n    \"2002-01-20 00:00:00\",\n    \"2002-01-26 00:00:00\",\n    \"2002-01-27 00:00:00\",\n    \"2002-02-02 00:00:00\",\n    \"2002-02-03 00:00:00\",\n    \"2002-02-09 00:00:00\",\n    \"2002-02-10 00:00:00\",\n    \"2002-02-16 00:00:00\",\n    \"2002-02-17 00:00:00\",\n    \"2002-02-23 00:00:00\",\n    \"2002-02-24 00:00:00\",\n    \"2002-03-02 00:00:00\",\n    \"2002-03-03 00:00:00\",\n    \"2002-03-09 00:00:00\",\n    \"2002-03-10 00:00:00\",\n    \"2002-03-16 00:00:00\",\n    \"2002-03-17 00:00:00\",\n    \"2002-03-23 00:00:00\",\n    \"2002-03-24 00:00:00\",\n    \"2002-03-30 00:00:00\",\n    \"2002-03-31 00:00:00\",\n    \"2002-04-06 00:00:00\",\n    \"2002-04-07 00:00:00\",\n    \"2002-04-13 00:00:00\",\n    \"2002-04-14 00:00:00\",\n    \"2002-04-20 00:00:00\",\n    \"2002-04-21 00:00:00\",\n    \"2002-04-27 00:00:00\",\n    \"2002-04-28 00:00:00\",\n    \"2002-05-04 00:00:00\",\n    \"2002-05-05 00:00:00\",\n    \"2002-05-11 00:00:00\",\n    \"2002-05-12 00:00:00\",\n    \"2002-05-18 00:00:00\",\n    \"2002-05-19 00:00:00\",\n    \"2002-05-25 00:00:00\",\n    \"2002-05-26 00:00:00\",\n    \"2002-06-01 00:00:00\",\n    \"2002-06-02 00:00:00\",\n    \"2002-06-08 00:00:00\",\n    \"2002-06-09 00:00:00\",\n    \"2002-06-15 00:00:00\",\n    \"2002-06-16 00:00:00\",\n    \"2002-06-22 00:00:00\",\n    \"2002-06-23 00:00:00\",\n    \"2002-06-29 00:00:00\",\n    \"2002-06-30 00:00:00\",\n    \"2002-07-06 00:00:00\",\n    \"2002-07-07 00:00:00\",\n    \"2002-07-13 00:00:00\",\n    \"2002-07-14 00:00:00\",\n    \"2002-07-20 00:00:00\",\n    \"2002-07-21 00:00:00\",\n    \"2002-07-27 00:00:00\",\n    \"2002-07-28 00:00:00\",\n    \"2002-08-03 00:00:00\",\n    \"2002-08-04 00:00:00\",\n    \"2002-08-10 00:00:00\",\n    \"2002-08-11 00:00:00\",\n    \"2002-08-17 00:00:00\",\n    \"2002-08-18 00:00:00\",\n    \"2002-08-24 00:00:00\",\n    \"2002-08-25 00:00:00\",\n    \"2002-08-31 00:00:00\",\n    \"2002-09-01 00:00:00\",\n    \"2002-09-07 00:00:00\",\n    \"2002-09-08 00:00:00\",\n    \"2002-09-14 00:00:00\",\n    \"2002-09-15 00:00:00\",\n    \"2002-09-21 00:00:00\",\n    \"2002-09-22 00:00:00\",\n    \"2002-09-28 00:00:00\",\n    \"2002-09-29 00:00:00\",\n    \"2002-10-05 00:00:00\",\n    \"2002-10-06 00:00:00\",\n    \"2002-10-12 00:00:00\",\n    \"2002-10-13 00:00:00\",\n    \"2002-10-19 00:00:00\",\n    \"2002-10-20 00:00:00\",\n    \"2002-10-26 00:00:00\",\n    \"2002-10-27 00:00:00\",\n    \"2002-11-02 00:00:00\",\n    \"2002-11-03 00:00:00\",\n    \"2002-11-09 00:00:00\",\n    \"2002-11-10 00:00:00\",\n    \"2002-11-16 00:00:00\",\n    \"2002-11-17 00:00:00\",\n    \"2002-11-23 00:00:00\",\n    \"2002-11-24 00:00:00\",\n    \"2002-11-30 00:00:00\",\n    \"2002-12-01 00:00:00\",\n    \"2002-12-07 00:00:00\",\n    \"2002-12-08 00:00:00\",\n    \"2002-12-14 00:00:00\",\n    \"2002-12-15 00:00:00\",\n    \"2002-12-21 00:00:00\",\n    \"2002-12-22 00:00:00\",\n    \"2002-12-28 00:00:00\",\n    \"2002-12-29 00:00:00\",\n    \"2003-01-04 00:00:00\",\n    \"2003-01-05 00:00:00\",\n    \"2003-01-11 00:00:00\",\n    \"2003-01-12 00:00:00\",\n    \"2003-01-18 00:00:00\",\n    \"2003-01-19 00:00:00\",\n    \"2003-01-25 00:00:00\",\n    \"2003-01-26 00:00:00\",\n    \"2003-02-01 00:00:00\",\n    \"2003-02-02 00:00:00\",\n    \"2003-02-08 00:00:00\",\n    \"2003-02-09 00:00:00\",\n    \"2003-02-15 00:00:00\",\n    \"2003-02-16 00:00:00\",\n    \"2003-02-22 00:00:00\",\n    \"2003-02-23 00:00:00\",\n    \"2003-03-01 00:00:00\",\n    \"2003-03-02 00:00:00\",\n    \"2003-03-08 00:00:00\",\n    \"2003-03-09 00:00:00\",\n    \"2003-03-15 00:00:00\",\n    \"2003-03-16 00:00:00\",\n    \"2003-03-22 00:00:00\",\n    \"2003-03-23 00:00:00\",\n    \"2003-03-29 00:00:00\",\n    \"2003-03-30 00:00:00\",\n    \"2003-04-05 00:00:00\",\n    \"2003-04-06 00:00:00\",\n    \"2003-04-12 00:00:00\",\n    \"2003-04-13 00:00:00\",\n    \"2003-04-19 00:00:00\",\n    \"2003-04-20 00:00:00\",\n    \"2003-04-26 00:00:00\",\n    \"2003-04-27 00:00:00\",\n    \"2003-05-03 00:00:00\",\n    \"2003-05-04 00:00:00\",\n    \"2003-05-10 00:00:00\",\n    \"2003-05-11 00:00:00\",\n    \"2003-05-17 00:00:00\",\n    \"2003-05-18 00:00:00\",\n    \"2003-05-24 00:00:00\",\n    \"2003-05-25 00:00:00\",\n    \"2003-05-31 00:00:00\",\n    \"2003-06-01 00:00:00\",\n    \"2003-06-07 00:00:00\",\n    \"2003-06-08 00:00:00\",\n    \"2003-06-14 00:00:00\",\n    \"2003-06-15 00:00:00\",\n    \"2003-06-21 00:00:00\",\n    \"2003-06-22 00:00:00\",\n    \"2003-06-28 00:00:00\",\n    \"2003-06-29 00:00:00\",\n    \"2003-07-05 00:00:00\",\n    \"2003-07-06 00:00:00\",\n    \"2003-07-12 00:00:00\",\n    \"2003-07-13 00:00:00\",\n    \"2003-07-19 00:00:00\",\n    \"2003-07-20 00:00:00\",\n    \"2003-07-26 00:00:00\",\n    \"2003-07-27 00:00:00\",\n    \"2003-08-02 00:00:00\",\n    \"2003-08-03 00:00:00\",\n    \"2003-08-09 00:00:00\",\n    \"2003-08-10 00:00:00\",\n    \"2003-08-16 00:00:00\",\n    \"2003-08-17 00:00:00\",\n    \"2003-08-23 00:00:00\",\n    \"2003-08-24 00:00:00\",\n    \"2003-08-30 00:00:00\",\n    \"2003-08-31 00:00:00\",\n    \"2003-09-06 00:00:00\",\n    \"2003-09-07 00:00:00\",\n    \"2003-09-13 00:00:00\",\n    \"2003-09-14 00:00:00\",\n    \"2003-09-20 00:00:00\",\n    \"2003-09-21 00:00:00\",\n    \"2003-09-27 00:00:00\",\n    \"2003-09-28 00:00:00\",\n    \"2003-10-04 00:00:00\",\n    \"2003-10-05 00:00:00\",\n    \"2003-10-11 00:00:00\",\n    \"2003-10-12 00:00:00\",\n    \"2003-10-18 00:00:00\",\n    \"2003-10-19 00:00:00\",\n    \"2003-10-25 00:00:00\",\n    \"2003-10-26 00:00:00\",\n    \"2003-11-01 00:00:00\",\n    \"2003-11-02 00:00:00\",\n    \"2003-11-08 00:00:00\",\n    \"2003-11-09 00:00:00\",\n    \"2003-11-15 00:00:00\",\n    \"2003-11-16 00:00:00\",\n    \"2003-11-22 00:00:00\",\n    \"2003-11-23 00:00:00\",\n    \"2003-11-29 00:00:00\",\n    \"2003-11-30 00:00:00\",\n    \"2003-12-06 00:00:00\",\n    \"2003-12-07 00:00:00\",\n    \"2003-12-13 00:00:00\",\n    \"2003-12-14 00:00:00\",\n    \"2003-12-20 00:00:00\",\n    \"2003-12-21 00:00:00\",\n    \"2003-12-27 00:00:00\",\n    \"2003-12-28 00:00:00\",\n    \"2004-01-03 00:00:00\",\n    \"2004-01-04 00:00:00\",\n    \"2004-01-10 00:00:00\",\n    \"2004-01-11 00:00:00\",\n    \"2004-01-17 00:00:00\",\n    \"2004-01-18 00:00:00\",\n    \"2004-01-24 00:00:00\",\n    \"2004-01-25 00:00:00\",\n    \"2004-01-31 00:00:00\",\n    \"2004-02-01 00:00:00\",\n    \"2004-02-07 00:00:00\",\n    \"2004-02-08 00:00:00\",\n    \"2004-02-14 00:00:00\",\n    \"2004-02-15 00:00:00\",\n    \"2004-02-21 00:00:00\",\n    \"2004-02-22 00:00:00\",\n    \"2004-02-28 00:00:00\",\n    \"2004-02-29 00:00:00\",\n    \"2004-03-06 00:00:00\",\n    \"2004-03-07 00:00:00\",\n    \"2004-03-13 00:00:00\",\n    \"2004-03-14 00:00:00\",\n    \"2004-03-20 00:00:00\",\n    \"2004-03-21 00:00:00\",\n    \"2004-03-27 00:00:00\",\n    \"2004-03-28 00:00:00\",\n    \"2004-04-03 00:00:00\",\n    \"2004-04-04 00:00:00\",\n    \"2004-04-10 00:00:00\",\n    \"2004-04-11 00:00:00\",\n    \"2004-04-17 00:00:00\",\n    \"2004-04-18 00:00:00\",\n    \"2004-04-24 00:00:00\",\n    \"2004-04-25 00:00:00\",\n    \"2004-05-01 00:00:00\",\n    \"2004-05-02 00:00:00\",\n    \"2004-05-08 00:00:00\",\n    \"2004-05-09 00:00:00\",\n    \"2004-05-15 00:00:00\",\n    \"2004-05-16 00:00:00\",\n    \"2004-05-22 00:00:00\",\n    \"2004-05-23 00:00:00\",\n    \"2004-05-29 00:00:00\",\n    \"2004-05-30 00:00:00\",\n    \"2004-06-05 00:00:00\",\n    \"2004-06-06 00:00:00\",\n    \"2004-06-12 00:00:00\",\n    \"2004-06-13 00:00:00\",\n    \"2004-06-19 00:00:00\",\n    \"2004-06-20 00:00:00\",\n    \"2004-06-26 00:00:00\",\n    \"2004-06-27 00:00:00\",\n    \"2004-07-03 00:00:00\",\n    \"2004-07-04 00:00:00\",\n    \"2004-07-10 00:00:00\",\n    \"2004-07-11 00:00:00\",\n    \"2004-07-17 00:00:00\",\n    \"2004-07-18 00:00:00\",\n    \"2004-07-24 00:00:00\",\n    \"2004-07-25 00:00:00\",\n    \"2004-07-31 00:00:00\",\n    \"2004-08-01 00:00:00\",\n    \"2004-08-07 00:00:00\",\n    \"2004-08-08 00:00:00\",\n    \"2004-08-14 00:00:00\",\n    \"2004-08-15 00:00:00\",\n    \"2004-08-21 00:00:00\",\n    \"2004-08-22 00:00:00\",\n    \"2004-08-28 00:00:00\",\n    \"2004-08-29 00:00:00\",\n    \"2004-09-04 00:00:00\",\n    \"2004-09-05 00:00:00\",\n    \"2004-09-11 00:00:00\",\n    \"2004-09-12 00:00:00\",\n    \"2004-09-18 00:00:00\",\n    \"2004-09-19 00:00:00\",\n    \"2004-09-25 00:00:00\",\n    \"2004-09-26 00:00:00\",\n    \"2004-10-02 00:00:00\",\n    \"2004-10-03 00:00:00\",\n    \"2004-10-09 00:00:00\",\n    \"2004-10-10 00:00:00\",\n    \"2004-10-16 00:00:00\",\n    \"2004-10-17 00:00:00\",\n    \"2004-10-23 00:00:00\",\n    \"2004-10-24 00:00:00\",\n    \"2004-10-30 00:00:00\",\n    \"2004-10-31 00:00:00\",\n    \"2004-11-06 00:00:00\",\n    \"2004-11-07 00:00:00\",\n    \"2004-11-13 00:00:00\",\n    \"2004-11-14 00:00:00\",\n    \"2004-11-20 00:00:00\",\n    \"2004-11-21 00:00:00\",\n    \"2004-11-27 00:00:00\",\n    \"2004-11-28 00:00:00\",\n    \"2004-12-04 00:00:00\",\n    \"2004-12-05 00:00:00\",\n    \"2004-12-11 00:00:00\",\n    \"2004-12-12 00:00:00\",\n    \"2004-12-18 00:00:00\",\n    \"2004-12-19 00:00:00\",\n    \"2004-12-25 00:00:00\",\n    \"2004-12-26 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-01-02 00:00:00\",\n    \"2005-01-08 00:00:00\",\n    \"2005-01-09 00:00:00\",\n    \"2005-01-15 00:00:00\",\n    \"2005-01-16 00:00:00\",\n    \"2005-01-22 00:00:00\",\n    \"2005-01-23 00:00:00\",\n    \"2005-01-29 00:00:00\",\n    \"2005-01-30 00:00:00\",\n    \"2005-02-05 00:00:00\",\n    \"2005-02-06 00:00:00\",\n    \"2005-02-12 00:00:00\",\n    \"2005-02-13 00:00:00\",\n    \"2005-02-19 00:00:00\",\n    \"2005-02-20 00:00:00\",\n    \"2005-02-26 00:00:00\",\n    \"2005-02-27 00:00:00\",\n    \"2005-03-05 00:00:00\",\n    \"2005-03-06 00:00:00\",\n    \"2005-03-12 00:00:00\",\n    \"2005-03-13 00:00:00\",\n    \"2005-03-19 00:00:00\",\n    \"2005-03-20 00:00:00\",\n    \"2005-03-26 00:00:00\",\n    \"2005-03-27 00:00:00\",\n    \"2005-04-02 00:00:00\",\n    \"2005-04-03 00:00:00\",\n    \"2005-04-09 00:00:00\",\n    \"2005-04-10 00:00:00\",\n    \"2005-04-16 00:00:00\",\n    \"2005-04-17 00:00:00\",\n    \"2005-04-23 00:00:00\",\n    \"2005-04-24 00:00:00\",\n    \"2005-04-30 00:00:00\",\n    \"2005-05-01 00:00:00\",\n    \"2005-05-07 00:00:00\",\n    \"2005-05-08 00:00:00\",\n    \"2005-05-14 00:00:00\",\n    \"2005-05-15 00:00:00\",\n    \"2005-05-21 00:00:00\",\n    \"2005-05-22 00:00:00\",\n    \"2005-05-28 00:00:00\",\n    \"2005-05-29 00:00:00\",\n    \"2005-06-04 00:00:00\",\n    \"2005-06-05 00:00:00\",\n    \"2005-06-11 00:00:00\",\n    \"2005-06-12 00:00:00\",\n    \"2005-06-18 00:00:00\",\n    \"2005-06-19 00:00:00\",\n    \"2005-06-25 00:00:00\",\n    \"2005-06-26 00:00:00\",\n    \"2005-07-02 00:00:00\",\n    \"2005-07-03 00:00:00\",\n    \"2005-07-09 00:00:00\",\n    \"2005-07-10 00:00:00\",\n    \"2005-07-16 00:00:00\",\n    \"2005-07-17 00:00:00\",\n    \"2005-07-23 00:00:00\",\n    \"2005-07-24 00:00:00\",\n    \"2005-07-30 00:00:00\",\n    \"2005-07-31 00:00:00\",\n    \"2005-08-06 00:00:00\",\n    \"2005-08-07 00:00:00\",\n    \"2005-08-13 00:00:00\",\n    \"2005-08-14 00:00:00\",\n    \"2005-08-20 00:00:00\",\n    \"2005-08-21 00:00:00\",\n    \"2005-08-27 00:00:00\",\n    \"2005-08-28 00:00:00\",\n    \"2005-09-03 00:00:00\",\n    \"2005-09-04 00:00:00\",\n    \"2005-09-10 00:00:00\",\n    \"2005-09-11 00:00:00\",\n    \"2005-09-17 00:00:00\",\n    \"2005-09-18 00:00:00\",\n    \"2005-09-24 00:00:00\",\n    \"2005-09-25 00:00:00\",\n    \"2005-10-01 00:00:00\",\n    \"2005-10-02 00:00:00\",\n    \"2005-10-08 00:00:00\",\n    \"2005-10-09 00:00:00\",\n    \"2005-10-15 00:00:00\",\n    \"2005-10-16 00:00:00\",\n    \"2005-10-22 00:00:00\",\n    \"2005-10-23 00:00:00\",\n    \"2005-10-29 00:00:00\",\n    \"2005-10-30 00:00:00\",\n    \"2005-11-05 00:00:00\",\n    \"2005-11-06 00:00:00\",\n    \"2005-11-12 00:00:00\",\n    \"2005-11-13 00:00:00\",\n    \"2005-11-19 00:00:00\",\n    \"2005-11-20 00:00:00\",\n    \"2005-11-26 00:00:00\",\n    \"2005-11-27 00:00:00\",\n    \"2005-12-03 00:00:00\",\n    \"2005-12-04 00:00:00\",\n    \"2005-12-10 00:00:00\",\n    \"2005-12-11 00:00:00\",\n    \"2005-12-17 00:00:00\",\n    \"2005-12-18 00:00:00\",\n    \"2005-12-24 00:00:00\",\n    \"2005-12-25 00:00:00\",\n    \"2005-12-31 00:00:00\",\n    \"2006-01-01 00:00:00\",\n    \"2006-01-07 00:00:00\",\n    \"2006-01-08 00:00:00\",\n    \"2006-01-14 00:00:00\",\n    \"2006-01-15 00:00:00\",\n    \"2006-01-21 00:00:00\",\n    \"2006-01-22 00:00:00\",\n    \"2006-01-28 00:00:00\",\n    \"2006-01-29 00:00:00\",\n    \"2006-02-04 00:00:00\",\n    \"2006-02-05 00:00:00\",\n    \"2006-02-11 00:00:00\",\n    \"2006-02-12 00:00:00\",\n    \"2006-02-18 00:00:00\",\n    \"2006-02-19 00:00:00\",\n    \"2006-02-25 00:00:00\",\n    \"2006-02-26 00:00:00\",\n    \"2006-03-04 00:00:00\",\n    \"2006-03-05 00:00:00\",\n    \"2006-03-11 00:00:00\",\n    \"2006-03-12 00:00:00\",\n    \"2006-03-18 00:00:00\",\n    \"2006-03-19 00:00:00\",\n    \"2006-03-25 00:00:00\",\n    \"2006-03-26 00:00:00\",\n    \"2006-04-01 00:00:00\",\n    \"2006-04-02 00:00:00\",\n    \"2006-04-08 00:00:00\",\n    \"2006-04-09 00:00:00\",\n    \"2006-04-15 00:00:00\",\n    \"2006-04-16 00:00:00\",\n    \"2006-04-22 00:00:00\",\n    \"2006-04-23 00:00:00\",\n    \"2006-04-29 00:00:00\",\n    \"2006-04-30 00:00:00\",\n    \"2006-05-06 00:00:00\",\n    \"2006-05-07 00:00:00\",\n    \"2006-05-13 00:00:00\",\n    \"2006-05-14 00:00:00\",\n    \"2006-05-20 00:00:00\",\n    \"2006-05-21 00:00:00\",\n    \"2006-05-27 00:00:00\",\n    \"2006-05-28 00:00:00\",\n    \"2006-06-03 00:00:00\",\n    \"2006-06-04 00:00:00\",\n    \"2006-06-10 00:00:00\",\n    \"2006-06-11 00:00:00\",\n    \"2006-06-17 00:00:00\",\n    \"2006-06-18 00:00:00\",\n    \"2006-06-24 00:00:00\",\n    \"2006-06-25 00:00:00\",\n    \"2006-07-01 00:00:00\",\n    \"2006-07-02 00:00:00\",\n    \"2006-07-08 00:00:00\",\n    \"2006-07-09 00:00:00\",\n    \"2006-07-15 00:00:00\",\n    \"2006-07-16 00:00:00\",\n    \"2006-07-22 00:00:00\",\n    \"2006-07-23 00:00:00\",\n    \"2006-07-29 00:00:00\",\n    \"2006-07-30 00:00:00\",\n    \"2006-08-05 00:00:00\",\n    \"2006-08-06 00:00:00\",\n    \"2006-08-12 00:00:00\",\n    \"2006-08-13 00:00:00\",\n    \"2006-08-19 00:00:00\",\n    \"2006-08-20 00:00:00\",\n    \"2006-08-26 00:00:00\",\n    \"2006-08-27 00:00:00\",\n    \"2006-09-02 00:00:00\",\n    \"2006-09-03 00:00:00\",\n    \"2006-09-09 00:00:00\",\n    \"2006-09-10 00:00:00\",\n    \"2006-09-16 00:00:00\",\n    \"2006-09-17 00:00:00\",\n    \"2006-09-23 00:00:00\",\n    \"2006-09-24 00:00:00\",\n    \"2006-09-30 00:00:00\",\n    \"2006-10-01 00:00:00\",\n    \"2006-10-07 00:00:00\",\n    \"2006-10-08 00:00:00\",\n    \"2006-10-14 00:00:00\",\n    \"2006-10-15 00:00:00\",\n    \"2006-10-21 00:00:00\",\n    \"2006-10-22 00:00:00\",\n    \"2006-10-28 00:00:00\",\n    \"2006-10-29 00:00:00\",\n    \"2006-11-04 00:00:00\",\n    \"2006-11-05 00:00:00\",\n    \"2006-11-11 00:00:00\",\n    \"2006-11-12 00:00:00\",\n    \"2006-11-18 00:00:00\",\n    \"2006-11-19 00:00:00\",\n    \"2006-11-25 00:00:00\",\n    \"2006-11-26 00:00:00\",\n    \"2006-12-02 00:00:00\",\n    \"2006-12-03 00:00:00\",\n    \"2006-12-09 00:00:00\",\n    \"2006-12-10 00:00:00\",\n    \"2006-12-16 00:00:00\",\n    \"2006-12-17 00:00:00\",\n    \"2006-12-23 00:00:00\",\n    \"2006-12-24 00:00:00\",\n    \"2006-12-30 00:00:00\",\n    \"2006-12-31 00:00:00\",\n    \"2007-01-06 00:00:00\",\n    \"2007-01-07 00:00:00\",\n    \"2007-01-13 00:00:00\",\n    \"2007-01-14 00:00:00\",\n    \"2007-01-20 00:00:00\",\n    \"2007-01-21 00:00:00\",\n    \"2007-01-27 00:00:00\",\n    \"2007-01-28 00:00:00\",\n    \"2007-02-03 00:00:00\",\n    \"2007-02-04 00:00:00\",\n    \"2007-02-10 00:00:00\",\n    \"2007-02-11 00:00:00\",\n    \"2007-02-17 00:00:00\",\n    \"2007-02-18 00:00:00\",\n    \"2007-02-24 00:00:00\",\n    \"2007-02-25 00:00:00\",\n    \"2007-03-03 00:00:00\",\n    \"2007-03-04 00:00:00\",\n    \"2007-03-10 00:00:00\",\n    \"2007-03-11 00:00:00\",\n    \"2007-03-17 00:00:00\",\n    \"2007-03-18 00:00:00\",\n    \"2007-03-24 00:00:00\",\n    \"2007-03-25 00:00:00\",\n    \"2007-03-31 00:00:00\",\n    \"2007-04-01 00:00:00\",\n    \"2007-04-07 00:00:00\",\n    \"2007-04-08 00:00:00\",\n    \"2007-04-14 00:00:00\",\n    \"2007-04-15 00:00:00\",\n    \"2007-04-21 00:00:00\",\n    \"2007-04-22 00:00:00\",\n    \"2007-04-28 00:00:00\",\n    \"2007-04-29 00:00:00\",\n    \"2007-05-05 00:00:00\",\n    \"2007-05-06 00:00:00\",\n    \"2007-05-12 00:00:00\",\n    \"2007-05-13 00:00:00\",\n    \"2007-05-19 00:00:00\",\n    \"2007-05-20 00:00:00\",\n    \"2007-05-26 00:00:00\",\n    \"2007-05-27 00:00:00\",\n    \"2007-06-02 00:00:00\",\n    \"2007-06-03 00:00:00\",\n    \"2007-06-09 00:00:00\",\n    \"2007-06-10 00:00:00\",\n    \"2007-06-16 00:00:00\",\n    \"2007-06-17 00:00:00\",\n    \"2007-06-23 00:00:00\",\n    \"2007-06-24 00:00:00\",\n    \"2007-06-30 00:00:00\",\n    \"2007-07-01 00:00:00\",\n    \"2007-07-07 00:00:00\",\n    \"2007-07-08 00:00:00\",\n    \"2007-07-14 00:00:00\",\n    \"2007-07-15 00:00:00\",\n    \"2007-07-21 00:00:00\",\n    \"2007-07-22 00:00:00\",\n    \"2007-07-28 00:00:00\",\n    \"2007-07-29 00:00:00\",\n    \"2007-08-04 00:00:00\",\n    \"2007-08-05 00:00:00\",\n    \"2007-08-11 00:00:00\",\n    \"2007-08-12 00:00:00\",\n    \"2007-08-18 00:00:00\",\n    \"2007-08-19 00:00:00\",\n    \"2007-08-25 00:00:00\",\n    \"2007-08-26 00:00:00\",\n    \"2007-09-01 00:00:00\",\n    \"2007-09-02 00:00:00\",\n    \"2007-09-08 00:00:00\",\n    \"2007-09-09 00:00:00\",\n    \"2007-09-15 00:00:00\",\n    \"2007-09-16 00:00:00\",\n    \"2007-09-22 00:00:00\",\n    \"2007-09-23 00:00:00\",\n    \"2007-09-29 00:00:00\",\n    \"2007-09-30 00:00:00\",\n    \"2007-10-06 00:00:00\",\n    \"2007-10-07 00:00:00\",\n    \"2007-10-13 00:00:00\",\n    \"2007-10-14 00:00:00\",\n    \"2007-10-20 00:00:00\",\n    \"2007-10-21 00:00:00\",\n    \"2007-10-27 00:00:00\",\n    \"2007-10-28 00:00:00\",\n    \"2007-11-03 00:00:00\",\n    \"2007-11-04 00:00:00\",\n    \"2007-11-10 00:00:00\",\n    \"2007-11-11 00:00:00\",\n    \"2007-11-17 00:00:00\",\n    \"2007-11-18 00:00:00\",\n    \"2007-11-24 00:00:00\",\n    \"2007-11-25 00:00:00\",\n    \"2007-12-01 00:00:00\",\n    \"2007-12-02 00:00:00\",\n    \"2007-12-08 00:00:00\",\n    \"2007-12-09 00:00:00\",\n    \"2007-12-15 00:00:00\",\n    \"2007-12-16 00:00:00\",\n    \"2007-12-22 00:00:00\",\n    \"2007-12-23 00:00:00\",\n    \"2007-12-29 00:00:00\",\n    \"2007-12-30 00:00:00\",\n    \"2008-01-05 00:00:00\",\n    \"2008-01-06 00:00:00\",\n    \"2008-01-12 00:00:00\",\n    \"2008-01-13 00:00:00\",\n    \"2008-01-19 00:00:00\",\n    \"2008-01-20 00:00:00\",\n    \"2008-01-26 00:00:00\",\n    \"2008-01-27 00:00:00\",\n    \"2008-02-02 00:00:00\",\n    \"2008-02-03 00:00:00\",\n    \"2008-02-09 00:00:00\",\n    \"2008-02-10 00:00:00\",\n    \"2008-02-16 00:00:00\",\n    \"2008-02-17 00:00:00\",\n    \"2008-02-23 00:00:00\",\n    \"2008-02-24 00:00:00\",\n    \"2008-03-01 00:00:00\",\n    \"2008-03-02 00:00:00\",\n    \"2008-03-08 00:00:00\",\n    \"2008-03-09 00:00:00\",\n    \"2008-03-15 00:00:00\",\n    \"2008-03-16 00:00:00\",\n    \"2008-03-22 00:00:00\",\n    \"2008-03-23 00:00:00\",\n    \"2008-03-29 00:00:00\",\n    \"2008-03-30 00:00:00\",\n    \"2008-04-05 00:00:00\",\n    \"2008-04-06 00:00:00\",\n    \"2008-04-12 00:00:00\",\n    \"2008-04-13 00:00:00\",\n    \"2008-04-19 00:00:00\",\n    \"2008-04-20 00:00:00\",\n    \"2008-04-26 00:00:00\",\n    \"2008-04-27 00:00:00\",\n    \"2008-05-03 00:00:00\",\n    \"2008-05-04 00:00:00\",\n    \"2008-05-10 00:00:00\",\n    \"2008-05-11 00:00:00\",\n    \"2008-05-17 00:00:00\",\n    \"2008-05-18 00:00:00\",\n    \"2008-05-24 00:00:00\",\n    \"2008-05-25 00:00:00\",\n    \"2008-05-31 00:00:00\",\n    \"2008-06-01 00:00:00\",\n    \"2008-06-07 00:00:00\",\n    \"2008-06-08 00:00:00\",\n    \"2008-06-14 00:00:00\",\n    \"2008-06-15 00:00:00\",\n    \"2008-06-21 00:00:00\",\n    \"2008-06-22 00:00:00\",\n    \"2008-06-28 00:00:00\",\n    \"2008-06-29 00:00:00\",\n    \"2008-07-05 00:00:00\",\n    \"2008-07-06 00:00:00\",\n    \"2008-07-12 00:00:00\",\n    \"2008-07-13 00:00:00\",\n    \"2008-07-19 00:00:00\",\n    \"2008-07-20 00:00:00\",\n    \"2008-07-26 00:00:00\",\n    \"2008-07-27 00:00:00\",\n    \"2008-08-02 00:00:00\",\n    \"2008-08-03 00:00:00\",\n    \"2008-08-09 00:00:00\",\n    \"2008-08-10 00:00:00\",\n    \"2008-08-16 00:00:00\",\n    \"2008-08-17 00:00:00\",\n    \"2008-08-23 00:00:00\",\n    \"2008-08-24 00:00:00\",\n    \"2008-08-30 00:00:00\",\n    \"2008-08-31 00:00:00\",\n    \"2008-09-06 00:00:00\",\n    \"2008-09-07 00:00:00\",\n    \"2008-09-13 00:00:00\",\n    \"2008-09-14 00:00:00\",\n    \"2008-09-20 00:00:00\",\n    \"2008-09-21 00:00:00\",\n    \"2008-09-27 00:00:00\",\n    \"2008-09-28 00:00:00\",\n    \"2008-10-04 00:00:00\",\n    \"2008-10-05 00:00:00\",\n    \"2008-10-11 00:00:00\",\n    \"2008-10-12 00:00:00\",\n    \"2008-10-18 00:00:00\",\n    \"2008-10-19 00:00:00\",\n    \"2008-10-25 00:00:00\",\n    \"2008-10-26 00:00:00\",\n    \"2008-11-01 00:00:00\",\n    \"2008-11-02 00:00:00\",\n    \"2008-11-08 00:00:00\",\n    \"2008-11-09 00:00:00\",\n    \"2008-11-15 00:00:00\",\n    \"2008-11-16 00:00:00\",\n    \"2008-11-22 00:00:00\",\n    \"2008-11-23 00:00:00\",\n    \"2008-11-29 00:00:00\",\n    \"2008-11-30 00:00:00\",\n    \"2008-12-06 00:00:00\",\n    \"2008-12-07 00:00:00\",\n    \"2008-12-13 00:00:00\",\n    \"2008-12-14 00:00:00\",\n    \"2008-12-20 00:00:00\",\n    \"2008-12-21 00:00:00\",\n    \"2008-12-27 00:00:00\",\n    \"2008-12-28 00:00:00\",\n    \"2009-01-03 00:00:00\",\n    \"2009-01-04 00:00:00\",\n    \"2009-01-10 00:00:00\",\n    \"2009-01-11 00:00:00\",\n    \"2009-01-17 00:00:00\",\n    \"2009-01-18 00:00:00\",\n    \"2009-01-24 00:00:00\",\n    \"2009-01-25 00:00:00\",\n    \"2009-01-31 00:00:00\",\n    \"2009-02-01 00:00:00\",\n    \"2009-02-07 00:00:00\",\n    \"2009-02-08 00:00:00\",\n    \"2009-02-14 00:00:00\",\n    \"2009-02-15 00:00:00\",\n    \"2009-02-21 00:00:00\",\n    \"2009-02-22 00:00:00\",\n    \"2009-02-28 00:00:00\",\n    \"2009-03-01 00:00:00\",\n    \"2009-03-07 00:00:00\",\n    \"2009-03-08 00:00:00\",\n    \"2009-03-14 00:00:00\",\n    \"2009-03-15 00:00:00\",\n    \"2009-03-21 00:00:00\",\n    \"2009-03-22 00:00:00\",\n    \"2009-03-28 00:00:00\",\n    \"2009-03-29 00:00:00\",\n    \"2009-04-04 00:00:00\",\n    \"2009-04-05 00:00:00\",\n    \"2009-04-11 00:00:00\",\n    \"2009-04-12 00:00:00\",\n    \"2009-04-18 00:00:00\",\n    \"2009-04-19 00:00:00\",\n    \"2009-04-25 00:00:00\",\n    \"2009-04-26 00:00:00\",\n    \"2009-05-02 00:00:00\",\n    \"2009-05-03 00:00:00\",\n    \"2009-05-09 00:00:00\",\n    \"2009-05-10 00:00:00\",\n    \"2009-05-16 00:00:00\",\n    \"2009-05-17 00:00:00\",\n    \"2009-05-23 00:00:00\",\n    \"2009-05-24 00:00:00\",\n    \"2009-05-30 00:00:00\",\n    \"2009-05-31 00:00:00\",\n    \"2009-06-06 00:00:00\",\n    \"2009-06-07 00:00:00\",\n    \"2009-06-13 00:00:00\",\n    \"2009-06-14 00:00:00\",\n    \"2009-06-20 00:00:00\",\n    \"2009-06-21 00:00:00\",\n    \"2009-06-27 00:00:00\",\n    \"2009-06-28 00:00:00\",\n    \"2009-07-04 00:00:00\",\n    \"2009-07-05 00:00:00\",\n    \"2009-07-11 00:00:00\",\n    \"2009-07-12 00:00:00\",\n    \"2009-07-18 00:00:00\",\n    \"2009-07-19 00:00:00\",\n    \"2009-07-25 00:00:00\",\n    \"2009-07-26 00:00:00\",\n    \"2009-08-01 00:00:00\",\n    \"2009-08-02 00:00:00\",\n    \"2009-08-08 00:00:00\",\n    \"2009-08-09 00:00:00\",\n    \"2009-08-15 00:00:00\",\n    \"2009-08-16 00:00:00\",\n    \"2009-08-22 00:00:00\",\n    \"2009-08-23 00:00:00\",\n    \"2009-08-29 00:00:00\",\n    \"2009-08-30 00:00:00\",\n    \"2009-09-05 00:00:00\",\n    \"2009-09-06 00:00:00\",\n    \"2009-09-12 00:00:00\",\n    \"2009-09-13 00:00:00\",\n    \"2009-09-19 00:00:00\",\n    \"2009-09-20 00:00:00\",\n    \"2009-09-26 00:00:00\",\n    \"2009-09-27 00:00:00\",\n    \"2009-10-03 00:00:00\",\n    \"2009-10-04 00:00:00\",\n    \"2009-10-10 00:00:00\",\n    \"2009-10-11 00:00:00\",\n    \"2009-10-17 00:00:00\",\n    \"2009-10-18 00:00:00\",\n    \"2009-10-24 00:00:00\",\n    \"2009-10-25 00:00:00\",\n    \"2009-10-31 00:00:00\",\n    \"2009-11-01 00:00:00\",\n    \"2009-11-07 00:00:00\",\n    \"2009-11-08 00:00:00\",\n    \"2009-11-14 00:00:00\",\n    \"2009-11-15 00:00:00\",\n    \"2009-11-21 00:00:00\",\n    \"2009-11-22 00:00:00\",\n    \"2009-11-28 00:00:00\",\n    \"2009-11-29 00:00:00\",\n    \"2009-12-05 00:00:00\",\n    \"2009-12-06 00:00:00\",\n    \"2009-12-12 00:00:00\",\n    \"2009-12-13 00:00:00\",\n    \"2009-12-19 00:00:00\",\n    \"2009-12-20 00:00:00\",\n    \"2009-12-26 00:00:00\",\n    \"2009-12-27 00:00:00\",\n    \"2010-01-02 00:00:00\",\n    \"2010-01-03 00:00:00\",\n    \"2010-01-09 00:00:00\",\n    \"2010-01-10 00:00:00\",\n    \"2010-01-16 00:00:00\",\n    \"2010-01-17 00:00:00\",\n    \"2010-01-23 00:00:00\",\n    \"2010-01-24 00:00:00\",\n    \"2010-01-30 00:00:00\",\n    \"2010-01-31 00:00:00\",\n    \"2010-02-06 00:00:00\",\n    \"2010-02-07 00:00:00\",\n    \"2010-02-13 00:00:00\",\n    \"2010-02-14 00:00:00\",\n    \"2010-02-20 00:00:00\",\n    \"2010-02-21 00:00:00\",\n    \"2010-02-27 00:00:00\",\n    \"2010-02-28 00:00:00\",\n    \"2010-03-06 00:00:00\",\n    \"2010-03-07 00:00:00\",\n    \"2010-03-13 00:00:00\",\n    \"2010-03-14 00:00:00\",\n    \"2010-03-20 00:00:00\",\n    \"2010-03-21 00:00:00\",\n    \"2010-03-27 00:00:00\",\n    \"2010-03-28 00:00:00\",\n    \"2010-04-03 00:00:00\",\n    \"2010-04-04 00:00:00\",\n    \"2010-04-10 00:00:00\",\n    \"2010-04-11 00:00:00\",\n    \"2010-04-17 00:00:00\",\n    \"2010-04-18 00:00:00\",\n    \"2010-04-24 00:00:00\",\n    \"2010-04-25 00:00:00\",\n    \"2010-05-01 00:00:00\",\n    \"2010-05-02 00:00:00\",\n    \"2010-05-08 00:00:00\",\n    \"2010-05-09 00:00:00\",\n    \"2010-05-15 00:00:00\",\n    \"2010-05-16 00:00:00\",\n    \"2010-05-22 00:00:00\",\n    \"2010-05-23 00:00:00\",\n    \"2010-05-29 00:00:00\",\n    \"2010-05-30 00:00:00\",\n    \"2010-06-05 00:00:00\",\n    \"2010-06-06 00:00:00\",\n    \"2010-06-12 00:00:00\",\n    \"2010-06-13 00:00:00\",\n    \"2010-06-19 00:00:00\",\n    \"2010-06-20 00:00:00\",\n    \"2010-06-26 00:00:00\",\n    \"2010-06-27 00:00:00\",\n    \"2010-07-03 00:00:00\",\n    \"2010-07-04 00:00:00\",\n    \"2010-07-10 00:00:00\",\n    \"2010-07-11 00:00:00\",\n    \"2010-07-17 00:00:00\",\n    \"2010-07-18 00:00:00\",\n    \"2010-07-24 00:00:00\",\n    \"2010-07-25 00:00:00\",\n    \"2010-07-31 00:00:00\",\n    \"2010-08-01 00:00:00\",\n    \"2010-08-07 00:00:00\",\n    \"2010-08-08 00:00:00\",\n    \"2010-08-14 00:00:00\",\n    \"2010-08-15 00:00:00\",\n    \"2010-08-21 00:00:00\",\n    \"2010-08-22 00:00:00\",\n    \"2010-08-28 00:00:00\",\n    \"2010-08-29 00:00:00\",\n    \"2010-09-04 00:00:00\",\n    \"2010-09-05 00:00:00\",\n    \"2010-09-11 00:00:00\",\n    \"2010-09-12 00:00:00\",\n    \"2010-09-18 00:00:00\",\n    \"2010-09-19 00:00:00\",\n    \"2010-09-25 00:00:00\",\n    \"2010-09-26 00:00:00\",\n    \"2010-10-02 00:00:00\",\n    \"2010-10-03 00:00:00\",\n    \"2010-10-09 00:00:00\",\n    \"2010-10-10 00:00:00\",\n    \"2010-10-16 00:00:00\",\n    \"2010-10-17 00:00:00\",\n    \"2010-10-23 00:00:00\",\n    \"2010-10-24 00:00:00\",\n    \"2010-10-30 00:00:00\",\n    \"2010-10-31 00:00:00\",\n    \"2010-11-06 00:00:00\",\n    \"2010-11-07 00:00:00\",\n    \"2010-11-13 00:00:00\",\n    \"2010-11-14 00:00:00\",\n    \"2010-11-20 00:00:00\",\n    \"2010-11-21 00:00:00\",\n    \"2010-11-27 00:00:00\",\n    \"2010-11-28 00:00:00\",\n    \"2010-12-04 00:00:00\",\n    \"2010-12-05 00:00:00\",\n    \"2010-12-11 00:00:00\",\n    \"2010-12-12 00:00:00\",\n    \"2010-12-18 00:00:00\",\n    \"2010-12-19 00:00:00\",\n    \"2010-12-25 00:00:00\",\n    \"2010-12-26 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-01-02 00:00:00\",\n    \"2011-01-08 00:00:00\",\n    \"2011-01-09 00:00:00\",\n    \"2011-01-15 00:00:00\",\n    \"2011-01-16 00:00:00\",\n    \"2011-01-22 00:00:00\",\n    \"2011-01-23 00:00:00\",\n    \"2011-01-29 00:00:00\",\n    \"2011-01-30 00:00:00\",\n    \"2011-02-05 00:00:00\",\n    \"2011-02-06 00:00:00\",\n    \"2011-02-12 00:00:00\",\n    \"2011-02-13 00:00:00\",\n    \"2011-02-19 00:00:00\",\n    \"2011-02-20 00:00:00\",\n    \"2011-02-26 00:00:00\",\n    \"2011-02-27 00:00:00\",\n    \"2011-03-05 00:00:00\",\n    \"2011-03-06 00:00:00\",\n    \"2011-03-12 00:00:00\",\n    \"2011-03-13 00:00:00\",\n    \"2011-03-19 00:00:00\",\n    \"2011-03-20 00:00:00\",\n    \"2011-03-26 00:00:00\",\n    \"2011-03-27 00:00:00\",\n    \"2011-04-02 00:00:00\",\n    \"2011-04-03 00:00:00\",\n    \"2011-04-09 00:00:00\",\n    \"2011-04-10 00:00:00\",\n    \"2011-04-16 00:00:00\",\n    \"2011-04-17 00:00:00\",\n    \"2011-04-23 00:00:00\",\n    \"2011-04-24 00:00:00\",\n    \"2011-04-30 00:00:00\",\n    \"2011-05-01 00:00:00\",\n    \"2011-05-07 00:00:00\",\n    \"2011-05-08 00:00:00\",\n    \"2011-05-14 00:00:00\",\n    \"2011-05-15 00:00:00\",\n    \"2011-05-21 00:00:00\",\n    \"2011-05-22 00:00:00\",\n    \"2011-05-28 00:00:00\",\n    \"2011-05-29 00:00:00\",\n    \"2011-06-04 00:00:00\",\n    \"2011-06-05 00:00:00\",\n    \"2011-06-11 00:00:00\",\n    \"2011-06-12 00:00:00\",\n    \"2011-06-18 00:00:00\",\n    \"2011-06-19 00:00:00\",\n    \"2011-06-25 00:00:00\",\n    \"2011-06-26 00:00:00\",\n    \"2011-07-02 00:00:00\",\n    \"2011-07-03 00:00:00\",\n    \"2011-07-09 00:00:00\",\n    \"2011-07-10 00:00:00\",\n    \"2011-07-16 00:00:00\",\n    \"2011-07-17 00:00:00\",\n    \"2011-07-23 00:00:00\",\n    \"2011-07-24 00:00:00\",\n    \"2011-07-30 00:00:00\",\n    \"2011-07-31 00:00:00\",\n    \"2011-08-06 00:00:00\",\n    \"2011-08-07 00:00:00\",\n    \"2011-08-13 00:00:00\",\n    \"2011-08-14 00:00:00\",\n    \"2011-08-20 00:00:00\",\n    \"2011-08-21 00:00:00\",\n    \"2011-08-27 00:00:00\",\n    \"2011-08-28 00:00:00\",\n    \"2011-09-03 00:00:00\",\n    \"2011-09-04 00:00:00\",\n    \"2011-09-10 00:00:00\",\n    \"2011-09-11 00:00:00\",\n    \"2011-09-17 00:00:00\",\n    \"2011-09-18 00:00:00\",\n    \"2011-09-24 00:00:00\",\n    \"2011-09-25 00:00:00\",\n    \"2011-10-01 00:00:00\",\n    \"2011-10-02 00:00:00\",\n    \"2011-10-08 00:00:00\",\n    \"2011-10-09 00:00:00\",\n    \"2011-10-15 00:00:00\",\n    \"2011-10-16 00:00:00\",\n    \"2011-10-22 00:00:00\",\n    \"2011-10-23 00:00:00\",\n    \"2011-10-29 00:00:00\",\n    \"2011-10-30 00:00:00\",\n    \"2011-11-05 00:00:00\",\n    \"2011-11-06 00:00:00\",\n    \"2011-11-12 00:00:00\",\n    \"2011-11-13 00:00:00\",\n    \"2011-11-19 00:00:00\",\n    \"2011-11-20 00:00:00\",\n    \"2011-11-26 00:00:00\",\n    \"2011-11-27 00:00:00\",\n    \"2011-12-03 00:00:00\",\n    \"2011-12-04 00:00:00\",\n    \"2011-12-10 00:00:00\",\n    \"2011-12-11 00:00:00\",\n    \"2011-12-17 00:00:00\",\n    \"2011-12-18 00:00:00\",\n    \"2011-12-24 00:00:00\",\n    \"2011-12-25 00:00:00\",\n    \"2011-12-31 00:00:00\",\n    \"2012-01-01 00:00:00\",\n    \"2012-01-07 00:00:00\",\n    \"2012-01-08 00:00:00\",\n    \"2012-01-14 00:00:00\",\n    \"2012-01-15 00:00:00\",\n    \"2012-01-21 00:00:00\",\n    \"2012-01-22 00:00:00\",\n    \"2012-01-28 00:00:00\",\n    \"2012-01-29 00:00:00\",\n    \"2012-02-04 00:00:00\",\n    \"2012-02-05 00:00:00\",\n    \"2012-02-11 00:00:00\",\n    \"2012-02-12 00:00:00\",\n    \"2012-02-18 00:00:00\",\n    \"2012-02-19 00:00:00\",\n    \"2012-02-25 00:00:00\",\n    \"2012-02-26 00:00:00\",\n    \"2012-03-03 00:00:00\",\n    \"2012-03-04 00:00:00\",\n    \"2012-03-10 00:00:00\",\n    \"2012-03-11 00:00:00\",\n    \"2012-03-17 00:00:00\",\n    \"2012-03-18 00:00:00\",\n    \"2012-03-24 00:00:00\",\n    \"2012-03-25 00:00:00\",\n    \"2012-03-31 00:00:00\",\n    \"2012-04-01 00:00:00\",\n    \"2012-04-07 00:00:00\",\n    \"2012-04-08 00:00:00\",\n    \"2012-04-14 00:00:00\",\n    \"2012-04-15 00:00:00\",\n    \"2012-04-21 00:00:00\",\n    \"2012-04-22 00:00:00\",\n    \"2012-04-28 00:00:00\",\n    \"2012-04-29 00:00:00\",\n    \"2012-05-05 00:00:00\",\n    \"2012-05-06 00:00:00\",\n    \"2012-05-12 00:00:00\",\n    \"2012-05-13 00:00:00\",\n    \"2012-05-19 00:00:00\",\n    \"2012-05-20 00:00:00\",\n    \"2012-05-26 00:00:00\",\n    \"2012-05-27 00:00:00\",\n    \"2012-06-02 00:00:00\",\n    \"2012-06-03 00:00:00\",\n    \"2012-06-09 00:00:00\",\n    \"2012-06-10 00:00:00\",\n    \"2012-06-16 00:00:00\",\n    \"2012-06-17 00:00:00\",\n    \"2012-06-23 00:00:00\",\n    \"2012-06-24 00:00:00\",\n    \"2012-06-30 00:00:00\",\n    \"2012-07-01 00:00:00\",\n    \"2012-07-07 00:00:00\",\n    \"2012-07-08 00:00:00\",\n    \"2012-07-14 00:00:00\",\n    \"2012-07-15 00:00:00\",\n    \"2012-07-21 00:00:00\",\n    \"2012-07-22 00:00:00\",\n    \"2012-07-28 00:00:00\",\n    \"2012-07-29 00:00:00\",\n    \"2012-08-04 00:00:00\",\n    \"2012-08-05 00:00:00\",\n    \"2012-08-11 00:00:00\",\n    \"2012-08-12 00:00:00\",\n    \"2012-08-18 00:00:00\",\n    \"2012-08-19 00:00:00\",\n    \"2012-08-25 00:00:00\",\n    \"2012-08-26 00:00:00\",\n    \"2012-09-01 00:00:00\",\n    \"2012-09-02 00:00:00\",\n    \"2012-09-08 00:00:00\",\n    \"2012-09-09 00:00:00\",\n    \"2012-09-15 00:00:00\",\n    \"2012-09-16 00:00:00\",\n    \"2012-09-22 00:00:00\",\n    \"2012-09-23 00:00:00\",\n    \"2012-09-29 00:00:00\",\n    \"2012-09-30 00:00:00\",\n    \"2012-10-06 00:00:00\",\n    \"2012-10-07 00:00:00\",\n    \"2012-10-13 00:00:00\",\n    \"2012-10-14 00:00:00\",\n    \"2012-10-20 00:00:00\",\n    \"2012-10-21 00:00:00\",\n    \"2012-10-27 00:00:00\",\n    \"2012-10-28 00:00:00\",\n    \"2012-11-03 00:00:00\",\n    \"2012-11-04 00:00:00\",\n    \"2012-11-10 00:00:00\",\n    \"2012-11-11 00:00:00\",\n    \"2012-11-17 00:00:00\",\n    \"2012-11-18 00:00:00\",\n    \"2012-11-24 00:00:00\",\n    \"2012-11-25 00:00:00\",\n    \"2012-12-01 00:00:00\",\n    \"2012-12-02 00:00:00\",\n    \"2012-12-08 00:00:00\",\n    \"2012-12-09 00:00:00\",\n    \"2012-12-15 00:00:00\",\n    \"2012-12-16 00:00:00\",\n    \"2012-12-22 00:00:00\",\n    \"2012-12-23 00:00:00\",\n    \"2012-12-29 00:00:00\",\n    \"2012-12-30 00:00:00\",\n    \"2013-01-05 00:00:00\",\n    \"2013-01-06 00:00:00\",\n    \"2013-01-12 00:00:00\",\n    \"2013-01-13 00:00:00\",\n    \"2013-01-19 00:00:00\",\n    \"2013-01-20 00:00:00\",\n    \"2013-01-26 00:00:00\",\n    \"2013-01-27 00:00:00\",\n    \"2013-02-02 00:00:00\",\n    \"2013-02-03 00:00:00\",\n    \"2013-02-09 00:00:00\",\n    \"2013-02-10 00:00:00\",\n    \"2013-02-16 00:00:00\",\n    \"2013-02-17 00:00:00\",\n    \"2013-02-23 00:00:00\",\n    \"2013-02-24 00:00:00\",\n    \"2013-03-02 00:00:00\",\n    \"2013-03-03 00:00:00\",\n    \"2013-03-09 00:00:00\",\n    \"2013-03-10 00:00:00\",\n    \"2013-03-16 00:00:00\",\n    \"2013-03-17 00:00:00\",\n    \"2013-03-23 00:00:00\",\n    \"2013-03-24 00:00:00\",\n    \"2013-03-30 00:00:00\",\n    \"2013-03-31 00:00:00\",\n    \"2013-04-06 00:00:00\",\n    \"2013-04-07 00:00:00\",\n    \"2013-04-13 00:00:00\",\n    \"2013-04-14 00:00:00\",\n    \"2013-04-20 00:00:00\",\n    \"2013-04-21 00:00:00\",\n    \"2013-04-27 00:00:00\",\n    \"2013-04-28 00:00:00\",\n    \"2013-05-04 00:00:00\",\n    \"2013-05-05 00:00:00\",\n    \"2013-05-11 00:00:00\",\n    \"2013-05-12 00:00:00\",\n    \"2013-05-18 00:00:00\",\n    \"2013-05-19 00:00:00\",\n    \"2013-05-25 00:00:00\",\n    \"2013-05-26 00:00:00\",\n    \"2013-06-01 00:00:00\",\n    \"2013-06-02 00:00:00\",\n    \"2013-06-08 00:00:00\",\n    \"2013-06-09 00:00:00\",\n    \"2013-06-15 00:00:00\",\n    \"2013-06-16 00:00:00\",\n    \"2013-06-22 00:00:00\",\n    \"2013-06-23 00:00:00\",\n    \"2013-06-29 00:00:00\",\n    \"2013-06-30 00:00:00\",\n    \"2013-07-06 00:00:00\",\n    \"2013-07-07 00:00:00\",\n    \"2013-07-13 00:00:00\",\n    \"2013-07-14 00:00:00\",\n    \"2013-07-20 00:00:00\",\n    \"2013-07-21 00:00:00\",\n    \"2013-07-27 00:00:00\",\n    \"2013-07-28 00:00:00\",\n    \"2013-08-03 00:00:00\",\n    \"2013-08-04 00:00:00\",\n    \"2013-08-10 00:00:00\",\n    \"2013-08-11 00:00:00\",\n    \"2013-08-17 00:00:00\",\n    \"2013-08-18 00:00:00\",\n    \"2013-08-24 00:00:00\",\n    \"2013-08-25 00:00:00\",\n    \"2013-08-31 00:00:00\",\n    \"2013-09-01 00:00:00\",\n    \"2013-09-07 00:00:00\",\n    \"2013-09-08 00:00:00\",\n    \"2013-09-14 00:00:00\",\n    \"2013-09-15 00:00:00\",\n    \"2013-09-21 00:00:00\",\n    \"2013-09-22 00:00:00\",\n    \"2013-09-28 00:00:00\",\n    \"2013-09-29 00:00:00\",\n    \"2013-10-05 00:00:00\",\n    \"2013-10-06 00:00:00\",\n    \"2013-10-12 00:00:00\",\n    \"2013-10-13 00:00:00\",\n    \"2013-10-19 00:00:00\",\n    \"2013-10-20 00:00:00\",\n    \"2013-10-26 00:00:00\",\n    \"2013-10-27 00:00:00\",\n    \"2013-11-02 00:00:00\",\n    \"2013-11-03 00:00:00\",\n    \"2013-11-09 00:00:00\",\n    \"2013-11-10 00:00:00\",\n    \"2013-11-16 00:00:00\",\n    \"2013-11-17 00:00:00\",\n    \"2013-11-23 00:00:00\",\n    \"2013-11-24 00:00:00\",\n    \"2013-11-30 00:00:00\",\n    \"2013-12-01 00:00:00\",\n    \"2013-12-07 00:00:00\",\n    \"2013-12-08 00:00:00\",\n    \"2013-12-14 00:00:00\",\n    \"2013-12-15 00:00:00\",\n    \"2013-12-21 00:00:00\",\n    \"2013-12-22 00:00:00\",\n    \"2013-12-28 00:00:00\",\n    \"2013-12-29 00:00:00\",\n    \"2014-01-04 00:00:00\",\n    \"2014-01-05 00:00:00\",\n    \"2014-01-11 00:00:00\",\n    \"2014-01-12 00:00:00\",\n    \"2014-01-18 00:00:00\",\n    \"2014-01-19 00:00:00\",\n    \"2014-01-25 00:00:00\",\n    \"2014-01-26 00:00:00\",\n    \"2014-02-01 00:00:00\",\n    \"2014-02-02 00:00:00\",\n    \"2014-02-08 00:00:00\",\n    \"2014-02-09 00:00:00\",\n    \"2014-02-15 00:00:00\",\n    \"2014-02-16 00:00:00\",\n    \"2014-02-22 00:00:00\",\n    \"2014-02-23 00:00:00\",\n    \"2014-03-01 00:00:00\",\n    \"2014-03-02 00:00:00\",\n    \"2014-03-08 00:00:00\",\n    \"2014-03-09 00:00:00\",\n    \"2014-03-15 00:00:00\",\n    \"2014-03-16 00:00:00\",\n    \"2014-03-22 00:00:00\",\n    \"2014-03-23 00:00:00\",\n    \"2014-03-29 00:00:00\",\n    \"2014-03-30 00:00:00\",\n    \"2014-04-05 00:00:00\",\n    \"2014-04-06 00:00:00\",\n    \"2014-04-12 00:00:00\",\n    \"2014-04-13 00:00:00\",\n    \"2014-04-19 00:00:00\",\n    \"2014-04-20 00:00:00\",\n    \"2014-04-26 00:00:00\",\n    \"2014-04-27 00:00:00\",\n    \"2014-05-03 00:00:00\",\n    \"2014-05-04 00:00:00\",\n    \"2014-05-10 00:00:00\",\n    \"2014-05-11 00:00:00\",\n    \"2014-05-17 00:00:00\",\n    \"2014-05-18 00:00:00\",\n    \"2014-05-24 00:00:00\",\n    \"2014-05-25 00:00:00\",\n    \"2014-05-31 00:00:00\",\n    \"2014-06-01 00:00:00\",\n    \"2014-06-07 00:00:00\",\n    \"2014-06-08 00:00:00\",\n    \"2014-06-14 00:00:00\",\n    \"2014-06-15 00:00:00\",\n    \"2014-06-21 00:00:00\",\n    \"2014-06-22 00:00:00\",\n    \"2014-06-28 00:00:00\",\n    \"2014-06-29 00:00:00\",\n    \"2014-07-05 00:00:00\",\n    \"2014-07-06 00:00:00\",\n    \"2014-07-12 00:00:00\",\n    \"2014-07-13 00:00:00\",\n    \"2014-07-19 00:00:00\",\n    \"2014-07-20 00:00:00\",\n    \"2014-07-26 00:00:00\",\n    \"2014-07-27 00:00:00\",\n    \"2014-08-02 00:00:00\",\n    \"2014-08-03 00:00:00\",\n    \"2014-08-09 00:00:00\",\n    \"2014-08-10 00:00:00\",\n    \"2014-08-16 00:00:00\",\n    \"2014-08-17 00:00:00\",\n    \"2014-08-23 00:00:00\",\n    \"2014-08-24 00:00:00\",\n    \"2014-08-30 00:00:00\",\n    \"2014-08-31 00:00:00\",\n    \"2014-09-06 00:00:00\",\n    \"2014-09-07 00:00:00\",\n    \"2014-09-13 00:00:00\",\n    \"2014-09-14 00:00:00\",\n    \"2014-09-20 00:00:00\",\n    \"2014-09-21 00:00:00\",\n    \"2014-09-27 00:00:00\",\n    \"2014-09-28 00:00:00\",\n    \"2014-10-04 00:00:00\",\n    \"2014-10-05 00:00:00\",\n    \"2014-10-11 00:00:00\",\n    \"2014-10-12 00:00:00\",\n    \"2014-10-18 00:00:00\",\n    \"2014-10-19 00:00:00\",\n    \"2014-10-25 00:00:00\",\n    \"2014-10-26 00:00:00\",\n    \"2014-11-01 00:00:00\",\n    \"2014-11-02 00:00:00\",\n    \"2014-11-08 00:00:00\",\n    \"2014-11-09 00:00:00\",\n    \"2014-11-15 00:00:00\",\n    \"2014-11-16 00:00:00\",\n    \"2014-11-22 00:00:00\",\n    \"2014-11-23 00:00:00\",\n    \"2014-11-29 00:00:00\",\n    \"2014-11-30 00:00:00\",\n    \"2014-12-06 00:00:00\",\n    \"2014-12-07 00:00:00\",\n    \"2014-12-13 00:00:00\",\n    \"2014-12-14 00:00:00\",\n    \"2014-12-20 00:00:00\",\n    \"2014-12-21 00:00:00\",\n    \"2014-12-27 00:00:00\",\n    \"2014-12-28 00:00:00\",\n    \"2015-01-03 00:00:00\",\n    \"2015-01-04 00:00:00\",\n    \"2015-01-10 00:00:00\",\n    \"2015-01-11 00:00:00\",\n    \"2015-01-17 00:00:00\",\n    \"2015-01-18 00:00:00\",\n    \"2015-01-24 00:00:00\",\n    \"2015-01-25 00:00:00\",\n    \"2015-01-31 00:00:00\",\n    \"2015-02-01 00:00:00\",\n    \"2015-02-07 00:00:00\",\n    \"2015-02-08 00:00:00\",\n    \"2015-02-14 00:00:00\",\n    \"2015-02-15 00:00:00\",\n    \"2015-02-21 00:00:00\",\n    \"2015-02-22 00:00:00\",\n    \"2015-02-28 00:00:00\",\n    \"2015-03-01 00:00:00\",\n    \"2015-03-07 00:00:00\",\n    \"2015-03-08 00:00:00\",\n    \"2015-03-14 00:00:00\",\n    \"2015-03-15 00:00:00\",\n    \"2015-03-21 00:00:00\",\n    \"2015-03-22 00:00:00\",\n    \"2015-03-28 00:00:00\",\n    \"2015-03-29 00:00:00\",\n    \"2015-04-04 00:00:00\",\n    \"2015-04-05 00:00:00\",\n    \"2015-04-11 00:00:00\",\n    \"2015-04-12 00:00:00\",\n    \"2015-04-18 00:00:00\",\n    \"2015-04-19 00:00:00\",\n    \"2015-04-25 00:00:00\",\n    \"2015-04-26 00:00:00\",\n    \"2015-05-02 00:00:00\",\n    \"2015-05-03 00:00:00\",\n    \"2015-05-09 00:00:00\",\n    \"2015-05-10 00:00:00\",\n    \"2015-05-16 00:00:00\",\n    \"2015-05-17 00:00:00\",\n    \"2015-05-23 00:00:00\",\n    \"2015-05-24 00:00:00\",\n    \"2015-05-30 00:00:00\",\n    \"2015-05-31 00:00:00\",\n    \"2015-06-06 00:00:00\",\n    \"2015-06-07 00:00:00\",\n    \"2015-06-13 00:00:00\",\n    \"2015-06-14 00:00:00\",\n    \"2015-06-20 00:00:00\",\n    \"2015-06-21 00:00:00\",\n    \"2015-06-27 00:00:00\",\n    \"2015-06-28 00:00:00\",\n    \"2015-07-04 00:00:00\",\n    \"2015-07-05 00:00:00\",\n    \"2015-07-11 00:00:00\",\n    \"2015-07-12 00:00:00\",\n    \"2015-07-18 00:00:00\",\n    \"2015-07-19 00:00:00\",\n    \"2015-07-25 00:00:00\",\n    \"2015-07-26 00:00:00\",\n    \"2015-08-01 00:00:00\",\n    \"2015-08-02 00:00:00\",\n    \"2015-08-08 00:00:00\",\n    \"2015-08-09 00:00:00\",\n    \"2015-08-15 00:00:00\",\n    \"2015-08-16 00:00:00\",\n    \"2015-08-22 00:00:00\",\n    \"2015-08-23 00:00:00\",\n    \"2015-08-29 00:00:00\",\n    \"2015-08-30 00:00:00\",\n    \"2015-09-05 00:00:00\",\n    \"2015-09-06 00:00:00\",\n    \"2015-09-12 00:00:00\",\n    \"2015-09-13 00:00:00\",\n    \"2015-09-19 00:00:00\",\n    \"2015-09-20 00:00:00\",\n    \"2015-09-26 00:00:00\",\n    \"2015-09-27 00:00:00\",\n    \"2015-10-03 00:00:00\",\n    \"2015-10-04 00:00:00\",\n    \"2015-10-10 00:00:00\",\n    \"2015-10-11 00:00:00\",\n    \"2015-10-17 00:00:00\",\n    \"2015-10-18 00:00:00\",\n    \"2015-10-24 00:00:00\",\n    \"2015-10-25 00:00:00\",\n    \"2015-10-31 00:00:00\",\n    \"2015-11-01 00:00:00\",\n    \"2015-11-07 00:00:00\",\n    \"2015-11-08 00:00:00\",\n    \"2015-11-14 00:00:00\",\n    \"2015-11-15 00:00:00\",\n    \"2015-11-21 00:00:00\",\n    \"2015-11-22 00:00:00\",\n    \"2015-11-28 00:00:00\",\n    \"2015-11-29 00:00:00\",\n    \"2015-12-05 00:00:00\",\n    \"2015-12-06 00:00:00\",\n    \"2015-12-12 00:00:00\",\n    \"2015-12-13 00:00:00\",\n    \"2015-12-19 00:00:00\",\n    \"2015-12-20 00:00:00\",\n    \"2015-12-26 00:00:00\",\n    \"2015-12-27 00:00:00\",\n    \"2016-01-02 00:00:00\",\n    \"2016-01-03 00:00:00\",\n    \"2016-01-09 00:00:00\",\n    \"2016-01-10 00:00:00\",\n    \"2016-01-16 00:00:00\",\n    \"2016-01-17 00:00:00\",\n    \"2016-01-23 00:00:00\",\n    \"2016-01-24 00:00:00\",\n    \"2016-01-30 00:00:00\",\n    \"2016-01-31 00:00:00\",\n    \"2016-02-06 00:00:00\",\n    \"2016-02-07 00:00:00\",\n    \"2016-02-13 00:00:00\",\n    \"2016-02-14 00:00:00\",\n    \"2016-02-20 00:00:00\",\n    \"2016-02-21 00:00:00\",\n    \"2016-02-27 00:00:00\",\n    \"2016-02-28 00:00:00\",\n    \"2016-03-05 00:00:00\",\n    \"2016-03-06 00:00:00\",\n    \"2016-03-12 00:00:00\",\n    \"2016-03-13 00:00:00\",\n    \"2016-03-19 00:00:00\",\n    \"2016-03-20 00:00:00\",\n    \"2016-03-26 00:00:00\",\n    \"2016-03-27 00:00:00\",\n    \"2016-04-02 00:00:00\",\n    \"2016-04-03 00:00:00\",\n    \"2016-04-09 00:00:00\",\n    \"2016-04-10 00:00:00\",\n    \"2016-04-16 00:00:00\",\n    \"2016-04-17 00:00:00\",\n    \"2016-04-23 00:00:00\",\n    \"2016-04-24 00:00:00\",\n    \"2016-04-30 00:00:00\",\n    \"2016-05-01 00:00:00\",\n    \"2016-05-07 00:00:00\",\n    \"2016-05-08 00:00:00\",\n    \"2016-05-14 00:00:00\",\n    \"2016-05-15 00:00:00\",\n    \"2016-05-21 00:00:00\",\n    \"2016-05-22 00:00:00\",\n    \"2016-05-28 00:00:00\",\n    \"2016-05-29 00:00:00\",\n    \"2016-06-04 00:00:00\",\n    \"2016-06-05 00:00:00\",\n    \"2016-06-11 00:00:00\",\n    \"2016-06-12 00:00:00\",\n    \"2016-06-18 00:00:00\",\n    \"2016-06-19 00:00:00\",\n    \"2016-06-25 00:00:00\",\n    \"2016-06-26 00:00:00\",\n    \"2016-07-02 00:00:00\",\n    \"2016-07-03 00:00:00\",\n    \"2016-07-09 00:00:00\",\n    \"2016-07-10 00:00:00\",\n    \"2016-07-16 00:00:00\",\n    \"2016-07-17 00:00:00\",\n    \"2016-07-23 00:00:00\",\n    \"2016-07-24 00:00:00\",\n    \"2016-07-30 00:00:00\",\n    \"2016-07-31 00:00:00\",\n    \"2016-08-06 00:00:00\",\n    \"2016-08-07 00:00:00\",\n    \"2016-08-13 00:00:00\",\n    \"2016-08-14 00:00:00\",\n    \"2016-08-20 00:00:00\",\n    \"2016-08-21 00:00:00\",\n    \"2016-08-27 00:00:00\",\n    \"2016-08-28 00:00:00\",\n    \"2016-09-03 00:00:00\",\n    \"2016-09-04 00:00:00\",\n    \"2016-09-10 00:00:00\",\n    \"2016-09-11 00:00:00\",\n    \"2016-09-17 00:00:00\",\n    \"2016-09-18 00:00:00\",\n    \"2016-09-24 00:00:00\",\n    \"2016-09-25 00:00:00\",\n    \"2016-10-01 00:00:00\",\n    \"2016-10-02 00:00:00\",\n    \"2016-10-08 00:00:00\",\n    \"2016-10-09 00:00:00\",\n    \"2016-10-15 00:00:00\",\n    \"2016-10-16 00:00:00\",\n    \"2016-10-22 00:00:00\",\n    \"2016-10-23 00:00:00\",\n    \"2016-10-29 00:00:00\",\n    \"2016-10-30 00:00:00\",\n    \"2016-11-05 00:00:00\",\n    \"2016-11-06 00:00:00\",\n    \"2016-11-12 00:00:00\",\n    \"2016-11-13 00:00:00\",\n    \"2016-11-19 00:00:00\",\n    \"2016-11-20 00:00:00\",\n    \"2016-11-26 00:00:00\",\n    \"2016-11-27 00:00:00\",\n    \"2016-12-03 00:00:00\",\n    \"2016-12-04 00:00:00\",\n    \"2016-12-10 00:00:00\",\n    \"2016-12-11 00:00:00\",\n    \"2016-12-17 00:00:00\",\n    \"2016-12-18 00:00:00\",\n    \"2016-12-24 00:00:00\",\n    \"2016-12-25 00:00:00\",\n    \"2016-12-31 00:00:00\",\n    \"2017-01-01 00:00:00\",\n    \"2017-01-07 00:00:00\",\n    \"2017-01-08 00:00:00\",\n    \"2017-01-14 00:00:00\",\n    \"2017-01-15 00:00:00\",\n    \"2017-01-21 00:00:00\",\n    \"2017-01-22 00:00:00\",\n    \"2017-01-28 00:00:00\",\n    \"2017-01-29 00:00:00\",\n    \"2017-02-04 00:00:00\",\n    \"2017-02-05 00:00:00\",\n    \"2017-02-11 00:00:00\",\n    \"2017-02-12 00:00:00\",\n    \"2017-02-18 00:00:00\",\n    \"2017-02-19 00:00:00\",\n    \"2017-02-25 00:00:00\",\n    \"2017-02-26 00:00:00\",\n    \"2017-03-04 00:00:00\",\n    \"2017-03-05 00:00:00\",\n    \"2017-03-11 00:00:00\",\n    \"2017-03-12 00:00:00\",\n    \"2017-03-18 00:00:00\",\n    \"2017-03-19 00:00:00\",\n    \"2017-03-25 00:00:00\",\n    \"2017-03-26 00:00:00\",\n    \"2017-04-01 00:00:00\",\n    \"2017-04-02 00:00:00\",\n    \"2017-04-08 00:00:00\",\n    \"2017-04-09 00:00:00\",\n    \"2017-04-15 00:00:00\",\n    \"2017-04-16 00:00:00\",\n    \"2017-04-22 00:00:00\",\n    \"2017-04-23 00:00:00\",\n    \"2017-04-29 00:00:00\",\n    \"2017-04-30 00:00:00\",\n    \"2017-05-06 00:00:00\",\n    \"2017-05-07 00:00:00\",\n    \"2017-05-13 00:00:00\",\n    \"2017-05-14 00:00:00\",\n    \"2017-05-20 00:00:00\",\n    \"2017-05-21 00:00:00\",\n    \"2017-05-27 00:00:00\",\n    \"2017-05-28 00:00:00\",\n    \"2017-06-03 00:00:00\",\n    \"2017-06-04 00:00:00\",\n    \"2017-06-10 00:00:00\",\n    \"2017-06-11 00:00:00\",\n    \"2017-06-17 00:00:00\",\n    \"2017-06-18 00:00:00\",\n    \"2017-06-24 00:00:00\",\n    \"2017-06-25 00:00:00\",\n    \"2017-07-01 00:00:00\",\n    \"2017-07-02 00:00:00\",\n    \"2017-07-08 00:00:00\",\n    \"2017-07-09 00:00:00\",\n    \"2017-07-15 00:00:00\",\n    \"2017-07-16 00:00:00\",\n    \"2017-07-22 00:00:00\",\n    \"2017-07-23 00:00:00\",\n    \"2017-07-29 00:00:00\",\n    \"2017-07-30 00:00:00\",\n    \"2017-08-05 00:00:00\",\n    \"2017-08-06 00:00:00\",\n    \"2017-08-12 00:00:00\",\n    \"2017-08-13 00:00:00\",\n    \"2017-08-19 00:00:00\",\n    \"2017-08-20 00:00:00\",\n    \"2017-08-26 00:00:00\",\n    \"2017-08-27 00:00:00\",\n    \"2017-09-02 00:00:00\",\n    \"2017-09-03 00:00:00\",\n    \"2017-09-09 00:00:00\",\n    \"2017-09-10 00:00:00\",\n    \"2017-09-16 00:00:00\",\n    \"2017-09-17 00:00:00\",\n    \"2017-09-23 00:00:00\",\n    \"2017-09-24 00:00:00\",\n    \"2017-09-30 00:00:00\",\n    \"2017-10-01 00:00:00\",\n    \"2017-10-07 00:00:00\",\n    \"2017-10-08 00:00:00\",\n    \"2017-10-14 00:00:00\",\n    \"2017-10-15 00:00:00\",\n    \"2017-10-21 00:00:00\",\n    \"2017-10-22 00:00:00\",\n    \"2017-10-28 00:00:00\",\n    \"2017-10-29 00:00:00\",\n    \"2017-11-04 00:00:00\",\n    \"2017-11-05 00:00:00\",\n    \"2017-11-11 00:00:00\",\n    \"2017-11-12 00:00:00\",\n    \"2017-11-18 00:00:00\",\n    \"2017-11-19 00:00:00\",\n    \"2017-11-25 00:00:00\",\n    \"2017-11-26 00:00:00\",\n    \"2017-12-02 00:00:00\",\n    \"2017-12-03 00:00:00\",\n    \"2017-12-09 00:00:00\",\n    \"2017-12-10 00:00:00\",\n    \"2017-12-16 00:00:00\",\n    \"2017-12-17 00:00:00\",\n    \"2017-12-23 00:00:00\",\n    \"2017-12-24 00:00:00\",\n    \"2017-12-30 00:00:00\",\n    \"2017-12-31 00:00:00\",\n    \"2018-01-06 00:00:00\",\n    \"2018-01-07 00:00:00\",\n    \"2018-01-13 00:00:00\",\n    \"2018-01-14 00:00:00\",\n    \"2018-01-20 00:00:00\",\n    \"2018-01-21 00:00:00\",\n    \"2018-01-27 00:00:00\",\n    \"2018-01-28 00:00:00\",\n    \"2018-02-03 00:00:00\",\n    \"2018-02-04 00:00:00\",\n    \"2018-02-10 00:00:00\",\n    \"2018-02-11 00:00:00\",\n    \"2018-02-17 00:00:00\",\n    \"2018-02-18 00:00:00\",\n    \"2018-02-24 00:00:00\",\n    \"2018-02-25 00:00:00\",\n    \"2018-03-03 00:00:00\",\n    \"2018-03-04 00:00:00\",\n    \"2018-03-10 00:00:00\",\n    \"2018-03-11 00:00:00\",\n    \"2018-03-17 00:00:00\",\n    \"2018-03-18 00:00:00\",\n    \"2018-03-24 00:00:00\",\n    \"2018-03-25 00:00:00\",\n    \"2018-03-31 00:00:00\",\n    \"2018-04-01 00:00:00\",\n    \"2018-04-07 00:00:00\",\n    \"2018-04-08 00:00:00\",\n    \"2018-04-14 00:00:00\",\n    \"2018-04-15 00:00:00\",\n    \"2018-04-21 00:00:00\",\n    \"2018-04-22 00:00:00\",\n    \"2018-04-28 00:00:00\",\n    \"2018-04-29 00:00:00\",\n    \"2018-05-05 00:00:00\",\n    \"2018-05-06 00:00:00\",\n    \"2018-05-12 00:00:00\",\n    \"2018-05-13 00:00:00\",\n    \"2018-05-19 00:00:00\",\n    \"2018-05-20 00:00:00\",\n    \"2018-05-26 00:00:00\",\n    \"2018-05-27 00:00:00\",\n    \"2018-06-02 00:00:00\",\n    \"2018-06-03 00:00:00\",\n    \"2018-06-09 00:00:00\",\n    \"2018-06-10 00:00:00\",\n    \"2018-06-16 00:00:00\",\n    \"2018-06-17 00:00:00\",\n    \"2018-06-23 00:00:00\",\n    \"2018-06-24 00:00:00\",\n    \"2018-06-30 00:00:00\",\n    \"2018-07-01 00:00:00\",\n    \"2018-07-07 00:00:00\",\n    \"2018-07-08 00:00:00\",\n    \"2018-07-14 00:00:00\",\n    \"2018-07-15 00:00:00\",\n    \"2018-07-21 00:00:00\",\n    \"2018-07-22 00:00:00\",\n    \"2018-07-28 00:00:00\",\n    \"2018-07-29 00:00:00\",\n    \"2018-08-04 00:00:00\",\n    \"2018-08-05 00:00:00\",\n    \"2018-08-11 00:00:00\",\n    \"2018-08-12 00:00:00\",\n    \"2018-08-18 00:00:00\",\n    \"2018-08-19 00:00:00\",\n    \"2018-08-25 00:00:00\",\n    \"2018-08-26 00:00:00\",\n    \"2018-09-01 00:00:00\",\n    \"2018-09-02 00:00:00\",\n    \"2018-09-08 00:00:00\",\n    \"2018-09-09 00:00:00\",\n    \"2018-09-15 00:00:00\",\n    \"2018-09-16 00:00:00\",\n    \"2018-09-22 00:00:00\",\n    \"2018-09-23 00:00:00\",\n    \"2018-09-29 00:00:00\",\n    \"2018-09-30 00:00:00\",\n    \"2018-10-06 00:00:00\",\n    \"2018-10-07 00:00:00\",\n    \"2018-10-13 00:00:00\",\n    \"2018-10-14 00:00:00\",\n    \"2018-10-20 00:00:00\",\n    \"2018-10-21 00:00:00\",\n    \"2018-10-27 00:00:00\",\n    \"2018-10-28 00:00:00\",\n    \"2018-11-03 00:00:00\",\n    \"2018-11-04 00:00:00\",\n    \"2018-11-10 00:00:00\",\n    \"2018-11-11 00:00:00\",\n    \"2018-11-17 00:00:00\",\n    \"2018-11-18 00:00:00\",\n    \"2018-11-24 00:00:00\",\n    \"2018-11-25 00:00:00\",\n    \"2018-12-01 00:00:00\",\n    \"2018-12-02 00:00:00\",\n    \"2018-12-08 00:00:00\",\n    \"2018-12-09 00:00:00\",\n    \"2018-12-15 00:00:00\",\n    \"2018-12-16 00:00:00\",\n    \"2018-12-22 00:00:00\",\n    \"2018-12-23 00:00:00\",\n    \"2018-12-29 00:00:00\",\n    \"2018-12-30 00:00:00\",\n    \"2019-01-05 00:00:00\",\n    \"2019-01-06 00:00:00\",\n    \"2019-01-12 00:00:00\",\n    \"2019-01-13 00:00:00\",\n    \"2019-01-19 00:00:00\",\n    \"2019-01-20 00:00:00\",\n    \"2019-01-26 00:00:00\",\n    \"2019-01-27 00:00:00\",\n    \"2019-02-02 00:00:00\",\n    \"2019-02-03 00:00:00\",\n    \"2019-02-09 00:00:00\",\n    \"2019-02-10 00:00:00\",\n    \"2019-02-16 00:00:00\",\n    \"2019-02-17 00:00:00\",\n    \"2019-02-23 00:00:00\",\n    \"2019-02-24 00:00:00\",\n    \"2019-03-02 00:00:00\",\n    \"2019-03-03 00:00:00\",\n    \"2019-03-09 00:00:00\",\n    \"2019-03-10 00:00:00\",\n    \"2019-03-16 00:00:00\",\n    \"2019-03-17 00:00:00\",\n    \"2019-03-23 00:00:00\",\n    \"2019-03-24 00:00:00\",\n    \"2019-03-30 00:00:00\",\n    \"2019-03-31 00:00:00\",\n    \"2019-04-06 00:00:00\",\n    \"2019-04-07 00:00:00\",\n    \"2019-04-13 00:00:00\",\n    \"2019-04-14 00:00:00\",\n    \"2019-04-20 00:00:00\",\n    \"2019-04-21 00:00:00\",\n    \"2019-04-27 00:00:00\",\n    \"2019-04-28 00:00:00\",\n    \"2019-05-04 00:00:00\",\n    \"2019-05-05 00:00:00\",\n    \"2019-05-11 00:00:00\",\n    \"2019-05-12 00:00:00\",\n    \"2019-05-18 00:00:00\",\n    \"2019-05-19 00:00:00\",\n    \"2019-05-25 00:00:00\",\n    \"2019-05-26 00:00:00\",\n    \"2019-06-01 00:00:00\",\n    \"2019-06-02 00:00:00\",\n    \"2019-06-08 00:00:00\",\n    \"2019-06-09 00:00:00\",\n    \"2019-06-15 00:00:00\",\n    \"2019-06-16 00:00:00\",\n    \"2019-06-22 00:00:00\",\n    \"2019-06-23 00:00:00\",\n    \"2019-06-29 00:00:00\",\n    \"2019-06-30 00:00:00\",\n    \"2019-07-06 00:00:00\",\n    \"2019-07-07 00:00:00\",\n    \"2019-07-13 00:00:00\",\n    \"2019-07-14 00:00:00\",\n    \"2019-07-20 00:00:00\",\n    \"2019-07-21 00:00:00\",\n    \"2019-07-27 00:00:00\",\n    \"2019-07-28 00:00:00\",\n    \"2019-08-03 00:00:00\",\n    \"2019-08-04 00:00:00\",\n    \"2019-08-10 00:00:00\",\n    \"2019-08-11 00:00:00\",\n    \"2019-08-17 00:00:00\",\n    \"2019-08-18 00:00:00\",\n    \"2019-08-24 00:00:00\",\n    \"2019-08-25 00:00:00\",\n    \"2019-08-31 00:00:00\",\n    \"2019-09-01 00:00:00\",\n    \"2019-09-07 00:00:00\",\n    \"2019-09-08 00:00:00\",\n    \"2019-09-14 00:00:00\",\n    \"2019-09-15 00:00:00\",\n    \"2019-09-21 00:00:00\",\n    \"2019-09-22 00:00:00\",\n    \"2019-09-28 00:00:00\",\n    \"2019-09-29 00:00:00\",\n    \"2019-10-05 00:00:00\",\n    \"2019-10-06 00:00:00\",\n    \"2019-10-12 00:00:00\",\n    \"2019-10-13 00:00:00\",\n    \"2019-10-19 00:00:00\",\n    \"2019-10-20 00:00:00\",\n    \"2019-10-26 00:00:00\",\n    \"2019-10-27 00:00:00\",\n    \"2019-11-02 00:00:00\",\n    \"2019-11-03 00:00:00\",\n    \"2019-11-09 00:00:00\",\n    \"2019-11-10 00:00:00\",\n    \"2019-11-16 00:00:00\",\n    \"2019-11-17 00:00:00\",\n    \"2019-11-23 00:00:00\",\n    \"2019-11-24 00:00:00\",\n    \"2019-11-30 00:00:00\",\n    \"2019-12-01 00:00:00\",\n    \"2019-12-07 00:00:00\",\n    \"2019-12-08 00:00:00\",\n    \"2019-12-14 00:00:00\",\n    \"2019-12-15 00:00:00\",\n    \"2019-12-21 00:00:00\",\n    \"2019-12-22 00:00:00\",\n    \"2019-12-28 00:00:00\",\n    \"2019-12-29 00:00:00\",\n    \"2020-01-04 00:00:00\",\n    \"2020-01-05 00:00:00\",\n    \"2020-01-11 00:00:00\",\n    \"2020-01-12 00:00:00\",\n    \"2020-01-18 00:00:00\",\n    \"2020-01-19 00:00:00\",\n    \"2020-01-25 00:00:00\",\n    \"2020-01-26 00:00:00\",\n    \"2020-02-01 00:00:00\",\n    \"2020-02-02 00:00:00\",\n    \"2020-02-08 00:00:00\",\n    \"2020-02-09 00:00:00\",\n    \"2020-02-15 00:00:00\",\n    \"2020-02-16 00:00:00\",\n    \"2020-02-22 00:00:00\",\n    \"2020-02-23 00:00:00\",\n    \"2020-02-29 00:00:00\",\n    \"2020-03-01 00:00:00\",\n    \"2020-03-07 00:00:00\",\n    \"2020-03-08 00:00:00\",\n    \"2020-03-14 00:00:00\",\n    \"2020-03-15 00:00:00\",\n    \"2020-03-21 00:00:00\",\n    \"2020-03-22 00:00:00\",\n    \"2020-03-28 00:00:00\",\n    \"2020-03-29 00:00:00\",\n    \"2020-04-04 00:00:00\",\n    \"2020-04-05 00:00:00\",\n    \"2020-04-11 00:00:00\",\n    \"2020-04-12 00:00:00\",\n    \"2020-04-18 00:00:00\",\n    \"2020-04-19 00:00:00\",\n    \"2020-04-25 00:00:00\",\n    \"2020-04-26 00:00:00\",\n    \"2020-05-02 00:00:00\",\n    \"2020-05-03 00:00:00\",\n    \"2020-05-09 00:00:00\",\n    \"2020-05-10 00:00:00\",\n    \"2020-05-16 00:00:00\",\n    \"2020-05-17 00:00:00\",\n    \"2020-05-23 00:00:00\",\n    \"2020-05-24 00:00:00\",\n    \"2020-05-30 00:00:00\",\n    \"2020-05-31 00:00:00\",\n    \"2020-06-06 00:00:00\",\n    \"2020-06-07 00:00:00\",\n    \"2020-06-13 00:00:00\",\n    \"2020-06-14 00:00:00\",\n    \"2020-06-20 00:00:00\",\n    \"2020-06-21 00:00:00\",\n    \"2020-06-27 00:00:00\",\n    \"2020-06-28 00:00:00\",\n    \"2020-07-04 00:00:00\",\n    \"2020-07-05 00:00:00\",\n    \"2020-07-11 00:00:00\",\n    \"2020-07-12 00:00:00\",\n    \"2020-07-18 00:00:00\",\n    \"2020-07-19 00:00:00\",\n    \"2020-07-25 00:00:00\",\n    \"2020-07-26 00:00:00\",\n    \"2020-08-01 00:00:00\",\n    \"2020-08-02 00:00:00\",\n    \"2020-08-08 00:00:00\",\n    \"2020-08-09 00:00:00\",\n    \"2020-08-15 00:00:00\",\n    \"2020-08-16 00:00:00\",\n    \"2020-08-22 00:00:00\",\n    \"2020-08-23 00:00:00\",\n    \"2020-08-29 00:00:00\",\n    \"2020-08-30 00:00:00\",\n    \"2020-09-05 00:00:00\",\n    \"2020-09-06 00:00:00\",\n    \"2020-09-12 00:00:00\",\n    \"2020-09-13 00:00:00\",\n    \"2020-09-19 00:00:00\",\n    \"2020-09-20 00:00:00\",\n    \"2020-09-26 00:00:00\",\n    \"2020-09-27 00:00:00\",\n    \"2020-10-03 00:00:00\",\n    \"2020-10-04 00:00:00\",\n    \"2020-10-10 00:00:00\",\n    \"2020-10-11 00:00:00\",\n    \"2020-10-17 00:00:00\",\n    \"2020-10-18 00:00:00\",\n    \"2020-10-24 00:00:00\",\n    \"2020-10-25 00:00:00\",\n    \"2020-10-31 00:00:00\",\n    \"2020-11-01 00:00:00\",\n    \"2020-11-07 00:00:00\",\n    \"2020-11-08 00:00:00\",\n    \"2020-11-14 00:00:00\",\n    \"2020-11-15 00:00:00\",\n    \"2020-11-21 00:00:00\",\n    \"2020-11-22 00:00:00\",\n    \"2020-11-28 00:00:00\",\n    \"2020-11-29 00:00:00\",\n    \"2020-12-05 00:00:00\",\n    \"2020-12-06 00:00:00\",\n    \"2020-12-12 00:00:00\",\n    \"2020-12-13 00:00:00\",\n    \"2020-12-19 00:00:00\",\n    \"2020-12-20 00:00:00\",\n    \"2020-12-26 00:00:00\",\n    \"2020-12-27 00:00:00\",\n    \"2021-01-02 00:00:00\",\n    \"2021-01-03 00:00:00\",\n    \"2021-01-09 00:00:00\",\n    \"2021-01-10 00:00:00\",\n    \"2021-01-16 00:00:00\",\n    \"2021-01-17 00:00:00\",\n    \"2021-01-23 00:00:00\",\n    \"2021-01-24 00:00:00\",\n    \"2021-01-30 00:00:00\",\n    \"2021-01-31 00:00:00\",\n    \"2021-02-06 00:00:00\",\n    \"2021-02-07 00:00:00\",\n    \"2021-02-13 00:00:00\",\n    \"2021-02-14 00:00:00\",\n    \"2021-02-20 00:00:00\",\n    \"2021-02-21 00:00:00\",\n    \"2021-02-27 00:00:00\",\n    \"2021-02-28 00:00:00\",\n    \"2021-03-06 00:00:00\",\n    \"2021-03-07 00:00:00\",\n    \"2021-03-13 00:00:00\",\n    \"2021-03-14 00:00:00\",\n    \"2021-03-20 00:00:00\",\n    \"2021-03-21 00:00:00\",\n    \"2021-03-27 00:00:00\",\n    \"2021-03-28 00:00:00\",\n    \"2021-04-03 00:00:00\",\n    \"2021-04-04 00:00:00\",\n    \"2021-04-10 00:00:00\",\n    \"2021-04-11 00:00:00\",\n    \"2021-04-17 00:00:00\",\n    \"2021-04-18 00:00:00\",\n    \"2021-04-24 00:00:00\",\n    \"2021-04-25 00:00:00\",\n    \"2021-05-01 00:00:00\",\n    \"2021-05-02 00:00:00\",\n    \"2021-05-08 00:00:00\",\n    \"2021-05-09 00:00:00\",\n    \"2021-05-15 00:00:00\",\n    \"2021-05-16 00:00:00\",\n    \"2021-05-22 00:00:00\",\n    \"2021-05-23 00:00:00\",\n    \"2021-05-29 00:00:00\",\n    \"2021-05-30 00:00:00\",\n    \"2021-06-05 00:00:00\",\n    \"2021-06-06 00:00:00\",\n    \"2021-06-12 00:00:00\",\n    \"2021-06-13 00:00:00\",\n    \"2021-06-19 00:00:00\",\n    \"2021-06-20 00:00:00\",\n    \"2021-06-26 00:00:00\",\n    \"2021-06-27 00:00:00\",\n    \"2021-07-03 00:00:00\",\n    \"2021-07-04 00:00:00\",\n    \"2021-07-10 00:00:00\",\n    \"2021-07-11 00:00:00\",\n    \"2021-07-17 00:00:00\",\n    \"2021-07-18 00:00:00\",\n    \"2021-07-24 00:00:00\",\n    \"2021-07-25 00:00:00\",\n    \"2021-07-31 00:00:00\",\n    \"2021-08-01 00:00:00\",\n    \"2021-08-07 00:00:00\",\n    \"2021-08-08 00:00:00\",\n    \"2021-08-14 00:00:00\",\n    \"2021-08-15 00:00:00\",\n    \"2021-08-21 00:00:00\",\n    \"2021-08-22 00:00:00\",\n    \"2021-08-28 00:00:00\",\n    \"2021-08-29 00:00:00\",\n    \"2021-09-04 00:00:00\",\n    \"2021-09-05 00:00:00\",\n    \"2021-09-11 00:00:00\",\n    \"2021-09-12 00:00:00\",\n    \"2021-09-18 00:00:00\",\n    \"2021-09-19 00:00:00\",\n    \"2021-09-25 00:00:00\",\n    \"2021-09-26 00:00:00\",\n    \"2021-10-02 00:00:00\",\n    \"2021-10-03 00:00:00\",\n    \"2021-10-09 00:00:00\",\n    \"2021-10-10 00:00:00\",\n    \"2021-10-16 00:00:00\",\n    \"2021-10-17 00:00:00\",\n    \"2021-10-23 00:00:00\",\n    \"2021-10-24 00:00:00\",\n    \"2021-10-30 00:00:00\",\n    \"2021-10-31 00:00:00\",\n    \"2021-11-06 00:00:00\",\n    \"2021-11-07 00:00:00\",\n    \"2021-11-13 00:00:00\",\n    \"2021-11-14 00:00:00\",\n    \"2021-11-20 00:00:00\",\n    \"2021-11-21 00:00:00\",\n    \"2021-11-27 00:00:00\",\n    \"2021-11-28 00:00:00\",\n    \"2021-12-04 00:00:00\",\n    \"2021-12-05 00:00:00\",\n    \"2021-12-11 00:00:00\",\n    \"2021-12-12 00:00:00\",\n    \"2021-12-18 00:00:00\",\n    \"2021-12-19 00:00:00\",\n    \"2021-12-25 00:00:00\",\n    \"2021-12-26 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-01-02 00:00:00\",\n    \"2022-01-08 00:00:00\",\n    \"2022-01-09 00:00:00\",\n    \"2022-01-15 00:00:00\",\n    \"2022-01-16 00:00:00\",\n    \"2022-01-22 00:00:00\",\n    \"2022-01-23 00:00:00\",\n    \"2022-01-29 00:00:00\",\n    \"2022-01-30 00:00:00\",\n    \"2022-02-05 00:00:00\",\n    \"2022-02-06 00:00:00\",\n    \"2022-02-12 00:00:00\",\n    \"2022-02-13 00:00:00\",\n    \"2022-02-19 00:00:00\",\n    \"2022-02-20 00:00:00\",\n    \"2022-02-26 00:00:00\",\n    \"2022-02-27 00:00:00\",\n    \"2022-03-05 00:00:00\",\n    \"2022-03-06 00:00:00\",\n    \"2022-03-12 00:00:00\",\n    \"2022-03-13 00:00:00\",\n    \"2022-03-19 00:00:00\",\n    \"2022-03-20 00:00:00\",\n    \"2022-03-26 00:00:00\",\n    \"2022-03-27 00:00:00\",\n    \"2022-04-02 00:00:00\",\n    \"2022-04-03 00:00:00\",\n    \"2022-04-09 00:00:00\",\n    \"2022-04-10 00:00:00\",\n    \"2022-04-16 00:00:00\",\n    \"2022-04-17 00:00:00\",\n    \"2022-04-23 00:00:00\",\n    \"2022-04-24 00:00:00\",\n    \"2022-04-30 00:00:00\",\n    \"2022-05-01 00:00:00\",\n    \"2022-05-07 00:00:00\",\n    \"2022-05-08 00:00:00\",\n    \"2022-05-14 00:00:00\",\n    \"2022-05-15 00:00:00\",\n    \"2022-05-21 00:00:00\",\n    \"2022-05-22 00:00:00\",\n    \"2022-05-28 00:00:00\",\n    \"2022-05-29 00:00:00\",\n    \"2022-06-04 00:00:00\",\n    \"2022-06-05 00:00:00\",\n    \"2022-06-11 00:00:00\",\n    \"2022-06-12 00:00:00\",\n    \"2022-06-18 00:00:00\",\n    \"2022-06-19 00:00:00\",\n    \"2022-06-25 00:00:00\",\n    \"2022-06-26 00:00:00\",\n    \"2022-07-02 00:00:00\",\n    \"2022-07-03 00:00:00\",\n    \"2022-07-09 00:00:00\",\n    \"2022-07-10 00:00:00\",\n    \"2022-07-16 00:00:00\",\n    \"2022-07-17 00:00:00\",\n    \"2022-07-23 00:00:00\",\n    \"2022-07-24 00:00:00\",\n    \"2022-07-30 00:00:00\",\n    \"2022-07-31 00:00:00\",\n    \"2022-08-06 00:00:00\",\n    \"2022-08-07 00:00:00\",\n    \"2022-08-13 00:00:00\",\n    \"2022-08-14 00:00:00\",\n    \"2022-08-20 00:00:00\",\n    \"2022-08-21 00:00:00\",\n    \"2022-08-27 00:00:00\",\n    \"2022-08-28 00:00:00\",\n    \"2022-09-03 00:00:00\",\n    \"2022-09-04 00:00:00\",\n    \"2022-09-10 00:00:00\",\n    \"2022-09-11 00:00:00\",\n    \"2022-09-17 00:00:00\",\n    \"2022-09-18 00:00:00\",\n    \"2022-09-24 00:00:00\",\n    \"2022-09-25 00:00:00\",\n    \"2022-10-01 00:00:00\",\n    \"2022-10-02 00:00:00\",\n    \"2022-10-08 00:00:00\",\n    \"2022-10-09 00:00:00\",\n    \"2022-10-15 00:00:00\",\n    \"2022-10-16 00:00:00\",\n    \"2022-10-22 00:00:00\",\n    \"2022-10-23 00:00:00\",\n    \"2022-10-29 00:00:00\",\n    \"2022-10-30 00:00:00\",\n    \"2022-11-05 00:00:00\",\n    \"2022-11-06 00:00:00\",\n    \"2022-11-12 00:00:00\",\n    \"2022-11-13 00:00:00\",\n    \"2022-11-19 00:00:00\",\n    \"2022-11-20 00:00:00\",\n    \"2022-11-26 00:00:00\",\n    \"2022-11-27 00:00:00\",\n    \"2022-12-03 00:00:00\",\n    \"2022-12-04 00:00:00\",\n    \"2022-12-10 00:00:00\",\n    \"2022-12-11 00:00:00\",\n    \"2022-12-17 00:00:00\",\n    \"2022-12-18 00:00:00\",\n    \"2022-12-24 00:00:00\",\n    \"2022-12-25 00:00:00\",\n    \"2022-12-31 00:00:00\",\n    \"2023-01-01 00:00:00\",\n    \"2023-01-07 00:00:00\",\n    \"2023-01-08 00:00:00\",\n    \"2023-01-14 00:00:00\",\n    \"2023-01-15 00:00:00\",\n    \"2023-01-21 00:00:00\",\n    \"2023-01-22 00:00:00\",\n    \"2023-01-28 00:00:00\",\n    \"2023-01-29 00:00:00\",\n    \"2023-02-04 00:00:00\",\n    \"2023-02-05 00:00:00\",\n    \"2023-02-11 00:00:00\",\n    \"2023-02-12 00:00:00\",\n    \"2023-02-18 00:00:00\",\n    \"2023-02-19 00:00:00\",\n    \"2023-02-25 00:00:00\",\n    \"2023-02-26 00:00:00\",\n    \"2023-03-04 00:00:00\",\n    \"2023-03-05 00:00:00\",\n    \"2023-03-11 00:00:00\",\n    \"2023-03-12 00:00:00\",\n    \"2023-03-18 00:00:00\",\n    \"2023-03-19 00:00:00\",\n    \"2023-03-25 00:00:00\",\n    \"2023-03-26 00:00:00\",\n    \"2023-04-01 00:00:00\",\n    \"2023-04-02 00:00:00\",\n    \"2023-04-08 00:00:00\",\n    \"2023-04-09 00:00:00\",\n    \"2023-04-15 00:00:00\",\n    \"2023-04-16 00:00:00\",\n    \"2023-04-22 00:00:00\",\n    \"2023-04-23 00:00:00\",\n    \"2023-04-29 00:00:00\",\n    \"2023-04-30 00:00:00\",\n    \"2023-05-06 00:00:00\",\n    \"2023-05-07 00:00:00\",\n    \"2023-05-13 00:00:00\",\n    \"2023-05-14 00:00:00\",\n    \"2023-05-20 00:00:00\",\n    \"2023-05-21 00:00:00\",\n    \"2023-05-27 00:00:00\",\n    \"2023-05-28 00:00:00\",\n    \"2023-06-03 00:00:00\",\n    \"2023-06-04 00:00:00\",\n    \"2023-06-10 00:00:00\",\n    \"2023-06-11 00:00:00\",\n    \"2023-06-17 00:00:00\",\n    \"2023-06-18 00:00:00\",\n    \"2023-06-24 00:00:00\",\n    \"2023-06-25 00:00:00\",\n    \"2023-07-01 00:00:00\",\n    \"2023-07-02 00:00:00\",\n    \"2023-07-08 00:00:00\",\n    \"2023-07-09 00:00:00\",\n    \"2023-07-15 00:00:00\",\n    \"2023-07-16 00:00:00\",\n    \"2023-07-22 00:00:00\",\n    \"2023-07-23 00:00:00\",\n    \"2023-07-29 00:00:00\",\n    \"2023-07-30 00:00:00\",\n    \"2023-08-05 00:00:00\",\n    \"2023-08-06 00:00:00\",\n    \"2023-08-12 00:00:00\",\n    \"2023-08-13 00:00:00\",\n    \"2023-08-19 00:00:00\",\n    \"2023-08-20 00:00:00\",\n    \"2023-08-26 00:00:00\",\n    \"2023-08-27 00:00:00\",\n    \"2023-09-02 00:00:00\",\n    \"2023-09-03 00:00:00\",\n    \"2023-09-09 00:00:00\",\n    \"2023-09-10 00:00:00\",\n    \"2023-09-16 00:00:00\",\n    \"2023-09-17 00:00:00\",\n    \"2023-09-23 00:00:00\",\n    \"2023-09-24 00:00:00\",\n    \"2023-09-30 00:00:00\",\n    \"2023-10-01 00:00:00\",\n    \"2023-10-07 00:00:00\",\n    \"2023-10-08 00:00:00\",\n    \"2023-10-14 00:00:00\",\n    \"2023-10-15 00:00:00\",\n    \"2023-10-21 00:00:00\",\n    \"2023-10-22 00:00:00\",\n    \"2023-10-28 00:00:00\",\n    \"2023-10-29 00:00:00\",\n    \"2023-11-04 00:00:00\",\n    \"2023-11-05 00:00:00\",\n    \"2023-11-11 00:00:00\",\n    \"2023-11-12 00:00:00\",\n    \"2023-11-18 00:00:00\",\n    \"2023-11-19 00:00:00\",\n    \"2023-11-25 00:00:00\",\n    \"2023-11-26 00:00:00\",\n    \"2023-12-02 00:00:00\",\n    \"2023-12-03 00:00:00\",\n    \"2023-12-09 00:00:00\",\n    \"2023-12-10 00:00:00\",\n    \"2023-12-16 00:00:00\",\n    \"2023-12-17 00:00:00\",\n    \"2023-12-23 00:00:00\",\n    \"2023-12-24 00:00:00\",\n    \"2023-12-30 00:00:00\",\n    \"2023-12-31 00:00:00\",\n    \"2024-01-06 00:00:00\",\n    \"2024-01-07 00:00:00\",\n    \"2024-01-13 00:00:00\",\n    \"2024-01-14 00:00:00\",\n    \"2024-01-20 00:00:00\",\n    \"2024-01-21 00:00:00\",\n    \"2024-01-27 00:00:00\",\n    \"2024-01-28 00:00:00\",\n    \"2024-02-03 00:00:00\",\n    \"2024-02-04 00:00:00\",\n    \"2024-02-10 00:00:00\",\n    \"2024-02-11 00:00:00\",\n    \"2024-02-17 00:00:00\",\n    \"2024-02-18 00:00:00\",\n    \"2024-02-24 00:00:00\",\n    \"2024-02-25 00:00:00\",\n    \"2024-03-02 00:00:00\",\n    \"2024-03-03 00:00:00\",\n    \"2024-03-09 00:00:00\",\n    \"2024-03-10 00:00:00\",\n    \"2024-03-16 00:00:00\",\n    \"2024-03-17 00:00:00\",\n    \"2024-03-23 00:00:00\",\n    \"2024-03-24 00:00:00\",\n    \"2024-03-30 00:00:00\",\n    \"2024-03-31 00:00:00\",\n    \"2024-04-06 00:00:00\",\n    \"2024-04-07 00:00:00\",\n    \"2024-04-13 00:00:00\",\n    \"2024-04-14 00:00:00\",\n    \"2024-04-20 00:00:00\",\n    \"2024-04-21 00:00:00\",\n    \"2024-04-27 00:00:00\",\n    \"2024-04-28 00:00:00\",\n    \"2024-05-04 00:00:00\",\n    \"2024-05-05 00:00:00\",\n    \"2024-05-11 00:00:00\",\n    \"2024-05-12 00:00:00\",\n    \"2024-05-18 00:00:00\",\n    \"2024-05-19 00:00:00\",\n    \"2024-05-25 00:00:00\",\n    \"2024-05-26 00:00:00\",\n    \"2024-06-01 00:00:00\",\n    \"2024-06-02 00:00:00\",\n    \"2024-06-08 00:00:00\",\n    \"2024-06-09 00:00:00\",\n    \"2024-06-15 00:00:00\",\n    \"2024-06-16 00:00:00\",\n    \"2024-06-22 00:00:00\",\n    \"2024-06-23 00:00:00\",\n    \"2024-06-29 00:00:00\",\n    \"2024-06-30 00:00:00\",\n    \"2024-07-06 00:00:00\",\n    \"2024-07-07 00:00:00\",\n    \"2024-07-13 00:00:00\",\n    \"2024-07-14 00:00:00\",\n    \"2024-07-20 00:00:00\",\n    \"2024-07-21 00:00:00\",\n    \"2024-07-27 00:00:00\",\n    \"2024-07-28 00:00:00\",\n    \"2024-08-03 00:00:00\",\n    \"2024-08-04 00:00:00\",\n    \"2024-08-10 00:00:00\",\n    \"2024-08-11 00:00:00\",\n    \"2024-08-17 00:00:00\",\n    \"2024-08-18 00:00:00\",\n    \"2024-08-24 00:00:00\",\n    \"2024-08-25 00:00:00\",\n    \"2024-08-31 00:00:00\",\n    \"2024-09-01 00:00:00\",\n    \"2024-09-07 00:00:00\",\n    \"2024-09-08 00:00:00\",\n    \"2024-09-14 00:00:00\",\n    \"2024-09-15 00:00:00\",\n    \"2024-09-21 00:00:00\",\n    \"2024-09-22 00:00:00\",\n    \"2024-09-28 00:00:00\",\n    \"2024-09-29 00:00:00\",\n    \"2024-10-05 00:00:00\",\n    \"2024-10-06 00:00:00\",\n    \"2024-10-12 00:00:00\",\n    \"2024-10-13 00:00:00\",\n    \"2024-10-19 00:00:00\",\n    \"2024-10-20 00:00:00\",\n    \"2024-10-26 00:00:00\",\n    \"2024-10-27 00:00:00\",\n    \"2024-11-02 00:00:00\",\n    \"2024-11-03 00:00:00\",\n    \"2024-11-09 00:00:00\",\n    \"2024-11-10 00:00:00\",\n    \"2024-11-16 00:00:00\",\n    \"2024-11-17 00:00:00\",\n    \"2024-11-23 00:00:00\",\n    \"2024-11-24 00:00:00\",\n    \"2024-11-30 00:00:00\",\n    \"2024-12-01 00:00:00\",\n    \"2024-12-07 00:00:00\",\n    \"2024-12-08 00:00:00\",\n    \"2024-12-14 00:00:00\",\n    \"2024-12-15 00:00:00\",\n    \"2024-12-21 00:00:00\",\n    \"2024-12-22 00:00:00\",\n    \"2024-12-28 00:00:00\",\n    \"2024-12-29 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-04 00:00:00\",\n    \"2025-01-05 00:00:00\",\n    \"2025-01-11 00:00:00\",\n    \"2025-01-12 00:00:00\",\n    \"2025-01-18 00:00:00\",\n    \"2025-01-19 00:00:00\",\n    \"2025-01-25 00:00:00\",\n    \"2025-01-28 00:00:00\",\n    \"2025-01-29 00:00:00\",\n    \"2025-01-30 00:00:00\",\n    \"2025-01-31 00:00:00\",\n    \"2025-02-01 00:00:00\",\n    \"2025-02-02 00:00:00\",\n    \"2025-02-03 00:00:00\",\n    \"2025-02-04 00:00:00\",\n    \"2025-02-09 00:00:00\",\n    \"2025-02-15 00:00:00\",\n    \"2025-02-16 00:00:00\",\n    \"2025-02-22 00:00:00\",\n    \"2025-02-23 00:00:00\",\n    \"2025-03-01 00:00:00\",\n    \"2025-03-02 00:00:00\",\n    \"2025-03-08 00:00:00\",\n    \"2025-03-09 00:00:00\",\n    \"2025-03-15 00:00:00\",\n    \"2025-03-16 00:00:00\",\n    \"2025-03-22 00:00:00\",\n    \"2025-03-23 00:00:00\",\n    \"2025-03-29 00:00:00\",\n    \"2025-03-30 00:00:00\",\n    \"2025-04-04 00:00:00\",\n    \"2025-04-05 00:00:00\",\n    \"2025-04-06 00:00:00\",\n    \"2025-04-12 00:00:00\",\n    \"2025-04-13 00:00:00\",\n    \"2025-04-19 00:00:00\",\n    \"2025-04-20 00:00:00\",\n    \"2025-04-26 00:00:00\",\n    \"2025-05-01 00:00:00\",\n    \"2025-05-02 00:00:00\",\n    \"2025-05-03 00:00:00\",\n    \"2025-05-04 00:00:00\",\n    \"2025-05-05 00:00:00\",\n    \"2025-05-10 00:00:00\",\n    \"2025-05-11 00:00:00\",\n    \"2025-05-17 00:00:00\",\n    \"2025-05-18 00:00:00\",\n    \"2025-05-24 00:00:00\",\n    \"2025-05-25 00:00:00\",\n    \"2025-05-31 00:00:00\",\n    \"2025-06-01 00:00:00\",\n    \"2025-06-02 00:00:00\",\n    \"2025-06-07 00:00:00\",\n    \"2025-06-08 00:00:00\",\n    \"2025-06-14 00:00:00\",\n    \"2025-06-15 00:00:00\",\n    \"2025-06-21 00:00:00\",\n    \"2025-06-22 00:00:00\",\n    \"2025-06-28 00:00:00\",\n    \"2025-06-29 00:00:00\",\n    \"2025-07-05 00:00:00\",\n    \"2025-07-06 00:00:00\",\n    \"2025-07-12 00:00:00\",\n    \"2025-07-13 00:00:00\",\n    \"2025-07-19 00:00:00\",\n    \"2025-07-20 00:00:00\",\n    \"2025-07-26 00:00:00\",\n    \"2025-07-27 00:00:00\",\n    \"2025-08-02 00:00:00\",\n    \"2025-08-03 00:00:00\",\n    \"2025-08-09 00:00:00\",\n    \"2025-08-10 00:00:00\",\n    \"2025-08-16 00:00:00\",\n    \"2025-08-17 00:00:00\",\n    \"2025-08-23 00:00:00\",\n    \"2025-08-24 00:00:00\",\n    \"2025-08-30 00:00:00\",\n    \"2025-08-31 00:00:00\",\n    \"2025-09-06 00:00:00\",\n    \"2025-09-07 00:00:00\",\n    \"2025-09-13 00:00:00\",\n    \"2025-09-14 00:00:00\",\n    \"2025-09-20 00:00:00\",\n    \"2025-09-21 00:00:00\",\n    \"2025-09-27 00:00:00\",\n    \"2025-10-01 00:00:00\",\n    \"2025-10-02 00:00:00\",\n    \"2025-10-03 00:00:00\",\n    \"2025-10-04 00:00:00\",\n    \"2025-10-05 00:00:00\",\n    \"2025-10-06 00:00:00\",\n    \"2025-10-07 00:00:00\",\n    \"2025-10-08 00:00:00\",\n    \"2025-10-12 00:00:00\",\n    \"2025-10-18 00:00:00\",\n    \"2025-10-19 00:00:00\",\n    \"2025-10-25 00:00:00\",\n    \"2025-10-26 00:00:00\",\n    \"2025-11-01 00:00:00\",\n    \"2025-11-02 00:00:00\",\n    \"2025-11-08 00:00:00\",\n    \"2025-11-09 00:00:00\",\n    \"2025-11-15 00:00:00\",\n    \"2025-11-16 00:00:00\",\n    \"2025-11-22 00:00:00\",\n    \"2025-11-23 00:00:00\",\n    \"2025-11-29 00:00:00\",\n    \"2025-11-30 00:00:00\",\n    \"2025-12-06 00:00:00\",\n    \"2025-12-07 00:00:00\",\n    \"2025-12-13 00:00:00\",\n    \"2025-12-14 00:00:00\",\n    \"2025-12-20 00:00:00\",\n    \"2025-12-21 00:00:00\",\n    \"2025-12-27 00:00:00\",\n    \"2025-12-28 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-02 00:00:00\",\n    \"2026-01-03 00:00:00\",\n    \"2026-01-10 00:00:00\",\n    \"2026-01-11 00:00:00\",\n    \"2026-01-17 00:00:00\",\n    \"2026-01-18 00:00:00\",\n    \"2026-01-24 00:00:00\",\n    \"2026-01-25 00:00:00\",\n    \"2026-01-31 00:00:00\",\n    \"2026-02-01 00:00:00\",\n    \"2026-02-07 00:00:00\",\n    \"2026-02-08 00:00:00\",\n    \"2026-02-15 00:00:00\",\n    \"2026-02-16 00:00:00\",\n    \"2026-02-17 00:00:00\",\n    \"2026-02-18 00:00:00\",\n    \"2026-02-19 00:00:00\",\n    \"2026-02-20 00:00:00\",\n    \"2026-02-21 00:00:00\",\n    \"2026-02-22 00:00:00\",\n    \"2026-02-23 00:00:00\",\n    \"2026-03-01 00:00:00\",\n    \"2026-03-07 00:00:00\",\n    \"2026-03-08 00:00:00\",\n    \"2026-03-14 00:00:00\",\n    \"2026-03-15 00:00:00\",\n    \"2026-03-21 00:00:00\",\n    \"2026-03-22 00:00:00\",\n    \"2026-03-28 00:00:00\",\n    \"2026-03-29 00:00:00\",\n    \"2026-04-04 00:00:00\",\n    \"2026-04-05 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-04-11 00:00:00\",\n    \"2026-04-12 00:00:00\",\n    \"2026-04-18 00:00:00\",\n    \"2026-04-19 00:00:00\",\n    \"2026-04-25 00:00:00\",\n    \"2026-04-26 00:00:00\",\n    \"2026-05-01 00:00:00\",\n    \"2026-05-02 00:00:00\",\n    \"2026-05-03 00:00:00\",\n    \"2026-05-04 00:00:00\",\n    \"2026-05-05 00:00:00\",\n    \"2026-05-10 00:00:00\",\n    \"2026-05-16 00:00:00\",\n    \"2026-05-17 00:00:00\",\n    \"2026-05-23 00:00:00\",\n    \"2026-05-24 00:00:00\",\n    \"2026-05-30 00:00:00\",\n    \"2026-05-31 00:00:00\",\n    \"2026-06-06 00:00:00\",\n    \"2026-06-07 00:00:00\",\n    \"2026-06-13 00:00:00\",\n    \"2026-06-14 00:00:00\",\n    \"2026-06-19 00:00:00\",\n    \"2026-06-20 00:00:00\",\n    \"2026-06-21 00:00:00\",\n    \"2026-06-27 00:00:00\",\n    \"2026-06-28 00:00:00\",\n    \"2026-07-04 00:00:00\",\n    \"2026-07-05 00:00:00\",\n    \"2026-07-11 00:00:00\",\n    \"2026-07-12 00:00:00\",\n    \"2026-07-18 00:00:00\",\n    \"2026-07-19 00:00:00\",\n    \"2026-07-25 00:00:00\",\n    \"2026-07-26 00:00:00\",\n    \"2026-08-01 00:00:00\",\n    \"2026-08-02 00:00:00\",\n    \"2026-08-08 00:00:00\",\n    \"2026-08-09 00:00:00\",\n    \"2026-08-15 00:00:00\",\n    \"2026-08-16 00:00:00\",\n    \"2026-08-22 00:00:00\",\n    \"2026-08-23 00:00:00\",\n    \"2026-08-29 00:00:00\",\n    \"2026-08-30 00:00:00\",\n    \"2026-09-05 00:00:00\",\n    \"2026-09-06 00:00:00\",\n    \"2026-09-12 00:00:00\",\n    \"2026-09-13 00:00:00\",\n    \"2026-09-19 00:00:00\",\n    \"2026-09-25 00:00:00\",\n    \"2026-09-26 00:00:00\",\n    \"2026-09-27 00:00:00\",\n    \"2026-10-01 00:00:00\",\n    \"2026-10-02 00:00:00\",\n    \"2026-10-03 00:00:00\",\n    \"2026-10-04 00:00:00\",\n    \"2026-10-05 00:00:00\",\n    \"2026-10-06 00:00:00\",\n    \"2026-10-07 00:00:00\",\n    \"2026-10-11 00:00:00\",\n    \"2026-10-17 00:00:00\",\n    \"2026-10-18 00:00:00\",\n    \"2026-10-24 00:00:00\",\n    \"2026-10-25 00:00:00\",\n    \"2026-10-31 00:00:00\",\n    \"2026-11-01 00:00:00\",\n    \"2026-11-07 00:00:00\",\n    \"2026-11-08 00:00:00\",\n    \"2026-11-14 00:00:00\",\n    \"2026-11-15 00:00:00\",\n    \"2026-11-21 00:00:00\",\n    \"2026-11-22 00:00:00\",\n    \"2026-11-28 00:00:00\",\n    \"2026-11-29 00:00:00\",\n    \"2026-12-05 00:00:00\",\n    \"2026-12-06 00:00:00\",\n    \"2026-12-12 00:00:00\",\n    \"2026-12-13 00:00:00\",\n    \"2026-12-19 00:00:00\",\n    \"2026-12-20 00:00:00\",\n    \"2026-12-26 00:00:00\",\n    \"2026-12-27 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-02 00:00:00\",\n    \"2027-01-03 00:00:00\",\n    \"2027-01-09 00:00:00\",\n    \"2027-01-10 00:00:00\",\n    \"2027-01-16 00:00:00\",\n    \"2027-01-17 00:00:00\",\n    \"2027-01-23 00:00:00\",\n    \"2027-01-24 00:00:00\",\n    \"2027-01-30 00:00:00\",\n    \"2027-02-05 00:00:00\",\n    \"2027-02-06 00:00:00\",\n    \"2027-02-07 00:00:00\",\n    \"2027-02-08 00:00:00\",\n    \"2027-02-09 00:00:00\",\n    \"2027-02-10 00:00:00\",\n    \"2027-02-11 00:00:00\",\n    \"2027-02-12 00:00:00\",\n    \"2027-02-14 00:00:00\",\n    \"2027-02-20 00:00:00\",\n    \"2027-02-21 00:00:00\",\n    \"2027-02-27 00:00:00\",\n    \"2027-02-28 00:00:00\",\n    \"2027-03-06 00:00:00\",\n    \"2027-03-07 00:00:00\",\n    \"2027-03-13 00:00:00\",\n    \"2027-03-14 00:00:00\",\n    \"2027-03-20 00:00:00\",\n    \"2027-03-21 00:00:00\",\n    \"2027-03-27 00:00:00\",\n    \"2027-03-28 00:00:00\",\n    \"2027-04-03 00:00:00\",\n    \"2027-04-04 00:00:00\",\n    \"2027-04-05 00:00:00\",\n    \"2027-04-10 00:00:00\",\n    \"2027-04-11 00:00:00\",\n    \"2027-04-17 00:00:00\",\n    \"2027-04-18 00:00:00\",\n    \"2027-04-24 00:00:00\",\n    \"2027-04-25 00:00:00\",\n    \"2027-05-01 00:00:00\",\n    \"2027-05-02 00:00:00\",\n    \"2027-05-03 00:00:00\",\n    \"2027-05-04 00:00:00\",\n    \"2027-05-05 00:00:00\",\n    \"2027-05-08 00:00:00\",\n    \"2027-05-15 00:00:00\",\n    \"2027-05-16 00:00:00\",\n    \"2027-05-22 00:00:00\",\n    \"2027-05-23 00:00:00\",\n    \"2027-05-29 00:00:00\",\n    \"2027-05-30 00:00:00\",\n    \"2027-06-05 00:00:00\",\n    \"2027-06-06 00:00:00\",\n    \"2027-06-09 00:00:00\",\n    \"2027-06-12 00:00:00\",\n    \"2027-06-13 00:00:00\",\n    \"2027-06-19 00:00:00\",\n    \"2027-06-20 00:00:00\",\n    \"2027-06-26 00:00:00\",\n    \"2027-06-27 00:00:00\",\n    \"2027-07-03 00:00:00\",\n    \"2027-07-04 00:00:00\",\n    \"2027-07-10 00:00:00\",\n    \"2027-07-11 00:00:00\",\n    \"2027-07-17 00:00:00\",\n    \"2027-07-18 00:00:00\",\n    \"2027-07-24 00:00:00\",\n    \"2027-07-25 00:00:00\",\n    \"2027-07-31 00:00:00\",\n    \"2027-08-01 00:00:00\",\n    \"2027-08-07 00:00:00\",\n    \"2027-08-08 00:00:00\",\n    \"2027-08-14 00:00:00\",\n    \"2027-08-15 00:00:00\",\n    \"2027-08-21 00:00:00\",\n    \"2027-08-22 00:00:00\",\n    \"2027-08-28 00:00:00\",\n    \"2027-08-29 00:00:00\",\n    \"2027-09-04 00:00:00\",\n    \"2027-09-05 00:00:00\",\n    \"2027-09-11 00:00:00\",\n    \"2027-09-12 00:00:00\",\n    \"2027-09-15 00:00:00\",\n    \"2027-09-18 00:00:00\",\n    \"2027-09-19 00:00:00\",\n    \"2027-09-25 00:00:00\",\n    \"2027-10-01 00:00:00\",\n    \"2027-10-02 00:00:00\",\n    \"2027-10-03 00:00:00\",\n    \"2027-10-04 00:00:00\",\n    \"2027-10-05 00:00:00\",\n    \"2027-10-06 00:00:00\",\n    \"2027-10-07 00:00:00\",\n    \"2027-10-10 00:00:00\",\n    \"2027-10-16 00:00:00\",\n    \"2027-10-17 00:00:00\",\n    \"2027-10-23 00:00:00\",\n    \"2027-10-24 00:00:00\",\n    \"2027-10-30 00:00:00\",\n    \"2027-10-31 00:00:00\",\n    \"2027-11-06 00:00:00\",\n    \"2027-11-07 00:00:00\",\n    \"2027-11-13 00:00:00\",\n    \"2027-11-14 00:00:00\",\n    \"2027-11-20 00:00:00\",\n    \"2027-11-21 00:00:00\",\n    \"2027-11-27 00:00:00\",\n    \"2027-11-28 00:00:00\",\n    \"2027-12-04 00:00:00\",\n    \"2027-12-05 00:00:00\",\n    \"2027-12-11 00:00:00\",\n    \"2027-12-12 00:00:00\",\n    \"2027-12-18 00:00:00\",\n    \"2027-12-19 00:00:00\",\n    \"2027-12-25 00:00:00\",\n    \"2027-12-26 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-01-02 00:00:00\",\n    \"2028-01-03 00:00:00\",\n    \"2028-01-08 00:00:00\",\n    \"2028-01-09 00:00:00\",\n    \"2028-01-15 00:00:00\",\n    \"2028-01-16 00:00:00\",\n    \"2028-01-22 00:00:00\",\n    \"2028-01-25 00:00:00\",\n    \"2028-01-26 00:00:00\",\n    \"2028-01-27 00:00:00\",\n    \"2028-01-28 00:00:00\",\n    \"2028-01-29 00:00:00\",\n    \"2028-01-30 00:00:00\",\n    \"2028-01-31 00:00:00\",\n    \"2028-02-01 00:00:00\",\n    \"2028-02-06 00:00:00\",\n    \"2028-02-12 00:00:00\",\n    \"2028-02-13 00:00:00\",\n    \"2028-02-19 00:00:00\",\n    \"2028-02-20 00:00:00\",\n    \"2028-02-26 00:00:00\",\n    \"2028-02-27 00:00:00\",\n    \"2028-03-04 00:00:00\",\n    \"2028-03-05 00:00:00\",\n    \"2028-03-11 00:00:00\",\n    \"2028-03-12 00:00:00\",\n    \"2028-03-18 00:00:00\",\n    \"2028-03-19 00:00:00\",\n    \"2028-03-25 00:00:00\",\n    \"2028-04-01 00:00:00\",\n    \"2028-04-02 00:00:00\",\n    \"2028-04-03 00:00:00\",\n    \"2028-04-04 00:00:00\",\n    \"2028-04-08 00:00:00\",\n    \"2028-04-09 00:00:00\",\n    \"2028-04-15 00:00:00\",\n    \"2028-04-16 00:00:00\",\n    \"2028-04-22 00:00:00\",\n    \"2028-04-23 00:00:00\",\n    \"2028-04-29 00:00:00\",\n    \"2028-04-30 00:00:00\",\n    \"2028-05-01 00:00:00\",\n    \"2028-05-02 00:00:00\",\n    \"2028-05-03 00:00:00\",\n    \"2028-05-06 00:00:00\",\n    \"2028-05-13 00:00:00\",\n    \"2028-05-14 00:00:00\",\n    \"2028-05-20 00:00:00\",\n    \"2028-05-21 00:00:00\",\n    \"2028-05-27 00:00:00\",\n    \"2028-05-28 00:00:00\",\n    \"2028-05-29 00:00:00\",\n    \"2028-06-03 00:00:00\",\n    \"2028-06-04 00:00:00\",\n    \"2028-06-10 00:00:00\",\n    \"2028-06-11 00:00:00\",\n    \"2028-06-17 00:00:00\",\n    \"2028-06-18 00:00:00\",\n    \"2028-06-24 00:00:00\",\n    \"2028-06-25 00:00:00\",\n    \"2028-07-01 00:00:00\",\n    \"2028-07-02 00:00:00\",\n    \"2028-07-08 00:00:00\",\n    \"2028-07-09 00:00:00\",\n    \"2028-07-15 00:00:00\",\n    \"2028-07-16 00:00:00\",\n    \"2028-07-22 00:00:00\",\n    \"2028-07-23 00:00:00\",\n    \"2028-07-29 00:00:00\",\n    \"2028-07-30 00:00:00\",\n    \"2028-08-05 00:00:00\",\n    \"2028-08-06 00:00:00\",\n    \"2028-08-12 00:00:00\",\n    \"2028-08-13 00:00:00\",\n    \"2028-08-19 00:00:00\",\n    \"2028-08-20 00:00:00\",\n    \"2028-08-26 00:00:00\",\n    \"2028-08-27 00:00:00\",\n    \"2028-09-02 00:00:00\",\n    \"2028-09-03 00:00:00\",\n    \"2028-09-09 00:00:00\",\n    \"2028-09-10 00:00:00\",\n    \"2028-09-16 00:00:00\",\n    \"2028-09-17 00:00:00\",\n    \"2028-09-23 00:00:00\",\n    \"2028-09-30 00:00:00\",\n    \"2028-10-01 00:00:00\",\n    \"2028-10-02 00:00:00\",\n    \"2028-10-03 00:00:00\",\n    \"2028-10-04 00:00:00\",\n    \"2028-10-05 00:00:00\",\n    \"2028-10-06 00:00:00\",\n    \"2028-10-07 00:00:00\",\n    \"2028-10-08 00:00:00\",\n    \"2028-10-14 00:00:00\",\n    \"2028-10-15 00:00:00\",\n    \"2028-10-21 00:00:00\",\n    \"2028-10-22 00:00:00\",\n    \"2028-10-28 00:00:00\",\n    \"2028-10-29 00:00:00\",\n    \"2028-11-04 00:00:00\",\n    \"2028-11-05 00:00:00\",\n    \"2028-11-11 00:00:00\",\n    \"2028-11-12 00:00:00\",\n    \"2028-11-18 00:00:00\",\n    \"2028-11-19 00:00:00\",\n    \"2028-11-25 00:00:00\",\n    \"2028-11-26 00:00:00\",\n    \"2028-12-02 00:00:00\",\n    \"2028-12-03 00:00:00\",\n    \"2028-12-09 00:00:00\",\n    \"2028-12-10 00:00:00\",\n    \"2028-12-16 00:00:00\",\n    \"2028-12-17 00:00:00\",\n    \"2028-12-23 00:00:00\",\n    \"2028-12-24 00:00:00\",\n    \"2028-12-30 00:00:00\",\n    \"2028-12-31 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-06 00:00:00\",\n    \"2029-01-07 00:00:00\",\n    \"2029-01-13 00:00:00\",\n    \"2029-01-14 00:00:00\",\n    \"2029-01-20 00:00:00\",\n    \"2029-01-21 00:00:00\",\n    \"2029-01-27 00:00:00\",\n    \"2029-01-28 00:00:00\",\n    \"2029-02-03 00:00:00\",\n    \"2029-02-04 00:00:00\",\n    \"2029-02-11 00:00:00\",\n    \"2029-02-12 00:00:00\",\n    \"2029-02-13 00:00:00\",\n    \"2029-02-14 00:00:00\",\n    \"2029-02-15 00:00:00\",\n    \"2029-02-16 00:00:00\",\n    \"2029-02-17 00:00:00\",\n    \"2029-02-18 00:00:00\",\n    \"2029-02-19 00:00:00\",\n    \"2029-02-25 00:00:00\",\n    \"2029-03-03 00:00:00\",\n    \"2029-03-04 00:00:00\",\n    \"2029-03-10 00:00:00\",\n    \"2029-03-11 00:00:00\",\n    \"2029-03-17 00:00:00\",\n    \"2029-03-18 00:00:00\",\n    \"2029-03-24 00:00:00\",\n    \"2029-03-25 00:00:00\",\n    \"2029-03-31 00:00:00\",\n    \"2029-04-01 00:00:00\",\n    \"2029-04-04 00:00:00\",\n    \"2029-04-07 00:00:00\",\n    \"2029-04-08 00:00:00\",\n    \"2029-04-14 00:00:00\",\n    \"2029-04-15 00:00:00\",\n    \"2029-04-21 00:00:00\",\n    \"2029-04-22 00:00:00\",\n    \"2029-04-28 00:00:00\",\n    \"2029-04-29 00:00:00\",\n    \"2029-04-30 00:00:00\",\n    \"2029-05-01 00:00:00\",\n    \"2029-05-02 00:00:00\",\n    \"2029-05-05 00:00:00\",\n    \"2029-05-06 00:00:00\",\n    \"2029-05-12 00:00:00\",\n    \"2029-05-13 00:00:00\",\n    \"2029-05-19 00:00:00\",\n    \"2029-05-20 00:00:00\",\n    \"2029-05-26 00:00:00\",\n    \"2029-05-27 00:00:00\",\n    \"2029-06-02 00:00:00\",\n    \"2029-06-03 00:00:00\",\n    \"2029-06-09 00:00:00\",\n    \"2029-06-10 00:00:00\",\n    \"2029-06-16 00:00:00\",\n    \"2029-06-17 00:00:00\",\n    \"2029-06-18 00:00:00\",\n    \"2029-06-23 00:00:00\",\n    \"2029-06-24 00:00:00\",\n    \"2029-06-30 00:00:00\",\n    \"2029-07-01 00:00:00\",\n    \"2029-07-07 00:00:00\",\n    \"2029-07-08 00:00:00\",\n    \"2029-07-14 00:00:00\",\n    \"2029-07-15 00:00:00\",\n    \"2029-07-21 00:00:00\",\n    \"2029-07-22 00:00:00\",\n    \"2029-07-28 00:00:00\",\n    \"2029-07-29 00:00:00\",\n    \"2029-08-04 00:00:00\",\n    \"2029-08-05 00:00:00\",\n    \"2029-08-11 00:00:00\",\n    \"2029-08-12 00:00:00\",\n    \"2029-08-18 00:00:00\",\n    \"2029-08-19 00:00:00\",\n    \"2029-08-25 00:00:00\",\n    \"2029-08-26 00:00:00\",\n    \"2029-09-01 00:00:00\",\n    \"2029-09-02 00:00:00\",\n    \"2029-09-08 00:00:00\",\n    \"2029-09-09 00:00:00\",\n    \"2029-09-15 00:00:00\",\n    \"2029-09-16 00:00:00\",\n    \"2029-09-22 00:00:00\",\n    \"2029-09-23 00:00:00\",\n    \"2029-09-24 00:00:00\",\n    \"2029-09-30 00:00:00\",\n    \"2029-10-01 00:00:00\",\n    \"2029-10-02 00:00:00\",\n    \"2029-10-03 00:00:00\",\n    \"2029-10-04 00:00:00\",\n    \"2029-10-05 00:00:00\",\n    \"2029-10-06 00:00:00\",\n    \"2029-10-07 00:00:00\",\n    \"2029-10-14 00:00:00\",\n    \"2029-10-20 00:00:00\",\n    \"2029-10-21 00:00:00\",\n    \"2029-10-27 00:00:00\",\n    \"2029-10-28 00:00:00\",\n    \"2029-11-03 00:00:00\",\n    \"2029-11-04 00:00:00\",\n    \"2029-11-10 00:00:00\",\n    \"2029-11-11 00:00:00\",\n    \"2029-11-17 00:00:00\",\n    \"2029-11-18 00:00:00\",\n    \"2029-11-24 00:00:00\",\n    \"2029-11-25 00:00:00\",\n    \"2029-12-01 00:00:00\",\n    \"2029-12-02 00:00:00\",\n    \"2029-12-08 00:00:00\",\n    \"2029-12-09 00:00:00\",\n    \"2029-12-15 00:00:00\",\n    \"2029-12-16 00:00:00\",\n    \"2029-12-22 00:00:00\",\n    \"2029-12-23 00:00:00\",\n    \"2029-12-30 00:00:00\",\n    \"2029-12-31 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-05 00:00:00\",\n    \"2030-01-06 00:00:00\",\n    \"2030-01-12 00:00:00\",\n    \"2030-01-13 00:00:00\",\n    \"2030-01-19 00:00:00\",\n    \"2030-01-20 00:00:00\",\n    \"2030-01-26 00:00:00\",\n    \"2030-02-01 00:00:00\",\n    \"2030-02-02 00:00:00\",\n    \"2030-02-03 00:00:00\",\n    \"2030-02-04 00:00:00\",\n    \"2030-02-05 00:00:00\",\n    \"2030-02-06 00:00:00\",\n    \"2030-02-07 00:00:00\",\n    \"2030-02-08 00:00:00\",\n    \"2030-02-10 00:00:00\",\n    \"2030-02-16 00:00:00\",\n    \"2030-02-17 00:00:00\",\n    \"2030-02-23 00:00:00\",\n    \"2030-02-24 00:00:00\",\n    \"2030-03-02 00:00:00\",\n    \"2030-03-03 00:00:00\",\n    \"2030-03-09 00:00:00\",\n    \"2030-03-10 00:00:00\",\n    \"2030-03-16 00:00:00\",\n    \"2030-03-17 00:00:00\",\n    \"2030-03-23 00:00:00\",\n    \"2030-03-24 00:00:00\",\n    \"2030-03-30 00:00:00\",\n    \"2030-03-31 00:00:00\",\n    \"2030-04-04 00:00:00\",\n    \"2030-04-05 00:00:00\",\n    \"2030-04-06 00:00:00\",\n    \"2030-04-07 00:00:00\",\n    \"2030-04-13 00:00:00\",\n    \"2030-04-20 00:00:00\",\n    \"2030-04-21 00:00:00\",\n    \"2030-04-27 00:00:00\",\n    \"2030-04-28 00:00:00\",\n    \"2030-05-01 00:00:00\",\n    \"2030-05-02 00:00:00\",\n    \"2030-05-04 00:00:00\",\n    \"2030-05-05 00:00:00\",\n    \"2030-05-11 00:00:00\",\n    \"2030-05-12 00:00:00\",\n    \"2030-05-18 00:00:00\",\n    \"2030-05-19 00:00:00\",\n    \"2030-05-25 00:00:00\",\n    \"2030-05-26 00:00:00\",\n    \"2030-06-01 00:00:00\",\n    \"2030-06-02 00:00:00\",\n    \"2030-06-05 00:00:00\",\n    \"2030-06-08 00:00:00\",\n    \"2030-06-09 00:00:00\",\n    \"2030-06-15 00:00:00\",\n    \"2030-06-16 00:00:00\",\n    \"2030-06-22 00:00:00\",\n    \"2030-06-23 00:00:00\",\n    \"2030-06-29 00:00:00\",\n    \"2030-06-30 00:00:00\",\n    \"2030-07-06 00:00:00\",\n    \"2030-07-07 00:00:00\",\n    \"2030-07-13 00:00:00\",\n    \"2030-07-14 00:00:00\",\n    \"2030-07-20 00:00:00\",\n    \"2030-07-21 00:00:00\",\n    \"2030-07-27 00:00:00\",\n    \"2030-07-28 00:00:00\",\n    \"2030-08-03 00:00:00\",\n    \"2030-08-04 00:00:00\",\n    \"2030-08-10 00:00:00\",\n    \"2030-08-11 00:00:00\",\n    \"2030-08-17 00:00:00\",\n    \"2030-08-18 00:00:00\",\n    \"2030-08-24 00:00:00\",\n    \"2030-08-25 00:00:00\",\n    \"2030-08-31 00:00:00\",\n    \"2030-09-01 00:00:00\",\n    \"2030-09-07 00:00:00\",\n    \"2030-09-08 00:00:00\",\n    \"2030-09-12 00:00:00\",\n    \"2030-09-13 00:00:00\",\n    \"2030-09-14 00:00:00\",\n    \"2030-09-15 00:00:00\",\n    \"2030-09-21 00:00:00\",\n    \"2030-09-28 00:00:00\",\n    \"2030-10-01 00:00:00\",\n    \"2030-10-02 00:00:00\",\n    \"2030-10-03 00:00:00\",\n    \"2030-10-04 00:00:00\",\n    \"2030-10-05 00:00:00\",\n    \"2030-10-06 00:00:00\",\n    \"2030-10-07 00:00:00\",\n    \"2030-10-13 00:00:00\",\n    \"2030-10-19 00:00:00\",\n    \"2030-10-20 00:00:00\",\n    \"2030-10-26 00:00:00\",\n    \"2030-10-27 00:00:00\",\n    \"2030-11-02 00:00:00\",\n    \"2030-11-03 00:00:00\",\n    \"2030-11-09 00:00:00\",\n    \"2030-11-10 00:00:00\",\n    \"2030-11-16 00:00:00\",\n    \"2030-11-17 00:00:00\",\n    \"2030-11-23 00:00:00\",\n    \"2030-11-24 00:00:00\",\n    \"2030-11-30 00:00:00\",\n    \"2030-12-01 00:00:00\",\n    \"2030-12-07 00:00:00\",\n    \"2030-12-08 00:00:00\",\n    \"2030-12-14 00:00:00\",\n    \"2030-12-15 00:00:00\",\n    \"2030-12-21 00:00:00\",\n    \"2030-12-22 00:00:00\",\n    \"2030-12-28 00:00:00\",\n    \"2030-12-29 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-04 00:00:00\",\n    \"2031-01-05 00:00:00\",\n    \"2031-01-11 00:00:00\",\n    \"2031-01-12 00:00:00\",\n    \"2031-01-18 00:00:00\",\n    \"2031-01-22 00:00:00\",\n    \"2031-01-23 00:00:00\",\n    \"2031-01-24 00:00:00\",\n    \"2031-01-25 00:00:00\",\n    \"2031-01-26 00:00:00\",\n    \"2031-01-27 00:00:00\",\n    \"2031-01-28 00:00:00\",\n    \"2031-01-29 00:00:00\",\n    \"2031-02-02 00:00:00\",\n    \"2031-02-08 00:00:00\",\n    \"2031-02-09 00:00:00\",\n    \"2031-02-15 00:00:00\",\n    \"2031-02-16 00:00:00\",\n    \"2031-02-22 00:00:00\",\n    \"2031-02-23 00:00:00\",\n    \"2031-03-01 00:00:00\",\n    \"2031-03-02 00:00:00\",\n    \"2031-03-08 00:00:00\",\n    \"2031-03-09 00:00:00\",\n    \"2031-03-15 00:00:00\",\n    \"2031-03-16 00:00:00\",\n    \"2031-03-22 00:00:00\",\n    \"2031-03-23 00:00:00\",\n    \"2031-03-29 00:00:00\",\n    \"2031-03-30 00:00:00\",\n    \"2031-04-04 00:00:00\",\n    \"2031-04-05 00:00:00\",\n    \"2031-04-06 00:00:00\",\n    \"2031-04-12 00:00:00\",\n    \"2031-04-13 00:00:00\",\n    \"2031-04-19 00:00:00\",\n    \"2031-04-20 00:00:00\",\n    \"2031-04-26 00:00:00\",\n    \"2031-05-01 00:00:00\",\n    \"2031-05-02 00:00:00\",\n    \"2031-05-03 00:00:00\",\n    \"2031-05-04 00:00:00\",\n    \"2031-05-05 00:00:00\",\n    \"2031-05-10 00:00:00\",\n    \"2031-05-11 00:00:00\",\n    \"2031-05-17 00:00:00\",\n    \"2031-05-18 00:00:00\",\n    \"2031-05-24 00:00:00\",\n    \"2031-05-25 00:00:00\",\n    \"2031-05-31 00:00:00\",\n    \"2031-06-01 00:00:00\",\n    \"2031-06-07 00:00:00\",\n    \"2031-06-08 00:00:00\",\n    \"2031-06-14 00:00:00\",\n    \"2031-06-21 00:00:00\",\n    \"2031-06-22 00:00:00\",\n    \"2031-06-23 00:00:00\",\n    \"2031-06-24 00:00:00\",\n    \"2031-06-28 00:00:00\",\n    \"2031-06-29 00:00:00\",\n    \"2031-07-05 00:00:00\",\n    \"2031-07-06 00:00:00\",\n    \"2031-07-12 00:00:00\",\n    \"2031-07-13 00:00:00\",\n    \"2031-07-19 00:00:00\",\n    \"2031-07-20 00:00:00\",\n    \"2031-07-26 00:00:00\",\n    \"2031-07-27 00:00:00\",\n    \"2031-08-02 00:00:00\",\n    \"2031-08-03 00:00:00\",\n    \"2031-08-09 00:00:00\",\n    \"2031-08-10 00:00:00\",\n    \"2031-08-16 00:00:00\",\n    \"2031-08-17 00:00:00\",\n    \"2031-08-23 00:00:00\",\n    \"2031-08-24 00:00:00\",\n    \"2031-08-30 00:00:00\",\n    \"2031-08-31 00:00:00\",\n    \"2031-09-06 00:00:00\",\n    \"2031-09-07 00:00:00\",\n    \"2031-09-13 00:00:00\",\n    \"2031-09-14 00:00:00\",\n    \"2031-09-20 00:00:00\",\n    \"2031-09-21 00:00:00\",\n    \"2031-09-27 00:00:00\",\n    \"2031-10-01 00:00:00\",\n    \"2031-10-02 00:00:00\",\n    \"2031-10-03 00:00:00\",\n    \"2031-10-04 00:00:00\",\n    \"2031-10-05 00:00:00\",\n    \"2031-10-06 00:00:00\",\n    \"2031-10-07 00:00:00\",\n    \"2031-10-08 00:00:00\",\n    \"2031-10-12 00:00:00\",\n    \"2031-10-18 00:00:00\",\n    \"2031-10-19 00:00:00\",\n    \"2031-10-25 00:00:00\",\n    \"2031-10-26 00:00:00\",\n    \"2031-11-01 00:00:00\",\n    \"2031-11-02 00:00:00\",\n    \"2031-11-08 00:00:00\",\n    \"2031-11-09 00:00:00\",\n    \"2031-11-15 00:00:00\",\n    \"2031-11-16 00:00:00\",\n    \"2031-11-22 00:00:00\",\n    \"2031-11-23 00:00:00\",\n    \"2031-11-29 00:00:00\",\n    \"2031-11-30 00:00:00\",\n    \"2031-12-06 00:00:00\",\n    \"2031-12-07 00:00:00\",\n    \"2031-12-13 00:00:00\",\n    \"2031-12-14 00:00:00\",\n    \"2031-12-20 00:00:00\",\n    \"2031-12-21 00:00:00\",\n    \"2031-12-27 00:00:00\",\n    \"2031-12-28 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-02 00:00:00\",\n    \"2032-01-03 00:00:00\",\n    \"2032-01-10 00:00:00\",\n    \"2032-01-11 00:00:00\",\n    \"2032-01-17 00:00:00\",\n    \"2032-01-18 00:00:00\",\n    \"2032-01-24 00:00:00\",\n    \"2032-01-25 00:00:00\",\n    \"2032-01-31 00:00:00\",\n    \"2032-02-01 00:00:00\",\n    \"2032-02-07 00:00:00\",\n    \"2032-02-10 00:00:00\",\n    \"2032-02-11 00:00:00\",\n    \"2032-02-12 00:00:00\",\n    \"2032-02-13 00:00:00\",\n    \"2032-02-14 00:00:00\",\n    \"2032-02-15 00:00:00\",\n    \"2032-02-16 00:00:00\",\n    \"2032-02-17 00:00:00\",\n    \"2032-02-22 00:00:00\",\n    \"2032-02-28 00:00:00\",\n    \"2032-02-29 00:00:00\",\n    \"2032-03-06 00:00:00\",\n    \"2032-03-07 00:00:00\",\n    \"2032-03-13 00:00:00\",\n    \"2032-03-14 00:00:00\",\n    \"2032-03-20 00:00:00\",\n    \"2032-03-21 00:00:00\",\n    \"2032-03-27 00:00:00\",\n    \"2032-03-28 00:00:00\",\n    \"2032-04-03 00:00:00\",\n    \"2032-04-04 00:00:00\",\n    \"2032-04-05 00:00:00\",\n    \"2032-04-10 00:00:00\",\n    \"2032-04-11 00:00:00\",\n    \"2032-04-17 00:00:00\",\n    \"2032-04-18 00:00:00\",\n    \"2032-04-24 00:00:00\",\n    \"2032-04-25 00:00:00\",\n    \"2032-05-01 00:00:00\",\n    \"2032-05-02 00:00:00\",\n    \"2032-05-03 00:00:00\",\n    \"2032-05-04 00:00:00\",\n    \"2032-05-05 00:00:00\",\n    \"2032-05-08 00:00:00\",\n    \"2032-05-15 00:00:00\",\n    \"2032-05-16 00:00:00\",\n    \"2032-05-22 00:00:00\",\n    \"2032-05-23 00:00:00\",\n    \"2032-05-29 00:00:00\",\n    \"2032-05-30 00:00:00\",\n    \"2032-06-05 00:00:00\",\n    \"2032-06-06 00:00:00\",\n    \"2032-06-12 00:00:00\",\n    \"2032-06-13 00:00:00\",\n    \"2032-06-14 00:00:00\",\n    \"2032-06-19 00:00:00\",\n    \"2032-06-20 00:00:00\",\n    \"2032-06-26 00:00:00\",\n    \"2032-06-27 00:00:00\",\n    \"2032-07-03 00:00:00\",\n    \"2032-07-04 00:00:00\",\n    \"2032-07-10 00:00:00\",\n    \"2032-07-11 00:00:00\",\n    \"2032-07-17 00:00:00\",\n    \"2032-07-18 00:00:00\",\n    \"2032-07-24 00:00:00\",\n    \"2032-07-25 00:00:00\",\n    \"2032-07-31 00:00:00\",\n    \"2032-08-01 00:00:00\",\n    \"2032-08-07 00:00:00\",\n    \"2032-08-08 00:00:00\",\n    \"2032-08-14 00:00:00\",\n    \"2032-08-15 00:00:00\",\n    \"2032-08-21 00:00:00\",\n    \"2032-08-22 00:00:00\",\n    \"2032-08-28 00:00:00\",\n    \"2032-08-29 00:00:00\",\n    \"2032-09-04 00:00:00\",\n    \"2032-09-05 00:00:00\",\n    \"2032-09-11 00:00:00\",\n    \"2032-09-12 00:00:00\",\n    \"2032-09-18 00:00:00\",\n    \"2032-09-19 00:00:00\",\n    \"2032-09-20 00:00:00\",\n    \"2032-09-25 00:00:00\",\n    \"2032-10-01 00:00:00\",\n    \"2032-10-02 00:00:00\",\n    \"2032-10-03 00:00:00\",\n    \"2032-10-04 00:00:00\",\n    \"2032-10-05 00:00:00\",\n    \"2032-10-06 00:00:00\",\n    \"2032-10-07 00:00:00\",\n    \"2032-10-10 00:00:00\",\n    \"2032-10-16 00:00:00\",\n    \"2032-10-17 00:00:00\",\n    \"2032-10-23 00:00:00\",\n    \"2032-10-24 00:00:00\",\n    \"2032-10-30 00:00:00\",\n    \"2032-10-31 00:00:00\",\n    \"2032-11-06 00:00:00\",\n    \"2032-11-07 00:00:00\",\n    \"2032-11-13 00:00:00\",\n    \"2032-11-14 00:00:00\",\n    \"2032-11-20 00:00:00\",\n    \"2032-11-21 00:00:00\",\n    \"2032-11-27 00:00:00\",\n    \"2032-11-28 00:00:00\",\n    \"2032-12-04 00:00:00\",\n    \"2032-12-05 00:00:00\",\n    \"2032-12-11 00:00:00\",\n    \"2032-12-12 00:00:00\",\n    \"2032-12-18 00:00:00\",\n    \"2032-12-19 00:00:00\",\n    \"2032-12-25 00:00:00\",\n    \"2032-12-26 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-01-02 00:00:00\",\n    \"2033-01-03 00:00:00\",\n    \"2033-01-08 00:00:00\",\n    \"2033-01-09 00:00:00\",\n    \"2033-01-15 00:00:00\",\n    \"2033-01-16 00:00:00\",\n    \"2033-01-22 00:00:00\",\n    \"2033-01-23 00:00:00\",\n    \"2033-01-30 00:00:00\",\n    \"2033-01-31 00:00:00\",\n    \"2033-02-01 00:00:00\",\n    \"2033-02-02 00:00:00\",\n    \"2033-02-03 00:00:00\",\n    \"2033-02-04 00:00:00\",\n    \"2033-02-05 00:00:00\",\n    \"2033-02-06 00:00:00\",\n    \"2033-02-13 00:00:00\",\n    \"2033-02-19 00:00:00\",\n    \"2033-02-20 00:00:00\",\n    \"2033-02-26 00:00:00\",\n    \"2033-02-27 00:00:00\",\n    \"2033-03-05 00:00:00\",\n    \"2033-03-06 00:00:00\",\n    \"2033-03-12 00:00:00\",\n    \"2033-03-13 00:00:00\",\n    \"2033-03-19 00:00:00\",\n    \"2033-03-20 00:00:00\",\n    \"2033-03-26 00:00:00\",\n    \"2033-03-27 00:00:00\",\n    \"2033-04-02 00:00:00\",\n    \"2033-04-03 00:00:00\",\n    \"2033-04-04 00:00:00\",\n    \"2033-04-09 00:00:00\",\n    \"2033-04-10 00:00:00\",\n    \"2033-04-16 00:00:00\",\n    \"2033-04-17 00:00:00\",\n    \"2033-04-23 00:00:00\",\n    \"2033-04-24 00:00:00\",\n    \"2033-04-30 00:00:00\",\n    \"2033-05-01 00:00:00\",\n    \"2033-05-02 00:00:00\",\n    \"2033-05-03 00:00:00\",\n    \"2033-05-04 00:00:00\",\n    \"2033-05-07 00:00:00\",\n    \"2033-05-14 00:00:00\",\n    \"2033-05-15 00:00:00\",\n    \"2033-05-21 00:00:00\",\n    \"2033-05-22 00:00:00\",\n    \"2033-05-28 00:00:00\",\n    \"2033-05-29 00:00:00\",\n    \"2033-06-01 00:00:00\",\n    \"2033-06-04 00:00:00\",\n    \"2033-06-05 00:00:00\",\n    \"2033-06-11 00:00:00\",\n    \"2033-06-12 00:00:00\",\n    \"2033-06-18 00:00:00\",\n    \"2033-06-19 00:00:00\",\n    \"2033-06-25 00:00:00\",\n    \"2033-06-26 00:00:00\",\n    \"2033-07-02 00:00:00\",\n    \"2033-07-03 00:00:00\",\n    \"2033-07-09 00:00:00\",\n    \"2033-07-10 00:00:00\",\n    \"2033-07-16 00:00:00\",\n    \"2033-07-17 00:00:00\",\n    \"2033-07-23 00:00:00\",\n    \"2033-07-24 00:00:00\",\n    \"2033-07-30 00:00:00\",\n    \"2033-07-31 00:00:00\",\n    \"2033-08-06 00:00:00\",\n    \"2033-08-07 00:00:00\",\n    \"2033-08-13 00:00:00\",\n    \"2033-08-14 00:00:00\",\n    \"2033-08-20 00:00:00\",\n    \"2033-08-21 00:00:00\",\n    \"2033-08-27 00:00:00\",\n    \"2033-08-28 00:00:00\",\n    \"2033-09-03 00:00:00\",\n    \"2033-09-04 00:00:00\",\n    \"2033-09-08 00:00:00\",\n    \"2033-09-09 00:00:00\",\n    \"2033-09-10 00:00:00\",\n    \"2033-09-11 00:00:00\",\n    \"2033-09-17 00:00:00\",\n    \"2033-09-24 00:00:00\",\n    \"2033-10-01 00:00:00\",\n    \"2033-10-02 00:00:00\",\n    \"2033-10-03 00:00:00\",\n    \"2033-10-04 00:00:00\",\n    \"2033-10-05 00:00:00\",\n    \"2033-10-06 00:00:00\",\n    \"2033-10-07 00:00:00\",\n    \"2033-10-08 00:00:00\",\n    \"2033-10-15 00:00:00\",\n    \"2033-10-16 00:00:00\",\n    \"2033-10-22 00:00:00\",\n    \"2033-10-23 00:00:00\",\n    \"2033-10-29 00:00:00\",\n    \"2033-10-30 00:00:00\",\n    \"2033-11-05 00:00:00\",\n    \"2033-11-06 00:00:00\",\n    \"2033-11-12 00:00:00\",\n    \"2033-11-13 00:00:00\",\n    \"2033-11-19 00:00:00\",\n    \"2033-11-20 00:00:00\",\n    \"2033-11-26 00:00:00\",\n    \"2033-11-27 00:00:00\",\n    \"2033-12-03 00:00:00\",\n    \"2033-12-04 00:00:00\",\n    \"2033-12-10 00:00:00\",\n    \"2033-12-11 00:00:00\",\n    \"2033-12-17 00:00:00\",\n    \"2033-12-18 00:00:00\",\n    \"2033-12-24 00:00:00\",\n    \"2033-12-25 00:00:00\",\n    \"2033-12-31 00:00:00\",\n    \"2034-01-01 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-01-07 00:00:00\",\n    \"2034-01-08 00:00:00\",\n    \"2034-01-14 00:00:00\",\n    \"2034-01-15 00:00:00\",\n    \"2034-01-21 00:00:00\",\n    \"2034-01-22 00:00:00\",\n    \"2034-01-28 00:00:00\",\n    \"2034-01-29 00:00:00\",\n    \"2034-02-04 00:00:00\",\n    \"2034-02-05 00:00:00\",\n    \"2034-02-11 00:00:00\",\n    \"2034-02-18 00:00:00\",\n    \"2034-02-19 00:00:00\",\n    \"2034-02-20 00:00:00\",\n    \"2034-02-21 00:00:00\",\n    \"2034-02-22 00:00:00\",\n    \"2034-02-23 00:00:00\",\n    \"2034-02-24 00:00:00\",\n    \"2034-02-25 00:00:00\",\n    \"2034-03-04 00:00:00\",\n    \"2034-03-05 00:00:00\",\n    \"2034-03-11 00:00:00\",\n    \"2034-03-12 00:00:00\",\n    \"2034-03-18 00:00:00\",\n    \"2034-03-19 00:00:00\",\n    \"2034-03-25 00:00:00\",\n    \"2034-04-01 00:00:00\",\n    \"2034-04-02 00:00:00\",\n    \"2034-04-03 00:00:00\",\n    \"2034-04-04 00:00:00\",\n    \"2034-04-08 00:00:00\",\n    \"2034-04-09 00:00:00\",\n    \"2034-04-15 00:00:00\",\n    \"2034-04-16 00:00:00\",\n    \"2034-04-22 00:00:00\",\n    \"2034-04-23 00:00:00\",\n    \"2034-04-29 00:00:00\",\n    \"2034-04-30 00:00:00\",\n    \"2034-05-01 00:00:00\",\n    \"2034-05-02 00:00:00\",\n    \"2034-05-03 00:00:00\",\n    \"2034-05-06 00:00:00\",\n    \"2034-05-13 00:00:00\",\n    \"2034-05-14 00:00:00\",\n    \"2034-05-20 00:00:00\",\n    \"2034-05-21 00:00:00\",\n    \"2034-05-27 00:00:00\",\n    \"2034-05-28 00:00:00\",\n    \"2034-06-03 00:00:00\",\n    \"2034-06-04 00:00:00\",\n    \"2034-06-10 00:00:00\",\n    \"2034-06-17 00:00:00\",\n    \"2034-06-18 00:00:00\",\n    \"2034-06-19 00:00:00\",\n    \"2034-06-20 00:00:00\",\n    \"2034-06-24 00:00:00\",\n    \"2034-06-25 00:00:00\",\n    \"2034-07-01 00:00:00\",\n    \"2034-07-02 00:00:00\",\n    \"2034-07-08 00:00:00\",\n    \"2034-07-09 00:00:00\",\n    \"2034-07-15 00:00:00\",\n    \"2034-07-16 00:00:00\",\n    \"2034-07-22 00:00:00\",\n    \"2034-07-23 00:00:00\",\n    \"2034-07-29 00:00:00\",\n    \"2034-07-30 00:00:00\",\n    \"2034-08-05 00:00:00\",\n    \"2034-08-06 00:00:00\",\n    \"2034-08-12 00:00:00\",\n    \"2034-08-13 00:00:00\",\n    \"2034-08-19 00:00:00\",\n    \"2034-08-20 00:00:00\",\n    \"2034-08-26 00:00:00\",\n    \"2034-08-27 00:00:00\",\n    \"2034-09-02 00:00:00\",\n    \"2034-09-03 00:00:00\",\n    \"2034-09-09 00:00:00\",\n    \"2034-09-10 00:00:00\",\n    \"2034-09-16 00:00:00\",\n    \"2034-09-17 00:00:00\",\n    \"2034-09-23 00:00:00\",\n    \"2034-09-27 00:00:00\",\n    \"2034-09-30 00:00:00\",\n    \"2034-10-01 00:00:00\",\n    \"2034-10-02 00:00:00\",\n    \"2034-10-03 00:00:00\",\n    \"2034-10-04 00:00:00\",\n    \"2034-10-05 00:00:00\",\n    \"2034-10-06 00:00:00\",\n    \"2034-10-07 00:00:00\",\n    \"2034-10-14 00:00:00\",\n    \"2034-10-15 00:00:00\",\n    \"2034-10-21 00:00:00\",\n    \"2034-10-22 00:00:00\",\n    \"2034-10-28 00:00:00\",\n    \"2034-10-29 00:00:00\",\n    \"2034-11-04 00:00:00\",\n    \"2034-11-05 00:00:00\",\n    \"2034-11-11 00:00:00\",\n    \"2034-11-12 00:00:00\",\n    \"2034-11-18 00:00:00\",\n    \"2034-11-19 00:00:00\",\n    \"2034-11-25 00:00:00\",\n    \"2034-11-26 00:00:00\",\n    \"2034-12-02 00:00:00\",\n    \"2034-12-03 00:00:00\",\n    \"2034-12-09 00:00:00\",\n    \"2034-12-10 00:00:00\",\n    \"2034-12-16 00:00:00\",\n    \"2034-12-17 00:00:00\",\n    \"2034-12-23 00:00:00\",\n    \"2034-12-24 00:00:00\",\n    \"2034-12-30 00:00:00\",\n    \"2034-12-31 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-06 00:00:00\",\n    \"2035-01-07 00:00:00\",\n    \"2035-01-13 00:00:00\",\n    \"2035-01-14 00:00:00\",\n    \"2035-01-20 00:00:00\",\n    \"2035-01-21 00:00:00\",\n    \"2035-01-27 00:00:00\",\n    \"2035-01-28 00:00:00\",\n    \"2035-02-03 00:00:00\",\n    \"2035-02-07 00:00:00\",\n    \"2035-02-08 00:00:00\",\n    \"2035-02-09 00:00:00\",\n    \"2035-02-10 00:00:00\",\n    \"2035-02-11 00:00:00\",\n    \"2035-02-12 00:00:00\",\n    \"2035-02-13 00:00:00\",\n    \"2035-02-14 00:00:00\",\n    \"2035-02-18 00:00:00\",\n    \"2035-02-24 00:00:00\",\n    \"2035-02-25 00:00:00\",\n    \"2035-03-03 00:00:00\",\n    \"2035-03-04 00:00:00\",\n    \"2035-03-10 00:00:00\",\n    \"2035-03-11 00:00:00\",\n    \"2035-03-17 00:00:00\",\n    \"2035-03-18 00:00:00\",\n    \"2035-03-24 00:00:00\",\n    \"2035-03-25 00:00:00\",\n    \"2035-03-31 00:00:00\",\n    \"2035-04-01 00:00:00\",\n    \"2035-04-04 00:00:00\",\n    \"2035-04-07 00:00:00\",\n    \"2035-04-08 00:00:00\",\n    \"2035-04-14 00:00:00\",\n    \"2035-04-15 00:00:00\",\n    \"2035-04-21 00:00:00\",\n    \"2035-04-22 00:00:00\",\n    \"2035-04-28 00:00:00\",\n    \"2035-04-29 00:00:00\",\n    \"2035-04-30 00:00:00\",\n    \"2035-05-01 00:00:00\",\n    \"2035-05-02 00:00:00\",\n    \"2035-05-05 00:00:00\",\n    \"2035-05-06 00:00:00\",\n    \"2035-05-12 00:00:00\",\n    \"2035-05-13 00:00:00\",\n    \"2035-05-19 00:00:00\",\n    \"2035-05-20 00:00:00\",\n    \"2035-05-26 00:00:00\",\n    \"2035-05-27 00:00:00\",\n    \"2035-06-02 00:00:00\",\n    \"2035-06-03 00:00:00\",\n    \"2035-06-09 00:00:00\",\n    \"2035-06-10 00:00:00\",\n    \"2035-06-11 00:00:00\",\n    \"2035-06-16 00:00:00\",\n    \"2035-06-17 00:00:00\",\n    \"2035-06-23 00:00:00\",\n    \"2035-06-24 00:00:00\",\n    \"2035-06-30 00:00:00\",\n    \"2035-07-01 00:00:00\",\n    \"2035-07-07 00:00:00\",\n    \"2035-07-08 00:00:00\",\n    \"2035-07-14 00:00:00\",\n    \"2035-07-15 00:00:00\",\n    \"2035-07-21 00:00:00\",\n    \"2035-07-22 00:00:00\",\n    \"2035-07-28 00:00:00\",\n    \"2035-07-29 00:00:00\",\n    \"2035-08-04 00:00:00\",\n    \"2035-08-05 00:00:00\",\n    \"2035-08-11 00:00:00\",\n    \"2035-08-12 00:00:00\",\n    \"2035-08-18 00:00:00\",\n    \"2035-08-19 00:00:00\",\n    \"2035-08-25 00:00:00\",\n    \"2035-08-26 00:00:00\",\n    \"2035-09-01 00:00:00\",\n    \"2035-09-02 00:00:00\",\n    \"2035-09-08 00:00:00\",\n    \"2035-09-09 00:00:00\",\n    \"2035-09-15 00:00:00\",\n    \"2035-09-16 00:00:00\",\n    \"2035-09-17 00:00:00\",\n    \"2035-09-22 00:00:00\",\n    \"2035-09-23 00:00:00\",\n    \"2035-09-30 00:00:00\",\n    \"2035-10-01 00:00:00\",\n    \"2035-10-02 00:00:00\",\n    \"2035-10-03 00:00:00\",\n    \"2035-10-04 00:00:00\",\n    \"2035-10-05 00:00:00\",\n    \"2035-10-06 00:00:00\",\n    \"2035-10-07 00:00:00\",\n    \"2035-10-14 00:00:00\",\n    \"2035-10-20 00:00:00\",\n    \"2035-10-21 00:00:00\",\n    \"2035-10-27 00:00:00\",\n    \"2035-10-28 00:00:00\",\n    \"2035-11-03 00:00:00\",\n    \"2035-11-04 00:00:00\",\n    \"2035-11-10 00:00:00\",\n    \"2035-11-11 00:00:00\",\n    \"2035-11-17 00:00:00\",\n    \"2035-11-18 00:00:00\",\n    \"2035-11-24 00:00:00\",\n    \"2035-11-25 00:00:00\",\n    \"2035-12-01 00:00:00\",\n    \"2035-12-02 00:00:00\",\n    \"2035-12-08 00:00:00\",\n    \"2035-12-09 00:00:00\",\n    \"2035-12-15 00:00:00\",\n    \"2035-12-16 00:00:00\",\n    \"2035-12-22 00:00:00\",\n    \"2035-12-23 00:00:00\",\n    \"2035-12-30 00:00:00\",\n    \"2035-12-31 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-05 00:00:00\",\n    \"2036-01-06 00:00:00\",\n    \"2036-01-12 00:00:00\",\n    \"2036-01-13 00:00:00\",\n    \"2036-01-19 00:00:00\",\n    \"2036-01-20 00:00:00\",\n    \"2036-01-27 00:00:00\",\n    \"2036-01-28 00:00:00\",\n    \"2036-01-29 00:00:00\",\n    \"2036-01-30 00:00:00\",\n    \"2036-01-31 00:00:00\",\n    \"2036-02-01 00:00:00\",\n    \"2036-02-02 00:00:00\",\n    \"2036-02-03 00:00:00\",\n    \"2036-02-10 00:00:00\",\n    \"2036-02-16 00:00:00\",\n    \"2036-02-17 00:00:00\",\n    \"2036-02-23 00:00:00\",\n    \"2036-02-24 00:00:00\",\n    \"2036-03-01 00:00:00\",\n    \"2036-03-02 00:00:00\",\n    \"2036-03-08 00:00:00\",\n    \"2036-03-09 00:00:00\",\n    \"2036-03-15 00:00:00\",\n    \"2036-03-16 00:00:00\",\n    \"2036-03-22 00:00:00\",\n    \"2036-03-23 00:00:00\",\n    \"2036-03-29 00:00:00\",\n    \"2036-03-30 00:00:00\",\n    \"2036-04-04 00:00:00\",\n    \"2036-04-05 00:00:00\",\n    \"2036-04-06 00:00:00\",\n    \"2036-04-12 00:00:00\",\n    \"2036-04-13 00:00:00\",\n    \"2036-04-19 00:00:00\",\n    \"2036-04-20 00:00:00\",\n    \"2036-04-26 00:00:00\",\n    \"2036-05-01 00:00:00\",\n    \"2036-05-02 00:00:00\",\n    \"2036-05-03 00:00:00\",\n    \"2036-05-04 00:00:00\",\n    \"2036-05-05 00:00:00\",\n    \"2036-05-10 00:00:00\",\n    \"2036-05-11 00:00:00\",\n    \"2036-05-17 00:00:00\",\n    \"2036-05-18 00:00:00\",\n    \"2036-05-24 00:00:00\",\n    \"2036-05-25 00:00:00\",\n    \"2036-05-30 00:00:00\",\n    \"2036-05-31 00:00:00\",\n    \"2036-06-01 00:00:00\",\n    \"2036-06-07 00:00:00\",\n    \"2036-06-08 00:00:00\",\n    \"2036-06-14 00:00:00\",\n    \"2036-06-15 00:00:00\",\n    \"2036-06-21 00:00:00\",\n    \"2036-06-22 00:00:00\",\n    \"2036-06-28 00:00:00\",\n    \"2036-06-29 00:00:00\",\n    \"2036-07-05 00:00:00\",\n    \"2036-07-06 00:00:00\",\n    \"2036-07-12 00:00:00\",\n    \"2036-07-13 00:00:00\",\n    \"2036-07-19 00:00:00\",\n    \"2036-07-20 00:00:00\",\n    \"2036-07-26 00:00:00\",\n    \"2036-07-27 00:00:00\",\n    \"2036-08-02 00:00:00\",\n    \"2036-08-03 00:00:00\",\n    \"2036-08-09 00:00:00\",\n    \"2036-08-10 00:00:00\",\n    \"2036-08-16 00:00:00\",\n    \"2036-08-17 00:00:00\",\n    \"2036-08-23 00:00:00\",\n    \"2036-08-24 00:00:00\",\n    \"2036-08-30 00:00:00\",\n    \"2036-08-31 00:00:00\",\n    \"2036-09-06 00:00:00\",\n    \"2036-09-07 00:00:00\",\n    \"2036-09-13 00:00:00\",\n    \"2036-09-14 00:00:00\",\n    \"2036-09-20 00:00:00\",\n    \"2036-09-21 00:00:00\",\n    \"2036-09-27 00:00:00\",\n    \"2036-10-01 00:00:00\",\n    \"2036-10-02 00:00:00\",\n    \"2036-10-03 00:00:00\",\n    \"2036-10-04 00:00:00\",\n    \"2036-10-05 00:00:00\",\n    \"2036-10-06 00:00:00\",\n    \"2036-10-07 00:00:00\",\n    \"2036-10-08 00:00:00\",\n    \"2036-10-12 00:00:00\",\n    \"2036-10-18 00:00:00\",\n    \"2036-10-19 00:00:00\",\n    \"2036-10-25 00:00:00\",\n    \"2036-10-26 00:00:00\",\n    \"2036-11-01 00:00:00\",\n    \"2036-11-02 00:00:00\",\n    \"2036-11-08 00:00:00\",\n    \"2036-11-09 00:00:00\",\n    \"2036-11-15 00:00:00\",\n    \"2036-11-16 00:00:00\",\n    \"2036-11-22 00:00:00\",\n    \"2036-11-23 00:00:00\",\n    \"2036-11-29 00:00:00\",\n    \"2036-11-30 00:00:00\",\n    \"2036-12-06 00:00:00\",\n    \"2036-12-07 00:00:00\",\n    \"2036-12-13 00:00:00\",\n    \"2036-12-14 00:00:00\",\n    \"2036-12-20 00:00:00\",\n    \"2036-12-21 00:00:00\",\n    \"2036-12-27 00:00:00\",\n    \"2036-12-28 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-02 00:00:00\",\n    \"2037-01-03 00:00:00\",\n    \"2037-01-10 00:00:00\",\n    \"2037-01-11 00:00:00\",\n    \"2037-01-17 00:00:00\",\n    \"2037-01-18 00:00:00\",\n    \"2037-01-24 00:00:00\",\n    \"2037-01-25 00:00:00\",\n    \"2037-01-31 00:00:00\",\n    \"2037-02-01 00:00:00\",\n    \"2037-02-07 00:00:00\",\n    \"2037-02-14 00:00:00\",\n    \"2037-02-15 00:00:00\",\n    \"2037-02-16 00:00:00\",\n    \"2037-02-17 00:00:00\",\n    \"2037-02-18 00:00:00\",\n    \"2037-02-19 00:00:00\",\n    \"2037-02-20 00:00:00\",\n    \"2037-02-21 00:00:00\",\n    \"2037-02-28 00:00:00\",\n    \"2037-03-01 00:00:00\",\n    \"2037-03-07 00:00:00\",\n    \"2037-03-08 00:00:00\",\n    \"2037-03-14 00:00:00\",\n    \"2037-03-15 00:00:00\",\n    \"2037-03-21 00:00:00\",\n    \"2037-03-22 00:00:00\",\n    \"2037-03-28 00:00:00\",\n    \"2037-03-29 00:00:00\",\n    \"2037-04-04 00:00:00\",\n    \"2037-04-05 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-04-11 00:00:00\",\n    \"2037-04-12 00:00:00\",\n    \"2037-04-18 00:00:00\",\n    \"2037-04-19 00:00:00\",\n    \"2037-04-25 00:00:00\",\n    \"2037-04-26 00:00:00\",\n    \"2037-05-01 00:00:00\",\n    \"2037-05-02 00:00:00\",\n    \"2037-05-03 00:00:00\",\n    \"2037-05-04 00:00:00\",\n    \"2037-05-05 00:00:00\",\n    \"2037-05-10 00:00:00\",\n    \"2037-05-16 00:00:00\",\n    \"2037-05-17 00:00:00\",\n    \"2037-05-23 00:00:00\",\n    \"2037-05-24 00:00:00\",\n    \"2037-05-30 00:00:00\",\n    \"2037-05-31 00:00:00\",\n    \"2037-06-06 00:00:00\",\n    \"2037-06-07 00:00:00\",\n    \"2037-06-13 00:00:00\",\n    \"2037-06-14 00:00:00\",\n    \"2037-06-18 00:00:00\",\n    \"2037-06-19 00:00:00\",\n    \"2037-06-20 00:00:00\",\n    \"2037-06-21 00:00:00\",\n    \"2037-06-27 00:00:00\",\n    \"2037-07-04 00:00:00\",\n    \"2037-07-05 00:00:00\",\n    \"2037-07-11 00:00:00\",\n    \"2037-07-12 00:00:00\",\n    \"2037-07-18 00:00:00\",\n    \"2037-07-19 00:00:00\",\n    \"2037-07-25 00:00:00\",\n    \"2037-07-26 00:00:00\",\n    \"2037-08-01 00:00:00\",\n    \"2037-08-02 00:00:00\",\n    \"2037-08-08 00:00:00\",\n    \"2037-08-09 00:00:00\",\n    \"2037-08-15 00:00:00\",\n    \"2037-08-16 00:00:00\",\n    \"2037-08-22 00:00:00\",\n    \"2037-08-23 00:00:00\",\n    \"2037-08-29 00:00:00\",\n    \"2037-08-30 00:00:00\",\n    \"2037-09-05 00:00:00\",\n    \"2037-09-06 00:00:00\",\n    \"2037-09-12 00:00:00\",\n    \"2037-09-13 00:00:00\",\n    \"2037-09-19 00:00:00\",\n    \"2037-09-20 00:00:00\",\n    \"2037-09-24 00:00:00\",\n    \"2037-09-25 00:00:00\",\n    \"2037-09-26 00:00:00\",\n    \"2037-10-01 00:00:00\",\n    \"2037-10-02 00:00:00\",\n    \"2037-10-03 00:00:00\",\n    \"2037-10-04 00:00:00\",\n    \"2037-10-05 00:00:00\",\n    \"2037-10-06 00:00:00\",\n    \"2037-10-07 00:00:00\",\n    \"2037-10-11 00:00:00\",\n    \"2037-10-17 00:00:00\",\n    \"2037-10-18 00:00:00\",\n    \"2037-10-24 00:00:00\",\n    \"2037-10-25 00:00:00\",\n    \"2037-10-31 00:00:00\",\n    \"2037-11-01 00:00:00\",\n    \"2037-11-07 00:00:00\",\n    \"2037-11-08 00:00:00\",\n    \"2037-11-14 00:00:00\",\n    \"2037-11-15 00:00:00\",\n    \"2037-11-21 00:00:00\",\n    \"2037-11-22 00:00:00\",\n    \"2037-11-28 00:00:00\",\n    \"2037-11-29 00:00:00\",\n    \"2037-12-05 00:00:00\",\n    \"2037-12-06 00:00:00\",\n    \"2037-12-12 00:00:00\",\n    \"2037-12-13 00:00:00\",\n    \"2037-12-19 00:00:00\",\n    \"2037-12-20 00:00:00\",\n    \"2037-12-26 00:00:00\",\n    \"2037-12-27 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-02 00:00:00\",\n    \"2038-01-03 00:00:00\",\n    \"2038-01-09 00:00:00\",\n    \"2038-01-10 00:00:00\",\n    \"2038-01-16 00:00:00\",\n    \"2038-01-17 00:00:00\",\n    \"2038-01-23 00:00:00\",\n    \"2038-01-24 00:00:00\",\n    \"2038-01-30 00:00:00\",\n    \"2038-02-03 00:00:00\",\n    \"2038-02-04 00:00:00\",\n    \"2038-02-05 00:00:00\",\n    \"2038-02-06 00:00:00\",\n    \"2038-02-07 00:00:00\",\n    \"2038-02-08 00:00:00\",\n    \"2038-02-09 00:00:00\",\n    \"2038-02-10 00:00:00\",\n    \"2038-02-14 00:00:00\",\n    \"2038-02-20 00:00:00\",\n    \"2038-02-21 00:00:00\",\n    \"2038-02-27 00:00:00\",\n    \"2038-02-28 00:00:00\",\n    \"2038-03-06 00:00:00\",\n    \"2038-03-07 00:00:00\",\n    \"2038-03-13 00:00:00\",\n    \"2038-03-14 00:00:00\",\n    \"2038-03-20 00:00:00\",\n    \"2038-03-21 00:00:00\",\n    \"2038-03-27 00:00:00\",\n    \"2038-03-28 00:00:00\",\n    \"2038-04-03 00:00:00\",\n    \"2038-04-04 00:00:00\",\n    \"2038-04-05 00:00:00\",\n    \"2038-04-10 00:00:00\",\n    \"2038-04-11 00:00:00\",\n    \"2038-04-17 00:00:00\",\n    \"2038-04-18 00:00:00\",\n    \"2038-04-24 00:00:00\",\n    \"2038-04-25 00:00:00\",\n    \"2038-05-01 00:00:00\",\n    \"2038-05-02 00:00:00\",\n    \"2038-05-03 00:00:00\",\n    \"2038-05-04 00:00:00\",\n    \"2038-05-05 00:00:00\",\n    \"2038-05-08 00:00:00\",\n    \"2038-05-15 00:00:00\",\n    \"2038-05-16 00:00:00\",\n    \"2038-05-22 00:00:00\",\n    \"2038-05-23 00:00:00\",\n    \"2038-05-29 00:00:00\",\n    \"2038-05-30 00:00:00\",\n    \"2038-06-05 00:00:00\",\n    \"2038-06-06 00:00:00\",\n    \"2038-06-07 00:00:00\",\n    \"2038-06-12 00:00:00\",\n    \"2038-06-13 00:00:00\",\n    \"2038-06-19 00:00:00\",\n    \"2038-06-20 00:00:00\",\n    \"2038-06-26 00:00:00\",\n    \"2038-06-27 00:00:00\",\n    \"2038-07-03 00:00:00\",\n    \"2038-07-04 00:00:00\",\n    \"2038-07-10 00:00:00\",\n    \"2038-07-11 00:00:00\",\n    \"2038-07-17 00:00:00\",\n    \"2038-07-18 00:00:00\",\n    \"2038-07-24 00:00:00\",\n    \"2038-07-25 00:00:00\",\n    \"2038-07-31 00:00:00\",\n    \"2038-08-01 00:00:00\",\n    \"2038-08-07 00:00:00\",\n    \"2038-08-08 00:00:00\",\n    \"2038-08-14 00:00:00\",\n    \"2038-08-15 00:00:00\",\n    \"2038-08-21 00:00:00\",\n    \"2038-08-22 00:00:00\",\n    \"2038-08-28 00:00:00\",\n    \"2038-08-29 00:00:00\",\n    \"2038-09-04 00:00:00\",\n    \"2038-09-05 00:00:00\",\n    \"2038-09-11 00:00:00\",\n    \"2038-09-12 00:00:00\",\n    \"2038-09-13 00:00:00\",\n    \"2038-09-18 00:00:00\",\n    \"2038-09-19 00:00:00\",\n    \"2038-09-25 00:00:00\",\n    \"2038-10-01 00:00:00\",\n    \"2038-10-02 00:00:00\",\n    \"2038-10-03 00:00:00\",\n    \"2038-10-04 00:00:00\",\n    \"2038-10-05 00:00:00\",\n    \"2038-10-06 00:00:00\",\n    \"2038-10-07 00:00:00\",\n    \"2038-10-10 00:00:00\",\n    \"2038-10-16 00:00:00\",\n    \"2038-10-17 00:00:00\",\n    \"2038-10-23 00:00:00\",\n    \"2038-10-24 00:00:00\",\n    \"2038-10-30 00:00:00\",\n    \"2038-10-31 00:00:00\",\n    \"2038-11-06 00:00:00\",\n    \"2038-11-07 00:00:00\",\n    \"2038-11-13 00:00:00\",\n    \"2038-11-14 00:00:00\",\n    \"2038-11-20 00:00:00\",\n    \"2038-11-21 00:00:00\",\n    \"2038-11-27 00:00:00\",\n    \"2038-11-28 00:00:00\",\n    \"2038-12-04 00:00:00\",\n    \"2038-12-05 00:00:00\",\n    \"2038-12-11 00:00:00\",\n    \"2038-12-12 00:00:00\",\n    \"2038-12-18 00:00:00\",\n    \"2038-12-19 00:00:00\",\n    \"2038-12-25 00:00:00\",\n    \"2038-12-26 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-01-02 00:00:00\",\n    \"2039-01-03 00:00:00\",\n    \"2039-01-08 00:00:00\",\n    \"2039-01-09 00:00:00\",\n    \"2039-01-15 00:00:00\",\n    \"2039-01-16 00:00:00\",\n    \"2039-01-23 00:00:00\",\n    \"2039-01-24 00:00:00\",\n    \"2039-01-25 00:00:00\",\n    \"2039-01-26 00:00:00\",\n    \"2039-01-27 00:00:00\",\n    \"2039-01-28 00:00:00\",\n    \"2039-01-29 00:00:00\",\n    \"2039-01-30 00:00:00\",\n    \"2039-02-06 00:00:00\",\n    \"2039-02-12 00:00:00\",\n    \"2039-02-13 00:00:00\",\n    \"2039-02-19 00:00:00\",\n    \"2039-02-20 00:00:00\",\n    \"2039-02-26 00:00:00\",\n    \"2039-02-27 00:00:00\",\n    \"2039-03-05 00:00:00\",\n    \"2039-03-06 00:00:00\",\n    \"2039-03-12 00:00:00\",\n    \"2039-03-13 00:00:00\",\n    \"2039-03-19 00:00:00\",\n    \"2039-03-20 00:00:00\",\n    \"2039-03-26 00:00:00\",\n    \"2039-03-27 00:00:00\",\n    \"2039-04-02 00:00:00\",\n    \"2039-04-03 00:00:00\",\n    \"2039-04-04 00:00:00\",\n    \"2039-04-09 00:00:00\",\n    \"2039-04-10 00:00:00\",\n    \"2039-04-16 00:00:00\",\n    \"2039-04-17 00:00:00\",\n    \"2039-04-23 00:00:00\",\n    \"2039-04-24 00:00:00\",\n    \"2039-04-30 00:00:00\",\n    \"2039-05-01 00:00:00\",\n    \"2039-05-02 00:00:00\",\n    \"2039-05-03 00:00:00\",\n    \"2039-05-04 00:00:00\",\n    \"2039-05-07 00:00:00\",\n    \"2039-05-14 00:00:00\",\n    \"2039-05-15 00:00:00\",\n    \"2039-05-21 00:00:00\",\n    \"2039-05-22 00:00:00\",\n    \"2039-05-27 00:00:00\",\n    \"2039-05-28 00:00:00\",\n    \"2039-05-29 00:00:00\",\n    \"2039-06-04 00:00:00\",\n    \"2039-06-05 00:00:00\",\n    \"2039-06-11 00:00:00\",\n    \"2039-06-12 00:00:00\",\n    \"2039-06-18 00:00:00\",\n    \"2039-06-19 00:00:00\",\n    \"2039-06-25 00:00:00\",\n    \"2039-06-26 00:00:00\",\n    \"2039-07-02 00:00:00\",\n    \"2039-07-03 00:00:00\",\n    \"2039-07-09 00:00:00\",\n    \"2039-07-10 00:00:00\",\n    \"2039-07-16 00:00:00\",\n    \"2039-07-17 00:00:00\",\n    \"2039-07-23 00:00:00\",\n    \"2039-07-24 00:00:00\",\n    \"2039-07-30 00:00:00\",\n    \"2039-07-31 00:00:00\",\n    \"2039-08-06 00:00:00\",\n    \"2039-08-07 00:00:00\",\n    \"2039-08-13 00:00:00\",\n    \"2039-08-14 00:00:00\",\n    \"2039-08-20 00:00:00\",\n    \"2039-08-21 00:00:00\",\n    \"2039-08-27 00:00:00\",\n    \"2039-08-28 00:00:00\",\n    \"2039-09-03 00:00:00\",\n    \"2039-09-04 00:00:00\",\n    \"2039-09-10 00:00:00\",\n    \"2039-09-11 00:00:00\",\n    \"2039-09-17 00:00:00\",\n    \"2039-09-18 00:00:00\",\n    \"2039-09-24 00:00:00\",\n    \"2039-10-01 00:00:00\",\n    \"2039-10-02 00:00:00\",\n    \"2039-10-03 00:00:00\",\n    \"2039-10-04 00:00:00\",\n    \"2039-10-05 00:00:00\",\n    \"2039-10-06 00:00:00\",\n    \"2039-10-07 00:00:00\",\n    \"2039-10-08 00:00:00\",\n    \"2039-10-15 00:00:00\",\n    \"2039-10-16 00:00:00\",\n    \"2039-10-22 00:00:00\",\n    \"2039-10-23 00:00:00\",\n    \"2039-10-29 00:00:00\",\n    \"2039-10-30 00:00:00\",\n    \"2039-11-05 00:00:00\",\n    \"2039-11-06 00:00:00\",\n    \"2039-11-12 00:00:00\",\n    \"2039-11-13 00:00:00\",\n    \"2039-11-19 00:00:00\",\n    \"2039-11-20 00:00:00\",\n    \"2039-11-26 00:00:00\",\n    \"2039-11-27 00:00:00\",\n    \"2039-12-03 00:00:00\",\n    \"2039-12-04 00:00:00\",\n    \"2039-12-10 00:00:00\",\n    \"2039-12-11 00:00:00\",\n    \"2039-12-17 00:00:00\",\n    \"2039-12-18 00:00:00\",\n    \"2039-12-24 00:00:00\",\n    \"2039-12-25 00:00:00\",\n    \"2039-12-31 00:00:00\",\n    \"2040-01-01 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-01-07 00:00:00\",\n    \"2040-01-08 00:00:00\",\n    \"2040-01-14 00:00:00\",\n    \"2040-01-15 00:00:00\",\n    \"2040-01-21 00:00:00\",\n    \"2040-01-22 00:00:00\",\n    \"2040-01-28 00:00:00\",\n    \"2040-01-29 00:00:00\",\n    \"2040-02-04 00:00:00\",\n    \"2040-02-11 00:00:00\",\n    \"2040-02-12 00:00:00\",\n    \"2040-02-13 00:00:00\",\n    \"2040-02-14 00:00:00\",\n    \"2040-02-15 00:00:00\",\n    \"2040-02-16 00:00:00\",\n    \"2040-02-17 00:00:00\",\n    \"2040-02-18 00:00:00\",\n    \"2040-02-25 00:00:00\",\n    \"2040-02-26 00:00:00\",\n    \"2040-03-03 00:00:00\",\n    \"2040-03-04 00:00:00\",\n    \"2040-03-10 00:00:00\",\n    \"2040-03-11 00:00:00\",\n    \"2040-03-17 00:00:00\",\n    \"2040-03-18 00:00:00\",\n    \"2040-03-24 00:00:00\",\n    \"2040-03-25 00:00:00\",\n    \"2040-03-31 00:00:00\",\n    \"2040-04-01 00:00:00\",\n    \"2040-04-04 00:00:00\",\n    \"2040-04-07 00:00:00\",\n    \"2040-04-08 00:00:00\",\n    \"2040-04-14 00:00:00\",\n    \"2040-04-15 00:00:00\",\n    \"2040-04-21 00:00:00\",\n    \"2040-04-22 00:00:00\",\n    \"2040-04-28 00:00:00\",\n    \"2040-04-29 00:00:00\",\n    \"2040-04-30 00:00:00\",\n    \"2040-05-01 00:00:00\",\n    \"2040-05-02 00:00:00\",\n    \"2040-05-05 00:00:00\",\n    \"2040-05-06 00:00:00\",\n    \"2040-05-12 00:00:00\",\n    \"2040-05-13 00:00:00\",\n    \"2040-05-19 00:00:00\",\n    \"2040-05-20 00:00:00\",\n    \"2040-05-26 00:00:00\",\n    \"2040-05-27 00:00:00\",\n    \"2040-06-02 00:00:00\",\n    \"2040-06-03 00:00:00\",\n    \"2040-06-09 00:00:00\",\n    \"2040-06-10 00:00:00\",\n    \"2040-06-14 00:00:00\",\n    \"2040-06-15 00:00:00\",\n    \"2040-06-16 00:00:00\",\n    \"2040-06-17 00:00:00\",\n    \"2040-06-23 00:00:00\",\n    \"2040-06-30 00:00:00\",\n    \"2040-07-01 00:00:00\",\n    \"2040-07-07 00:00:00\",\n    \"2040-07-08 00:00:00\",\n    \"2040-07-14 00:00:00\",\n    \"2040-07-15 00:00:00\",\n    \"2040-07-21 00:00:00\",\n    \"2040-07-22 00:00:00\",\n    \"2040-07-28 00:00:00\",\n    \"2040-07-29 00:00:00\",\n    \"2040-08-04 00:00:00\",\n    \"2040-08-05 00:00:00\",\n    \"2040-08-11 00:00:00\",\n    \"2040-08-12 00:00:00\",\n    \"2040-08-18 00:00:00\",\n    \"2040-08-19 00:00:00\",\n    \"2040-08-25 00:00:00\",\n    \"2040-08-26 00:00:00\",\n    \"2040-09-01 00:00:00\",\n    \"2040-09-02 00:00:00\",\n    \"2040-09-08 00:00:00\",\n    \"2040-09-09 00:00:00\",\n    \"2040-09-15 00:00:00\",\n    \"2040-09-16 00:00:00\",\n    \"2040-09-20 00:00:00\",\n    \"2040-09-21 00:00:00\",\n    \"2040-09-22 00:00:00\",\n    \"2040-09-23 00:00:00\",\n    \"2040-10-01 00:00:00\",\n    \"2040-10-02 00:00:00\",\n    \"2040-10-03 00:00:00\",\n    \"2040-10-04 00:00:00\",\n    \"2040-10-05 00:00:00\",\n    \"2040-10-06 00:00:00\",\n    \"2040-10-07 00:00:00\",\n    \"2040-10-14 00:00:00\",\n    \"2040-10-20 00:00:00\",\n    \"2040-10-21 00:00:00\",\n    \"2040-10-27 00:00:00\",\n    \"2040-10-28 00:00:00\",\n    \"2040-11-03 00:00:00\",\n    \"2040-11-04 00:00:00\",\n    \"2040-11-10 00:00:00\",\n    \"2040-11-11 00:00:00\",\n    \"2040-11-17 00:00:00\",\n    \"2040-11-18 00:00:00\",\n    \"2040-11-24 00:00:00\",\n    \"2040-11-25 00:00:00\",\n    \"2040-12-01 00:00:00\",\n    \"2040-12-02 00:00:00\",\n    \"2040-12-08 00:00:00\",\n    \"2040-12-09 00:00:00\",\n    \"2040-12-15 00:00:00\",\n    \"2040-12-16 00:00:00\",\n    \"2040-12-22 00:00:00\",\n    \"2040-12-23 00:00:00\",\n    \"2040-12-30 00:00:00\",\n    \"2040-12-31 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-05 00:00:00\",\n    \"2041-01-06 00:00:00\",\n    \"2041-01-12 00:00:00\",\n    \"2041-01-13 00:00:00\",\n    \"2041-01-19 00:00:00\",\n    \"2041-01-20 00:00:00\",\n    \"2041-01-26 00:00:00\",\n    \"2041-01-31 00:00:00\",\n    \"2041-02-01 00:00:00\",\n    \"2041-02-02 00:00:00\",\n    \"2041-02-03 00:00:00\",\n    \"2041-02-04 00:00:00\",\n    \"2041-02-05 00:00:00\",\n    \"2041-02-06 00:00:00\",\n    \"2041-02-07 00:00:00\",\n    \"2041-02-10 00:00:00\",\n    \"2041-02-16 00:00:00\",\n    \"2041-02-17 00:00:00\",\n    \"2041-02-23 00:00:00\",\n    \"2041-02-24 00:00:00\",\n    \"2041-03-02 00:00:00\",\n    \"2041-03-03 00:00:00\",\n    \"2041-03-09 00:00:00\",\n    \"2041-03-10 00:00:00\",\n    \"2041-03-16 00:00:00\",\n    \"2041-03-17 00:00:00\",\n    \"2041-03-23 00:00:00\",\n    \"2041-03-24 00:00:00\",\n    \"2041-03-30 00:00:00\",\n    \"2041-03-31 00:00:00\",\n    \"2041-04-04 00:00:00\",\n    \"2041-04-05 00:00:00\",\n    \"2041-04-06 00:00:00\",\n    \"2041-04-07 00:00:00\",\n    \"2041-04-13 00:00:00\",\n    \"2041-04-20 00:00:00\",\n    \"2041-04-21 00:00:00\",\n    \"2041-04-27 00:00:00\",\n    \"2041-04-28 00:00:00\",\n    \"2041-05-01 00:00:00\",\n    \"2041-05-02 00:00:00\",\n    \"2041-05-04 00:00:00\",\n    \"2041-05-05 00:00:00\",\n    \"2041-05-11 00:00:00\",\n    \"2041-05-12 00:00:00\",\n    \"2041-05-18 00:00:00\",\n    \"2041-05-19 00:00:00\",\n    \"2041-05-25 00:00:00\",\n    \"2041-05-26 00:00:00\",\n    \"2041-06-01 00:00:00\",\n    \"2041-06-02 00:00:00\",\n    \"2041-06-03 00:00:00\",\n    \"2041-06-08 00:00:00\",\n    \"2041-06-09 00:00:00\",\n    \"2041-06-15 00:00:00\",\n    \"2041-06-16 00:00:00\",\n    \"2041-06-22 00:00:00\",\n    \"2041-06-23 00:00:00\",\n    \"2041-06-29 00:00:00\",\n    \"2041-06-30 00:00:00\",\n    \"2041-07-06 00:00:00\",\n    \"2041-07-07 00:00:00\",\n    \"2041-07-13 00:00:00\",\n    \"2041-07-14 00:00:00\",\n    \"2041-07-20 00:00:00\",\n    \"2041-07-21 00:00:00\",\n    \"2041-07-27 00:00:00\",\n    \"2041-07-28 00:00:00\",\n    \"2041-08-03 00:00:00\",\n    \"2041-08-04 00:00:00\",\n    \"2041-08-10 00:00:00\",\n    \"2041-08-11 00:00:00\",\n    \"2041-08-17 00:00:00\",\n    \"2041-08-18 00:00:00\",\n    \"2041-08-24 00:00:00\",\n    \"2041-08-25 00:00:00\",\n    \"2041-08-31 00:00:00\",\n    \"2041-09-07 00:00:00\",\n    \"2041-09-08 00:00:00\",\n    \"2041-09-09 00:00:00\",\n    \"2041-09-10 00:00:00\",\n    \"2041-09-14 00:00:00\",\n    \"2041-09-15 00:00:00\",\n    \"2041-09-21 00:00:00\",\n    \"2041-09-22 00:00:00\",\n    \"2041-09-28 00:00:00\",\n    \"2041-10-01 00:00:00\",\n    \"2041-10-02 00:00:00\",\n    \"2041-10-03 00:00:00\",\n    \"2041-10-04 00:00:00\",\n    \"2041-10-05 00:00:00\",\n    \"2041-10-06 00:00:00\",\n    \"2041-10-07 00:00:00\",\n    \"2041-10-13 00:00:00\",\n    \"2041-10-19 00:00:00\",\n    \"2041-10-20 00:00:00\",\n    \"2041-10-26 00:00:00\",\n    \"2041-10-27 00:00:00\",\n    \"2041-11-02 00:00:00\",\n    \"2041-11-03 00:00:00\",\n    \"2041-11-09 00:00:00\",\n    \"2041-11-10 00:00:00\",\n    \"2041-11-16 00:00:00\",\n    \"2041-11-17 00:00:00\",\n    \"2041-11-23 00:00:00\",\n    \"2041-11-24 00:00:00\",\n    \"2041-11-30 00:00:00\",\n    \"2041-12-01 00:00:00\",\n    \"2041-12-07 00:00:00\",\n    \"2041-12-08 00:00:00\",\n    \"2041-12-14 00:00:00\",\n    \"2041-12-15 00:00:00\",\n    \"2041-12-21 00:00:00\",\n    \"2041-12-22 00:00:00\",\n    \"2041-12-28 00:00:00\",\n    \"2041-12-29 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-04 00:00:00\",\n    \"2042-01-05 00:00:00\",\n    \"2042-01-11 00:00:00\",\n    \"2042-01-12 00:00:00\",\n    \"2042-01-18 00:00:00\",\n    \"2042-01-21 00:00:00\",\n    \"2042-01-22 00:00:00\",\n    \"2042-01-23 00:00:00\",\n    \"2042-01-24 00:00:00\",\n    \"2042-01-25 00:00:00\",\n    \"2042-01-26 00:00:00\",\n    \"2042-01-27 00:00:00\",\n    \"2042-01-28 00:00:00\",\n    \"2042-02-02 00:00:00\",\n    \"2042-02-08 00:00:00\",\n    \"2042-02-09 00:00:00\",\n    \"2042-02-15 00:00:00\",\n    \"2042-02-16 00:00:00\",\n    \"2042-02-22 00:00:00\",\n    \"2042-02-23 00:00:00\",\n    \"2042-03-01 00:00:00\",\n    \"2042-03-02 00:00:00\",\n    \"2042-03-08 00:00:00\",\n    \"2042-03-09 00:00:00\",\n    \"2042-03-15 00:00:00\",\n    \"2042-03-16 00:00:00\",\n    \"2042-03-22 00:00:00\",\n    \"2042-03-23 00:00:00\",\n    \"2042-03-29 00:00:00\",\n    \"2042-03-30 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-05 00:00:00\",\n    \"2042-04-06 00:00:00\",\n    \"2042-04-12 00:00:00\",\n    \"2042-04-13 00:00:00\",\n    \"2042-04-19 00:00:00\",\n    \"2042-04-20 00:00:00\",\n    \"2042-04-26 00:00:00\",\n    \"2042-05-01 00:00:00\",\n    \"2042-05-02 00:00:00\",\n    \"2042-05-03 00:00:00\",\n    \"2042-05-04 00:00:00\",\n    \"2042-05-05 00:00:00\",\n    \"2042-05-10 00:00:00\",\n    \"2042-05-11 00:00:00\",\n    \"2042-05-17 00:00:00\",\n    \"2042-05-18 00:00:00\",\n    \"2042-05-24 00:00:00\",\n    \"2042-05-25 00:00:00\",\n    \"2042-05-31 00:00:00\",\n    \"2042-06-01 00:00:00\",\n    \"2042-06-07 00:00:00\",\n    \"2042-06-08 00:00:00\",\n    \"2042-06-14 00:00:00\",\n    \"2042-06-15 00:00:00\",\n    \"2042-06-21 00:00:00\",\n    \"2042-06-22 00:00:00\",\n    \"2042-06-23 00:00:00\",\n    \"2042-06-28 00:00:00\",\n    \"2042-06-29 00:00:00\",\n    \"2042-07-05 00:00:00\",\n    \"2042-07-06 00:00:00\",\n    \"2042-07-12 00:00:00\",\n    \"2042-07-13 00:00:00\",\n    \"2042-07-19 00:00:00\",\n    \"2042-07-20 00:00:00\",\n    \"2042-07-26 00:00:00\",\n    \"2042-07-27 00:00:00\",\n    \"2042-08-02 00:00:00\",\n    \"2042-08-03 00:00:00\",\n    \"2042-08-09 00:00:00\",\n    \"2042-08-10 00:00:00\",\n    \"2042-08-16 00:00:00\",\n    \"2042-08-17 00:00:00\",\n    \"2042-08-23 00:00:00\",\n    \"2042-08-24 00:00:00\",\n    \"2042-08-30 00:00:00\",\n    \"2042-08-31 00:00:00\",\n    \"2042-09-06 00:00:00\",\n    \"2042-09-07 00:00:00\",\n    \"2042-09-13 00:00:00\",\n    \"2042-09-14 00:00:00\",\n    \"2042-09-20 00:00:00\",\n    \"2042-09-27 00:00:00\",\n    \"2042-09-28 00:00:00\",\n    \"2042-09-29 00:00:00\",\n    \"2042-10-01 00:00:00\",\n    \"2042-10-02 00:00:00\",\n    \"2042-10-03 00:00:00\",\n    \"2042-10-04 00:00:00\",\n    \"2042-10-05 00:00:00\",\n    \"2042-10-06 00:00:00\",\n    \"2042-10-07 00:00:00\",\n    \"2042-10-12 00:00:00\",\n    \"2042-10-18 00:00:00\",\n    \"2042-10-19 00:00:00\",\n    \"2042-10-25 00:00:00\",\n    \"2042-10-26 00:00:00\",\n    \"2042-11-01 00:00:00\",\n    \"2042-11-02 00:00:00\",\n    \"2042-11-08 00:00:00\",\n    \"2042-11-09 00:00:00\",\n    \"2042-11-15 00:00:00\",\n    \"2042-11-16 00:00:00\",\n    \"2042-11-22 00:00:00\",\n    \"2042-11-23 00:00:00\",\n    \"2042-11-29 00:00:00\",\n    \"2042-11-30 00:00:00\",\n    \"2042-12-06 00:00:00\",\n    \"2042-12-07 00:00:00\",\n    \"2042-12-13 00:00:00\",\n    \"2042-12-14 00:00:00\",\n    \"2042-12-20 00:00:00\",\n    \"2042-12-21 00:00:00\",\n    \"2042-12-27 00:00:00\",\n    \"2042-12-28 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-02 00:00:00\",\n    \"2043-01-03 00:00:00\",\n    \"2043-01-10 00:00:00\",\n    \"2043-01-11 00:00:00\",\n    \"2043-01-17 00:00:00\",\n    \"2043-01-18 00:00:00\",\n    \"2043-01-24 00:00:00\",\n    \"2043-01-25 00:00:00\",\n    \"2043-01-31 00:00:00\",\n    \"2043-02-01 00:00:00\",\n    \"2043-02-08 00:00:00\",\n    \"2043-02-09 00:00:00\",\n    \"2043-02-10 00:00:00\",\n    \"2043-02-11 00:00:00\",\n    \"2043-02-12 00:00:00\",\n    \"2043-02-13 00:00:00\",\n    \"2043-02-14 00:00:00\",\n    \"2043-02-15 00:00:00\",\n    \"2043-02-16 00:00:00\",\n    \"2043-02-22 00:00:00\",\n    \"2043-02-28 00:00:00\",\n    \"2043-03-01 00:00:00\",\n    \"2043-03-07 00:00:00\",\n    \"2043-03-08 00:00:00\",\n    \"2043-03-14 00:00:00\",\n    \"2043-03-15 00:00:00\",\n    \"2043-03-21 00:00:00\",\n    \"2043-03-22 00:00:00\",\n    \"2043-03-28 00:00:00\",\n    \"2043-03-29 00:00:00\",\n    \"2043-04-04 00:00:00\",\n    \"2043-04-05 00:00:00\",\n    \"2043-04-06 00:00:00\",\n    \"2043-04-11 00:00:00\",\n    \"2043-04-12 00:00:00\",\n    \"2043-04-18 00:00:00\",\n    \"2043-04-19 00:00:00\",\n    \"2043-04-25 00:00:00\",\n    \"2043-04-26 00:00:00\",\n    \"2043-05-01 00:00:00\",\n    \"2043-05-02 00:00:00\",\n    \"2043-05-03 00:00:00\",\n    \"2043-05-04 00:00:00\",\n    \"2043-05-05 00:00:00\",\n    \"2043-05-10 00:00:00\",\n    \"2043-05-16 00:00:00\",\n    \"2043-05-17 00:00:00\",\n    \"2043-05-23 00:00:00\",\n    \"2043-05-24 00:00:00\",\n    \"2043-05-30 00:00:00\",\n    \"2043-05-31 00:00:00\",\n    \"2043-06-06 00:00:00\",\n    \"2043-06-07 00:00:00\",\n    \"2043-06-11 00:00:00\",\n    \"2043-06-12 00:00:00\",\n    \"2043-06-13 00:00:00\",\n    \"2043-06-14 00:00:00\",\n    \"2043-06-20 00:00:00\",\n    \"2043-06-27 00:00:00\",\n    \"2043-06-28 00:00:00\",\n    \"2043-07-04 00:00:00\",\n    \"2043-07-05 00:00:00\",\n    \"2043-07-11 00:00:00\",\n    \"2043-07-12 00:00:00\",\n    \"2043-07-18 00:00:00\",\n    \"2043-07-19 00:00:00\",\n    \"2043-07-25 00:00:00\",\n    \"2043-07-26 00:00:00\",\n    \"2043-08-01 00:00:00\",\n    \"2043-08-02 00:00:00\",\n    \"2043-08-08 00:00:00\",\n    \"2043-08-09 00:00:00\",\n    \"2043-08-15 00:00:00\",\n    \"2043-08-16 00:00:00\",\n    \"2043-08-22 00:00:00\",\n    \"2043-08-23 00:00:00\",\n    \"2043-08-29 00:00:00\",\n    \"2043-08-30 00:00:00\",\n    \"2043-09-05 00:00:00\",\n    \"2043-09-06 00:00:00\",\n    \"2043-09-12 00:00:00\",\n    \"2043-09-13 00:00:00\",\n    \"2043-09-17 00:00:00\",\n    \"2043-09-18 00:00:00\",\n    \"2043-09-19 00:00:00\",\n    \"2043-09-20 00:00:00\",\n    \"2043-09-26 00:00:00\",\n    \"2043-10-01 00:00:00\",\n    \"2043-10-02 00:00:00\",\n    \"2043-10-03 00:00:00\",\n    \"2043-10-04 00:00:00\",\n    \"2043-10-05 00:00:00\",\n    \"2043-10-06 00:00:00\",\n    \"2043-10-07 00:00:00\",\n    \"2043-10-11 00:00:00\",\n    \"2043-10-17 00:00:00\",\n    \"2043-10-18 00:00:00\",\n    \"2043-10-24 00:00:00\",\n    \"2043-10-25 00:00:00\",\n    \"2043-10-31 00:00:00\",\n    \"2043-11-01 00:00:00\",\n    \"2043-11-07 00:00:00\",\n    \"2043-11-08 00:00:00\",\n    \"2043-11-14 00:00:00\",\n    \"2043-11-15 00:00:00\",\n    \"2043-11-21 00:00:00\",\n    \"2043-11-22 00:00:00\",\n    \"2043-11-28 00:00:00\",\n    \"2043-11-29 00:00:00\",\n    \"2043-12-05 00:00:00\",\n    \"2043-12-06 00:00:00\",\n    \"2043-12-12 00:00:00\",\n    \"2043-12-13 00:00:00\",\n    \"2043-12-19 00:00:00\",\n    \"2043-12-20 00:00:00\",\n    \"2043-12-26 00:00:00\",\n    \"2043-12-27 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-02 00:00:00\",\n    \"2044-01-03 00:00:00\",\n    \"2044-01-09 00:00:00\",\n    \"2044-01-10 00:00:00\",\n    \"2044-01-16 00:00:00\",\n    \"2044-01-17 00:00:00\",\n    \"2044-01-23 00:00:00\",\n    \"2044-01-29 00:00:00\",\n    \"2044-01-30 00:00:00\",\n    \"2044-01-31 00:00:00\",\n    \"2044-02-01 00:00:00\",\n    \"2044-02-02 00:00:00\",\n    \"2044-02-03 00:00:00\",\n    \"2044-02-04 00:00:00\",\n    \"2044-02-05 00:00:00\",\n    \"2044-02-07 00:00:00\",\n    \"2044-02-13 00:00:00\",\n    \"2044-02-14 00:00:00\",\n    \"2044-02-20 00:00:00\",\n    \"2044-02-21 00:00:00\",\n    \"2044-02-27 00:00:00\",\n    \"2044-02-28 00:00:00\",\n    \"2044-03-05 00:00:00\",\n    \"2044-03-06 00:00:00\",\n    \"2044-03-12 00:00:00\",\n    \"2044-03-13 00:00:00\",\n    \"2044-03-19 00:00:00\",\n    \"2044-03-20 00:00:00\",\n    \"2044-03-26 00:00:00\",\n    \"2044-03-27 00:00:00\",\n    \"2044-04-02 00:00:00\",\n    \"2044-04-03 00:00:00\",\n    \"2044-04-04 00:00:00\",\n    \"2044-04-09 00:00:00\",\n    \"2044-04-10 00:00:00\",\n    \"2044-04-16 00:00:00\",\n    \"2044-04-17 00:00:00\",\n    \"2044-04-23 00:00:00\",\n    \"2044-04-24 00:00:00\",\n    \"2044-04-30 00:00:00\",\n    \"2044-05-01 00:00:00\",\n    \"2044-05-02 00:00:00\",\n    \"2044-05-03 00:00:00\",\n    \"2044-05-04 00:00:00\",\n    \"2044-05-07 00:00:00\",\n    \"2044-05-14 00:00:00\",\n    \"2044-05-15 00:00:00\",\n    \"2044-05-21 00:00:00\",\n    \"2044-05-28 00:00:00\",\n    \"2044-05-29 00:00:00\",\n    \"2044-05-30 00:00:00\",\n    \"2044-05-31 00:00:00\",\n    \"2044-06-04 00:00:00\",\n    \"2044-06-05 00:00:00\",\n    \"2044-06-11 00:00:00\",\n    \"2044-06-12 00:00:00\",\n    \"2044-06-18 00:00:00\",\n    \"2044-06-19 00:00:00\",\n    \"2044-06-25 00:00:00\",\n    \"2044-06-26 00:00:00\",\n    \"2044-07-02 00:00:00\",\n    \"2044-07-03 00:00:00\",\n    \"2044-07-09 00:00:00\",\n    \"2044-07-10 00:00:00\",\n    \"2044-07-16 00:00:00\",\n    \"2044-07-17 00:00:00\",\n    \"2044-07-23 00:00:00\",\n    \"2044-07-24 00:00:00\",\n    \"2044-07-30 00:00:00\",\n    \"2044-07-31 00:00:00\",\n    \"2044-08-06 00:00:00\",\n    \"2044-08-07 00:00:00\",\n    \"2044-08-13 00:00:00\",\n    \"2044-08-14 00:00:00\",\n    \"2044-08-20 00:00:00\",\n    \"2044-08-21 00:00:00\",\n    \"2044-08-27 00:00:00\",\n    \"2044-08-28 00:00:00\",\n    \"2044-09-03 00:00:00\",\n    \"2044-09-04 00:00:00\",\n    \"2044-09-10 00:00:00\",\n    \"2044-09-11 00:00:00\",\n    \"2044-09-17 00:00:00\",\n    \"2044-09-18 00:00:00\",\n    \"2044-09-24 00:00:00\",\n    \"2044-10-01 00:00:00\",\n    \"2044-10-02 00:00:00\",\n    \"2044-10-03 00:00:00\",\n    \"2044-10-04 00:00:00\",\n    \"2044-10-05 00:00:00\",\n    \"2044-10-06 00:00:00\",\n    \"2044-10-07 00:00:00\",\n    \"2044-10-08 00:00:00\",\n    \"2044-10-15 00:00:00\",\n    \"2044-10-16 00:00:00\",\n    \"2044-10-22 00:00:00\",\n    \"2044-10-23 00:00:00\",\n    \"2044-10-29 00:00:00\",\n    \"2044-10-30 00:00:00\",\n    \"2044-11-05 00:00:00\",\n    \"2044-11-06 00:00:00\",\n    \"2044-11-12 00:00:00\",\n    \"2044-11-13 00:00:00\",\n    \"2044-11-19 00:00:00\",\n    \"2044-11-20 00:00:00\",\n    \"2044-11-26 00:00:00\",\n    \"2044-11-27 00:00:00\",\n    \"2044-12-03 00:00:00\",\n    \"2044-12-04 00:00:00\",\n    \"2044-12-10 00:00:00\",\n    \"2044-12-11 00:00:00\",\n    \"2044-12-17 00:00:00\",\n    \"2044-12-18 00:00:00\",\n    \"2044-12-24 00:00:00\",\n    \"2044-12-25 00:00:00\",\n    \"2044-12-31 00:00:00\",\n    \"2045-01-01 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-01-07 00:00:00\",\n    \"2045-01-08 00:00:00\",\n    \"2045-01-14 00:00:00\",\n    \"2045-01-15 00:00:00\",\n    \"2045-01-21 00:00:00\",\n    \"2045-01-22 00:00:00\",\n    \"2045-01-28 00:00:00\",\n    \"2045-01-29 00:00:00\",\n    \"2045-02-04 00:00:00\",\n    \"2045-02-05 00:00:00\",\n    \"2045-02-11 00:00:00\",\n    \"2045-02-16 00:00:00\",\n    \"2045-02-17 00:00:00\",\n    \"2045-02-18 00:00:00\",\n    \"2045-02-19 00:00:00\",\n    \"2045-02-20 00:00:00\",\n    \"2045-02-21 00:00:00\",\n    \"2045-02-22 00:00:00\",\n    \"2045-02-23 00:00:00\",\n    \"2045-02-26 00:00:00\",\n    \"2045-03-04 00:00:00\",\n    \"2045-03-05 00:00:00\",\n    \"2045-03-11 00:00:00\",\n    \"2045-03-12 00:00:00\",\n    \"2045-03-18 00:00:00\",\n    \"2045-03-19 00:00:00\",\n    \"2045-03-25 00:00:00\",\n    \"2045-04-01 00:00:00\",\n    \"2045-04-02 00:00:00\",\n    \"2045-04-03 00:00:00\",\n    \"2045-04-04 00:00:00\",\n    \"2045-04-08 00:00:00\",\n    \"2045-04-09 00:00:00\",\n    \"2045-04-15 00:00:00\",\n    \"2045-04-16 00:00:00\",\n    \"2045-04-22 00:00:00\",\n    \"2045-04-23 00:00:00\",\n    \"2045-04-29 00:00:00\",\n    \"2045-04-30 00:00:00\",\n    \"2045-05-01 00:00:00\",\n    \"2045-05-02 00:00:00\",\n    \"2045-05-03 00:00:00\",\n    \"2045-05-06 00:00:00\",\n    \"2045-05-13 00:00:00\",\n    \"2045-05-14 00:00:00\",\n    \"2045-05-20 00:00:00\",\n    \"2045-05-21 00:00:00\",\n    \"2045-05-27 00:00:00\",\n    \"2045-05-28 00:00:00\",\n    \"2045-06-03 00:00:00\",\n    \"2045-06-04 00:00:00\",\n    \"2045-06-10 00:00:00\",\n    \"2045-06-11 00:00:00\",\n    \"2045-06-17 00:00:00\",\n    \"2045-06-18 00:00:00\",\n    \"2045-06-19 00:00:00\",\n    \"2045-06-24 00:00:00\",\n    \"2045-06-25 00:00:00\",\n    \"2045-07-01 00:00:00\",\n    \"2045-07-02 00:00:00\",\n    \"2045-07-08 00:00:00\",\n    \"2045-07-09 00:00:00\",\n    \"2045-07-15 00:00:00\",\n    \"2045-07-16 00:00:00\",\n    \"2045-07-22 00:00:00\",\n    \"2045-07-23 00:00:00\",\n    \"2045-07-29 00:00:00\",\n    \"2045-07-30 00:00:00\",\n    \"2045-08-05 00:00:00\",\n    \"2045-08-06 00:00:00\",\n    \"2045-08-12 00:00:00\",\n    \"2045-08-13 00:00:00\",\n    \"2045-08-19 00:00:00\",\n    \"2045-08-20 00:00:00\",\n    \"2045-08-26 00:00:00\",\n    \"2045-08-27 00:00:00\",\n    \"2045-09-02 00:00:00\",\n    \"2045-09-03 00:00:00\",\n    \"2045-09-09 00:00:00\",\n    \"2045-09-10 00:00:00\",\n    \"2045-09-16 00:00:00\",\n    \"2045-09-23 00:00:00\",\n    \"2045-09-24 00:00:00\",\n    \"2045-09-25 00:00:00\",\n    \"2045-09-30 00:00:00\",\n    \"2045-10-01 00:00:00\",\n    \"2045-10-02 00:00:00\",\n    \"2045-10-03 00:00:00\",\n    \"2045-10-04 00:00:00\",\n    \"2045-10-05 00:00:00\",\n    \"2045-10-06 00:00:00\",\n    \"2045-10-07 00:00:00\",\n    \"2045-10-14 00:00:00\",\n    \"2045-10-15 00:00:00\",\n    \"2045-10-21 00:00:00\",\n    \"2045-10-22 00:00:00\",\n    \"2045-10-28 00:00:00\",\n    \"2045-10-29 00:00:00\",\n    \"2045-11-04 00:00:00\",\n    \"2045-11-05 00:00:00\",\n    \"2045-11-11 00:00:00\",\n    \"2045-11-12 00:00:00\",\n    \"2045-11-18 00:00:00\",\n    \"2045-11-19 00:00:00\",\n    \"2045-11-25 00:00:00\",\n    \"2045-11-26 00:00:00\",\n    \"2045-12-02 00:00:00\",\n    \"2045-12-03 00:00:00\",\n    \"2045-12-09 00:00:00\",\n    \"2045-12-10 00:00:00\",\n    \"2045-12-16 00:00:00\",\n    \"2045-12-17 00:00:00\",\n    \"2045-12-23 00:00:00\",\n    \"2045-12-24 00:00:00\",\n    \"2045-12-30 00:00:00\",\n    \"2045-12-31 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-06 00:00:00\",\n    \"2046-01-07 00:00:00\",\n    \"2046-01-13 00:00:00\",\n    \"2046-01-14 00:00:00\",\n    \"2046-01-20 00:00:00\",\n    \"2046-01-21 00:00:00\",\n    \"2046-01-27 00:00:00\",\n    \"2046-01-28 00:00:00\",\n    \"2046-02-04 00:00:00\",\n    \"2046-02-05 00:00:00\",\n    \"2046-02-06 00:00:00\",\n    \"2046-02-07 00:00:00\",\n    \"2046-02-08 00:00:00\",\n    \"2046-02-09 00:00:00\",\n    \"2046-02-10 00:00:00\",\n    \"2046-02-11 00:00:00\",\n    \"2046-02-12 00:00:00\",\n    \"2046-02-18 00:00:00\",\n    \"2046-02-24 00:00:00\",\n    \"2046-02-25 00:00:00\",\n    \"2046-03-03 00:00:00\",\n    \"2046-03-04 00:00:00\",\n    \"2046-03-10 00:00:00\",\n    \"2046-03-11 00:00:00\",\n    \"2046-03-17 00:00:00\",\n    \"2046-03-18 00:00:00\",\n    \"2046-03-24 00:00:00\",\n    \"2046-03-25 00:00:00\",\n    \"2046-03-31 00:00:00\",\n    \"2046-04-01 00:00:00\",\n    \"2046-04-04 00:00:00\",\n    \"2046-04-07 00:00:00\",\n    \"2046-04-08 00:00:00\",\n    \"2046-04-14 00:00:00\",\n    \"2046-04-15 00:00:00\",\n    \"2046-04-21 00:00:00\",\n    \"2046-04-22 00:00:00\",\n    \"2046-04-28 00:00:00\",\n    \"2046-04-29 00:00:00\",\n    \"2046-04-30 00:00:00\",\n    \"2046-05-01 00:00:00\",\n    \"2046-05-02 00:00:00\",\n    \"2046-05-05 00:00:00\",\n    \"2046-05-06 00:00:00\",\n    \"2046-05-12 00:00:00\",\n    \"2046-05-13 00:00:00\",\n    \"2046-05-19 00:00:00\",\n    \"2046-05-20 00:00:00\",\n    \"2046-05-26 00:00:00\",\n    \"2046-05-27 00:00:00\",\n    \"2046-06-02 00:00:00\",\n    \"2046-06-03 00:00:00\",\n    \"2046-06-08 00:00:00\",\n    \"2046-06-09 00:00:00\",\n    \"2046-06-10 00:00:00\",\n    \"2046-06-16 00:00:00\",\n    \"2046-06-17 00:00:00\",\n    \"2046-06-23 00:00:00\",\n    \"2046-06-24 00:00:00\",\n    \"2046-06-30 00:00:00\",\n    \"2046-07-01 00:00:00\",\n    \"2046-07-07 00:00:00\",\n    \"2046-07-08 00:00:00\",\n    \"2046-07-14 00:00:00\",\n    \"2046-07-15 00:00:00\",\n    \"2046-07-21 00:00:00\",\n    \"2046-07-22 00:00:00\",\n    \"2046-07-28 00:00:00\",\n    \"2046-07-29 00:00:00\",\n    \"2046-08-04 00:00:00\",\n    \"2046-08-05 00:00:00\",\n    \"2046-08-11 00:00:00\",\n    \"2046-08-12 00:00:00\",\n    \"2046-08-18 00:00:00\",\n    \"2046-08-19 00:00:00\",\n    \"2046-08-25 00:00:00\",\n    \"2046-08-26 00:00:00\",\n    \"2046-09-01 00:00:00\",\n    \"2046-09-02 00:00:00\",\n    \"2046-09-08 00:00:00\",\n    \"2046-09-09 00:00:00\",\n    \"2046-09-15 00:00:00\",\n    \"2046-09-16 00:00:00\",\n    \"2046-09-17 00:00:00\",\n    \"2046-09-22 00:00:00\",\n    \"2046-09-23 00:00:00\",\n    \"2046-09-30 00:00:00\",\n    \"2046-10-01 00:00:00\",\n    \"2046-10-02 00:00:00\",\n    \"2046-10-03 00:00:00\",\n    \"2046-10-04 00:00:00\",\n    \"2046-10-05 00:00:00\",\n    \"2046-10-06 00:00:00\",\n    \"2046-10-07 00:00:00\",\n    \"2046-10-14 00:00:00\",\n    \"2046-10-20 00:00:00\",\n    \"2046-10-21 00:00:00\",\n    \"2046-10-27 00:00:00\",\n    \"2046-10-28 00:00:00\",\n    \"2046-11-03 00:00:00\",\n    \"2046-11-04 00:00:00\",\n    \"2046-11-10 00:00:00\",\n    \"2046-11-11 00:00:00\",\n    \"2046-11-17 00:00:00\",\n    \"2046-11-18 00:00:00\",\n    \"2046-11-24 00:00:00\",\n    \"2046-11-25 00:00:00\",\n    \"2046-12-01 00:00:00\",\n    \"2046-12-02 00:00:00\",\n    \"2046-12-08 00:00:00\",\n    \"2046-12-09 00:00:00\",\n    \"2046-12-15 00:00:00\",\n    \"2046-12-16 00:00:00\",\n    \"2046-12-22 00:00:00\",\n    \"2046-12-23 00:00:00\",\n    \"2046-12-30 00:00:00\",\n    \"2046-12-31 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-05 00:00:00\",\n    \"2047-01-06 00:00:00\",\n    \"2047-01-12 00:00:00\",\n    \"2047-01-13 00:00:00\",\n    \"2047-01-19 00:00:00\",\n    \"2047-01-25 00:00:00\",\n    \"2047-01-26 00:00:00\",\n    \"2047-01-27 00:00:00\",\n    \"2047-01-28 00:00:00\",\n    \"2047-01-29 00:00:00\",\n    \"2047-01-30 00:00:00\",\n    \"2047-01-31 00:00:00\",\n    \"2047-02-01 00:00:00\",\n    \"2047-02-03 00:00:00\",\n    \"2047-02-09 00:00:00\",\n    \"2047-02-10 00:00:00\",\n    \"2047-02-16 00:00:00\",\n    \"2047-02-17 00:00:00\",\n    \"2047-02-23 00:00:00\",\n    \"2047-02-24 00:00:00\",\n    \"2047-03-02 00:00:00\",\n    \"2047-03-03 00:00:00\",\n    \"2047-03-09 00:00:00\",\n    \"2047-03-10 00:00:00\",\n    \"2047-03-16 00:00:00\",\n    \"2047-03-17 00:00:00\",\n    \"2047-03-23 00:00:00\",\n    \"2047-03-24 00:00:00\",\n    \"2047-03-30 00:00:00\",\n    \"2047-03-31 00:00:00\",\n    \"2047-04-04 00:00:00\",\n    \"2047-04-05 00:00:00\",\n    \"2047-04-06 00:00:00\",\n    \"2047-04-07 00:00:00\",\n    \"2047-04-13 00:00:00\",\n    \"2047-04-20 00:00:00\",\n    \"2047-04-21 00:00:00\",\n    \"2047-04-27 00:00:00\",\n    \"2047-04-28 00:00:00\",\n    \"2047-05-01 00:00:00\",\n    \"2047-05-02 00:00:00\",\n    \"2047-05-04 00:00:00\",\n    \"2047-05-05 00:00:00\",\n    \"2047-05-11 00:00:00\",\n    \"2047-05-12 00:00:00\",\n    \"2047-05-18 00:00:00\",\n    \"2047-05-19 00:00:00\",\n    \"2047-05-25 00:00:00\",\n    \"2047-05-26 00:00:00\",\n    \"2047-05-29 00:00:00\",\n    \"2047-06-01 00:00:00\",\n    \"2047-06-02 00:00:00\",\n    \"2047-06-08 00:00:00\",\n    \"2047-06-09 00:00:00\",\n    \"2047-06-15 00:00:00\",\n    \"2047-06-16 00:00:00\",\n    \"2047-06-22 00:00:00\",\n    \"2047-06-23 00:00:00\",\n    \"2047-06-29 00:00:00\",\n    \"2047-06-30 00:00:00\",\n    \"2047-07-06 00:00:00\",\n    \"2047-07-07 00:00:00\",\n    \"2047-07-13 00:00:00\",\n    \"2047-07-14 00:00:00\",\n    \"2047-07-20 00:00:00\",\n    \"2047-07-21 00:00:00\",\n    \"2047-07-27 00:00:00\",\n    \"2047-07-28 00:00:00\",\n    \"2047-08-03 00:00:00\",\n    \"2047-08-04 00:00:00\",\n    \"2047-08-10 00:00:00\",\n    \"2047-08-11 00:00:00\",\n    \"2047-08-17 00:00:00\",\n    \"2047-08-18 00:00:00\",\n    \"2047-08-24 00:00:00\",\n    \"2047-08-25 00:00:00\",\n    \"2047-08-31 00:00:00\",\n    \"2047-09-01 00:00:00\",\n    \"2047-09-07 00:00:00\",\n    \"2047-09-08 00:00:00\",\n    \"2047-09-14 00:00:00\",\n    \"2047-09-15 00:00:00\",\n    \"2047-09-21 00:00:00\",\n    \"2047-09-22 00:00:00\",\n    \"2047-09-28 00:00:00\",\n    \"2047-10-01 00:00:00\",\n    \"2047-10-02 00:00:00\",\n    \"2047-10-03 00:00:00\",\n    \"2047-10-04 00:00:00\",\n    \"2047-10-05 00:00:00\",\n    \"2047-10-06 00:00:00\",\n    \"2047-10-07 00:00:00\",\n    \"2047-10-08 00:00:00\",\n    \"2047-10-13 00:00:00\",\n    \"2047-10-19 00:00:00\",\n    \"2047-10-20 00:00:00\",\n    \"2047-10-26 00:00:00\",\n    \"2047-10-27 00:00:00\",\n    \"2047-11-02 00:00:00\",\n    \"2047-11-03 00:00:00\",\n    \"2047-11-09 00:00:00\",\n    \"2047-11-10 00:00:00\",\n    \"2047-11-16 00:00:00\",\n    \"2047-11-17 00:00:00\",\n    \"2047-11-23 00:00:00\",\n    \"2047-11-24 00:00:00\",\n    \"2047-11-30 00:00:00\",\n    \"2047-12-01 00:00:00\",\n    \"2047-12-07 00:00:00\",\n    \"2047-12-08 00:00:00\",\n    \"2047-12-14 00:00:00\",\n    \"2047-12-15 00:00:00\",\n    \"2047-12-21 00:00:00\",\n    \"2047-12-22 00:00:00\",\n    \"2047-12-28 00:00:00\",\n    \"2047-12-29 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-04 00:00:00\",\n    \"2048-01-05 00:00:00\",\n    \"2048-01-11 00:00:00\",\n    \"2048-01-12 00:00:00\",\n    \"2048-01-18 00:00:00\",\n    \"2048-01-19 00:00:00\",\n    \"2048-01-25 00:00:00\",\n    \"2048-01-26 00:00:00\",\n    \"2048-02-01 00:00:00\",\n    \"2048-02-02 00:00:00\",\n    \"2048-02-08 00:00:00\",\n    \"2048-02-13 00:00:00\",\n    \"2048-02-14 00:00:00\",\n    \"2048-02-15 00:00:00\",\n    \"2048-02-16 00:00:00\",\n    \"2048-02-17 00:00:00\",\n    \"2048-02-18 00:00:00\",\n    \"2048-02-19 00:00:00\",\n    \"2048-02-20 00:00:00\",\n    \"2048-02-23 00:00:00\",\n    \"2048-02-29 00:00:00\",\n    \"2048-03-01 00:00:00\",\n    \"2048-03-07 00:00:00\",\n    \"2048-03-08 00:00:00\",\n    \"2048-03-14 00:00:00\",\n    \"2048-03-15 00:00:00\",\n    \"2048-03-21 00:00:00\",\n    \"2048-03-22 00:00:00\",\n    \"2048-03-28 00:00:00\",\n    \"2048-03-29 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-04 00:00:00\",\n    \"2048-04-05 00:00:00\",\n    \"2048-04-11 00:00:00\",\n    \"2048-04-12 00:00:00\",\n    \"2048-04-18 00:00:00\",\n    \"2048-04-19 00:00:00\",\n    \"2048-04-25 00:00:00\",\n    \"2048-04-26 00:00:00\",\n    \"2048-05-01 00:00:00\",\n    \"2048-05-02 00:00:00\",\n    \"2048-05-03 00:00:00\",\n    \"2048-05-04 00:00:00\",\n    \"2048-05-05 00:00:00\",\n    \"2048-05-10 00:00:00\",\n    \"2048-05-16 00:00:00\",\n    \"2048-05-17 00:00:00\",\n    \"2048-05-23 00:00:00\",\n    \"2048-05-24 00:00:00\",\n    \"2048-05-30 00:00:00\",\n    \"2048-05-31 00:00:00\",\n    \"2048-06-06 00:00:00\",\n    \"2048-06-07 00:00:00\",\n    \"2048-06-13 00:00:00\",\n    \"2048-06-14 00:00:00\",\n    \"2048-06-15 00:00:00\",\n    \"2048-06-20 00:00:00\",\n    \"2048-06-21 00:00:00\",\n    \"2048-06-27 00:00:00\",\n    \"2048-06-28 00:00:00\",\n    \"2048-07-04 00:00:00\",\n    \"2048-07-05 00:00:00\",\n    \"2048-07-11 00:00:00\",\n    \"2048-07-12 00:00:00\",\n    \"2048-07-18 00:00:00\",\n    \"2048-07-19 00:00:00\",\n    \"2048-07-25 00:00:00\",\n    \"2048-07-26 00:00:00\",\n    \"2048-08-01 00:00:00\",\n    \"2048-08-02 00:00:00\",\n    \"2048-08-08 00:00:00\",\n    \"2048-08-09 00:00:00\",\n    \"2048-08-15 00:00:00\",\n    \"2048-08-16 00:00:00\",\n    \"2048-08-22 00:00:00\",\n    \"2048-08-23 00:00:00\",\n    \"2048-08-29 00:00:00\",\n    \"2048-08-30 00:00:00\",\n    \"2048-09-05 00:00:00\",\n    \"2048-09-06 00:00:00\",\n    \"2048-09-12 00:00:00\",\n    \"2048-09-19 00:00:00\",\n    \"2048-09-20 00:00:00\",\n    \"2048-09-21 00:00:00\",\n    \"2048-09-22 00:00:00\",\n    \"2048-09-26 00:00:00\",\n    \"2048-10-01 00:00:00\",\n    \"2048-10-02 00:00:00\",\n    \"2048-10-03 00:00:00\",\n    \"2048-10-04 00:00:00\",\n    \"2048-10-05 00:00:00\",\n    \"2048-10-06 00:00:00\",\n    \"2048-10-07 00:00:00\",\n    \"2048-10-11 00:00:00\",\n    \"2048-10-17 00:00:00\",\n    \"2048-10-18 00:00:00\",\n    \"2048-10-24 00:00:00\",\n    \"2048-10-25 00:00:00\",\n    \"2048-10-31 00:00:00\",\n    \"2048-11-01 00:00:00\",\n    \"2048-11-07 00:00:00\",\n    \"2048-11-08 00:00:00\",\n    \"2048-11-14 00:00:00\",\n    \"2048-11-15 00:00:00\",\n    \"2048-11-21 00:00:00\",\n    \"2048-11-22 00:00:00\",\n    \"2048-11-28 00:00:00\",\n    \"2048-11-29 00:00:00\",\n    \"2048-12-05 00:00:00\",\n    \"2048-12-06 00:00:00\",\n    \"2048-12-12 00:00:00\",\n    \"2048-12-13 00:00:00\",\n    \"2048-12-19 00:00:00\",\n    \"2048-12-20 00:00:00\",\n    \"2048-12-26 00:00:00\",\n    \"2048-12-27 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-02 00:00:00\",\n    \"2049-01-03 00:00:00\",\n    \"2049-01-09 00:00:00\",\n    \"2049-01-10 00:00:00\",\n    \"2049-01-16 00:00:00\",\n    \"2049-01-17 00:00:00\",\n    \"2049-01-23 00:00:00\",\n    \"2049-01-24 00:00:00\",\n    \"2049-01-31 00:00:00\",\n    \"2049-02-01 00:00:00\",\n    \"2049-02-02 00:00:00\",\n    \"2049-02-03 00:00:00\",\n    \"2049-02-04 00:00:00\",\n    \"2049-02-05 00:00:00\",\n    \"2049-02-06 00:00:00\",\n    \"2049-02-07 00:00:00\",\n    \"2049-02-08 00:00:00\",\n    \"2049-02-14 00:00:00\",\n    \"2049-02-20 00:00:00\",\n    \"2049-02-21 00:00:00\",\n    \"2049-02-27 00:00:00\",\n    \"2049-02-28 00:00:00\",\n    \"2049-03-06 00:00:00\",\n    \"2049-03-07 00:00:00\",\n    \"2049-03-13 00:00:00\",\n    \"2049-03-14 00:00:00\",\n    \"2049-03-20 00:00:00\",\n    \"2049-03-21 00:00:00\",\n    \"2049-03-27 00:00:00\",\n    \"2049-03-28 00:00:00\",\n    \"2049-04-03 00:00:00\",\n    \"2049-04-04 00:00:00\",\n    \"2049-04-05 00:00:00\",\n    \"2049-04-10 00:00:00\",\n    \"2049-04-11 00:00:00\",\n    \"2049-04-17 00:00:00\",\n    \"2049-04-18 00:00:00\",\n    \"2049-04-24 00:00:00\",\n    \"2049-04-25 00:00:00\",\n    \"2049-05-01 00:00:00\",\n    \"2049-05-02 00:00:00\",\n    \"2049-05-03 00:00:00\",\n    \"2049-05-04 00:00:00\",\n    \"2049-05-05 00:00:00\",\n    \"2049-05-08 00:00:00\",\n    \"2049-05-15 00:00:00\",\n    \"2049-05-16 00:00:00\",\n    \"2049-05-22 00:00:00\",\n    \"2049-05-23 00:00:00\",\n    \"2049-05-29 00:00:00\",\n    \"2049-05-30 00:00:00\",\n    \"2049-06-04 00:00:00\",\n    \"2049-06-05 00:00:00\",\n    \"2049-06-06 00:00:00\",\n    \"2049-06-12 00:00:00\",\n    \"2049-06-13 00:00:00\",\n    \"2049-06-19 00:00:00\",\n    \"2049-06-20 00:00:00\",\n    \"2049-06-26 00:00:00\",\n    \"2049-06-27 00:00:00\",\n    \"2049-07-03 00:00:00\",\n    \"2049-07-04 00:00:00\",\n    \"2049-07-10 00:00:00\",\n    \"2049-07-11 00:00:00\",\n    \"2049-07-17 00:00:00\",\n    \"2049-07-18 00:00:00\",\n    \"2049-07-24 00:00:00\",\n    \"2049-07-25 00:00:00\",\n    \"2049-07-31 00:00:00\",\n    \"2049-08-01 00:00:00\",\n    \"2049-08-07 00:00:00\",\n    \"2049-08-08 00:00:00\",\n    \"2049-08-14 00:00:00\",\n    \"2049-08-15 00:00:00\",\n    \"2049-08-21 00:00:00\",\n    \"2049-08-22 00:00:00\",\n    \"2049-08-28 00:00:00\",\n    \"2049-08-29 00:00:00\",\n    \"2049-09-04 00:00:00\",\n    \"2049-09-05 00:00:00\",\n    \"2049-09-11 00:00:00\",\n    \"2049-09-12 00:00:00\",\n    \"2049-09-13 00:00:00\",\n    \"2049-09-18 00:00:00\",\n    \"2049-09-19 00:00:00\",\n    \"2049-09-25 00:00:00\",\n    \"2049-10-01 00:00:00\",\n    \"2049-10-02 00:00:00\",\n    \"2049-10-03 00:00:00\",\n    \"2049-10-04 00:00:00\",\n    \"2049-10-05 00:00:00\",\n    \"2049-10-06 00:00:00\",\n    \"2049-10-07 00:00:00\",\n    \"2049-10-10 00:00:00\",\n    \"2049-10-16 00:00:00\",\n    \"2049-10-17 00:00:00\",\n    \"2049-10-23 00:00:00\",\n    \"2049-10-24 00:00:00\",\n    \"2049-10-30 00:00:00\",\n    \"2049-10-31 00:00:00\",\n    \"2049-11-06 00:00:00\",\n    \"2049-11-07 00:00:00\",\n    \"2049-11-13 00:00:00\",\n    \"2049-11-14 00:00:00\",\n    \"2049-11-20 00:00:00\",\n    \"2049-11-21 00:00:00\",\n    \"2049-11-27 00:00:00\",\n    \"2049-11-28 00:00:00\",\n    \"2049-12-04 00:00:00\",\n    \"2049-12-05 00:00:00\",\n    \"2049-12-11 00:00:00\",\n    \"2049-12-12 00:00:00\",\n    \"2049-12-18 00:00:00\",\n    \"2049-12-19 00:00:00\",\n    \"2049-12-25 00:00:00\",\n    \"2049-12-26 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-01-02 00:00:00\",\n    \"2050-01-03 00:00:00\",\n    \"2050-01-08 00:00:00\",\n    \"2050-01-09 00:00:00\",\n    \"2050-01-15 00:00:00\",\n    \"2050-01-22 00:00:00\",\n    \"2050-01-23 00:00:00\",\n    \"2050-01-24 00:00:00\",\n    \"2050-01-25 00:00:00\",\n    \"2050-01-26 00:00:00\",\n    \"2050-01-27 00:00:00\",\n    \"2050-01-28 00:00:00\",\n    \"2050-01-29 00:00:00\",\n    \"2050-02-05 00:00:00\",\n    \"2050-02-06 00:00:00\",\n    \"2050-02-12 00:00:00\",\n    \"2050-02-13 00:00:00\",\n    \"2050-02-19 00:00:00\",\n    \"2050-02-20 00:00:00\",\n    \"2050-02-26 00:00:00\",\n    \"2050-02-27 00:00:00\",\n    \"2050-03-05 00:00:00\",\n    \"2050-03-06 00:00:00\",\n    \"2050-03-12 00:00:00\",\n    \"2050-03-13 00:00:00\",\n    \"2050-03-19 00:00:00\",\n    \"2050-03-20 00:00:00\",\n    \"2050-03-26 00:00:00\",\n    \"2050-03-27 00:00:00\",\n    \"2050-04-02 00:00:00\",\n    \"2050-04-03 00:00:00\",\n    \"2050-04-04 00:00:00\",\n    \"2050-04-09 00:00:00\",\n    \"2050-04-10 00:00:00\",\n    \"2050-04-16 00:00:00\",\n    \"2050-04-17 00:00:00\",\n    \"2050-04-23 00:00:00\",\n    \"2050-04-24 00:00:00\",\n    \"2050-04-30 00:00:00\",\n    \"2050-05-01 00:00:00\",\n    \"2050-05-02 00:00:00\",\n    \"2050-05-03 00:00:00\",\n    \"2050-05-04 00:00:00\",\n    \"2050-05-07 00:00:00\",\n    \"2050-05-14 00:00:00\",\n    \"2050-05-15 00:00:00\",\n    \"2050-05-21 00:00:00\",\n    \"2050-05-22 00:00:00\",\n    \"2050-05-28 00:00:00\",\n    \"2050-05-29 00:00:00\",\n    \"2050-06-04 00:00:00\",\n    \"2050-06-05 00:00:00\",\n    \"2050-06-11 00:00:00\",\n    \"2050-06-12 00:00:00\",\n    \"2050-06-18 00:00:00\",\n    \"2050-06-19 00:00:00\",\n    \"2050-06-23 00:00:00\",\n    \"2050-06-24 00:00:00\",\n    \"2050-06-25 00:00:00\",\n    \"2050-06-26 00:00:00\",\n    \"2050-07-02 00:00:00\",\n    \"2050-07-09 00:00:00\",\n    \"2050-07-10 00:00:00\",\n    \"2050-07-16 00:00:00\",\n    \"2050-07-17 00:00:00\",\n    \"2050-07-23 00:00:00\",\n    \"2050-07-24 00:00:00\",\n    \"2050-07-30 00:00:00\",\n    \"2050-07-31 00:00:00\",\n    \"2050-08-06 00:00:00\",\n    \"2050-08-07 00:00:00\",\n    \"2050-08-13 00:00:00\",\n    \"2050-08-14 00:00:00\",\n    \"2050-08-20 00:00:00\",\n    \"2050-08-21 00:00:00\",\n    \"2050-08-27 00:00:00\",\n    \"2050-08-28 00:00:00\",\n    \"2050-09-03 00:00:00\",\n    \"2050-09-04 00:00:00\",\n    \"2050-09-10 00:00:00\",\n    \"2050-09-11 00:00:00\",\n    \"2050-09-17 00:00:00\",\n    \"2050-09-18 00:00:00\",\n    \"2050-09-24 00:00:00\",\n    \"2050-09-30 00:00:00\",\n    \"2050-10-01 00:00:00\",\n    \"2050-10-02 00:00:00\",\n    \"2050-10-03 00:00:00\",\n    \"2050-10-04 00:00:00\",\n    \"2050-10-05 00:00:00\",\n    \"2050-10-06 00:00:00\",\n    \"2050-10-07 00:00:00\",\n    \"2050-10-08 00:00:00\",\n    \"2050-10-15 00:00:00\",\n    \"2050-10-16 00:00:00\",\n    \"2050-10-22 00:00:00\",\n    \"2050-10-23 00:00:00\",\n    \"2050-10-29 00:00:00\",\n    \"2050-10-30 00:00:00\",\n    \"2050-11-05 00:00:00\",\n    \"2050-11-06 00:00:00\",\n    \"2050-11-12 00:00:00\",\n    \"2050-11-13 00:00:00\",\n    \"2050-11-19 00:00:00\",\n    \"2050-11-20 00:00:00\",\n    \"2050-11-26 00:00:00\",\n    \"2050-11-27 00:00:00\",\n    \"2050-12-03 00:00:00\",\n    \"2050-12-04 00:00:00\",\n    \"2050-12-10 00:00:00\",\n    \"2050-12-11 00:00:00\",\n    \"2050-12-17 00:00:00\",\n    \"2050-12-18 00:00:00\",\n    \"2050-12-24 00:00:00\",\n    \"2050-12-25 00:00:00\",\n    \"2050-12-31 00:00:00\",\n    \"2051-01-01 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-01-07 00:00:00\",\n    \"2051-01-08 00:00:00\",\n    \"2051-01-14 00:00:00\",\n    \"2051-01-15 00:00:00\",\n    \"2051-01-21 00:00:00\",\n    \"2051-01-22 00:00:00\",\n    \"2051-01-28 00:00:00\",\n    \"2051-01-29 00:00:00\",\n    \"2051-02-04 00:00:00\",\n    \"2051-02-10 00:00:00\",\n    \"2051-02-11 00:00:00\",\n    \"2051-02-12 00:00:00\",\n    \"2051-02-13 00:00:00\",\n    \"2051-02-14 00:00:00\",\n    \"2051-02-15 00:00:00\",\n    \"2051-02-16 00:00:00\",\n    \"2051-02-17 00:00:00\",\n    \"2051-02-19 00:00:00\",\n    \"2051-02-25 00:00:00\",\n    \"2051-02-26 00:00:00\",\n    \"2051-03-04 00:00:00\",\n    \"2051-03-05 00:00:00\",\n    \"2051-03-11 00:00:00\",\n    \"2051-03-12 00:00:00\",\n    \"2051-03-18 00:00:00\",\n    \"2051-03-19 00:00:00\",\n    \"2051-03-25 00:00:00\",\n    \"2051-04-01 00:00:00\",\n    \"2051-04-02 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-04-04 00:00:00\",\n    \"2051-04-08 00:00:00\",\n    \"2051-04-09 00:00:00\",\n    \"2051-04-15 00:00:00\",\n    \"2051-04-16 00:00:00\",\n    \"2051-04-22 00:00:00\",\n    \"2051-04-23 00:00:00\",\n    \"2051-04-29 00:00:00\",\n    \"2051-04-30 00:00:00\",\n    \"2051-05-01 00:00:00\",\n    \"2051-05-02 00:00:00\",\n    \"2051-05-03 00:00:00\",\n    \"2051-05-06 00:00:00\",\n    \"2051-05-13 00:00:00\",\n    \"2051-05-14 00:00:00\",\n    \"2051-05-20 00:00:00\",\n    \"2051-05-21 00:00:00\",\n    \"2051-05-27 00:00:00\",\n    \"2051-05-28 00:00:00\",\n    \"2051-06-03 00:00:00\",\n    \"2051-06-10 00:00:00\",\n    \"2051-06-11 00:00:00\",\n    \"2051-06-12 00:00:00\",\n    \"2051-06-13 00:00:00\",\n    \"2051-06-17 00:00:00\",\n    \"2051-06-18 00:00:00\",\n    \"2051-06-24 00:00:00\",\n    \"2051-06-25 00:00:00\",\n    \"2051-07-01 00:00:00\",\n    \"2051-07-02 00:00:00\",\n    \"2051-07-08 00:00:00\",\n    \"2051-07-09 00:00:00\",\n    \"2051-07-15 00:00:00\",\n    \"2051-07-16 00:00:00\",\n    \"2051-07-22 00:00:00\",\n    \"2051-07-23 00:00:00\",\n    \"2051-07-29 00:00:00\",\n    \"2051-07-30 00:00:00\",\n    \"2051-08-05 00:00:00\",\n    \"2051-08-06 00:00:00\",\n    \"2051-08-12 00:00:00\",\n    \"2051-08-13 00:00:00\",\n    \"2051-08-19 00:00:00\",\n    \"2051-08-20 00:00:00\",\n    \"2051-08-26 00:00:00\",\n    \"2051-08-27 00:00:00\",\n    \"2051-09-02 00:00:00\",\n    \"2051-09-03 00:00:00\",\n    \"2051-09-09 00:00:00\",\n    \"2051-09-16 00:00:00\",\n    \"2051-09-17 00:00:00\",\n    \"2051-09-18 00:00:00\",\n    \"2051-09-19 00:00:00\",\n    \"2051-09-23 00:00:00\",\n    \"2051-09-30 00:00:00\",\n    \"2051-10-01 00:00:00\",\n    \"2051-10-02 00:00:00\",\n    \"2051-10-03 00:00:00\",\n    \"2051-10-04 00:00:00\",\n    \"2051-10-05 00:00:00\",\n    \"2051-10-06 00:00:00\",\n    \"2051-10-07 00:00:00\",\n    \"2051-10-14 00:00:00\",\n    \"2051-10-15 00:00:00\",\n    \"2051-10-21 00:00:00\",\n    \"2051-10-22 00:00:00\",\n    \"2051-10-28 00:00:00\",\n    \"2051-10-29 00:00:00\",\n    \"2051-11-04 00:00:00\",\n    \"2051-11-05 00:00:00\",\n    \"2051-11-11 00:00:00\",\n    \"2051-11-12 00:00:00\",\n    \"2051-11-18 00:00:00\",\n    \"2051-11-19 00:00:00\",\n    \"2051-11-25 00:00:00\",\n    \"2051-11-26 00:00:00\",\n    \"2051-12-02 00:00:00\",\n    \"2051-12-03 00:00:00\",\n    \"2051-12-09 00:00:00\",\n    \"2051-12-10 00:00:00\",\n    \"2051-12-16 00:00:00\",\n    \"2051-12-17 00:00:00\",\n    \"2051-12-23 00:00:00\",\n    \"2051-12-24 00:00:00\",\n    \"2051-12-30 00:00:00\",\n    \"2051-12-31 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-06 00:00:00\",\n    \"2052-01-07 00:00:00\",\n    \"2052-01-13 00:00:00\",\n    \"2052-01-14 00:00:00\",\n    \"2052-01-20 00:00:00\",\n    \"2052-01-21 00:00:00\",\n    \"2052-01-27 00:00:00\",\n    \"2052-01-31 00:00:00\",\n    \"2052-02-01 00:00:00\",\n    \"2052-02-02 00:00:00\",\n    \"2052-02-03 00:00:00\",\n    \"2052-02-04 00:00:00\",\n    \"2052-02-05 00:00:00\",\n    \"2052-02-06 00:00:00\",\n    \"2052-02-07 00:00:00\",\n    \"2052-02-11 00:00:00\",\n    \"2052-02-17 00:00:00\",\n    \"2052-02-18 00:00:00\",\n    \"2052-02-24 00:00:00\",\n    \"2052-02-25 00:00:00\",\n    \"2052-03-02 00:00:00\",\n    \"2052-03-03 00:00:00\",\n    \"2052-03-09 00:00:00\",\n    \"2052-03-10 00:00:00\",\n    \"2052-03-16 00:00:00\",\n    \"2052-03-17 00:00:00\",\n    \"2052-03-23 00:00:00\",\n    \"2052-03-24 00:00:00\",\n    \"2052-03-30 00:00:00\",\n    \"2052-03-31 00:00:00\",\n    \"2052-04-03 00:00:00\",\n    \"2052-04-06 00:00:00\",\n    \"2052-04-07 00:00:00\",\n    \"2052-04-13 00:00:00\",\n    \"2052-04-14 00:00:00\",\n    \"2052-04-20 00:00:00\",\n    \"2052-04-21 00:00:00\",\n    \"2052-04-27 00:00:00\",\n    \"2052-04-28 00:00:00\",\n    \"2052-05-01 00:00:00\",\n    \"2052-05-02 00:00:00\",\n    \"2052-05-04 00:00:00\",\n    \"2052-05-05 00:00:00\",\n    \"2052-05-11 00:00:00\",\n    \"2052-05-12 00:00:00\",\n    \"2052-05-18 00:00:00\",\n    \"2052-05-19 00:00:00\",\n    \"2052-05-25 00:00:00\",\n    \"2052-05-26 00:00:00\",\n    \"2052-06-01 00:00:00\",\n    \"2052-06-02 00:00:00\",\n    \"2052-06-03 00:00:00\",\n    \"2052-06-08 00:00:00\",\n    \"2052-06-09 00:00:00\",\n    \"2052-06-15 00:00:00\",\n    \"2052-06-16 00:00:00\",\n    \"2052-06-22 00:00:00\",\n    \"2052-06-23 00:00:00\",\n    \"2052-06-29 00:00:00\",\n    \"2052-06-30 00:00:00\",\n    \"2052-07-06 00:00:00\",\n    \"2052-07-07 00:00:00\",\n    \"2052-07-13 00:00:00\",\n    \"2052-07-14 00:00:00\",\n    \"2052-07-20 00:00:00\",\n    \"2052-07-21 00:00:00\",\n    \"2052-07-27 00:00:00\",\n    \"2052-07-28 00:00:00\",\n    \"2052-08-03 00:00:00\",\n    \"2052-08-04 00:00:00\",\n    \"2052-08-10 00:00:00\",\n    \"2052-08-11 00:00:00\",\n    \"2052-08-17 00:00:00\",\n    \"2052-08-18 00:00:00\",\n    \"2052-08-24 00:00:00\",\n    \"2052-08-25 00:00:00\",\n    \"2052-08-31 00:00:00\",\n    \"2052-09-01 00:00:00\",\n    \"2052-09-07 00:00:00\",\n    \"2052-09-08 00:00:00\",\n    \"2052-09-09 00:00:00\",\n    \"2052-09-14 00:00:00\",\n    \"2052-09-15 00:00:00\",\n    \"2052-09-21 00:00:00\",\n    \"2052-09-22 00:00:00\",\n    \"2052-09-28 00:00:00\",\n    \"2052-10-01 00:00:00\",\n    \"2052-10-02 00:00:00\",\n    \"2052-10-03 00:00:00\",\n    \"2052-10-04 00:00:00\",\n    \"2052-10-05 00:00:00\",\n    \"2052-10-06 00:00:00\",\n    \"2052-10-07 00:00:00\",\n    \"2052-10-13 00:00:00\",\n    \"2052-10-19 00:00:00\",\n    \"2052-10-20 00:00:00\",\n    \"2052-10-26 00:00:00\",\n    \"2052-10-27 00:00:00\",\n    \"2052-11-02 00:00:00\",\n    \"2052-11-03 00:00:00\",\n    \"2052-11-09 00:00:00\",\n    \"2052-11-10 00:00:00\",\n    \"2052-11-16 00:00:00\",\n    \"2052-11-17 00:00:00\",\n    \"2052-11-23 00:00:00\",\n    \"2052-11-24 00:00:00\",\n    \"2052-11-30 00:00:00\",\n    \"2052-12-01 00:00:00\",\n    \"2052-12-07 00:00:00\",\n    \"2052-12-08 00:00:00\",\n    \"2052-12-14 00:00:00\",\n    \"2052-12-15 00:00:00\",\n    \"2052-12-21 00:00:00\",\n    \"2052-12-22 00:00:00\",\n    \"2052-12-28 00:00:00\",\n    \"2052-12-29 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-04 00:00:00\",\n    \"2053-01-05 00:00:00\",\n    \"2053-01-11 00:00:00\",\n    \"2053-01-12 00:00:00\",\n    \"2053-01-18 00:00:00\",\n    \"2053-01-19 00:00:00\",\n    \"2053-01-25 00:00:00\",\n    \"2053-01-26 00:00:00\",\n    \"2053-02-01 00:00:00\",\n    \"2053-02-02 00:00:00\",\n    \"2053-02-08 00:00:00\",\n    \"2053-02-09 00:00:00\",\n    \"2053-02-15 00:00:00\",\n    \"2053-02-18 00:00:00\",\n    \"2053-02-19 00:00:00\",\n    \"2053-02-20 00:00:00\",\n    \"2053-02-21 00:00:00\",\n    \"2053-02-22 00:00:00\",\n    \"2053-02-23 00:00:00\",\n    \"2053-02-24 00:00:00\",\n    \"2053-02-25 00:00:00\",\n    \"2053-03-02 00:00:00\",\n    \"2053-03-08 00:00:00\",\n    \"2053-03-09 00:00:00\",\n    \"2053-03-15 00:00:00\",\n    \"2053-03-16 00:00:00\",\n    \"2053-03-22 00:00:00\",\n    \"2053-03-23 00:00:00\",\n    \"2053-03-29 00:00:00\",\n    \"2053-03-30 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-05 00:00:00\",\n    \"2053-04-06 00:00:00\",\n    \"2053-04-12 00:00:00\",\n    \"2053-04-13 00:00:00\",\n    \"2053-04-19 00:00:00\",\n    \"2053-04-20 00:00:00\",\n    \"2053-04-26 00:00:00\",\n    \"2053-05-01 00:00:00\",\n    \"2053-05-02 00:00:00\",\n    \"2053-05-03 00:00:00\",\n    \"2053-05-04 00:00:00\",\n    \"2053-05-05 00:00:00\",\n    \"2053-05-10 00:00:00\",\n    \"2053-05-11 00:00:00\",\n    \"2053-05-17 00:00:00\",\n    \"2053-05-18 00:00:00\",\n    \"2053-05-24 00:00:00\",\n    \"2053-05-25 00:00:00\",\n    \"2053-05-31 00:00:00\",\n    \"2053-06-01 00:00:00\",\n    \"2053-06-07 00:00:00\",\n    \"2053-06-08 00:00:00\",\n    \"2053-06-14 00:00:00\",\n    \"2053-06-15 00:00:00\",\n    \"2053-06-20 00:00:00\",\n    \"2053-06-21 00:00:00\",\n    \"2053-06-22 00:00:00\",\n    \"2053-06-28 00:00:00\",\n    \"2053-06-29 00:00:00\",\n    \"2053-07-05 00:00:00\",\n    \"2053-07-06 00:00:00\",\n    \"2053-07-12 00:00:00\",\n    \"2053-07-13 00:00:00\",\n    \"2053-07-19 00:00:00\",\n    \"2053-07-20 00:00:00\",\n    \"2053-07-26 00:00:00\",\n    \"2053-07-27 00:00:00\",\n    \"2053-08-02 00:00:00\",\n    \"2053-08-03 00:00:00\",\n    \"2053-08-09 00:00:00\",\n    \"2053-08-10 00:00:00\",\n    \"2053-08-16 00:00:00\",\n    \"2053-08-17 00:00:00\",\n    \"2053-08-23 00:00:00\",\n    \"2053-08-24 00:00:00\",\n    \"2053-08-30 00:00:00\",\n    \"2053-08-31 00:00:00\",\n    \"2053-09-06 00:00:00\",\n    \"2053-09-07 00:00:00\",\n    \"2053-09-13 00:00:00\",\n    \"2053-09-14 00:00:00\",\n    \"2053-09-20 00:00:00\",\n    \"2053-09-26 00:00:00\",\n    \"2053-09-27 00:00:00\",\n    \"2053-09-28 00:00:00\",\n    \"2053-10-01 00:00:00\",\n    \"2053-10-02 00:00:00\",\n    \"2053-10-03 00:00:00\",\n    \"2053-10-04 00:00:00\",\n    \"2053-10-05 00:00:00\",\n    \"2053-10-06 00:00:00\",\n    \"2053-10-07 00:00:00\",\n    \"2053-10-12 00:00:00\",\n    \"2053-10-18 00:00:00\",\n    \"2053-10-19 00:00:00\",\n    \"2053-10-25 00:00:00\",\n    \"2053-10-26 00:00:00\",\n    \"2053-11-01 00:00:00\",\n    \"2053-11-02 00:00:00\",\n    \"2053-11-08 00:00:00\",\n    \"2053-11-09 00:00:00\",\n    \"2053-11-15 00:00:00\",\n    \"2053-11-16 00:00:00\",\n    \"2053-11-22 00:00:00\",\n    \"2053-11-23 00:00:00\",\n    \"2053-11-29 00:00:00\",\n    \"2053-11-30 00:00:00\",\n    \"2053-12-06 00:00:00\",\n    \"2053-12-07 00:00:00\",\n    \"2053-12-13 00:00:00\",\n    \"2053-12-14 00:00:00\",\n    \"2053-12-20 00:00:00\",\n    \"2053-12-21 00:00:00\",\n    \"2053-12-27 00:00:00\",\n    \"2053-12-28 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-02 00:00:00\",\n    \"2054-01-03 00:00:00\",\n    \"2054-01-10 00:00:00\",\n    \"2054-01-11 00:00:00\",\n    \"2054-01-17 00:00:00\",\n    \"2054-01-18 00:00:00\",\n    \"2054-01-24 00:00:00\",\n    \"2054-01-25 00:00:00\",\n    \"2054-01-31 00:00:00\",\n    \"2054-02-07 00:00:00\",\n    \"2054-02-08 00:00:00\",\n    \"2054-02-09 00:00:00\",\n    \"2054-02-10 00:00:00\",\n    \"2054-02-11 00:00:00\",\n    \"2054-02-12 00:00:00\",\n    \"2054-02-13 00:00:00\",\n    \"2054-02-14 00:00:00\",\n    \"2054-02-21 00:00:00\",\n    \"2054-02-22 00:00:00\",\n    \"2054-02-28 00:00:00\",\n    \"2054-03-01 00:00:00\",\n    \"2054-03-07 00:00:00\",\n    \"2054-03-08 00:00:00\",\n    \"2054-03-14 00:00:00\",\n    \"2054-03-15 00:00:00\",\n    \"2054-03-21 00:00:00\",\n    \"2054-03-22 00:00:00\",\n    \"2054-03-28 00:00:00\",\n    \"2054-03-29 00:00:00\",\n    \"2054-04-04 00:00:00\",\n    \"2054-04-05 00:00:00\",\n    \"2054-04-06 00:00:00\",\n    \"2054-04-11 00:00:00\",\n    \"2054-04-12 00:00:00\",\n    \"2054-04-18 00:00:00\",\n    \"2054-04-19 00:00:00\",\n    \"2054-04-25 00:00:00\",\n    \"2054-04-26 00:00:00\",\n    \"2054-05-01 00:00:00\",\n    \"2054-05-02 00:00:00\",\n    \"2054-05-03 00:00:00\",\n    \"2054-05-04 00:00:00\",\n    \"2054-05-05 00:00:00\",\n    \"2054-05-10 00:00:00\",\n    \"2054-05-16 00:00:00\",\n    \"2054-05-17 00:00:00\",\n    \"2054-05-23 00:00:00\",\n    \"2054-05-24 00:00:00\",\n    \"2054-05-30 00:00:00\",\n    \"2054-05-31 00:00:00\",\n    \"2054-06-06 00:00:00\",\n    \"2054-06-07 00:00:00\",\n    \"2054-06-10 00:00:00\",\n    \"2054-06-13 00:00:00\",\n    \"2054-06-14 00:00:00\",\n    \"2054-06-20 00:00:00\",\n    \"2054-06-21 00:00:00\",\n    \"2054-06-27 00:00:00\",\n    \"2054-06-28 00:00:00\",\n    \"2054-07-04 00:00:00\",\n    \"2054-07-05 00:00:00\",\n    \"2054-07-11 00:00:00\",\n    \"2054-07-12 00:00:00\",\n    \"2054-07-18 00:00:00\",\n    \"2054-07-19 00:00:00\",\n    \"2054-07-25 00:00:00\",\n    \"2054-07-26 00:00:00\",\n    \"2054-08-01 00:00:00\",\n    \"2054-08-02 00:00:00\",\n    \"2054-08-08 00:00:00\",\n    \"2054-08-09 00:00:00\",\n    \"2054-08-15 00:00:00\",\n    \"2054-08-16 00:00:00\",\n    \"2054-08-22 00:00:00\",\n    \"2054-08-23 00:00:00\",\n    \"2054-08-29 00:00:00\",\n    \"2054-08-30 00:00:00\",\n    \"2054-09-05 00:00:00\",\n    \"2054-09-06 00:00:00\",\n    \"2054-09-12 00:00:00\",\n    \"2054-09-13 00:00:00\",\n    \"2054-09-16 00:00:00\",\n    \"2054-09-19 00:00:00\",\n    \"2054-09-20 00:00:00\",\n    \"2054-09-26 00:00:00\",\n    \"2054-10-01 00:00:00\",\n    \"2054-10-02 00:00:00\",\n    \"2054-10-03 00:00:00\",\n    \"2054-10-04 00:00:00\",\n    \"2054-10-05 00:00:00\",\n    \"2054-10-06 00:00:00\",\n    \"2054-10-07 00:00:00\",\n    \"2054-10-11 00:00:00\",\n    \"2054-10-17 00:00:00\",\n    \"2054-10-18 00:00:00\",\n    \"2054-10-24 00:00:00\",\n    \"2054-10-25 00:00:00\",\n    \"2054-10-31 00:00:00\",\n    \"2054-11-01 00:00:00\",\n    \"2054-11-07 00:00:00\",\n    \"2054-11-08 00:00:00\",\n    \"2054-11-14 00:00:00\",\n    \"2054-11-15 00:00:00\",\n    \"2054-11-21 00:00:00\",\n    \"2054-11-22 00:00:00\",\n    \"2054-11-28 00:00:00\",\n    \"2054-11-29 00:00:00\",\n    \"2054-12-05 00:00:00\",\n    \"2054-12-06 00:00:00\",\n    \"2054-12-12 00:00:00\",\n    \"2054-12-13 00:00:00\",\n    \"2054-12-19 00:00:00\",\n    \"2054-12-20 00:00:00\",\n    \"2054-12-26 00:00:00\",\n    \"2054-12-27 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-02 00:00:00\",\n    \"2055-01-03 00:00:00\",\n    \"2055-01-09 00:00:00\",\n    \"2055-01-10 00:00:00\",\n    \"2055-01-16 00:00:00\",\n    \"2055-01-17 00:00:00\",\n    \"2055-01-23 00:00:00\",\n    \"2055-01-27 00:00:00\",\n    \"2055-01-28 00:00:00\",\n    \"2055-01-29 00:00:00\",\n    \"2055-01-30 00:00:00\",\n    \"2055-01-31 00:00:00\",\n    \"2055-02-01 00:00:00\",\n    \"2055-02-02 00:00:00\",\n    \"2055-02-03 00:00:00\",\n    \"2055-02-07 00:00:00\",\n    \"2055-02-13 00:00:00\",\n    \"2055-02-14 00:00:00\",\n    \"2055-02-20 00:00:00\",\n    \"2055-02-21 00:00:00\",\n    \"2055-02-27 00:00:00\",\n    \"2055-02-28 00:00:00\",\n    \"2055-03-06 00:00:00\",\n    \"2055-03-07 00:00:00\",\n    \"2055-03-13 00:00:00\",\n    \"2055-03-14 00:00:00\",\n    \"2055-03-20 00:00:00\",\n    \"2055-03-21 00:00:00\",\n    \"2055-03-27 00:00:00\",\n    \"2055-03-28 00:00:00\",\n    \"2055-04-03 00:00:00\",\n    \"2055-04-04 00:00:00\",\n    \"2055-04-05 00:00:00\",\n    \"2055-04-10 00:00:00\",\n    \"2055-04-11 00:00:00\",\n    \"2055-04-17 00:00:00\",\n    \"2055-04-18 00:00:00\",\n    \"2055-04-24 00:00:00\",\n    \"2055-04-25 00:00:00\",\n    \"2055-05-01 00:00:00\",\n    \"2055-05-02 00:00:00\",\n    \"2055-05-03 00:00:00\",\n    \"2055-05-04 00:00:00\",\n    \"2055-05-05 00:00:00\",\n    \"2055-05-08 00:00:00\",\n    \"2055-05-15 00:00:00\",\n    \"2055-05-16 00:00:00\",\n    \"2055-05-22 00:00:00\",\n    \"2055-05-23 00:00:00\",\n    \"2055-05-29 00:00:00\",\n    \"2055-05-30 00:00:00\",\n    \"2055-05-31 00:00:00\",\n    \"2055-06-05 00:00:00\",\n    \"2055-06-06 00:00:00\",\n    \"2055-06-12 00:00:00\",\n    \"2055-06-13 00:00:00\",\n    \"2055-06-19 00:00:00\",\n    \"2055-06-20 00:00:00\",\n    \"2055-06-26 00:00:00\",\n    \"2055-06-27 00:00:00\",\n    \"2055-07-03 00:00:00\",\n    \"2055-07-04 00:00:00\",\n    \"2055-07-10 00:00:00\",\n    \"2055-07-11 00:00:00\",\n    \"2055-07-17 00:00:00\",\n    \"2055-07-18 00:00:00\",\n    \"2055-07-24 00:00:00\",\n    \"2055-07-25 00:00:00\",\n    \"2055-07-31 00:00:00\",\n    \"2055-08-01 00:00:00\",\n    \"2055-08-07 00:00:00\",\n    \"2055-08-08 00:00:00\",\n    \"2055-08-14 00:00:00\",\n    \"2055-08-15 00:00:00\",\n    \"2055-08-21 00:00:00\",\n    \"2055-08-22 00:00:00\",\n    \"2055-08-28 00:00:00\",\n    \"2055-08-29 00:00:00\",\n    \"2055-09-04 00:00:00\",\n    \"2055-09-05 00:00:00\",\n    \"2055-09-11 00:00:00\",\n    \"2055-09-12 00:00:00\",\n    \"2055-09-18 00:00:00\",\n    \"2055-09-19 00:00:00\",\n    \"2055-09-25 00:00:00\",\n    \"2055-10-01 00:00:00\",\n    \"2055-10-02 00:00:00\",\n    \"2055-10-03 00:00:00\",\n    \"2055-10-04 00:00:00\",\n    \"2055-10-05 00:00:00\",\n    \"2055-10-06 00:00:00\",\n    \"2055-10-07 00:00:00\",\n    \"2055-10-08 00:00:00\",\n    \"2055-10-10 00:00:00\",\n    \"2055-10-16 00:00:00\",\n    \"2055-10-17 00:00:00\",\n    \"2055-10-23 00:00:00\",\n    \"2055-10-24 00:00:00\",\n    \"2055-10-30 00:00:00\",\n    \"2055-10-31 00:00:00\",\n    \"2055-11-06 00:00:00\",\n    \"2055-11-07 00:00:00\",\n    \"2055-11-13 00:00:00\",\n    \"2055-11-14 00:00:00\",\n    \"2055-11-20 00:00:00\",\n    \"2055-11-21 00:00:00\",\n    \"2055-11-27 00:00:00\",\n    \"2055-11-28 00:00:00\",\n    \"2055-12-04 00:00:00\",\n    \"2055-12-05 00:00:00\",\n    \"2055-12-11 00:00:00\",\n    \"2055-12-12 00:00:00\",\n    \"2055-12-18 00:00:00\",\n    \"2055-12-19 00:00:00\",\n    \"2055-12-25 00:00:00\",\n    \"2055-12-26 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-01-02 00:00:00\",\n    \"2056-01-03 00:00:00\",\n    \"2056-01-08 00:00:00\",\n    \"2056-01-09 00:00:00\",\n    \"2056-01-15 00:00:00\",\n    \"2056-01-16 00:00:00\",\n    \"2056-01-22 00:00:00\",\n    \"2056-01-23 00:00:00\",\n    \"2056-01-29 00:00:00\",\n    \"2056-01-30 00:00:00\",\n    \"2056-02-05 00:00:00\",\n    \"2056-02-06 00:00:00\",\n    \"2056-02-13 00:00:00\",\n    \"2056-02-14 00:00:00\",\n    \"2056-02-15 00:00:00\",\n    \"2056-02-16 00:00:00\",\n    \"2056-02-17 00:00:00\",\n    \"2056-02-18 00:00:00\",\n    \"2056-02-19 00:00:00\",\n    \"2056-02-20 00:00:00\",\n    \"2056-02-21 00:00:00\",\n    \"2056-02-27 00:00:00\",\n    \"2056-03-04 00:00:00\",\n    \"2056-03-05 00:00:00\",\n    \"2056-03-11 00:00:00\",\n    \"2056-03-12 00:00:00\",\n    \"2056-03-18 00:00:00\",\n    \"2056-03-19 00:00:00\",\n    \"2056-03-25 00:00:00\",\n    \"2056-03-26 00:00:00\",\n    \"2056-04-01 00:00:00\",\n    \"2056-04-02 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-04-08 00:00:00\",\n    \"2056-04-09 00:00:00\",\n    \"2056-04-15 00:00:00\",\n    \"2056-04-16 00:00:00\",\n    \"2056-04-22 00:00:00\",\n    \"2056-04-23 00:00:00\",\n    \"2056-04-29 00:00:00\",\n    \"2056-04-30 00:00:00\",\n    \"2056-05-01 00:00:00\",\n    \"2056-05-02 00:00:00\",\n    \"2056-05-03 00:00:00\",\n    \"2056-05-06 00:00:00\",\n    \"2056-05-13 00:00:00\",\n    \"2056-05-14 00:00:00\",\n    \"2056-05-20 00:00:00\",\n    \"2056-05-21 00:00:00\",\n    \"2056-05-27 00:00:00\",\n    \"2056-05-28 00:00:00\",\n    \"2056-06-03 00:00:00\",\n    \"2056-06-04 00:00:00\",\n    \"2056-06-10 00:00:00\",\n    \"2056-06-11 00:00:00\",\n    \"2056-06-17 00:00:00\",\n    \"2056-06-18 00:00:00\",\n    \"2056-06-19 00:00:00\",\n    \"2056-06-24 00:00:00\",\n    \"2056-06-25 00:00:00\",\n    \"2056-07-01 00:00:00\",\n    \"2056-07-02 00:00:00\",\n    \"2056-07-08 00:00:00\",\n    \"2056-07-09 00:00:00\",\n    \"2056-07-15 00:00:00\",\n    \"2056-07-16 00:00:00\",\n    \"2056-07-22 00:00:00\",\n    \"2056-07-23 00:00:00\",\n    \"2056-07-29 00:00:00\",\n    \"2056-07-30 00:00:00\",\n    \"2056-08-05 00:00:00\",\n    \"2056-08-06 00:00:00\",\n    \"2056-08-12 00:00:00\",\n    \"2056-08-13 00:00:00\",\n    \"2056-08-19 00:00:00\",\n    \"2056-08-20 00:00:00\",\n    \"2056-08-26 00:00:00\",\n    \"2056-08-27 00:00:00\",\n    \"2056-09-02 00:00:00\",\n    \"2056-09-03 00:00:00\",\n    \"2056-09-09 00:00:00\",\n    \"2056-09-10 00:00:00\",\n    \"2056-09-16 00:00:00\",\n    \"2056-09-23 00:00:00\",\n    \"2056-09-24 00:00:00\",\n    \"2056-09-25 00:00:00\",\n    \"2056-09-30 00:00:00\",\n    \"2056-10-01 00:00:00\",\n    \"2056-10-02 00:00:00\",\n    \"2056-10-03 00:00:00\",\n    \"2056-10-04 00:00:00\",\n    \"2056-10-05 00:00:00\",\n    \"2056-10-06 00:00:00\",\n    \"2056-10-07 00:00:00\",\n    \"2056-10-14 00:00:00\",\n    \"2056-10-15 00:00:00\",\n    \"2056-10-21 00:00:00\",\n    \"2056-10-22 00:00:00\",\n    \"2056-10-28 00:00:00\",\n    \"2056-10-29 00:00:00\",\n    \"2056-11-04 00:00:00\",\n    \"2056-11-05 00:00:00\",\n    \"2056-11-11 00:00:00\",\n    \"2056-11-12 00:00:00\",\n    \"2056-11-18 00:00:00\",\n    \"2056-11-19 00:00:00\",\n    \"2056-11-25 00:00:00\",\n    \"2056-11-26 00:00:00\",\n    \"2056-12-02 00:00:00\",\n    \"2056-12-03 00:00:00\",\n    \"2056-12-09 00:00:00\",\n    \"2056-12-10 00:00:00\",\n    \"2056-12-16 00:00:00\",\n    \"2056-12-17 00:00:00\",\n    \"2056-12-23 00:00:00\",\n    \"2056-12-24 00:00:00\",\n    \"2056-12-30 00:00:00\",\n    \"2056-12-31 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-06 00:00:00\",\n    \"2057-01-07 00:00:00\",\n    \"2057-01-13 00:00:00\",\n    \"2057-01-14 00:00:00\",\n    \"2057-01-20 00:00:00\",\n    \"2057-01-21 00:00:00\",\n    \"2057-01-27 00:00:00\",\n    \"2057-02-03 00:00:00\",\n    \"2057-02-04 00:00:00\",\n    \"2057-02-05 00:00:00\",\n    \"2057-02-06 00:00:00\",\n    \"2057-02-07 00:00:00\",\n    \"2057-02-08 00:00:00\",\n    \"2057-02-09 00:00:00\",\n    \"2057-02-10 00:00:00\",\n    \"2057-02-17 00:00:00\",\n    \"2057-02-18 00:00:00\",\n    \"2057-02-24 00:00:00\",\n    \"2057-02-25 00:00:00\",\n    \"2057-03-03 00:00:00\",\n    \"2057-03-04 00:00:00\",\n    \"2057-03-10 00:00:00\",\n    \"2057-03-11 00:00:00\",\n    \"2057-03-17 00:00:00\",\n    \"2057-03-18 00:00:00\",\n    \"2057-03-24 00:00:00\",\n    \"2057-03-25 00:00:00\",\n    \"2057-03-31 00:00:00\",\n    \"2057-04-01 00:00:00\",\n    \"2057-04-04 00:00:00\",\n    \"2057-04-07 00:00:00\",\n    \"2057-04-08 00:00:00\",\n    \"2057-04-14 00:00:00\",\n    \"2057-04-15 00:00:00\",\n    \"2057-04-21 00:00:00\",\n    \"2057-04-22 00:00:00\",\n    \"2057-04-28 00:00:00\",\n    \"2057-04-29 00:00:00\",\n    \"2057-04-30 00:00:00\",\n    \"2057-05-01 00:00:00\",\n    \"2057-05-02 00:00:00\",\n    \"2057-05-05 00:00:00\",\n    \"2057-05-06 00:00:00\",\n    \"2057-05-12 00:00:00\",\n    \"2057-05-13 00:00:00\",\n    \"2057-05-19 00:00:00\",\n    \"2057-05-20 00:00:00\",\n    \"2057-05-26 00:00:00\",\n    \"2057-05-27 00:00:00\",\n    \"2057-06-02 00:00:00\",\n    \"2057-06-03 00:00:00\",\n    \"2057-06-06 00:00:00\",\n    \"2057-06-09 00:00:00\",\n    \"2057-06-10 00:00:00\",\n    \"2057-06-16 00:00:00\",\n    \"2057-06-17 00:00:00\",\n    \"2057-06-23 00:00:00\",\n    \"2057-06-24 00:00:00\",\n    \"2057-06-30 00:00:00\",\n    \"2057-07-01 00:00:00\",\n    \"2057-07-07 00:00:00\",\n    \"2057-07-08 00:00:00\",\n    \"2057-07-14 00:00:00\",\n    \"2057-07-15 00:00:00\",\n    \"2057-07-21 00:00:00\",\n    \"2057-07-22 00:00:00\",\n    \"2057-07-28 00:00:00\",\n    \"2057-07-29 00:00:00\",\n    \"2057-08-04 00:00:00\",\n    \"2057-08-05 00:00:00\",\n    \"2057-08-11 00:00:00\",\n    \"2057-08-12 00:00:00\",\n    \"2057-08-18 00:00:00\",\n    \"2057-08-19 00:00:00\",\n    \"2057-08-25 00:00:00\",\n    \"2057-08-26 00:00:00\",\n    \"2057-09-01 00:00:00\",\n    \"2057-09-02 00:00:00\",\n    \"2057-09-08 00:00:00\",\n    \"2057-09-09 00:00:00\",\n    \"2057-09-13 00:00:00\",\n    \"2057-09-14 00:00:00\",\n    \"2057-09-15 00:00:00\",\n    \"2057-09-16 00:00:00\",\n    \"2057-09-22 00:00:00\",\n    \"2057-09-30 00:00:00\",\n    \"2057-10-01 00:00:00\",\n    \"2057-10-02 00:00:00\",\n    \"2057-10-03 00:00:00\",\n    \"2057-10-04 00:00:00\",\n    \"2057-10-05 00:00:00\",\n    \"2057-10-06 00:00:00\",\n    \"2057-10-07 00:00:00\",\n    \"2057-10-14 00:00:00\",\n    \"2057-10-20 00:00:00\",\n    \"2057-10-21 00:00:00\",\n    \"2057-10-27 00:00:00\",\n    \"2057-10-28 00:00:00\",\n    \"2057-11-03 00:00:00\",\n    \"2057-11-04 00:00:00\",\n    \"2057-11-10 00:00:00\",\n    \"2057-11-11 00:00:00\",\n    \"2057-11-17 00:00:00\",\n    \"2057-11-18 00:00:00\",\n    \"2057-11-24 00:00:00\",\n    \"2057-11-25 00:00:00\",\n    \"2057-12-01 00:00:00\",\n    \"2057-12-02 00:00:00\",\n    \"2057-12-08 00:00:00\",\n    \"2057-12-09 00:00:00\",\n    \"2057-12-15 00:00:00\",\n    \"2057-12-16 00:00:00\",\n    \"2057-12-22 00:00:00\",\n    \"2057-12-23 00:00:00\",\n    \"2057-12-30 00:00:00\",\n    \"2057-12-31 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-05 00:00:00\",\n    \"2058-01-06 00:00:00\",\n    \"2058-01-12 00:00:00\",\n    \"2058-01-13 00:00:00\",\n    \"2058-01-19 00:00:00\",\n    \"2058-01-23 00:00:00\",\n    \"2058-01-24 00:00:00\",\n    \"2058-01-25 00:00:00\",\n    \"2058-01-26 00:00:00\",\n    \"2058-01-27 00:00:00\",\n    \"2058-01-28 00:00:00\",\n    \"2058-01-29 00:00:00\",\n    \"2058-01-30 00:00:00\",\n    \"2058-02-03 00:00:00\",\n    \"2058-02-09 00:00:00\",\n    \"2058-02-10 00:00:00\",\n    \"2058-02-16 00:00:00\",\n    \"2058-02-17 00:00:00\",\n    \"2058-02-23 00:00:00\",\n    \"2058-02-24 00:00:00\",\n    \"2058-03-02 00:00:00\",\n    \"2058-03-03 00:00:00\",\n    \"2058-03-09 00:00:00\",\n    \"2058-03-10 00:00:00\",\n    \"2058-03-16 00:00:00\",\n    \"2058-03-17 00:00:00\",\n    \"2058-03-23 00:00:00\",\n    \"2058-03-24 00:00:00\",\n    \"2058-03-30 00:00:00\",\n    \"2058-03-31 00:00:00\",\n    \"2058-04-04 00:00:00\",\n    \"2058-04-05 00:00:00\",\n    \"2058-04-06 00:00:00\",\n    \"2058-04-07 00:00:00\",\n    \"2058-04-13 00:00:00\",\n    \"2058-04-20 00:00:00\",\n    \"2058-04-21 00:00:00\",\n    \"2058-04-27 00:00:00\",\n    \"2058-04-28 00:00:00\",\n    \"2058-05-01 00:00:00\",\n    \"2058-05-02 00:00:00\",\n    \"2058-05-04 00:00:00\",\n    \"2058-05-05 00:00:00\",\n    \"2058-05-11 00:00:00\",\n    \"2058-05-12 00:00:00\",\n    \"2058-05-18 00:00:00\",\n    \"2058-05-19 00:00:00\",\n    \"2058-05-25 00:00:00\",\n    \"2058-05-26 00:00:00\",\n    \"2058-06-01 00:00:00\",\n    \"2058-06-02 00:00:00\",\n    \"2058-06-08 00:00:00\",\n    \"2058-06-09 00:00:00\",\n    \"2058-06-15 00:00:00\",\n    \"2058-06-22 00:00:00\",\n    \"2058-06-23 00:00:00\",\n    \"2058-06-24 00:00:00\",\n    \"2058-06-25 00:00:00\",\n    \"2058-06-29 00:00:00\",\n    \"2058-06-30 00:00:00\",\n    \"2058-07-06 00:00:00\",\n    \"2058-07-07 00:00:00\",\n    \"2058-07-13 00:00:00\",\n    \"2058-07-14 00:00:00\",\n    \"2058-07-20 00:00:00\",\n    \"2058-07-21 00:00:00\",\n    \"2058-07-27 00:00:00\",\n    \"2058-07-28 00:00:00\",\n    \"2058-08-03 00:00:00\",\n    \"2058-08-04 00:00:00\",\n    \"2058-08-10 00:00:00\",\n    \"2058-08-11 00:00:00\",\n    \"2058-08-17 00:00:00\",\n    \"2058-08-18 00:00:00\",\n    \"2058-08-24 00:00:00\",\n    \"2058-08-25 00:00:00\",\n    \"2058-08-31 00:00:00\",\n    \"2058-09-01 00:00:00\",\n    \"2058-09-07 00:00:00\",\n    \"2058-09-08 00:00:00\",\n    \"2058-09-14 00:00:00\",\n    \"2058-09-15 00:00:00\",\n    \"2058-09-21 00:00:00\",\n    \"2058-09-22 00:00:00\",\n    \"2058-09-28 00:00:00\",\n    \"2058-10-01 00:00:00\",\n    \"2058-10-02 00:00:00\",\n    \"2058-10-03 00:00:00\",\n    \"2058-10-04 00:00:00\",\n    \"2058-10-05 00:00:00\",\n    \"2058-10-06 00:00:00\",\n    \"2058-10-07 00:00:00\",\n    \"2058-10-08 00:00:00\",\n    \"2058-10-13 00:00:00\",\n    \"2058-10-19 00:00:00\",\n    \"2058-10-20 00:00:00\",\n    \"2058-10-26 00:00:00\",\n    \"2058-10-27 00:00:00\",\n    \"2058-11-02 00:00:00\",\n    \"2058-11-03 00:00:00\",\n    \"2058-11-09 00:00:00\",\n    \"2058-11-10 00:00:00\",\n    \"2058-11-16 00:00:00\",\n    \"2058-11-17 00:00:00\",\n    \"2058-11-23 00:00:00\",\n    \"2058-11-24 00:00:00\",\n    \"2058-11-30 00:00:00\",\n    \"2058-12-01 00:00:00\",\n    \"2058-12-07 00:00:00\",\n    \"2058-12-08 00:00:00\",\n    \"2058-12-14 00:00:00\",\n    \"2058-12-15 00:00:00\",\n    \"2058-12-21 00:00:00\",\n    \"2058-12-22 00:00:00\",\n    \"2058-12-28 00:00:00\",\n    \"2058-12-29 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-04 00:00:00\",\n    \"2059-01-05 00:00:00\",\n    \"2059-01-11 00:00:00\",\n    \"2059-01-12 00:00:00\",\n    \"2059-01-18 00:00:00\",\n    \"2059-01-19 00:00:00\",\n    \"2059-01-25 00:00:00\",\n    \"2059-01-26 00:00:00\",\n    \"2059-02-01 00:00:00\",\n    \"2059-02-02 00:00:00\",\n    \"2059-02-08 00:00:00\",\n    \"2059-02-11 00:00:00\",\n    \"2059-02-12 00:00:00\",\n    \"2059-02-13 00:00:00\",\n    \"2059-02-14 00:00:00\",\n    \"2059-02-15 00:00:00\",\n    \"2059-02-16 00:00:00\",\n    \"2059-02-17 00:00:00\",\n    \"2059-02-18 00:00:00\",\n    \"2059-02-23 00:00:00\",\n    \"2059-03-01 00:00:00\",\n    \"2059-03-02 00:00:00\",\n    \"2059-03-08 00:00:00\",\n    \"2059-03-09 00:00:00\",\n    \"2059-03-15 00:00:00\",\n    \"2059-03-16 00:00:00\",\n    \"2059-03-22 00:00:00\",\n    \"2059-03-23 00:00:00\",\n    \"2059-03-29 00:00:00\",\n    \"2059-03-30 00:00:00\",\n    \"2059-04-04 00:00:00\",\n    \"2059-04-05 00:00:00\",\n    \"2059-04-06 00:00:00\",\n    \"2059-04-12 00:00:00\",\n    \"2059-04-13 00:00:00\",\n    \"2059-04-19 00:00:00\",\n    \"2059-04-20 00:00:00\",\n    \"2059-04-26 00:00:00\",\n    \"2059-05-01 00:00:00\",\n    \"2059-05-02 00:00:00\",\n    \"2059-05-03 00:00:00\",\n    \"2059-05-04 00:00:00\",\n    \"2059-05-05 00:00:00\",\n    \"2059-05-10 00:00:00\",\n    \"2059-05-11 00:00:00\",\n    \"2059-05-17 00:00:00\",\n    \"2059-05-18 00:00:00\",\n    \"2059-05-24 00:00:00\",\n    \"2059-05-25 00:00:00\",\n    \"2059-05-31 00:00:00\",\n    \"2059-06-01 00:00:00\",\n    \"2059-06-07 00:00:00\",\n    \"2059-06-08 00:00:00\",\n    \"2059-06-14 00:00:00\",\n    \"2059-06-15 00:00:00\",\n    \"2059-06-16 00:00:00\",\n    \"2059-06-21 00:00:00\",\n    \"2059-06-22 00:00:00\",\n    \"2059-06-28 00:00:00\",\n    \"2059-06-29 00:00:00\",\n    \"2059-07-05 00:00:00\",\n    \"2059-07-06 00:00:00\",\n    \"2059-07-12 00:00:00\",\n    \"2059-07-13 00:00:00\",\n    \"2059-07-19 00:00:00\",\n    \"2059-07-20 00:00:00\",\n    \"2059-07-26 00:00:00\",\n    \"2059-07-27 00:00:00\",\n    \"2059-08-02 00:00:00\",\n    \"2059-08-03 00:00:00\",\n    \"2059-08-09 00:00:00\",\n    \"2059-08-10 00:00:00\",\n    \"2059-08-16 00:00:00\",\n    \"2059-08-17 00:00:00\",\n    \"2059-08-23 00:00:00\",\n    \"2059-08-24 00:00:00\",\n    \"2059-08-30 00:00:00\",\n    \"2059-08-31 00:00:00\",\n    \"2059-09-06 00:00:00\",\n    \"2059-09-07 00:00:00\",\n    \"2059-09-13 00:00:00\",\n    \"2059-09-14 00:00:00\",\n    \"2059-09-20 00:00:00\",\n    \"2059-09-21 00:00:00\",\n    \"2059-09-22 00:00:00\",\n    \"2059-09-27 00:00:00\",\n    \"2059-10-01 00:00:00\",\n    \"2059-10-02 00:00:00\",\n    \"2059-10-03 00:00:00\",\n    \"2059-10-04 00:00:00\",\n    \"2059-10-05 00:00:00\",\n    \"2059-10-06 00:00:00\",\n    \"2059-10-07 00:00:00\",\n    \"2059-10-12 00:00:00\",\n    \"2059-10-18 00:00:00\",\n    \"2059-10-19 00:00:00\",\n    \"2059-10-25 00:00:00\",\n    \"2059-10-26 00:00:00\",\n    \"2059-11-01 00:00:00\",\n    \"2059-11-02 00:00:00\",\n    \"2059-11-08 00:00:00\",\n    \"2059-11-09 00:00:00\",\n    \"2059-11-15 00:00:00\",\n    \"2059-11-16 00:00:00\",\n    \"2059-11-22 00:00:00\",\n    \"2059-11-23 00:00:00\",\n    \"2059-11-29 00:00:00\",\n    \"2059-11-30 00:00:00\",\n    \"2059-12-06 00:00:00\",\n    \"2059-12-07 00:00:00\",\n    \"2059-12-13 00:00:00\",\n    \"2059-12-14 00:00:00\",\n    \"2059-12-20 00:00:00\",\n    \"2059-12-21 00:00:00\",\n    \"2059-12-27 00:00:00\",\n    \"2059-12-28 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-02 00:00:00\",\n    \"2060-01-03 00:00:00\",\n    \"2060-01-10 00:00:00\",\n    \"2060-01-11 00:00:00\",\n    \"2060-01-17 00:00:00\",\n    \"2060-01-18 00:00:00\",\n    \"2060-01-24 00:00:00\",\n    \"2060-01-25 00:00:00\",\n    \"2060-02-01 00:00:00\",\n    \"2060-02-02 00:00:00\",\n    \"2060-02-03 00:00:00\",\n    \"2060-02-04 00:00:00\",\n    \"2060-02-05 00:00:00\",\n    \"2060-02-06 00:00:00\",\n    \"2060-02-07 00:00:00\",\n    \"2060-02-08 00:00:00\",\n    \"2060-02-15 00:00:00\",\n    \"2060-02-21 00:00:00\",\n    \"2060-02-22 00:00:00\",\n    \"2060-02-28 00:00:00\",\n    \"2060-02-29 00:00:00\",\n    \"2060-03-06 00:00:00\",\n    \"2060-03-07 00:00:00\",\n    \"2060-03-13 00:00:00\",\n    \"2060-03-14 00:00:00\",\n    \"2060-03-20 00:00:00\",\n    \"2060-03-21 00:00:00\",\n    \"2060-03-27 00:00:00\",\n    \"2060-03-28 00:00:00\",\n    \"2060-04-03 00:00:00\",\n    \"2060-04-04 00:00:00\",\n    \"2060-04-05 00:00:00\",\n    \"2060-04-10 00:00:00\",\n    \"2060-04-11 00:00:00\",\n    \"2060-04-17 00:00:00\",\n    \"2060-04-18 00:00:00\",\n    \"2060-04-24 00:00:00\",\n    \"2060-04-25 00:00:00\",\n    \"2060-05-01 00:00:00\",\n    \"2060-05-02 00:00:00\",\n    \"2060-05-03 00:00:00\",\n    \"2060-05-04 00:00:00\",\n    \"2060-05-05 00:00:00\",\n    \"2060-05-08 00:00:00\",\n    \"2060-05-15 00:00:00\",\n    \"2060-05-16 00:00:00\",\n    \"2060-05-22 00:00:00\",\n    \"2060-05-23 00:00:00\",\n    \"2060-05-29 00:00:00\",\n    \"2060-05-30 00:00:00\",\n    \"2060-06-03 00:00:00\",\n    \"2060-06-04 00:00:00\",\n    \"2060-06-05 00:00:00\",\n    \"2060-06-06 00:00:00\",\n    \"2060-06-12 00:00:00\",\n    \"2060-06-19 00:00:00\",\n    \"2060-06-20 00:00:00\",\n    \"2060-06-26 00:00:00\",\n    \"2060-06-27 00:00:00\",\n    \"2060-07-03 00:00:00\",\n    \"2060-07-04 00:00:00\",\n    \"2060-07-10 00:00:00\",\n    \"2060-07-11 00:00:00\",\n    \"2060-07-17 00:00:00\",\n    \"2060-07-18 00:00:00\",\n    \"2060-07-24 00:00:00\",\n    \"2060-07-25 00:00:00\",\n    \"2060-07-31 00:00:00\",\n    \"2060-08-01 00:00:00\",\n    \"2060-08-07 00:00:00\",\n    \"2060-08-08 00:00:00\",\n    \"2060-08-14 00:00:00\",\n    \"2060-08-15 00:00:00\",\n    \"2060-08-21 00:00:00\",\n    \"2060-08-22 00:00:00\",\n    \"2060-08-28 00:00:00\",\n    \"2060-08-29 00:00:00\",\n    \"2060-09-04 00:00:00\",\n    \"2060-09-05 00:00:00\",\n    \"2060-09-09 00:00:00\",\n    \"2060-09-10 00:00:00\",\n    \"2060-09-11 00:00:00\",\n    \"2060-09-12 00:00:00\",\n    \"2060-09-18 00:00:00\",\n    \"2060-09-25 00:00:00\",\n    \"2060-10-01 00:00:00\",\n    \"2060-10-02 00:00:00\",\n    \"2060-10-03 00:00:00\",\n    \"2060-10-04 00:00:00\",\n    \"2060-10-05 00:00:00\",\n    \"2060-10-06 00:00:00\",\n    \"2060-10-07 00:00:00\",\n    \"2060-10-10 00:00:00\",\n    \"2060-10-16 00:00:00\",\n    \"2060-10-17 00:00:00\",\n    \"2060-10-23 00:00:00\",\n    \"2060-10-24 00:00:00\",\n    \"2060-10-30 00:00:00\",\n    \"2060-10-31 00:00:00\",\n    \"2060-11-06 00:00:00\",\n    \"2060-11-07 00:00:00\",\n    \"2060-11-13 00:00:00\",\n    \"2060-11-14 00:00:00\",\n    \"2060-11-20 00:00:00\",\n    \"2060-11-21 00:00:00\",\n    \"2060-11-27 00:00:00\",\n    \"2060-11-28 00:00:00\",\n    \"2060-12-04 00:00:00\",\n    \"2060-12-05 00:00:00\",\n    \"2060-12-11 00:00:00\",\n    \"2060-12-12 00:00:00\",\n    \"2060-12-18 00:00:00\",\n    \"2060-12-19 00:00:00\",\n    \"2060-12-25 00:00:00\",\n    \"2060-12-26 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-01-02 00:00:00\",\n    \"2061-01-03 00:00:00\",\n    \"2061-01-08 00:00:00\",\n    \"2061-01-09 00:00:00\",\n    \"2061-01-15 00:00:00\",\n    \"2061-01-20 00:00:00\",\n    \"2061-01-21 00:00:00\",\n    \"2061-01-22 00:00:00\",\n    \"2061-01-23 00:00:00\",\n    \"2061-01-24 00:00:00\",\n    \"2061-01-25 00:00:00\",\n    \"2061-01-26 00:00:00\",\n    \"2061-01-27 00:00:00\",\n    \"2061-01-30 00:00:00\",\n    \"2061-02-05 00:00:00\",\n    \"2061-02-06 00:00:00\",\n    \"2061-02-12 00:00:00\",\n    \"2061-02-13 00:00:00\",\n    \"2061-02-19 00:00:00\",\n    \"2061-02-20 00:00:00\",\n    \"2061-02-26 00:00:00\",\n    \"2061-02-27 00:00:00\",\n    \"2061-03-05 00:00:00\",\n    \"2061-03-06 00:00:00\",\n    \"2061-03-12 00:00:00\",\n    \"2061-03-13 00:00:00\",\n    \"2061-03-19 00:00:00\",\n    \"2061-03-20 00:00:00\",\n    \"2061-03-26 00:00:00\",\n    \"2061-03-27 00:00:00\",\n    \"2061-04-02 00:00:00\",\n    \"2061-04-03 00:00:00\",\n    \"2061-04-04 00:00:00\",\n    \"2061-04-09 00:00:00\",\n    \"2061-04-10 00:00:00\",\n    \"2061-04-16 00:00:00\",\n    \"2061-04-17 00:00:00\",\n    \"2061-04-23 00:00:00\",\n    \"2061-04-24 00:00:00\",\n    \"2061-04-30 00:00:00\",\n    \"2061-05-01 00:00:00\",\n    \"2061-05-02 00:00:00\",\n    \"2061-05-03 00:00:00\",\n    \"2061-05-04 00:00:00\",\n    \"2061-05-07 00:00:00\",\n    \"2061-05-14 00:00:00\",\n    \"2061-05-15 00:00:00\",\n    \"2061-05-21 00:00:00\",\n    \"2061-05-22 00:00:00\",\n    \"2061-05-28 00:00:00\",\n    \"2061-05-29 00:00:00\",\n    \"2061-06-04 00:00:00\",\n    \"2061-06-05 00:00:00\",\n    \"2061-06-11 00:00:00\",\n    \"2061-06-12 00:00:00\",\n    \"2061-06-18 00:00:00\",\n    \"2061-06-19 00:00:00\",\n    \"2061-06-22 00:00:00\",\n    \"2061-06-25 00:00:00\",\n    \"2061-06-26 00:00:00\",\n    \"2061-07-02 00:00:00\",\n    \"2061-07-03 00:00:00\",\n    \"2061-07-09 00:00:00\",\n    \"2061-07-10 00:00:00\",\n    \"2061-07-16 00:00:00\",\n    \"2061-07-17 00:00:00\",\n    \"2061-07-23 00:00:00\",\n    \"2061-07-24 00:00:00\",\n    \"2061-07-30 00:00:00\",\n    \"2061-07-31 00:00:00\",\n    \"2061-08-06 00:00:00\",\n    \"2061-08-07 00:00:00\",\n    \"2061-08-13 00:00:00\",\n    \"2061-08-14 00:00:00\",\n    \"2061-08-20 00:00:00\",\n    \"2061-08-21 00:00:00\",\n    \"2061-08-27 00:00:00\",\n    \"2061-08-28 00:00:00\",\n    \"2061-09-03 00:00:00\",\n    \"2061-09-04 00:00:00\",\n    \"2061-09-10 00:00:00\",\n    \"2061-09-11 00:00:00\",\n    \"2061-09-17 00:00:00\",\n    \"2061-09-18 00:00:00\",\n    \"2061-09-24 00:00:00\",\n    \"2061-09-28 00:00:00\",\n    \"2061-10-01 00:00:00\",\n    \"2061-10-02 00:00:00\",\n    \"2061-10-03 00:00:00\",\n    \"2061-10-04 00:00:00\",\n    \"2061-10-05 00:00:00\",\n    \"2061-10-06 00:00:00\",\n    \"2061-10-07 00:00:00\",\n    \"2061-10-08 00:00:00\",\n    \"2061-10-15 00:00:00\",\n    \"2061-10-16 00:00:00\",\n    \"2061-10-22 00:00:00\",\n    \"2061-10-23 00:00:00\",\n    \"2061-10-29 00:00:00\",\n    \"2061-10-30 00:00:00\",\n    \"2061-11-05 00:00:00\",\n    \"2061-11-06 00:00:00\",\n    \"2061-11-12 00:00:00\",\n    \"2061-11-13 00:00:00\",\n    \"2061-11-19 00:00:00\",\n    \"2061-11-20 00:00:00\",\n    \"2061-11-26 00:00:00\",\n    \"2061-11-27 00:00:00\",\n    \"2061-12-03 00:00:00\",\n    \"2061-12-04 00:00:00\",\n    \"2061-12-10 00:00:00\",\n    \"2061-12-11 00:00:00\",\n    \"2061-12-17 00:00:00\",\n    \"2061-12-18 00:00:00\",\n    \"2061-12-24 00:00:00\",\n    \"2061-12-25 00:00:00\",\n    \"2061-12-31 00:00:00\",\n    \"2062-01-01 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-01-07 00:00:00\",\n    \"2062-01-08 00:00:00\",\n    \"2062-01-14 00:00:00\",\n    \"2062-01-15 00:00:00\",\n    \"2062-01-21 00:00:00\",\n    \"2062-01-22 00:00:00\",\n    \"2062-01-28 00:00:00\",\n    \"2062-01-29 00:00:00\",\n    \"2062-02-04 00:00:00\",\n    \"2062-02-08 00:00:00\",\n    \"2062-02-09 00:00:00\",\n    \"2062-02-10 00:00:00\",\n    \"2062-02-11 00:00:00\",\n    \"2062-02-12 00:00:00\",\n    \"2062-02-13 00:00:00\",\n    \"2062-02-14 00:00:00\",\n    \"2062-02-15 00:00:00\",\n    \"2062-02-19 00:00:00\",\n    \"2062-02-25 00:00:00\",\n    \"2062-02-26 00:00:00\",\n    \"2062-03-04 00:00:00\",\n    \"2062-03-05 00:00:00\",\n    \"2062-03-11 00:00:00\",\n    \"2062-03-12 00:00:00\",\n    \"2062-03-18 00:00:00\",\n    \"2062-03-19 00:00:00\",\n    \"2062-03-25 00:00:00\",\n    \"2062-04-01 00:00:00\",\n    \"2062-04-02 00:00:00\",\n    \"2062-04-03 00:00:00\",\n    \"2062-04-04 00:00:00\",\n    \"2062-04-08 00:00:00\",\n    \"2062-04-09 00:00:00\",\n    \"2062-04-15 00:00:00\",\n    \"2062-04-16 00:00:00\",\n    \"2062-04-22 00:00:00\",\n    \"2062-04-23 00:00:00\",\n    \"2062-04-29 00:00:00\",\n    \"2062-04-30 00:00:00\",\n    \"2062-05-01 00:00:00\",\n    \"2062-05-02 00:00:00\",\n    \"2062-05-03 00:00:00\",\n    \"2062-05-06 00:00:00\",\n    \"2062-05-13 00:00:00\",\n    \"2062-05-14 00:00:00\",\n    \"2062-05-20 00:00:00\",\n    \"2062-05-21 00:00:00\",\n    \"2062-05-27 00:00:00\",\n    \"2062-05-28 00:00:00\",\n    \"2062-06-03 00:00:00\",\n    \"2062-06-04 00:00:00\",\n    \"2062-06-10 00:00:00\",\n    \"2062-06-11 00:00:00\",\n    \"2062-06-12 00:00:00\",\n    \"2062-06-17 00:00:00\",\n    \"2062-06-18 00:00:00\",\n    \"2062-06-24 00:00:00\",\n    \"2062-06-25 00:00:00\",\n    \"2062-07-01 00:00:00\",\n    \"2062-07-02 00:00:00\",\n    \"2062-07-08 00:00:00\",\n    \"2062-07-09 00:00:00\",\n    \"2062-07-15 00:00:00\",\n    \"2062-07-16 00:00:00\",\n    \"2062-07-22 00:00:00\",\n    \"2062-07-23 00:00:00\",\n    \"2062-07-29 00:00:00\",\n    \"2062-07-30 00:00:00\",\n    \"2062-08-05 00:00:00\",\n    \"2062-08-06 00:00:00\",\n    \"2062-08-12 00:00:00\",\n    \"2062-08-13 00:00:00\",\n    \"2062-08-19 00:00:00\",\n    \"2062-08-20 00:00:00\",\n    \"2062-08-26 00:00:00\",\n    \"2062-08-27 00:00:00\",\n    \"2062-09-02 00:00:00\",\n    \"2062-09-03 00:00:00\",\n    \"2062-09-09 00:00:00\",\n    \"2062-09-10 00:00:00\",\n    \"2062-09-16 00:00:00\",\n    \"2062-09-17 00:00:00\",\n    \"2062-09-18 00:00:00\",\n    \"2062-09-23 00:00:00\",\n    \"2062-09-30 00:00:00\",\n    \"2062-10-01 00:00:00\",\n    \"2062-10-02 00:00:00\",\n    \"2062-10-03 00:00:00\",\n    \"2062-10-04 00:00:00\",\n    \"2062-10-05 00:00:00\",\n    \"2062-10-06 00:00:00\",\n    \"2062-10-07 00:00:00\",\n    \"2062-10-14 00:00:00\",\n    \"2062-10-15 00:00:00\",\n    \"2062-10-21 00:00:00\",\n    \"2062-10-22 00:00:00\",\n    \"2062-10-28 00:00:00\",\n    \"2062-10-29 00:00:00\",\n    \"2062-11-04 00:00:00\",\n    \"2062-11-05 00:00:00\",\n    \"2062-11-11 00:00:00\",\n    \"2062-11-12 00:00:00\",\n    \"2062-11-18 00:00:00\",\n    \"2062-11-19 00:00:00\",\n    \"2062-11-25 00:00:00\",\n    \"2062-11-26 00:00:00\",\n    \"2062-12-02 00:00:00\",\n    \"2062-12-03 00:00:00\",\n    \"2062-12-09 00:00:00\",\n    \"2062-12-10 00:00:00\",\n    \"2062-12-16 00:00:00\",\n    \"2062-12-17 00:00:00\",\n    \"2062-12-23 00:00:00\",\n    \"2062-12-24 00:00:00\",\n    \"2062-12-30 00:00:00\",\n    \"2062-12-31 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-06 00:00:00\",\n    \"2063-01-07 00:00:00\",\n    \"2063-01-13 00:00:00\",\n    \"2063-01-14 00:00:00\",\n    \"2063-01-20 00:00:00\",\n    \"2063-01-21 00:00:00\",\n    \"2063-01-28 00:00:00\",\n    \"2063-01-29 00:00:00\",\n    \"2063-01-30 00:00:00\",\n    \"2063-01-31 00:00:00\",\n    \"2063-02-01 00:00:00\",\n    \"2063-02-02 00:00:00\",\n    \"2063-02-03 00:00:00\",\n    \"2063-02-04 00:00:00\",\n    \"2063-02-11 00:00:00\",\n    \"2063-02-17 00:00:00\",\n    \"2063-02-18 00:00:00\",\n    \"2063-02-24 00:00:00\",\n    \"2063-02-25 00:00:00\",\n    \"2063-03-03 00:00:00\",\n    \"2063-03-04 00:00:00\",\n    \"2063-03-10 00:00:00\",\n    \"2063-03-11 00:00:00\",\n    \"2063-03-17 00:00:00\",\n    \"2063-03-18 00:00:00\",\n    \"2063-03-24 00:00:00\",\n    \"2063-03-25 00:00:00\",\n    \"2063-03-31 00:00:00\",\n    \"2063-04-01 00:00:00\",\n    \"2063-04-04 00:00:00\",\n    \"2063-04-07 00:00:00\",\n    \"2063-04-08 00:00:00\",\n    \"2063-04-14 00:00:00\",\n    \"2063-04-15 00:00:00\",\n    \"2063-04-21 00:00:00\",\n    \"2063-04-22 00:00:00\",\n    \"2063-04-28 00:00:00\",\n    \"2063-04-29 00:00:00\",\n    \"2063-04-30 00:00:00\",\n    \"2063-05-01 00:00:00\",\n    \"2063-05-02 00:00:00\",\n    \"2063-05-05 00:00:00\",\n    \"2063-05-06 00:00:00\",\n    \"2063-05-12 00:00:00\",\n    \"2063-05-13 00:00:00\",\n    \"2063-05-19 00:00:00\",\n    \"2063-05-20 00:00:00\",\n    \"2063-05-26 00:00:00\",\n    \"2063-05-27 00:00:00\",\n    \"2063-06-01 00:00:00\",\n    \"2063-06-02 00:00:00\",\n    \"2063-06-03 00:00:00\",\n    \"2063-06-09 00:00:00\",\n    \"2063-06-10 00:00:00\",\n    \"2063-06-16 00:00:00\",\n    \"2063-06-17 00:00:00\",\n    \"2063-06-23 00:00:00\",\n    \"2063-06-24 00:00:00\",\n    \"2063-06-30 00:00:00\",\n    \"2063-07-01 00:00:00\",\n    \"2063-07-07 00:00:00\",\n    \"2063-07-08 00:00:00\",\n    \"2063-07-14 00:00:00\",\n    \"2063-07-15 00:00:00\",\n    \"2063-07-21 00:00:00\",\n    \"2063-07-22 00:00:00\",\n    \"2063-07-28 00:00:00\",\n    \"2063-07-29 00:00:00\",\n    \"2063-08-04 00:00:00\",\n    \"2063-08-05 00:00:00\",\n    \"2063-08-11 00:00:00\",\n    \"2063-08-12 00:00:00\",\n    \"2063-08-18 00:00:00\",\n    \"2063-08-19 00:00:00\",\n    \"2063-08-25 00:00:00\",\n    \"2063-08-26 00:00:00\",\n    \"2063-09-01 00:00:00\",\n    \"2063-09-02 00:00:00\",\n    \"2063-09-08 00:00:00\",\n    \"2063-09-09 00:00:00\",\n    \"2063-09-15 00:00:00\",\n    \"2063-09-16 00:00:00\",\n    \"2063-09-22 00:00:00\",\n    \"2063-09-23 00:00:00\",\n    \"2063-09-30 00:00:00\",\n    \"2063-10-01 00:00:00\",\n    \"2063-10-02 00:00:00\",\n    \"2063-10-03 00:00:00\",\n    \"2063-10-04 00:00:00\",\n    \"2063-10-05 00:00:00\",\n    \"2063-10-06 00:00:00\",\n    \"2063-10-07 00:00:00\",\n    \"2063-10-08 00:00:00\",\n    \"2063-10-14 00:00:00\",\n    \"2063-10-20 00:00:00\",\n    \"2063-10-21 00:00:00\",\n    \"2063-10-27 00:00:00\",\n    \"2063-10-28 00:00:00\",\n    \"2063-11-03 00:00:00\",\n    \"2063-11-04 00:00:00\",\n    \"2063-11-10 00:00:00\",\n    \"2063-11-11 00:00:00\",\n    \"2063-11-17 00:00:00\",\n    \"2063-11-18 00:00:00\",\n    \"2063-11-24 00:00:00\",\n    \"2063-11-25 00:00:00\",\n    \"2063-12-01 00:00:00\",\n    \"2063-12-02 00:00:00\",\n    \"2063-12-08 00:00:00\",\n    \"2063-12-09 00:00:00\",\n    \"2063-12-15 00:00:00\",\n    \"2063-12-16 00:00:00\",\n    \"2063-12-22 00:00:00\",\n    \"2063-12-23 00:00:00\",\n    \"2063-12-30 00:00:00\",\n    \"2063-12-31 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-05 00:00:00\",\n    \"2064-01-06 00:00:00\",\n    \"2064-01-12 00:00:00\",\n    \"2064-01-13 00:00:00\",\n    \"2064-01-19 00:00:00\",\n    \"2064-01-20 00:00:00\",\n    \"2064-01-26 00:00:00\",\n    \"2064-01-27 00:00:00\",\n    \"2064-02-02 00:00:00\",\n    \"2064-02-03 00:00:00\",\n    \"2064-02-09 00:00:00\",\n    \"2064-02-16 00:00:00\",\n    \"2064-02-17 00:00:00\",\n    \"2064-02-18 00:00:00\",\n    \"2064-02-19 00:00:00\",\n    \"2064-02-20 00:00:00\",\n    \"2064-02-21 00:00:00\",\n    \"2064-02-22 00:00:00\",\n    \"2064-02-23 00:00:00\",\n    \"2064-03-01 00:00:00\",\n    \"2064-03-02 00:00:00\",\n    \"2064-03-08 00:00:00\",\n    \"2064-03-09 00:00:00\",\n    \"2064-03-15 00:00:00\",\n    \"2064-03-16 00:00:00\",\n    \"2064-03-22 00:00:00\",\n    \"2064-03-23 00:00:00\",\n    \"2064-03-29 00:00:00\",\n    \"2064-03-30 00:00:00\",\n    \"2064-04-03 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-05 00:00:00\",\n    \"2064-04-06 00:00:00\",\n    \"2064-04-12 00:00:00\",\n    \"2064-04-19 00:00:00\",\n    \"2064-04-20 00:00:00\",\n    \"2064-04-26 00:00:00\",\n    \"2064-05-01 00:00:00\",\n    \"2064-05-02 00:00:00\",\n    \"2064-05-03 00:00:00\",\n    \"2064-05-04 00:00:00\",\n    \"2064-05-05 00:00:00\",\n    \"2064-05-10 00:00:00\",\n    \"2064-05-11 00:00:00\",\n    \"2064-05-17 00:00:00\",\n    \"2064-05-18 00:00:00\",\n    \"2064-05-24 00:00:00\",\n    \"2064-05-25 00:00:00\",\n    \"2064-05-31 00:00:00\",\n    \"2064-06-01 00:00:00\",\n    \"2064-06-07 00:00:00\",\n    \"2064-06-08 00:00:00\",\n    \"2064-06-14 00:00:00\",\n    \"2064-06-15 00:00:00\",\n    \"2064-06-19 00:00:00\",\n    \"2064-06-20 00:00:00\",\n    \"2064-06-21 00:00:00\",\n    \"2064-06-22 00:00:00\",\n    \"2064-06-28 00:00:00\",\n    \"2064-07-05 00:00:00\",\n    \"2064-07-06 00:00:00\",\n    \"2064-07-12 00:00:00\",\n    \"2064-07-13 00:00:00\",\n    \"2064-07-19 00:00:00\",\n    \"2064-07-20 00:00:00\",\n    \"2064-07-26 00:00:00\",\n    \"2064-07-27 00:00:00\",\n    \"2064-08-02 00:00:00\",\n    \"2064-08-03 00:00:00\",\n    \"2064-08-09 00:00:00\",\n    \"2064-08-10 00:00:00\",\n    \"2064-08-16 00:00:00\",\n    \"2064-08-17 00:00:00\",\n    \"2064-08-23 00:00:00\",\n    \"2064-08-24 00:00:00\",\n    \"2064-08-30 00:00:00\",\n    \"2064-08-31 00:00:00\",\n    \"2064-09-06 00:00:00\",\n    \"2064-09-07 00:00:00\",\n    \"2064-09-13 00:00:00\",\n    \"2064-09-14 00:00:00\",\n    \"2064-09-20 00:00:00\",\n    \"2064-09-21 00:00:00\",\n    \"2064-09-25 00:00:00\",\n    \"2064-09-26 00:00:00\",\n    \"2064-09-27 00:00:00\",\n    \"2064-10-01 00:00:00\",\n    \"2064-10-02 00:00:00\",\n    \"2064-10-03 00:00:00\",\n    \"2064-10-04 00:00:00\",\n    \"2064-10-05 00:00:00\",\n    \"2064-10-06 00:00:00\",\n    \"2064-10-07 00:00:00\",\n    \"2064-10-12 00:00:00\",\n    \"2064-10-18 00:00:00\",\n    \"2064-10-19 00:00:00\",\n    \"2064-10-25 00:00:00\",\n    \"2064-10-26 00:00:00\",\n    \"2064-11-01 00:00:00\",\n    \"2064-11-02 00:00:00\",\n    \"2064-11-08 00:00:00\",\n    \"2064-11-09 00:00:00\",\n    \"2064-11-15 00:00:00\",\n    \"2064-11-16 00:00:00\",\n    \"2064-11-22 00:00:00\",\n    \"2064-11-23 00:00:00\",\n    \"2064-11-29 00:00:00\",\n    \"2064-11-30 00:00:00\",\n    \"2064-12-06 00:00:00\",\n    \"2064-12-07 00:00:00\",\n    \"2064-12-13 00:00:00\",\n    \"2064-12-14 00:00:00\",\n    \"2064-12-20 00:00:00\",\n    \"2064-12-21 00:00:00\",\n    \"2064-12-27 00:00:00\",\n    \"2064-12-28 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-02 00:00:00\",\n    \"2065-01-03 00:00:00\",\n    \"2065-01-10 00:00:00\",\n    \"2065-01-11 00:00:00\",\n    \"2065-01-17 00:00:00\",\n    \"2065-01-18 00:00:00\",\n    \"2065-01-24 00:00:00\",\n    \"2065-01-25 00:00:00\",\n    \"2065-01-31 00:00:00\",\n    \"2065-02-04 00:00:00\",\n    \"2065-02-05 00:00:00\",\n    \"2065-02-06 00:00:00\",\n    \"2065-02-07 00:00:00\",\n    \"2065-02-08 00:00:00\",\n    \"2065-02-09 00:00:00\",\n    \"2065-02-10 00:00:00\",\n    \"2065-02-11 00:00:00\",\n    \"2065-02-15 00:00:00\",\n    \"2065-02-21 00:00:00\",\n    \"2065-02-22 00:00:00\",\n    \"2065-02-28 00:00:00\",\n    \"2065-03-01 00:00:00\",\n    \"2065-03-07 00:00:00\",\n    \"2065-03-08 00:00:00\",\n    \"2065-03-14 00:00:00\",\n    \"2065-03-15 00:00:00\",\n    \"2065-03-21 00:00:00\",\n    \"2065-03-22 00:00:00\",\n    \"2065-03-28 00:00:00\",\n    \"2065-03-29 00:00:00\",\n    \"2065-04-04 00:00:00\",\n    \"2065-04-05 00:00:00\",\n    \"2065-04-06 00:00:00\",\n    \"2065-04-11 00:00:00\",\n    \"2065-04-12 00:00:00\",\n    \"2065-04-18 00:00:00\",\n    \"2065-04-19 00:00:00\",\n    \"2065-04-25 00:00:00\",\n    \"2065-04-26 00:00:00\",\n    \"2065-05-01 00:00:00\",\n    \"2065-05-02 00:00:00\",\n    \"2065-05-03 00:00:00\",\n    \"2065-05-04 00:00:00\",\n    \"2065-05-05 00:00:00\",\n    \"2065-05-10 00:00:00\",\n    \"2065-05-16 00:00:00\",\n    \"2065-05-17 00:00:00\",\n    \"2065-05-23 00:00:00\",\n    \"2065-05-24 00:00:00\",\n    \"2065-05-30 00:00:00\",\n    \"2065-05-31 00:00:00\",\n    \"2065-06-06 00:00:00\",\n    \"2065-06-07 00:00:00\",\n    \"2065-06-08 00:00:00\",\n    \"2065-06-13 00:00:00\",\n    \"2065-06-14 00:00:00\",\n    \"2065-06-20 00:00:00\",\n    \"2065-06-21 00:00:00\",\n    \"2065-06-27 00:00:00\",\n    \"2065-06-28 00:00:00\",\n    \"2065-07-04 00:00:00\",\n    \"2065-07-05 00:00:00\",\n    \"2065-07-11 00:00:00\",\n    \"2065-07-12 00:00:00\",\n    \"2065-07-18 00:00:00\",\n    \"2065-07-19 00:00:00\",\n    \"2065-07-25 00:00:00\",\n    \"2065-07-26 00:00:00\",\n    \"2065-08-01 00:00:00\",\n    \"2065-08-02 00:00:00\",\n    \"2065-08-08 00:00:00\",\n    \"2065-08-09 00:00:00\",\n    \"2065-08-15 00:00:00\",\n    \"2065-08-16 00:00:00\",\n    \"2065-08-22 00:00:00\",\n    \"2065-08-23 00:00:00\",\n    \"2065-08-29 00:00:00\",\n    \"2065-08-30 00:00:00\",\n    \"2065-09-05 00:00:00\",\n    \"2065-09-12 00:00:00\",\n    \"2065-09-13 00:00:00\",\n    \"2065-09-14 00:00:00\",\n    \"2065-09-15 00:00:00\",\n    \"2065-09-19 00:00:00\",\n    \"2065-09-20 00:00:00\",\n    \"2065-09-26 00:00:00\",\n    \"2065-10-01 00:00:00\",\n    \"2065-10-02 00:00:00\",\n    \"2065-10-03 00:00:00\",\n    \"2065-10-04 00:00:00\",\n    \"2065-10-05 00:00:00\",\n    \"2065-10-06 00:00:00\",\n    \"2065-10-07 00:00:00\",\n    \"2065-10-11 00:00:00\",\n    \"2065-10-17 00:00:00\",\n    \"2065-10-18 00:00:00\",\n    \"2065-10-24 00:00:00\",\n    \"2065-10-25 00:00:00\",\n    \"2065-10-31 00:00:00\",\n    \"2065-11-01 00:00:00\",\n    \"2065-11-07 00:00:00\",\n    \"2065-11-08 00:00:00\",\n    \"2065-11-14 00:00:00\",\n    \"2065-11-15 00:00:00\",\n    \"2065-11-21 00:00:00\",\n    \"2065-11-22 00:00:00\",\n    \"2065-11-28 00:00:00\",\n    \"2065-11-29 00:00:00\",\n    \"2065-12-05 00:00:00\",\n    \"2065-12-06 00:00:00\",\n    \"2065-12-12 00:00:00\",\n    \"2065-12-13 00:00:00\",\n    \"2065-12-19 00:00:00\",\n    \"2065-12-20 00:00:00\",\n    \"2065-12-26 00:00:00\",\n    \"2065-12-27 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-02 00:00:00\",\n    \"2066-01-03 00:00:00\",\n    \"2066-01-09 00:00:00\",\n    \"2066-01-10 00:00:00\",\n    \"2066-01-16 00:00:00\",\n    \"2066-01-17 00:00:00\",\n    \"2066-01-24 00:00:00\",\n    \"2066-01-25 00:00:00\",\n    \"2066-01-26 00:00:00\",\n    \"2066-01-27 00:00:00\",\n    \"2066-01-28 00:00:00\",\n    \"2066-01-29 00:00:00\",\n    \"2066-01-30 00:00:00\",\n    \"2066-01-31 00:00:00\",\n    \"2066-02-01 00:00:00\",\n    \"2066-02-07 00:00:00\",\n    \"2066-02-13 00:00:00\",\n    \"2066-02-14 00:00:00\",\n    \"2066-02-20 00:00:00\",\n    \"2066-02-21 00:00:00\",\n    \"2066-02-27 00:00:00\",\n    \"2066-02-28 00:00:00\",\n    \"2066-03-06 00:00:00\",\n    \"2066-03-07 00:00:00\",\n    \"2066-03-13 00:00:00\",\n    \"2066-03-14 00:00:00\",\n    \"2066-03-20 00:00:00\",\n    \"2066-03-21 00:00:00\",\n    \"2066-03-27 00:00:00\",\n    \"2066-03-28 00:00:00\",\n    \"2066-04-03 00:00:00\",\n    \"2066-04-04 00:00:00\",\n    \"2066-04-05 00:00:00\",\n    \"2066-04-10 00:00:00\",\n    \"2066-04-11 00:00:00\",\n    \"2066-04-17 00:00:00\",\n    \"2066-04-18 00:00:00\",\n    \"2066-04-24 00:00:00\",\n    \"2066-04-25 00:00:00\",\n    \"2066-05-01 00:00:00\",\n    \"2066-05-02 00:00:00\",\n    \"2066-05-03 00:00:00\",\n    \"2066-05-04 00:00:00\",\n    \"2066-05-05 00:00:00\",\n    \"2066-05-08 00:00:00\",\n    \"2066-05-15 00:00:00\",\n    \"2066-05-16 00:00:00\",\n    \"2066-05-22 00:00:00\",\n    \"2066-05-23 00:00:00\",\n    \"2066-05-28 00:00:00\",\n    \"2066-05-29 00:00:00\",\n    \"2066-05-30 00:00:00\",\n    \"2066-06-05 00:00:00\",\n    \"2066-06-06 00:00:00\",\n    \"2066-06-12 00:00:00\",\n    \"2066-06-13 00:00:00\",\n    \"2066-06-19 00:00:00\",\n    \"2066-06-20 00:00:00\",\n    \"2066-06-26 00:00:00\",\n    \"2066-06-27 00:00:00\",\n    \"2066-07-03 00:00:00\",\n    \"2066-07-04 00:00:00\",\n    \"2066-07-10 00:00:00\",\n    \"2066-07-11 00:00:00\",\n    \"2066-07-17 00:00:00\",\n    \"2066-07-18 00:00:00\",\n    \"2066-07-24 00:00:00\",\n    \"2066-07-25 00:00:00\",\n    \"2066-07-31 00:00:00\",\n    \"2066-08-01 00:00:00\",\n    \"2066-08-07 00:00:00\",\n    \"2066-08-08 00:00:00\",\n    \"2066-08-14 00:00:00\",\n    \"2066-08-15 00:00:00\",\n    \"2066-08-21 00:00:00\",\n    \"2066-08-22 00:00:00\",\n    \"2066-08-28 00:00:00\",\n    \"2066-08-29 00:00:00\",\n    \"2066-09-04 00:00:00\",\n    \"2066-09-05 00:00:00\",\n    \"2066-09-11 00:00:00\",\n    \"2066-09-12 00:00:00\",\n    \"2066-09-18 00:00:00\",\n    \"2066-09-19 00:00:00\",\n    \"2066-09-25 00:00:00\",\n    \"2066-10-01 00:00:00\",\n    \"2066-10-02 00:00:00\",\n    \"2066-10-03 00:00:00\",\n    \"2066-10-04 00:00:00\",\n    \"2066-10-05 00:00:00\",\n    \"2066-10-06 00:00:00\",\n    \"2066-10-07 00:00:00\",\n    \"2066-10-08 00:00:00\",\n    \"2066-10-10 00:00:00\",\n    \"2066-10-16 00:00:00\",\n    \"2066-10-17 00:00:00\",\n    \"2066-10-23 00:00:00\",\n    \"2066-10-24 00:00:00\",\n    \"2066-10-30 00:00:00\",\n    \"2066-10-31 00:00:00\",\n    \"2066-11-06 00:00:00\",\n    \"2066-11-07 00:00:00\",\n    \"2066-11-13 00:00:00\",\n    \"2066-11-14 00:00:00\",\n    \"2066-11-20 00:00:00\",\n    \"2066-11-21 00:00:00\",\n    \"2066-11-27 00:00:00\",\n    \"2066-11-28 00:00:00\",\n    \"2066-12-04 00:00:00\",\n    \"2066-12-05 00:00:00\",\n    \"2066-12-11 00:00:00\",\n    \"2066-12-12 00:00:00\",\n    \"2066-12-18 00:00:00\",\n    \"2066-12-19 00:00:00\",\n    \"2066-12-25 00:00:00\",\n    \"2066-12-26 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-01-02 00:00:00\",\n    \"2067-01-03 00:00:00\",\n    \"2067-01-08 00:00:00\",\n    \"2067-01-09 00:00:00\",\n    \"2067-01-15 00:00:00\",\n    \"2067-01-16 00:00:00\",\n    \"2067-01-22 00:00:00\",\n    \"2067-01-23 00:00:00\",\n    \"2067-01-29 00:00:00\",\n    \"2067-01-30 00:00:00\",\n    \"2067-02-05 00:00:00\",\n    \"2067-02-06 00:00:00\",\n    \"2067-02-13 00:00:00\",\n    \"2067-02-14 00:00:00\",\n    \"2067-02-15 00:00:00\",\n    \"2067-02-16 00:00:00\",\n    \"2067-02-17 00:00:00\",\n    \"2067-02-18 00:00:00\",\n    \"2067-02-19 00:00:00\",\n    \"2067-02-20 00:00:00\",\n    \"2067-02-27 00:00:00\",\n    \"2067-03-05 00:00:00\",\n    \"2067-03-06 00:00:00\",\n    \"2067-03-12 00:00:00\",\n    \"2067-03-13 00:00:00\",\n    \"2067-03-19 00:00:00\",\n    \"2067-03-20 00:00:00\",\n    \"2067-03-26 00:00:00\",\n    \"2067-03-27 00:00:00\",\n    \"2067-04-02 00:00:00\",\n    \"2067-04-03 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-04-09 00:00:00\",\n    \"2067-04-10 00:00:00\",\n    \"2067-04-16 00:00:00\",\n    \"2067-04-17 00:00:00\",\n    \"2067-04-23 00:00:00\",\n    \"2067-04-24 00:00:00\",\n    \"2067-04-30 00:00:00\",\n    \"2067-05-01 00:00:00\",\n    \"2067-05-02 00:00:00\",\n    \"2067-05-03 00:00:00\",\n    \"2067-05-04 00:00:00\",\n    \"2067-05-07 00:00:00\",\n    \"2067-05-14 00:00:00\",\n    \"2067-05-15 00:00:00\",\n    \"2067-05-21 00:00:00\",\n    \"2067-05-22 00:00:00\",\n    \"2067-05-28 00:00:00\",\n    \"2067-05-29 00:00:00\",\n    \"2067-06-04 00:00:00\",\n    \"2067-06-05 00:00:00\",\n    \"2067-06-11 00:00:00\",\n    \"2067-06-12 00:00:00\",\n    \"2067-06-16 00:00:00\",\n    \"2067-06-17 00:00:00\",\n    \"2067-06-18 00:00:00\",\n    \"2067-06-19 00:00:00\",\n    \"2067-06-25 00:00:00\",\n    \"2067-07-02 00:00:00\",\n    \"2067-07-03 00:00:00\",\n    \"2067-07-09 00:00:00\",\n    \"2067-07-10 00:00:00\",\n    \"2067-07-16 00:00:00\",\n    \"2067-07-17 00:00:00\",\n    \"2067-07-23 00:00:00\",\n    \"2067-07-24 00:00:00\",\n    \"2067-07-30 00:00:00\",\n    \"2067-07-31 00:00:00\",\n    \"2067-08-06 00:00:00\",\n    \"2067-08-07 00:00:00\",\n    \"2067-08-13 00:00:00\",\n    \"2067-08-14 00:00:00\",\n    \"2067-08-20 00:00:00\",\n    \"2067-08-21 00:00:00\",\n    \"2067-08-27 00:00:00\",\n    \"2067-08-28 00:00:00\",\n    \"2067-09-03 00:00:00\",\n    \"2067-09-04 00:00:00\",\n    \"2067-09-10 00:00:00\",\n    \"2067-09-11 00:00:00\",\n    \"2067-09-17 00:00:00\",\n    \"2067-09-23 00:00:00\",\n    \"2067-09-24 00:00:00\",\n    \"2067-09-25 00:00:00\",\n    \"2067-10-01 00:00:00\",\n    \"2067-10-02 00:00:00\",\n    \"2067-10-03 00:00:00\",\n    \"2067-10-04 00:00:00\",\n    \"2067-10-05 00:00:00\",\n    \"2067-10-06 00:00:00\",\n    \"2067-10-07 00:00:00\",\n    \"2067-10-08 00:00:00\",\n    \"2067-10-15 00:00:00\",\n    \"2067-10-16 00:00:00\",\n    \"2067-10-22 00:00:00\",\n    \"2067-10-23 00:00:00\",\n    \"2067-10-29 00:00:00\",\n    \"2067-10-30 00:00:00\",\n    \"2067-11-05 00:00:00\",\n    \"2067-11-06 00:00:00\",\n    \"2067-11-12 00:00:00\",\n    \"2067-11-13 00:00:00\",\n    \"2067-11-19 00:00:00\",\n    \"2067-11-20 00:00:00\",\n    \"2067-11-26 00:00:00\",\n    \"2067-11-27 00:00:00\",\n    \"2067-12-03 00:00:00\",\n    \"2067-12-04 00:00:00\",\n    \"2067-12-10 00:00:00\",\n    \"2067-12-11 00:00:00\",\n    \"2067-12-17 00:00:00\",\n    \"2067-12-18 00:00:00\",\n    \"2067-12-24 00:00:00\",\n    \"2067-12-25 00:00:00\",\n    \"2067-12-31 00:00:00\",\n    \"2068-01-01 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-01-07 00:00:00\",\n    \"2068-01-08 00:00:00\",\n    \"2068-01-14 00:00:00\",\n    \"2068-01-15 00:00:00\",\n    \"2068-01-21 00:00:00\",\n    \"2068-01-22 00:00:00\",\n    \"2068-01-28 00:00:00\",\n    \"2068-02-02 00:00:00\",\n    \"2068-02-03 00:00:00\",\n    \"2068-02-04 00:00:00\",\n    \"2068-02-05 00:00:00\",\n    \"2068-02-06 00:00:00\",\n    \"2068-02-07 00:00:00\",\n    \"2068-02-08 00:00:00\",\n    \"2068-02-09 00:00:00\",\n    \"2068-02-12 00:00:00\",\n    \"2068-02-18 00:00:00\",\n    \"2068-02-19 00:00:00\",\n    \"2068-02-25 00:00:00\",\n    \"2068-02-26 00:00:00\",\n    \"2068-03-03 00:00:00\",\n    \"2068-03-04 00:00:00\",\n    \"2068-03-10 00:00:00\",\n    \"2068-03-11 00:00:00\",\n    \"2068-03-17 00:00:00\",\n    \"2068-03-18 00:00:00\",\n    \"2068-03-24 00:00:00\",\n    \"2068-03-31 00:00:00\",\n    \"2068-04-01 00:00:00\",\n    \"2068-04-02 00:00:00\",\n    \"2068-04-03 00:00:00\",\n    \"2068-04-07 00:00:00\",\n    \"2068-04-08 00:00:00\",\n    \"2068-04-14 00:00:00\",\n    \"2068-04-15 00:00:00\",\n    \"2068-04-21 00:00:00\",\n    \"2068-04-22 00:00:00\",\n    \"2068-04-28 00:00:00\",\n    \"2068-04-29 00:00:00\",\n    \"2068-04-30 00:00:00\",\n    \"2068-05-01 00:00:00\",\n    \"2068-05-02 00:00:00\",\n    \"2068-05-05 00:00:00\",\n    \"2068-05-06 00:00:00\",\n    \"2068-05-12 00:00:00\",\n    \"2068-05-13 00:00:00\",\n    \"2068-05-19 00:00:00\",\n    \"2068-05-20 00:00:00\",\n    \"2068-05-26 00:00:00\",\n    \"2068-05-27 00:00:00\",\n    \"2068-06-02 00:00:00\",\n    \"2068-06-03 00:00:00\",\n    \"2068-06-04 00:00:00\",\n    \"2068-06-09 00:00:00\",\n    \"2068-06-10 00:00:00\",\n    \"2068-06-16 00:00:00\",\n    \"2068-06-17 00:00:00\",\n    \"2068-06-23 00:00:00\",\n    \"2068-06-24 00:00:00\",\n    \"2068-06-30 00:00:00\",\n    \"2068-07-01 00:00:00\",\n    \"2068-07-07 00:00:00\",\n    \"2068-07-08 00:00:00\",\n    \"2068-07-14 00:00:00\",\n    \"2068-07-15 00:00:00\",\n    \"2068-07-21 00:00:00\",\n    \"2068-07-22 00:00:00\",\n    \"2068-07-28 00:00:00\",\n    \"2068-07-29 00:00:00\",\n    \"2068-08-04 00:00:00\",\n    \"2068-08-05 00:00:00\",\n    \"2068-08-11 00:00:00\",\n    \"2068-08-12 00:00:00\",\n    \"2068-08-18 00:00:00\",\n    \"2068-08-19 00:00:00\",\n    \"2068-08-25 00:00:00\",\n    \"2068-08-26 00:00:00\",\n    \"2068-09-01 00:00:00\",\n    \"2068-09-08 00:00:00\",\n    \"2068-09-09 00:00:00\",\n    \"2068-09-10 00:00:00\",\n    \"2068-09-11 00:00:00\",\n    \"2068-09-15 00:00:00\",\n    \"2068-09-16 00:00:00\",\n    \"2068-09-22 00:00:00\",\n    \"2068-09-23 00:00:00\",\n    \"2068-09-30 00:00:00\",\n    \"2068-10-01 00:00:00\",\n    \"2068-10-02 00:00:00\",\n    \"2068-10-03 00:00:00\",\n    \"2068-10-04 00:00:00\",\n    \"2068-10-05 00:00:00\",\n    \"2068-10-06 00:00:00\",\n    \"2068-10-07 00:00:00\",\n    \"2068-10-14 00:00:00\",\n    \"2068-10-20 00:00:00\",\n    \"2068-10-21 00:00:00\",\n    \"2068-10-27 00:00:00\",\n    \"2068-10-28 00:00:00\",\n    \"2068-11-03 00:00:00\",\n    \"2068-11-04 00:00:00\",\n    \"2068-11-10 00:00:00\",\n    \"2068-11-11 00:00:00\",\n    \"2068-11-17 00:00:00\",\n    \"2068-11-18 00:00:00\",\n    \"2068-11-24 00:00:00\",\n    \"2068-11-25 00:00:00\",\n    \"2068-12-01 00:00:00\",\n    \"2068-12-02 00:00:00\",\n    \"2068-12-08 00:00:00\",\n    \"2068-12-09 00:00:00\",\n    \"2068-12-15 00:00:00\",\n    \"2068-12-16 00:00:00\",\n    \"2068-12-22 00:00:00\",\n    \"2068-12-23 00:00:00\",\n    \"2068-12-30 00:00:00\",\n    \"2068-12-31 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-05 00:00:00\",\n    \"2069-01-06 00:00:00\",\n    \"2069-01-12 00:00:00\",\n    \"2069-01-13 00:00:00\",\n    \"2069-01-19 00:00:00\",\n    \"2069-01-22 00:00:00\",\n    \"2069-01-23 00:00:00\",\n    \"2069-01-24 00:00:00\",\n    \"2069-01-25 00:00:00\",\n    \"2069-01-26 00:00:00\",\n    \"2069-01-27 00:00:00\",\n    \"2069-01-28 00:00:00\",\n    \"2069-01-29 00:00:00\",\n    \"2069-02-03 00:00:00\",\n    \"2069-02-09 00:00:00\",\n    \"2069-02-10 00:00:00\",\n    \"2069-02-16 00:00:00\",\n    \"2069-02-17 00:00:00\",\n    \"2069-02-23 00:00:00\",\n    \"2069-02-24 00:00:00\",\n    \"2069-03-02 00:00:00\",\n    \"2069-03-03 00:00:00\",\n    \"2069-03-09 00:00:00\",\n    \"2069-03-10 00:00:00\",\n    \"2069-03-16 00:00:00\",\n    \"2069-03-17 00:00:00\",\n    \"2069-03-23 00:00:00\",\n    \"2069-03-24 00:00:00\",\n    \"2069-03-30 00:00:00\",\n    \"2069-03-31 00:00:00\",\n    \"2069-04-04 00:00:00\",\n    \"2069-04-05 00:00:00\",\n    \"2069-04-06 00:00:00\",\n    \"2069-04-07 00:00:00\",\n    \"2069-04-13 00:00:00\",\n    \"2069-04-20 00:00:00\",\n    \"2069-04-21 00:00:00\",\n    \"2069-04-27 00:00:00\",\n    \"2069-04-28 00:00:00\",\n    \"2069-05-01 00:00:00\",\n    \"2069-05-02 00:00:00\",\n    \"2069-05-04 00:00:00\",\n    \"2069-05-05 00:00:00\",\n    \"2069-05-11 00:00:00\",\n    \"2069-05-12 00:00:00\",\n    \"2069-05-18 00:00:00\",\n    \"2069-05-19 00:00:00\",\n    \"2069-05-25 00:00:00\",\n    \"2069-05-26 00:00:00\",\n    \"2069-06-01 00:00:00\",\n    \"2069-06-02 00:00:00\",\n    \"2069-06-08 00:00:00\",\n    \"2069-06-09 00:00:00\",\n    \"2069-06-15 00:00:00\",\n    \"2069-06-16 00:00:00\",\n    \"2069-06-22 00:00:00\",\n    \"2069-06-23 00:00:00\",\n    \"2069-06-24 00:00:00\",\n    \"2069-06-29 00:00:00\",\n    \"2069-06-30 00:00:00\",\n    \"2069-07-06 00:00:00\",\n    \"2069-07-07 00:00:00\",\n    \"2069-07-13 00:00:00\",\n    \"2069-07-14 00:00:00\",\n    \"2069-07-20 00:00:00\",\n    \"2069-07-21 00:00:00\",\n    \"2069-07-27 00:00:00\",\n    \"2069-07-28 00:00:00\",\n    \"2069-08-03 00:00:00\",\n    \"2069-08-04 00:00:00\",\n    \"2069-08-10 00:00:00\",\n    \"2069-08-11 00:00:00\",\n    \"2069-08-17 00:00:00\",\n    \"2069-08-18 00:00:00\",\n    \"2069-08-24 00:00:00\",\n    \"2069-08-25 00:00:00\",\n    \"2069-08-31 00:00:00\",\n    \"2069-09-01 00:00:00\",\n    \"2069-09-07 00:00:00\",\n    \"2069-09-08 00:00:00\",\n    \"2069-09-14 00:00:00\",\n    \"2069-09-15 00:00:00\",\n    \"2069-09-21 00:00:00\",\n    \"2069-09-28 00:00:00\",\n    \"2069-09-29 00:00:00\",\n    \"2069-09-30 00:00:00\",\n    \"2069-10-01 00:00:00\",\n    \"2069-10-02 00:00:00\",\n    \"2069-10-03 00:00:00\",\n    \"2069-10-04 00:00:00\",\n    \"2069-10-05 00:00:00\",\n    \"2069-10-06 00:00:00\",\n    \"2069-10-07 00:00:00\",\n    \"2069-10-13 00:00:00\",\n    \"2069-10-19 00:00:00\",\n    \"2069-10-20 00:00:00\",\n    \"2069-10-26 00:00:00\",\n    \"2069-10-27 00:00:00\",\n    \"2069-11-02 00:00:00\",\n    \"2069-11-03 00:00:00\",\n    \"2069-11-09 00:00:00\",\n    \"2069-11-10 00:00:00\",\n    \"2069-11-16 00:00:00\",\n    \"2069-11-17 00:00:00\",\n    \"2069-11-23 00:00:00\",\n    \"2069-11-24 00:00:00\",\n    \"2069-11-30 00:00:00\",\n    \"2069-12-01 00:00:00\",\n    \"2069-12-07 00:00:00\",\n    \"2069-12-08 00:00:00\",\n    \"2069-12-14 00:00:00\",\n    \"2069-12-15 00:00:00\",\n    \"2069-12-21 00:00:00\",\n    \"2069-12-22 00:00:00\",\n    \"2069-12-28 00:00:00\",\n    \"2069-12-29 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-04 00:00:00\",\n    \"2070-01-05 00:00:00\",\n    \"2070-01-11 00:00:00\",\n    \"2070-01-12 00:00:00\",\n    \"2070-01-18 00:00:00\",\n    \"2070-01-19 00:00:00\",\n    \"2070-01-25 00:00:00\",\n    \"2070-01-26 00:00:00\",\n    \"2070-02-01 00:00:00\",\n    \"2070-02-02 00:00:00\",\n    \"2070-02-09 00:00:00\",\n    \"2070-02-10 00:00:00\",\n    \"2070-02-11 00:00:00\",\n    \"2070-02-12 00:00:00\",\n    \"2070-02-13 00:00:00\",\n    \"2070-02-14 00:00:00\",\n    \"2070-02-15 00:00:00\",\n    \"2070-02-16 00:00:00\",\n    \"2070-02-17 00:00:00\",\n    \"2070-02-23 00:00:00\",\n    \"2070-03-01 00:00:00\",\n    \"2070-03-02 00:00:00\",\n    \"2070-03-08 00:00:00\",\n    \"2070-03-09 00:00:00\",\n    \"2070-03-15 00:00:00\",\n    \"2070-03-16 00:00:00\",\n    \"2070-03-22 00:00:00\",\n    \"2070-03-23 00:00:00\",\n    \"2070-03-29 00:00:00\",\n    \"2070-03-30 00:00:00\",\n    \"2070-04-04 00:00:00\",\n    \"2070-04-05 00:00:00\",\n    \"2070-04-06 00:00:00\",\n    \"2070-04-12 00:00:00\",\n    \"2070-04-13 00:00:00\",\n    \"2070-04-19 00:00:00\",\n    \"2070-04-20 00:00:00\",\n    \"2070-04-26 00:00:00\",\n    \"2070-05-01 00:00:00\",\n    \"2070-05-02 00:00:00\",\n    \"2070-05-03 00:00:00\",\n    \"2070-05-04 00:00:00\",\n    \"2070-05-05 00:00:00\",\n    \"2070-05-10 00:00:00\",\n    \"2070-05-11 00:00:00\",\n    \"2070-05-17 00:00:00\",\n    \"2070-05-18 00:00:00\",\n    \"2070-05-24 00:00:00\",\n    \"2070-05-25 00:00:00\",\n    \"2070-05-31 00:00:00\",\n    \"2070-06-01 00:00:00\",\n    \"2070-06-07 00:00:00\",\n    \"2070-06-08 00:00:00\",\n    \"2070-06-13 00:00:00\",\n    \"2070-06-14 00:00:00\",\n    \"2070-06-15 00:00:00\",\n    \"2070-06-21 00:00:00\",\n    \"2070-06-22 00:00:00\",\n    \"2070-06-28 00:00:00\",\n    \"2070-06-29 00:00:00\",\n    \"2070-07-05 00:00:00\",\n    \"2070-07-06 00:00:00\",\n    \"2070-07-12 00:00:00\",\n    \"2070-07-13 00:00:00\",\n    \"2070-07-19 00:00:00\",\n    \"2070-07-20 00:00:00\",\n    \"2070-07-26 00:00:00\",\n    \"2070-07-27 00:00:00\",\n    \"2070-08-02 00:00:00\",\n    \"2070-08-03 00:00:00\",\n    \"2070-08-09 00:00:00\",\n    \"2070-08-10 00:00:00\",\n    \"2070-08-16 00:00:00\",\n    \"2070-08-17 00:00:00\",\n    \"2070-08-23 00:00:00\",\n    \"2070-08-24 00:00:00\",\n    \"2070-08-30 00:00:00\",\n    \"2070-08-31 00:00:00\",\n    \"2070-09-06 00:00:00\",\n    \"2070-09-07 00:00:00\",\n    \"2070-09-13 00:00:00\",\n    \"2070-09-14 00:00:00\",\n    \"2070-09-19 00:00:00\",\n    \"2070-09-20 00:00:00\",\n    \"2070-09-21 00:00:00\",\n    \"2070-09-27 00:00:00\",\n    \"2070-10-01 00:00:00\",\n    \"2070-10-02 00:00:00\",\n    \"2070-10-03 00:00:00\",\n    \"2070-10-04 00:00:00\",\n    \"2070-10-05 00:00:00\",\n    \"2070-10-06 00:00:00\",\n    \"2070-10-07 00:00:00\",\n    \"2070-10-12 00:00:00\",\n    \"2070-10-18 00:00:00\",\n    \"2070-10-19 00:00:00\",\n    \"2070-10-25 00:00:00\",\n    \"2070-10-26 00:00:00\",\n    \"2070-11-01 00:00:00\",\n    \"2070-11-02 00:00:00\",\n    \"2070-11-08 00:00:00\",\n    \"2070-11-09 00:00:00\",\n    \"2070-11-15 00:00:00\",\n    \"2070-11-16 00:00:00\",\n    \"2070-11-22 00:00:00\",\n    \"2070-11-23 00:00:00\",\n    \"2070-11-29 00:00:00\",\n    \"2070-11-30 00:00:00\",\n    \"2070-12-06 00:00:00\",\n    \"2070-12-07 00:00:00\",\n    \"2070-12-13 00:00:00\",\n    \"2070-12-14 00:00:00\",\n    \"2070-12-20 00:00:00\",\n    \"2070-12-21 00:00:00\",\n    \"2070-12-27 00:00:00\",\n    \"2070-12-28 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-02 00:00:00\",\n    \"2071-01-03 00:00:00\",\n    \"2071-01-10 00:00:00\",\n    \"2071-01-11 00:00:00\",\n    \"2071-01-17 00:00:00\",\n    \"2071-01-18 00:00:00\",\n    \"2071-01-24 00:00:00\",\n    \"2071-01-30 00:00:00\",\n    \"2071-01-31 00:00:00\",\n    \"2071-02-01 00:00:00\",\n    \"2071-02-02 00:00:00\",\n    \"2071-02-03 00:00:00\",\n    \"2071-02-04 00:00:00\",\n    \"2071-02-05 00:00:00\",\n    \"2071-02-06 00:00:00\",\n    \"2071-02-08 00:00:00\",\n    \"2071-02-14 00:00:00\",\n    \"2071-02-15 00:00:00\",\n    \"2071-02-21 00:00:00\",\n    \"2071-02-22 00:00:00\",\n    \"2071-02-28 00:00:00\",\n    \"2071-03-01 00:00:00\",\n    \"2071-03-07 00:00:00\",\n    \"2071-03-08 00:00:00\",\n    \"2071-03-14 00:00:00\",\n    \"2071-03-15 00:00:00\",\n    \"2071-03-21 00:00:00\",\n    \"2071-03-22 00:00:00\",\n    \"2071-03-28 00:00:00\",\n    \"2071-03-29 00:00:00\",\n    \"2071-04-04 00:00:00\",\n    \"2071-04-05 00:00:00\",\n    \"2071-04-06 00:00:00\",\n    \"2071-04-11 00:00:00\",\n    \"2071-04-12 00:00:00\",\n    \"2071-04-18 00:00:00\",\n    \"2071-04-19 00:00:00\",\n    \"2071-04-25 00:00:00\",\n    \"2071-04-26 00:00:00\",\n    \"2071-05-01 00:00:00\",\n    \"2071-05-02 00:00:00\",\n    \"2071-05-03 00:00:00\",\n    \"2071-05-04 00:00:00\",\n    \"2071-05-05 00:00:00\",\n    \"2071-05-10 00:00:00\",\n    \"2071-05-16 00:00:00\",\n    \"2071-05-17 00:00:00\",\n    \"2071-05-23 00:00:00\",\n    \"2071-05-30 00:00:00\",\n    \"2071-05-31 00:00:00\",\n    \"2071-06-01 00:00:00\",\n    \"2071-06-02 00:00:00\",\n    \"2071-06-06 00:00:00\",\n    \"2071-06-07 00:00:00\",\n    \"2071-06-13 00:00:00\",\n    \"2071-06-14 00:00:00\",\n    \"2071-06-20 00:00:00\",\n    \"2071-06-21 00:00:00\",\n    \"2071-06-27 00:00:00\",\n    \"2071-06-28 00:00:00\",\n    \"2071-07-04 00:00:00\",\n    \"2071-07-05 00:00:00\",\n    \"2071-07-11 00:00:00\",\n    \"2071-07-12 00:00:00\",\n    \"2071-07-18 00:00:00\",\n    \"2071-07-19 00:00:00\",\n    \"2071-07-25 00:00:00\",\n    \"2071-07-26 00:00:00\",\n    \"2071-08-01 00:00:00\",\n    \"2071-08-02 00:00:00\",\n    \"2071-08-08 00:00:00\",\n    \"2071-08-09 00:00:00\",\n    \"2071-08-15 00:00:00\",\n    \"2071-08-16 00:00:00\",\n    \"2071-08-22 00:00:00\",\n    \"2071-08-23 00:00:00\",\n    \"2071-08-29 00:00:00\",\n    \"2071-09-05 00:00:00\",\n    \"2071-09-06 00:00:00\",\n    \"2071-09-07 00:00:00\",\n    \"2071-09-08 00:00:00\",\n    \"2071-09-12 00:00:00\",\n    \"2071-09-13 00:00:00\",\n    \"2071-09-19 00:00:00\",\n    \"2071-09-20 00:00:00\",\n    \"2071-09-26 00:00:00\",\n    \"2071-10-01 00:00:00\",\n    \"2071-10-02 00:00:00\",\n    \"2071-10-03 00:00:00\",\n    \"2071-10-04 00:00:00\",\n    \"2071-10-05 00:00:00\",\n    \"2071-10-06 00:00:00\",\n    \"2071-10-07 00:00:00\",\n    \"2071-10-11 00:00:00\",\n    \"2071-10-17 00:00:00\",\n    \"2071-10-18 00:00:00\",\n    \"2071-10-24 00:00:00\",\n    \"2071-10-25 00:00:00\",\n    \"2071-10-31 00:00:00\",\n    \"2071-11-01 00:00:00\",\n    \"2071-11-07 00:00:00\",\n    \"2071-11-08 00:00:00\",\n    \"2071-11-14 00:00:00\",\n    \"2071-11-15 00:00:00\",\n    \"2071-11-21 00:00:00\",\n    \"2071-11-22 00:00:00\",\n    \"2071-11-28 00:00:00\",\n    \"2071-11-29 00:00:00\",\n    \"2071-12-05 00:00:00\",\n    \"2071-12-06 00:00:00\",\n    \"2071-12-12 00:00:00\",\n    \"2071-12-13 00:00:00\",\n    \"2071-12-19 00:00:00\",\n    \"2071-12-20 00:00:00\",\n    \"2071-12-26 00:00:00\",\n    \"2071-12-27 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-02 00:00:00\",\n    \"2072-01-03 00:00:00\",\n    \"2072-01-09 00:00:00\",\n    \"2072-01-10 00:00:00\",\n    \"2072-01-16 00:00:00\",\n    \"2072-01-17 00:00:00\",\n    \"2072-01-23 00:00:00\",\n    \"2072-01-24 00:00:00\",\n    \"2072-01-30 00:00:00\",\n    \"2072-01-31 00:00:00\",\n    \"2072-02-06 00:00:00\",\n    \"2072-02-07 00:00:00\",\n    \"2072-02-13 00:00:00\",\n    \"2072-02-18 00:00:00\",\n    \"2072-02-19 00:00:00\",\n    \"2072-02-20 00:00:00\",\n    \"2072-02-21 00:00:00\",\n    \"2072-02-22 00:00:00\",\n    \"2072-02-23 00:00:00\",\n    \"2072-02-24 00:00:00\",\n    \"2072-02-25 00:00:00\",\n    \"2072-02-28 00:00:00\",\n    \"2072-03-05 00:00:00\",\n    \"2072-03-06 00:00:00\",\n    \"2072-03-12 00:00:00\",\n    \"2072-03-13 00:00:00\",\n    \"2072-03-19 00:00:00\",\n    \"2072-03-20 00:00:00\",\n    \"2072-03-26 00:00:00\",\n    \"2072-03-27 00:00:00\",\n    \"2072-04-02 00:00:00\",\n    \"2072-04-03 00:00:00\",\n    \"2072-04-04 00:00:00\",\n    \"2072-04-09 00:00:00\",\n    \"2072-04-10 00:00:00\",\n    \"2072-04-16 00:00:00\",\n    \"2072-04-17 00:00:00\",\n    \"2072-04-23 00:00:00\",\n    \"2072-04-24 00:00:00\",\n    \"2072-04-30 00:00:00\",\n    \"2072-05-01 00:00:00\",\n    \"2072-05-02 00:00:00\",\n    \"2072-05-03 00:00:00\",\n    \"2072-05-04 00:00:00\",\n    \"2072-05-07 00:00:00\",\n    \"2072-05-14 00:00:00\",\n    \"2072-05-15 00:00:00\",\n    \"2072-05-21 00:00:00\",\n    \"2072-05-22 00:00:00\",\n    \"2072-05-28 00:00:00\",\n    \"2072-05-29 00:00:00\",\n    \"2072-06-04 00:00:00\",\n    \"2072-06-05 00:00:00\",\n    \"2072-06-11 00:00:00\",\n    \"2072-06-12 00:00:00\",\n    \"2072-06-18 00:00:00\",\n    \"2072-06-19 00:00:00\",\n    \"2072-06-20 00:00:00\",\n    \"2072-06-25 00:00:00\",\n    \"2072-06-26 00:00:00\",\n    \"2072-07-02 00:00:00\",\n    \"2072-07-03 00:00:00\",\n    \"2072-07-09 00:00:00\",\n    \"2072-07-10 00:00:00\",\n    \"2072-07-16 00:00:00\",\n    \"2072-07-17 00:00:00\",\n    \"2072-07-23 00:00:00\",\n    \"2072-07-24 00:00:00\",\n    \"2072-07-30 00:00:00\",\n    \"2072-07-31 00:00:00\",\n    \"2072-08-06 00:00:00\",\n    \"2072-08-07 00:00:00\",\n    \"2072-08-13 00:00:00\",\n    \"2072-08-14 00:00:00\",\n    \"2072-08-20 00:00:00\",\n    \"2072-08-21 00:00:00\",\n    \"2072-08-27 00:00:00\",\n    \"2072-08-28 00:00:00\",\n    \"2072-09-03 00:00:00\",\n    \"2072-09-04 00:00:00\",\n    \"2072-09-10 00:00:00\",\n    \"2072-09-11 00:00:00\",\n    \"2072-09-17 00:00:00\",\n    \"2072-09-24 00:00:00\",\n    \"2072-09-25 00:00:00\",\n    \"2072-09-26 00:00:00\",\n    \"2072-10-01 00:00:00\",\n    \"2072-10-02 00:00:00\",\n    \"2072-10-03 00:00:00\",\n    \"2072-10-04 00:00:00\",\n    \"2072-10-05 00:00:00\",\n    \"2072-10-06 00:00:00\",\n    \"2072-10-07 00:00:00\",\n    \"2072-10-08 00:00:00\",\n    \"2072-10-15 00:00:00\",\n    \"2072-10-16 00:00:00\",\n    \"2072-10-22 00:00:00\",\n    \"2072-10-23 00:00:00\",\n    \"2072-10-29 00:00:00\",\n    \"2072-10-30 00:00:00\",\n    \"2072-11-05 00:00:00\",\n    \"2072-11-06 00:00:00\",\n    \"2072-11-12 00:00:00\",\n    \"2072-11-13 00:00:00\",\n    \"2072-11-19 00:00:00\",\n    \"2072-11-20 00:00:00\",\n    \"2072-11-26 00:00:00\",\n    \"2072-11-27 00:00:00\",\n    \"2072-12-03 00:00:00\",\n    \"2072-12-04 00:00:00\",\n    \"2072-12-10 00:00:00\",\n    \"2072-12-11 00:00:00\",\n    \"2072-12-17 00:00:00\",\n    \"2072-12-18 00:00:00\",\n    \"2072-12-24 00:00:00\",\n    \"2072-12-25 00:00:00\",\n    \"2072-12-31 00:00:00\",\n    \"2073-01-01 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-01-07 00:00:00\",\n    \"2073-01-08 00:00:00\",\n    \"2073-01-14 00:00:00\",\n    \"2073-01-15 00:00:00\",\n    \"2073-01-21 00:00:00\",\n    \"2073-01-22 00:00:00\",\n    \"2073-01-28 00:00:00\",\n    \"2073-01-29 00:00:00\",\n    \"2073-02-05 00:00:00\",\n    \"2073-02-06 00:00:00\",\n    \"2073-02-07 00:00:00\",\n    \"2073-02-08 00:00:00\",\n    \"2073-02-09 00:00:00\",\n    \"2073-02-10 00:00:00\",\n    \"2073-02-11 00:00:00\",\n    \"2073-02-12 00:00:00\",\n    \"2073-02-13 00:00:00\",\n    \"2073-02-19 00:00:00\",\n    \"2073-02-25 00:00:00\",\n    \"2073-02-26 00:00:00\",\n    \"2073-03-04 00:00:00\",\n    \"2073-03-05 00:00:00\",\n    \"2073-03-11 00:00:00\",\n    \"2073-03-12 00:00:00\",\n    \"2073-03-18 00:00:00\",\n    \"2073-03-19 00:00:00\",\n    \"2073-03-25 00:00:00\",\n    \"2073-04-01 00:00:00\",\n    \"2073-04-02 00:00:00\",\n    \"2073-04-03 00:00:00\",\n    \"2073-04-04 00:00:00\",\n    \"2073-04-08 00:00:00\",\n    \"2073-04-09 00:00:00\",\n    \"2073-04-15 00:00:00\",\n    \"2073-04-16 00:00:00\",\n    \"2073-04-22 00:00:00\",\n    \"2073-04-23 00:00:00\",\n    \"2073-04-29 00:00:00\",\n    \"2073-04-30 00:00:00\",\n    \"2073-05-01 00:00:00\",\n    \"2073-05-02 00:00:00\",\n    \"2073-05-03 00:00:00\",\n    \"2073-05-06 00:00:00\",\n    \"2073-05-13 00:00:00\",\n    \"2073-05-14 00:00:00\",\n    \"2073-05-20 00:00:00\",\n    \"2073-05-21 00:00:00\",\n    \"2073-05-27 00:00:00\",\n    \"2073-05-28 00:00:00\",\n    \"2073-06-03 00:00:00\",\n    \"2073-06-04 00:00:00\",\n    \"2073-06-10 00:00:00\",\n    \"2073-06-11 00:00:00\",\n    \"2073-06-12 00:00:00\",\n    \"2073-06-17 00:00:00\",\n    \"2073-06-18 00:00:00\",\n    \"2073-06-24 00:00:00\",\n    \"2073-06-25 00:00:00\",\n    \"2073-07-01 00:00:00\",\n    \"2073-07-02 00:00:00\",\n    \"2073-07-08 00:00:00\",\n    \"2073-07-09 00:00:00\",\n    \"2073-07-15 00:00:00\",\n    \"2073-07-16 00:00:00\",\n    \"2073-07-22 00:00:00\",\n    \"2073-07-23 00:00:00\",\n    \"2073-07-29 00:00:00\",\n    \"2073-07-30 00:00:00\",\n    \"2073-08-05 00:00:00\",\n    \"2073-08-06 00:00:00\",\n    \"2073-08-12 00:00:00\",\n    \"2073-08-13 00:00:00\",\n    \"2073-08-19 00:00:00\",\n    \"2073-08-20 00:00:00\",\n    \"2073-08-26 00:00:00\",\n    \"2073-08-27 00:00:00\",\n    \"2073-09-02 00:00:00\",\n    \"2073-09-03 00:00:00\",\n    \"2073-09-09 00:00:00\",\n    \"2073-09-10 00:00:00\",\n    \"2073-09-16 00:00:00\",\n    \"2073-09-17 00:00:00\",\n    \"2073-09-18 00:00:00\",\n    \"2073-09-23 00:00:00\",\n    \"2073-09-30 00:00:00\",\n    \"2073-10-01 00:00:00\",\n    \"2073-10-02 00:00:00\",\n    \"2073-10-03 00:00:00\",\n    \"2073-10-04 00:00:00\",\n    \"2073-10-05 00:00:00\",\n    \"2073-10-06 00:00:00\",\n    \"2073-10-07 00:00:00\",\n    \"2073-10-14 00:00:00\",\n    \"2073-10-15 00:00:00\",\n    \"2073-10-21 00:00:00\",\n    \"2073-10-22 00:00:00\",\n    \"2073-10-28 00:00:00\",\n    \"2073-10-29 00:00:00\",\n    \"2073-11-04 00:00:00\",\n    \"2073-11-05 00:00:00\",\n    \"2073-11-11 00:00:00\",\n    \"2073-11-12 00:00:00\",\n    \"2073-11-18 00:00:00\",\n    \"2073-11-19 00:00:00\",\n    \"2073-11-25 00:00:00\",\n    \"2073-11-26 00:00:00\",\n    \"2073-12-02 00:00:00\",\n    \"2073-12-03 00:00:00\",\n    \"2073-12-09 00:00:00\",\n    \"2073-12-10 00:00:00\",\n    \"2073-12-16 00:00:00\",\n    \"2073-12-17 00:00:00\",\n    \"2073-12-23 00:00:00\",\n    \"2073-12-24 00:00:00\",\n    \"2073-12-30 00:00:00\",\n    \"2073-12-31 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-06 00:00:00\",\n    \"2074-01-07 00:00:00\",\n    \"2074-01-13 00:00:00\",\n    \"2074-01-14 00:00:00\",\n    \"2074-01-20 00:00:00\",\n    \"2074-01-26 00:00:00\",\n    \"2074-01-27 00:00:00\",\n    \"2074-01-28 00:00:00\",\n    \"2074-01-29 00:00:00\",\n    \"2074-01-30 00:00:00\",\n    \"2074-01-31 00:00:00\",\n    \"2074-02-01 00:00:00\",\n    \"2074-02-02 00:00:00\",\n    \"2074-02-04 00:00:00\",\n    \"2074-02-10 00:00:00\",\n    \"2074-02-11 00:00:00\",\n    \"2074-02-17 00:00:00\",\n    \"2074-02-18 00:00:00\",\n    \"2074-02-24 00:00:00\",\n    \"2074-02-25 00:00:00\",\n    \"2074-03-03 00:00:00\",\n    \"2074-03-04 00:00:00\",\n    \"2074-03-10 00:00:00\",\n    \"2074-03-11 00:00:00\",\n    \"2074-03-17 00:00:00\",\n    \"2074-03-18 00:00:00\",\n    \"2074-03-24 00:00:00\",\n    \"2074-03-25 00:00:00\",\n    \"2074-03-31 00:00:00\",\n    \"2074-04-01 00:00:00\",\n    \"2074-04-04 00:00:00\",\n    \"2074-04-07 00:00:00\",\n    \"2074-04-08 00:00:00\",\n    \"2074-04-14 00:00:00\",\n    \"2074-04-15 00:00:00\",\n    \"2074-04-21 00:00:00\",\n    \"2074-04-22 00:00:00\",\n    \"2074-04-28 00:00:00\",\n    \"2074-04-29 00:00:00\",\n    \"2074-04-30 00:00:00\",\n    \"2074-05-01 00:00:00\",\n    \"2074-05-02 00:00:00\",\n    \"2074-05-05 00:00:00\",\n    \"2074-05-06 00:00:00\",\n    \"2074-05-12 00:00:00\",\n    \"2074-05-13 00:00:00\",\n    \"2074-05-19 00:00:00\",\n    \"2074-05-20 00:00:00\",\n    \"2074-05-26 00:00:00\",\n    \"2074-05-27 00:00:00\",\n    \"2074-05-30 00:00:00\",\n    \"2074-06-02 00:00:00\",\n    \"2074-06-03 00:00:00\",\n    \"2074-06-09 00:00:00\",\n    \"2074-06-10 00:00:00\",\n    \"2074-06-16 00:00:00\",\n    \"2074-06-17 00:00:00\",\n    \"2074-06-23 00:00:00\",\n    \"2074-06-24 00:00:00\",\n    \"2074-06-30 00:00:00\",\n    \"2074-07-01 00:00:00\",\n    \"2074-07-07 00:00:00\",\n    \"2074-07-08 00:00:00\",\n    \"2074-07-14 00:00:00\",\n    \"2074-07-15 00:00:00\",\n    \"2074-07-21 00:00:00\",\n    \"2074-07-22 00:00:00\",\n    \"2074-07-28 00:00:00\",\n    \"2074-07-29 00:00:00\",\n    \"2074-08-04 00:00:00\",\n    \"2074-08-05 00:00:00\",\n    \"2074-08-11 00:00:00\",\n    \"2074-08-12 00:00:00\",\n    \"2074-08-18 00:00:00\",\n    \"2074-08-19 00:00:00\",\n    \"2074-08-25 00:00:00\",\n    \"2074-08-26 00:00:00\",\n    \"2074-09-01 00:00:00\",\n    \"2074-09-02 00:00:00\",\n    \"2074-09-08 00:00:00\",\n    \"2074-09-09 00:00:00\",\n    \"2074-09-15 00:00:00\",\n    \"2074-09-16 00:00:00\",\n    \"2074-09-22 00:00:00\",\n    \"2074-09-23 00:00:00\",\n    \"2074-09-30 00:00:00\",\n    \"2074-10-01 00:00:00\",\n    \"2074-10-02 00:00:00\",\n    \"2074-10-03 00:00:00\",\n    \"2074-10-04 00:00:00\",\n    \"2074-10-05 00:00:00\",\n    \"2074-10-06 00:00:00\",\n    \"2074-10-07 00:00:00\",\n    \"2074-10-08 00:00:00\",\n    \"2074-10-14 00:00:00\",\n    \"2074-10-20 00:00:00\",\n    \"2074-10-21 00:00:00\",\n    \"2074-10-27 00:00:00\",\n    \"2074-10-28 00:00:00\",\n    \"2074-11-03 00:00:00\",\n    \"2074-11-04 00:00:00\",\n    \"2074-11-10 00:00:00\",\n    \"2074-11-11 00:00:00\",\n    \"2074-11-17 00:00:00\",\n    \"2074-11-18 00:00:00\",\n    \"2074-11-24 00:00:00\",\n    \"2074-11-25 00:00:00\",\n    \"2074-12-01 00:00:00\",\n    \"2074-12-02 00:00:00\",\n    \"2074-12-08 00:00:00\",\n    \"2074-12-09 00:00:00\",\n    \"2074-12-15 00:00:00\",\n    \"2074-12-16 00:00:00\",\n    \"2074-12-22 00:00:00\",\n    \"2074-12-23 00:00:00\",\n    \"2074-12-30 00:00:00\",\n    \"2074-12-31 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-05 00:00:00\",\n    \"2075-01-06 00:00:00\",\n    \"2075-01-12 00:00:00\",\n    \"2075-01-13 00:00:00\",\n    \"2075-01-19 00:00:00\",\n    \"2075-01-20 00:00:00\",\n    \"2075-01-26 00:00:00\",\n    \"2075-01-27 00:00:00\",\n    \"2075-02-02 00:00:00\",\n    \"2075-02-03 00:00:00\",\n    \"2075-02-09 00:00:00\",\n    \"2075-02-14 00:00:00\",\n    \"2075-02-15 00:00:00\",\n    \"2075-02-16 00:00:00\",\n    \"2075-02-17 00:00:00\",\n    \"2075-02-18 00:00:00\",\n    \"2075-02-19 00:00:00\",\n    \"2075-02-20 00:00:00\",\n    \"2075-02-21 00:00:00\",\n    \"2075-02-24 00:00:00\",\n    \"2075-03-02 00:00:00\",\n    \"2075-03-03 00:00:00\",\n    \"2075-03-09 00:00:00\",\n    \"2075-03-10 00:00:00\",\n    \"2075-03-16 00:00:00\",\n    \"2075-03-17 00:00:00\",\n    \"2075-03-23 00:00:00\",\n    \"2075-03-24 00:00:00\",\n    \"2075-03-30 00:00:00\",\n    \"2075-03-31 00:00:00\",\n    \"2075-04-04 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-06 00:00:00\",\n    \"2075-04-07 00:00:00\",\n    \"2075-04-13 00:00:00\",\n    \"2075-04-20 00:00:00\",\n    \"2075-04-21 00:00:00\",\n    \"2075-04-27 00:00:00\",\n    \"2075-04-28 00:00:00\",\n    \"2075-05-01 00:00:00\",\n    \"2075-05-02 00:00:00\",\n    \"2075-05-04 00:00:00\",\n    \"2075-05-05 00:00:00\",\n    \"2075-05-11 00:00:00\",\n    \"2075-05-12 00:00:00\",\n    \"2075-05-18 00:00:00\",\n    \"2075-05-19 00:00:00\",\n    \"2075-05-25 00:00:00\",\n    \"2075-05-26 00:00:00\",\n    \"2075-06-01 00:00:00\",\n    \"2075-06-02 00:00:00\",\n    \"2075-06-08 00:00:00\",\n    \"2075-06-09 00:00:00\",\n    \"2075-06-15 00:00:00\",\n    \"2075-06-16 00:00:00\",\n    \"2075-06-17 00:00:00\",\n    \"2075-06-22 00:00:00\",\n    \"2075-06-23 00:00:00\",\n    \"2075-06-29 00:00:00\",\n    \"2075-06-30 00:00:00\",\n    \"2075-07-06 00:00:00\",\n    \"2075-07-07 00:00:00\",\n    \"2075-07-13 00:00:00\",\n    \"2075-07-14 00:00:00\",\n    \"2075-07-20 00:00:00\",\n    \"2075-07-21 00:00:00\",\n    \"2075-07-27 00:00:00\",\n    \"2075-07-28 00:00:00\",\n    \"2075-08-03 00:00:00\",\n    \"2075-08-04 00:00:00\",\n    \"2075-08-10 00:00:00\",\n    \"2075-08-11 00:00:00\",\n    \"2075-08-17 00:00:00\",\n    \"2075-08-18 00:00:00\",\n    \"2075-08-24 00:00:00\",\n    \"2075-08-25 00:00:00\",\n    \"2075-08-31 00:00:00\",\n    \"2075-09-01 00:00:00\",\n    \"2075-09-07 00:00:00\",\n    \"2075-09-08 00:00:00\",\n    \"2075-09-14 00:00:00\",\n    \"2075-09-21 00:00:00\",\n    \"2075-09-22 00:00:00\",\n    \"2075-09-23 00:00:00\",\n    \"2075-09-24 00:00:00\",\n    \"2075-09-28 00:00:00\",\n    \"2075-10-01 00:00:00\",\n    \"2075-10-02 00:00:00\",\n    \"2075-10-03 00:00:00\",\n    \"2075-10-04 00:00:00\",\n    \"2075-10-05 00:00:00\",\n    \"2075-10-06 00:00:00\",\n    \"2075-10-07 00:00:00\",\n    \"2075-10-13 00:00:00\",\n    \"2075-10-19 00:00:00\",\n    \"2075-10-20 00:00:00\",\n    \"2075-10-26 00:00:00\",\n    \"2075-10-27 00:00:00\",\n    \"2075-11-02 00:00:00\",\n    \"2075-11-03 00:00:00\",\n    \"2075-11-09 00:00:00\",\n    \"2075-11-10 00:00:00\",\n    \"2075-11-16 00:00:00\",\n    \"2075-11-17 00:00:00\",\n    \"2075-11-23 00:00:00\",\n    \"2075-11-24 00:00:00\",\n    \"2075-11-30 00:00:00\",\n    \"2075-12-01 00:00:00\",\n    \"2075-12-07 00:00:00\",\n    \"2075-12-08 00:00:00\",\n    \"2075-12-14 00:00:00\",\n    \"2075-12-15 00:00:00\",\n    \"2075-12-21 00:00:00\",\n    \"2075-12-22 00:00:00\",\n    \"2075-12-28 00:00:00\",\n    \"2075-12-29 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-04 00:00:00\",\n    \"2076-01-05 00:00:00\",\n    \"2076-01-11 00:00:00\",\n    \"2076-01-12 00:00:00\",\n    \"2076-01-18 00:00:00\",\n    \"2076-01-19 00:00:00\",\n    \"2076-01-25 00:00:00\",\n    \"2076-01-26 00:00:00\",\n    \"2076-02-01 00:00:00\",\n    \"2076-02-04 00:00:00\",\n    \"2076-02-05 00:00:00\",\n    \"2076-02-06 00:00:00\",\n    \"2076-02-07 00:00:00\",\n    \"2076-02-08 00:00:00\",\n    \"2076-02-09 00:00:00\",\n    \"2076-02-10 00:00:00\",\n    \"2076-02-11 00:00:00\",\n    \"2076-02-16 00:00:00\",\n    \"2076-02-22 00:00:00\",\n    \"2076-02-23 00:00:00\",\n    \"2076-02-29 00:00:00\",\n    \"2076-03-01 00:00:00\",\n    \"2076-03-07 00:00:00\",\n    \"2076-03-08 00:00:00\",\n    \"2076-03-14 00:00:00\",\n    \"2076-03-15 00:00:00\",\n    \"2076-03-21 00:00:00\",\n    \"2076-03-22 00:00:00\",\n    \"2076-03-28 00:00:00\",\n    \"2076-03-29 00:00:00\",\n    \"2076-04-03 00:00:00\",\n    \"2076-04-04 00:00:00\",\n    \"2076-04-05 00:00:00\",\n    \"2076-04-11 00:00:00\",\n    \"2076-04-12 00:00:00\",\n    \"2076-04-18 00:00:00\",\n    \"2076-04-19 00:00:00\",\n    \"2076-04-25 00:00:00\",\n    \"2076-04-26 00:00:00\",\n    \"2076-05-01 00:00:00\",\n    \"2076-05-02 00:00:00\",\n    \"2076-05-03 00:00:00\",\n    \"2076-05-04 00:00:00\",\n    \"2076-05-05 00:00:00\",\n    \"2076-05-10 00:00:00\",\n    \"2076-05-16 00:00:00\",\n    \"2076-05-17 00:00:00\",\n    \"2076-05-23 00:00:00\",\n    \"2076-05-24 00:00:00\",\n    \"2076-05-30 00:00:00\",\n    \"2076-05-31 00:00:00\",\n    \"2076-06-06 00:00:00\",\n    \"2076-06-07 00:00:00\",\n    \"2076-06-08 00:00:00\",\n    \"2076-06-13 00:00:00\",\n    \"2076-06-14 00:00:00\",\n    \"2076-06-20 00:00:00\",\n    \"2076-06-21 00:00:00\",\n    \"2076-06-27 00:00:00\",\n    \"2076-06-28 00:00:00\",\n    \"2076-07-04 00:00:00\",\n    \"2076-07-05 00:00:00\",\n    \"2076-07-11 00:00:00\",\n    \"2076-07-12 00:00:00\",\n    \"2076-07-18 00:00:00\",\n    \"2076-07-19 00:00:00\",\n    \"2076-07-25 00:00:00\",\n    \"2076-07-26 00:00:00\",\n    \"2076-08-01 00:00:00\",\n    \"2076-08-02 00:00:00\",\n    \"2076-08-08 00:00:00\",\n    \"2076-08-09 00:00:00\",\n    \"2076-08-15 00:00:00\",\n    \"2076-08-16 00:00:00\",\n    \"2076-08-22 00:00:00\",\n    \"2076-08-23 00:00:00\",\n    \"2076-08-29 00:00:00\",\n    \"2076-08-30 00:00:00\",\n    \"2076-09-05 00:00:00\",\n    \"2076-09-06 00:00:00\",\n    \"2076-09-12 00:00:00\",\n    \"2076-09-13 00:00:00\",\n    \"2076-09-14 00:00:00\",\n    \"2076-09-19 00:00:00\",\n    \"2076-09-20 00:00:00\",\n    \"2076-09-26 00:00:00\",\n    \"2076-10-01 00:00:00\",\n    \"2076-10-02 00:00:00\",\n    \"2076-10-03 00:00:00\",\n    \"2076-10-04 00:00:00\",\n    \"2076-10-05 00:00:00\",\n    \"2076-10-06 00:00:00\",\n    \"2076-10-07 00:00:00\",\n    \"2076-10-11 00:00:00\",\n    \"2076-10-17 00:00:00\",\n    \"2076-10-18 00:00:00\",\n    \"2076-10-24 00:00:00\",\n    \"2076-10-25 00:00:00\",\n    \"2076-10-31 00:00:00\",\n    \"2076-11-01 00:00:00\",\n    \"2076-11-07 00:00:00\",\n    \"2076-11-08 00:00:00\",\n    \"2076-11-14 00:00:00\",\n    \"2076-11-15 00:00:00\",\n    \"2076-11-21 00:00:00\",\n    \"2076-11-22 00:00:00\",\n    \"2076-11-28 00:00:00\",\n    \"2076-11-29 00:00:00\",\n    \"2076-12-05 00:00:00\",\n    \"2076-12-06 00:00:00\",\n    \"2076-12-12 00:00:00\",\n    \"2076-12-13 00:00:00\",\n    \"2076-12-19 00:00:00\",\n    \"2076-12-20 00:00:00\",\n    \"2076-12-26 00:00:00\",\n    \"2076-12-27 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-02 00:00:00\",\n    \"2077-01-03 00:00:00\",\n    \"2077-01-09 00:00:00\",\n    \"2077-01-10 00:00:00\",\n    \"2077-01-16 00:00:00\",\n    \"2077-01-23 00:00:00\",\n    \"2077-01-24 00:00:00\",\n    \"2077-01-25 00:00:00\",\n    \"2077-01-26 00:00:00\",\n    \"2077-01-27 00:00:00\",\n    \"2077-01-28 00:00:00\",\n    \"2077-01-29 00:00:00\",\n    \"2077-01-30 00:00:00\",\n    \"2077-02-06 00:00:00\",\n    \"2077-02-07 00:00:00\",\n    \"2077-02-13 00:00:00\",\n    \"2077-02-14 00:00:00\",\n    \"2077-02-20 00:00:00\",\n    \"2077-02-21 00:00:00\",\n    \"2077-02-27 00:00:00\",\n    \"2077-02-28 00:00:00\",\n    \"2077-03-06 00:00:00\",\n    \"2077-03-07 00:00:00\",\n    \"2077-03-13 00:00:00\",\n    \"2077-03-14 00:00:00\",\n    \"2077-03-20 00:00:00\",\n    \"2077-03-21 00:00:00\",\n    \"2077-03-27 00:00:00\",\n    \"2077-03-28 00:00:00\",\n    \"2077-04-03 00:00:00\",\n    \"2077-04-04 00:00:00\",\n    \"2077-04-05 00:00:00\",\n    \"2077-04-10 00:00:00\",\n    \"2077-04-11 00:00:00\",\n    \"2077-04-17 00:00:00\",\n    \"2077-04-18 00:00:00\",\n    \"2077-04-24 00:00:00\",\n    \"2077-04-25 00:00:00\",\n    \"2077-05-01 00:00:00\",\n    \"2077-05-02 00:00:00\",\n    \"2077-05-03 00:00:00\",\n    \"2077-05-04 00:00:00\",\n    \"2077-05-05 00:00:00\",\n    \"2077-05-08 00:00:00\",\n    \"2077-05-15 00:00:00\",\n    \"2077-05-16 00:00:00\",\n    \"2077-05-22 00:00:00\",\n    \"2077-05-23 00:00:00\",\n    \"2077-05-29 00:00:00\",\n    \"2077-05-30 00:00:00\",\n    \"2077-06-05 00:00:00\",\n    \"2077-06-06 00:00:00\",\n    \"2077-06-12 00:00:00\",\n    \"2077-06-13 00:00:00\",\n    \"2077-06-19 00:00:00\",\n    \"2077-06-20 00:00:00\",\n    \"2077-06-24 00:00:00\",\n    \"2077-06-25 00:00:00\",\n    \"2077-06-26 00:00:00\",\n    \"2077-06-27 00:00:00\",\n    \"2077-07-03 00:00:00\",\n    \"2077-07-10 00:00:00\",\n    \"2077-07-11 00:00:00\",\n    \"2077-07-17 00:00:00\",\n    \"2077-07-18 00:00:00\",\n    \"2077-07-24 00:00:00\",\n    \"2077-07-25 00:00:00\",\n    \"2077-07-31 00:00:00\",\n    \"2077-08-01 00:00:00\",\n    \"2077-08-07 00:00:00\",\n    \"2077-08-08 00:00:00\",\n    \"2077-08-14 00:00:00\",\n    \"2077-08-15 00:00:00\",\n    \"2077-08-21 00:00:00\",\n    \"2077-08-22 00:00:00\",\n    \"2077-08-28 00:00:00\",\n    \"2077-08-29 00:00:00\",\n    \"2077-09-04 00:00:00\",\n    \"2077-09-05 00:00:00\",\n    \"2077-09-11 00:00:00\",\n    \"2077-09-12 00:00:00\",\n    \"2077-09-18 00:00:00\",\n    \"2077-09-19 00:00:00\",\n    \"2077-09-25 00:00:00\",\n    \"2077-10-01 00:00:00\",\n    \"2077-10-02 00:00:00\",\n    \"2077-10-03 00:00:00\",\n    \"2077-10-04 00:00:00\",\n    \"2077-10-05 00:00:00\",\n    \"2077-10-06 00:00:00\",\n    \"2077-10-07 00:00:00\",\n    \"2077-10-08 00:00:00\",\n    \"2077-10-10 00:00:00\",\n    \"2077-10-16 00:00:00\",\n    \"2077-10-17 00:00:00\",\n    \"2077-10-23 00:00:00\",\n    \"2077-10-24 00:00:00\",\n    \"2077-10-30 00:00:00\",\n    \"2077-10-31 00:00:00\",\n    \"2077-11-06 00:00:00\",\n    \"2077-11-07 00:00:00\",\n    \"2077-11-13 00:00:00\",\n    \"2077-11-14 00:00:00\",\n    \"2077-11-20 00:00:00\",\n    \"2077-11-21 00:00:00\",\n    \"2077-11-27 00:00:00\",\n    \"2077-11-28 00:00:00\",\n    \"2077-12-04 00:00:00\",\n    \"2077-12-05 00:00:00\",\n    \"2077-12-11 00:00:00\",\n    \"2077-12-12 00:00:00\",\n    \"2077-12-18 00:00:00\",\n    \"2077-12-19 00:00:00\",\n    \"2077-12-25 00:00:00\",\n    \"2077-12-26 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-01-02 00:00:00\",\n    \"2078-01-03 00:00:00\",\n    \"2078-01-08 00:00:00\",\n    \"2078-01-09 00:00:00\",\n    \"2078-01-15 00:00:00\",\n    \"2078-01-16 00:00:00\",\n    \"2078-01-22 00:00:00\",\n    \"2078-01-23 00:00:00\",\n    \"2078-01-29 00:00:00\",\n    \"2078-01-30 00:00:00\",\n    \"2078-02-05 00:00:00\",\n    \"2078-02-11 00:00:00\",\n    \"2078-02-12 00:00:00\",\n    \"2078-02-13 00:00:00\",\n    \"2078-02-14 00:00:00\",\n    \"2078-02-15 00:00:00\",\n    \"2078-02-16 00:00:00\",\n    \"2078-02-17 00:00:00\",\n    \"2078-02-18 00:00:00\",\n    \"2078-02-20 00:00:00\",\n    \"2078-02-26 00:00:00\",\n    \"2078-02-27 00:00:00\",\n    \"2078-03-05 00:00:00\",\n    \"2078-03-06 00:00:00\",\n    \"2078-03-12 00:00:00\",\n    \"2078-03-13 00:00:00\",\n    \"2078-03-19 00:00:00\",\n    \"2078-03-20 00:00:00\",\n    \"2078-03-26 00:00:00\",\n    \"2078-03-27 00:00:00\",\n    \"2078-04-02 00:00:00\",\n    \"2078-04-03 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-04-09 00:00:00\",\n    \"2078-04-10 00:00:00\",\n    \"2078-04-16 00:00:00\",\n    \"2078-04-17 00:00:00\",\n    \"2078-04-23 00:00:00\",\n    \"2078-04-24 00:00:00\",\n    \"2078-04-30 00:00:00\",\n    \"2078-05-01 00:00:00\",\n    \"2078-05-02 00:00:00\",\n    \"2078-05-03 00:00:00\",\n    \"2078-05-04 00:00:00\",\n    \"2078-05-07 00:00:00\",\n    \"2078-05-14 00:00:00\",\n    \"2078-05-15 00:00:00\",\n    \"2078-05-21 00:00:00\",\n    \"2078-05-22 00:00:00\",\n    \"2078-05-28 00:00:00\",\n    \"2078-05-29 00:00:00\",\n    \"2078-06-04 00:00:00\",\n    \"2078-06-11 00:00:00\",\n    \"2078-06-12 00:00:00\",\n    \"2078-06-13 00:00:00\",\n    \"2078-06-14 00:00:00\",\n    \"2078-06-18 00:00:00\",\n    \"2078-06-19 00:00:00\",\n    \"2078-06-25 00:00:00\",\n    \"2078-06-26 00:00:00\",\n    \"2078-07-02 00:00:00\",\n    \"2078-07-03 00:00:00\",\n    \"2078-07-09 00:00:00\",\n    \"2078-07-10 00:00:00\",\n    \"2078-07-16 00:00:00\",\n    \"2078-07-17 00:00:00\",\n    \"2078-07-23 00:00:00\",\n    \"2078-07-24 00:00:00\",\n    \"2078-07-30 00:00:00\",\n    \"2078-07-31 00:00:00\",\n    \"2078-08-06 00:00:00\",\n    \"2078-08-07 00:00:00\",\n    \"2078-08-13 00:00:00\",\n    \"2078-08-14 00:00:00\",\n    \"2078-08-20 00:00:00\",\n    \"2078-08-21 00:00:00\",\n    \"2078-08-27 00:00:00\",\n    \"2078-08-28 00:00:00\",\n    \"2078-09-03 00:00:00\",\n    \"2078-09-04 00:00:00\",\n    \"2078-09-10 00:00:00\",\n    \"2078-09-17 00:00:00\",\n    \"2078-09-18 00:00:00\",\n    \"2078-09-19 00:00:00\",\n    \"2078-09-20 00:00:00\",\n    \"2078-09-24 00:00:00\",\n    \"2078-10-01 00:00:00\",\n    \"2078-10-02 00:00:00\",\n    \"2078-10-03 00:00:00\",\n    \"2078-10-04 00:00:00\",\n    \"2078-10-05 00:00:00\",\n    \"2078-10-06 00:00:00\",\n    \"2078-10-07 00:00:00\",\n    \"2078-10-08 00:00:00\",\n    \"2078-10-15 00:00:00\",\n    \"2078-10-16 00:00:00\",\n    \"2078-10-22 00:00:00\",\n    \"2078-10-23 00:00:00\",\n    \"2078-10-29 00:00:00\",\n    \"2078-10-30 00:00:00\",\n    \"2078-11-05 00:00:00\",\n    \"2078-11-06 00:00:00\",\n    \"2078-11-12 00:00:00\",\n    \"2078-11-13 00:00:00\",\n    \"2078-11-19 00:00:00\",\n    \"2078-11-20 00:00:00\",\n    \"2078-11-26 00:00:00\",\n    \"2078-11-27 00:00:00\",\n    \"2078-12-03 00:00:00\",\n    \"2078-12-04 00:00:00\",\n    \"2078-12-10 00:00:00\",\n    \"2078-12-11 00:00:00\",\n    \"2078-12-17 00:00:00\",\n    \"2078-12-18 00:00:00\",\n    \"2078-12-24 00:00:00\",\n    \"2078-12-25 00:00:00\",\n    \"2078-12-31 00:00:00\",\n    \"2079-01-01 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-01-07 00:00:00\",\n    \"2079-01-08 00:00:00\",\n    \"2079-01-14 00:00:00\",\n    \"2079-01-15 00:00:00\",\n    \"2079-01-21 00:00:00\",\n    \"2079-01-22 00:00:00\",\n    \"2079-01-28 00:00:00\",\n    \"2079-02-01 00:00:00\",\n    \"2079-02-02 00:00:00\",\n    \"2079-02-03 00:00:00\",\n    \"2079-02-04 00:00:00\",\n    \"2079-02-05 00:00:00\",\n    \"2079-02-06 00:00:00\",\n    \"2079-02-07 00:00:00\",\n    \"2079-02-08 00:00:00\",\n    \"2079-02-12 00:00:00\",\n    \"2079-02-18 00:00:00\",\n    \"2079-02-19 00:00:00\",\n    \"2079-02-25 00:00:00\",\n    \"2079-02-26 00:00:00\",\n    \"2079-03-04 00:00:00\",\n    \"2079-03-05 00:00:00\",\n    \"2079-03-11 00:00:00\",\n    \"2079-03-12 00:00:00\",\n    \"2079-03-18 00:00:00\",\n    \"2079-03-19 00:00:00\",\n    \"2079-03-25 00:00:00\",\n    \"2079-04-01 00:00:00\",\n    \"2079-04-02 00:00:00\",\n    \"2079-04-03 00:00:00\",\n    \"2079-04-04 00:00:00\",\n    \"2079-04-08 00:00:00\",\n    \"2079-04-09 00:00:00\",\n    \"2079-04-15 00:00:00\",\n    \"2079-04-16 00:00:00\",\n    \"2079-04-22 00:00:00\",\n    \"2079-04-23 00:00:00\",\n    \"2079-04-29 00:00:00\",\n    \"2079-04-30 00:00:00\",\n    \"2079-05-01 00:00:00\",\n    \"2079-05-02 00:00:00\",\n    \"2079-05-03 00:00:00\",\n    \"2079-05-06 00:00:00\",\n    \"2079-05-13 00:00:00\",\n    \"2079-05-14 00:00:00\",\n    \"2079-05-20 00:00:00\",\n    \"2079-05-21 00:00:00\",\n    \"2079-05-27 00:00:00\",\n    \"2079-05-28 00:00:00\",\n    \"2079-06-03 00:00:00\",\n    \"2079-06-04 00:00:00\",\n    \"2079-06-05 00:00:00\",\n    \"2079-06-10 00:00:00\",\n    \"2079-06-11 00:00:00\",\n    \"2079-06-17 00:00:00\",\n    \"2079-06-18 00:00:00\",\n    \"2079-06-24 00:00:00\",\n    \"2079-06-25 00:00:00\",\n    \"2079-07-01 00:00:00\",\n    \"2079-07-02 00:00:00\",\n    \"2079-07-08 00:00:00\",\n    \"2079-07-09 00:00:00\",\n    \"2079-07-15 00:00:00\",\n    \"2079-07-16 00:00:00\",\n    \"2079-07-22 00:00:00\",\n    \"2079-07-23 00:00:00\",\n    \"2079-07-29 00:00:00\",\n    \"2079-07-30 00:00:00\",\n    \"2079-08-05 00:00:00\",\n    \"2079-08-06 00:00:00\",\n    \"2079-08-12 00:00:00\",\n    \"2079-08-13 00:00:00\",\n    \"2079-08-19 00:00:00\",\n    \"2079-08-20 00:00:00\",\n    \"2079-08-26 00:00:00\",\n    \"2079-08-27 00:00:00\",\n    \"2079-09-02 00:00:00\",\n    \"2079-09-03 00:00:00\",\n    \"2079-09-09 00:00:00\",\n    \"2079-09-10 00:00:00\",\n    \"2079-09-11 00:00:00\",\n    \"2079-09-16 00:00:00\",\n    \"2079-09-17 00:00:00\",\n    \"2079-09-23 00:00:00\",\n    \"2079-09-30 00:00:00\",\n    \"2079-10-01 00:00:00\",\n    \"2079-10-02 00:00:00\",\n    \"2079-10-03 00:00:00\",\n    \"2079-10-04 00:00:00\",\n    \"2079-10-05 00:00:00\",\n    \"2079-10-06 00:00:00\",\n    \"2079-10-07 00:00:00\",\n    \"2079-10-14 00:00:00\",\n    \"2079-10-15 00:00:00\",\n    \"2079-10-21 00:00:00\",\n    \"2079-10-22 00:00:00\",\n    \"2079-10-28 00:00:00\",\n    \"2079-10-29 00:00:00\",\n    \"2079-11-04 00:00:00\",\n    \"2079-11-05 00:00:00\",\n    \"2079-11-11 00:00:00\",\n    \"2079-11-12 00:00:00\",\n    \"2079-11-18 00:00:00\",\n    \"2079-11-19 00:00:00\",\n    \"2079-11-25 00:00:00\",\n    \"2079-11-26 00:00:00\",\n    \"2079-12-02 00:00:00\",\n    \"2079-12-03 00:00:00\",\n    \"2079-12-09 00:00:00\",\n    \"2079-12-10 00:00:00\",\n    \"2079-12-16 00:00:00\",\n    \"2079-12-17 00:00:00\",\n    \"2079-12-23 00:00:00\",\n    \"2079-12-24 00:00:00\",\n    \"2079-12-30 00:00:00\",\n    \"2079-12-31 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-06 00:00:00\",\n    \"2080-01-07 00:00:00\",\n    \"2080-01-13 00:00:00\",\n    \"2080-01-14 00:00:00\",\n    \"2080-01-21 00:00:00\",\n    \"2080-01-22 00:00:00\",\n    \"2080-01-23 00:00:00\",\n    \"2080-01-24 00:00:00\",\n    \"2080-01-25 00:00:00\",\n    \"2080-01-26 00:00:00\",\n    \"2080-01-27 00:00:00\",\n    \"2080-01-28 00:00:00\",\n    \"2080-02-04 00:00:00\",\n    \"2080-02-10 00:00:00\",\n    \"2080-02-11 00:00:00\",\n    \"2080-02-17 00:00:00\",\n    \"2080-02-18 00:00:00\",\n    \"2080-02-24 00:00:00\",\n    \"2080-02-25 00:00:00\",\n    \"2080-03-02 00:00:00\",\n    \"2080-03-03 00:00:00\",\n    \"2080-03-09 00:00:00\",\n    \"2080-03-10 00:00:00\",\n    \"2080-03-16 00:00:00\",\n    \"2080-03-17 00:00:00\",\n    \"2080-03-23 00:00:00\",\n    \"2080-03-24 00:00:00\",\n    \"2080-03-30 00:00:00\",\n    \"2080-03-31 00:00:00\",\n    \"2080-04-03 00:00:00\",\n    \"2080-04-06 00:00:00\",\n    \"2080-04-07 00:00:00\",\n    \"2080-04-13 00:00:00\",\n    \"2080-04-14 00:00:00\",\n    \"2080-04-20 00:00:00\",\n    \"2080-04-21 00:00:00\",\n    \"2080-04-27 00:00:00\",\n    \"2080-04-28 00:00:00\",\n    \"2080-05-01 00:00:00\",\n    \"2080-05-02 00:00:00\",\n    \"2080-05-04 00:00:00\",\n    \"2080-05-05 00:00:00\",\n    \"2080-05-11 00:00:00\",\n    \"2080-05-12 00:00:00\",\n    \"2080-05-18 00:00:00\",\n    \"2080-05-19 00:00:00\",\n    \"2080-05-25 00:00:00\",\n    \"2080-05-26 00:00:00\",\n    \"2080-06-01 00:00:00\",\n    \"2080-06-02 00:00:00\",\n    \"2080-06-08 00:00:00\",\n    \"2080-06-09 00:00:00\",\n    \"2080-06-15 00:00:00\",\n    \"2080-06-16 00:00:00\",\n    \"2080-06-22 00:00:00\",\n    \"2080-06-23 00:00:00\",\n    \"2080-06-24 00:00:00\",\n    \"2080-06-29 00:00:00\",\n    \"2080-06-30 00:00:00\",\n    \"2080-07-06 00:00:00\",\n    \"2080-07-07 00:00:00\",\n    \"2080-07-13 00:00:00\",\n    \"2080-07-14 00:00:00\",\n    \"2080-07-20 00:00:00\",\n    \"2080-07-21 00:00:00\",\n    \"2080-07-27 00:00:00\",\n    \"2080-07-28 00:00:00\",\n    \"2080-08-03 00:00:00\",\n    \"2080-08-04 00:00:00\",\n    \"2080-08-10 00:00:00\",\n    \"2080-08-11 00:00:00\",\n    \"2080-08-17 00:00:00\",\n    \"2080-08-18 00:00:00\",\n    \"2080-08-24 00:00:00\",\n    \"2080-08-25 00:00:00\",\n    \"2080-08-31 00:00:00\",\n    \"2080-09-01 00:00:00\",\n    \"2080-09-07 00:00:00\",\n    \"2080-09-08 00:00:00\",\n    \"2080-09-14 00:00:00\",\n    \"2080-09-15 00:00:00\",\n    \"2080-09-21 00:00:00\",\n    \"2080-09-28 00:00:00\",\n    \"2080-09-29 00:00:00\",\n    \"2080-09-30 00:00:00\",\n    \"2080-10-01 00:00:00\",\n    \"2080-10-02 00:00:00\",\n    \"2080-10-03 00:00:00\",\n    \"2080-10-04 00:00:00\",\n    \"2080-10-05 00:00:00\",\n    \"2080-10-06 00:00:00\",\n    \"2080-10-07 00:00:00\",\n    \"2080-10-13 00:00:00\",\n    \"2080-10-19 00:00:00\",\n    \"2080-10-20 00:00:00\",\n    \"2080-10-26 00:00:00\",\n    \"2080-10-27 00:00:00\",\n    \"2080-11-02 00:00:00\",\n    \"2080-11-03 00:00:00\",\n    \"2080-11-09 00:00:00\",\n    \"2080-11-10 00:00:00\",\n    \"2080-11-16 00:00:00\",\n    \"2080-11-17 00:00:00\",\n    \"2080-11-23 00:00:00\",\n    \"2080-11-24 00:00:00\",\n    \"2080-11-30 00:00:00\",\n    \"2080-12-01 00:00:00\",\n    \"2080-12-07 00:00:00\",\n    \"2080-12-08 00:00:00\",\n    \"2080-12-14 00:00:00\",\n    \"2080-12-15 00:00:00\",\n    \"2080-12-21 00:00:00\",\n    \"2080-12-22 00:00:00\",\n    \"2080-12-28 00:00:00\",\n    \"2080-12-29 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-04 00:00:00\",\n    \"2081-01-05 00:00:00\",\n    \"2081-01-11 00:00:00\",\n    \"2081-01-12 00:00:00\",\n    \"2081-01-18 00:00:00\",\n    \"2081-01-19 00:00:00\",\n    \"2081-01-25 00:00:00\",\n    \"2081-01-26 00:00:00\",\n    \"2081-02-01 00:00:00\",\n    \"2081-02-08 00:00:00\",\n    \"2081-02-09 00:00:00\",\n    \"2081-02-10 00:00:00\",\n    \"2081-02-11 00:00:00\",\n    \"2081-02-12 00:00:00\",\n    \"2081-02-13 00:00:00\",\n    \"2081-02-14 00:00:00\",\n    \"2081-02-15 00:00:00\",\n    \"2081-02-22 00:00:00\",\n    \"2081-02-23 00:00:00\",\n    \"2081-03-01 00:00:00\",\n    \"2081-03-02 00:00:00\",\n    \"2081-03-08 00:00:00\",\n    \"2081-03-09 00:00:00\",\n    \"2081-03-15 00:00:00\",\n    \"2081-03-16 00:00:00\",\n    \"2081-03-22 00:00:00\",\n    \"2081-03-23 00:00:00\",\n    \"2081-03-29 00:00:00\",\n    \"2081-03-30 00:00:00\",\n    \"2081-04-03 00:00:00\",\n    \"2081-04-04 00:00:00\",\n    \"2081-04-05 00:00:00\",\n    \"2081-04-06 00:00:00\",\n    \"2081-04-12 00:00:00\",\n    \"2081-04-19 00:00:00\",\n    \"2081-04-20 00:00:00\",\n    \"2081-04-26 00:00:00\",\n    \"2081-05-01 00:00:00\",\n    \"2081-05-02 00:00:00\",\n    \"2081-05-03 00:00:00\",\n    \"2081-05-04 00:00:00\",\n    \"2081-05-05 00:00:00\",\n    \"2081-05-10 00:00:00\",\n    \"2081-05-11 00:00:00\",\n    \"2081-05-17 00:00:00\",\n    \"2081-05-18 00:00:00\",\n    \"2081-05-24 00:00:00\",\n    \"2081-05-25 00:00:00\",\n    \"2081-05-31 00:00:00\",\n    \"2081-06-01 00:00:00\",\n    \"2081-06-07 00:00:00\",\n    \"2081-06-08 00:00:00\",\n    \"2081-06-11 00:00:00\",\n    \"2081-06-14 00:00:00\",\n    \"2081-06-15 00:00:00\",\n    \"2081-06-21 00:00:00\",\n    \"2081-06-22 00:00:00\",\n    \"2081-06-28 00:00:00\",\n    \"2081-06-29 00:00:00\",\n    \"2081-07-05 00:00:00\",\n    \"2081-07-06 00:00:00\",\n    \"2081-07-12 00:00:00\",\n    \"2081-07-13 00:00:00\",\n    \"2081-07-19 00:00:00\",\n    \"2081-07-20 00:00:00\",\n    \"2081-07-26 00:00:00\",\n    \"2081-07-27 00:00:00\",\n    \"2081-08-02 00:00:00\",\n    \"2081-08-03 00:00:00\",\n    \"2081-08-09 00:00:00\",\n    \"2081-08-10 00:00:00\",\n    \"2081-08-16 00:00:00\",\n    \"2081-08-17 00:00:00\",\n    \"2081-08-23 00:00:00\",\n    \"2081-08-24 00:00:00\",\n    \"2081-08-30 00:00:00\",\n    \"2081-08-31 00:00:00\",\n    \"2081-09-06 00:00:00\",\n    \"2081-09-07 00:00:00\",\n    \"2081-09-13 00:00:00\",\n    \"2081-09-14 00:00:00\",\n    \"2081-09-17 00:00:00\",\n    \"2081-09-20 00:00:00\",\n    \"2081-09-21 00:00:00\",\n    \"2081-09-27 00:00:00\",\n    \"2081-10-01 00:00:00\",\n    \"2081-10-02 00:00:00\",\n    \"2081-10-03 00:00:00\",\n    \"2081-10-04 00:00:00\",\n    \"2081-10-05 00:00:00\",\n    \"2081-10-06 00:00:00\",\n    \"2081-10-07 00:00:00\",\n    \"2081-10-12 00:00:00\",\n    \"2081-10-18 00:00:00\",\n    \"2081-10-19 00:00:00\",\n    \"2081-10-25 00:00:00\",\n    \"2081-10-26 00:00:00\",\n    \"2081-11-01 00:00:00\",\n    \"2081-11-02 00:00:00\",\n    \"2081-11-08 00:00:00\",\n    \"2081-11-09 00:00:00\",\n    \"2081-11-15 00:00:00\",\n    \"2081-11-16 00:00:00\",\n    \"2081-11-22 00:00:00\",\n    \"2081-11-23 00:00:00\",\n    \"2081-11-29 00:00:00\",\n    \"2081-11-30 00:00:00\",\n    \"2081-12-06 00:00:00\",\n    \"2081-12-07 00:00:00\",\n    \"2081-12-13 00:00:00\",\n    \"2081-12-14 00:00:00\",\n    \"2081-12-20 00:00:00\",\n    \"2081-12-21 00:00:00\",\n    \"2081-12-27 00:00:00\",\n    \"2081-12-28 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-02 00:00:00\",\n    \"2082-01-03 00:00:00\",\n    \"2082-01-10 00:00:00\",\n    \"2082-01-11 00:00:00\",\n    \"2082-01-17 00:00:00\",\n    \"2082-01-18 00:00:00\",\n    \"2082-01-24 00:00:00\",\n    \"2082-01-28 00:00:00\",\n    \"2082-01-29 00:00:00\",\n    \"2082-01-30 00:00:00\",\n    \"2082-01-31 00:00:00\",\n    \"2082-02-01 00:00:00\",\n    \"2082-02-02 00:00:00\",\n    \"2082-02-03 00:00:00\",\n    \"2082-02-04 00:00:00\",\n    \"2082-02-08 00:00:00\",\n    \"2082-02-14 00:00:00\",\n    \"2082-02-15 00:00:00\",\n    \"2082-02-21 00:00:00\",\n    \"2082-02-22 00:00:00\",\n    \"2082-02-28 00:00:00\",\n    \"2082-03-01 00:00:00\",\n    \"2082-03-07 00:00:00\",\n    \"2082-03-08 00:00:00\",\n    \"2082-03-14 00:00:00\",\n    \"2082-03-15 00:00:00\",\n    \"2082-03-21 00:00:00\",\n    \"2082-03-22 00:00:00\",\n    \"2082-03-28 00:00:00\",\n    \"2082-03-29 00:00:00\",\n    \"2082-04-04 00:00:00\",\n    \"2082-04-05 00:00:00\",\n    \"2082-04-06 00:00:00\",\n    \"2082-04-11 00:00:00\",\n    \"2082-04-12 00:00:00\",\n    \"2082-04-18 00:00:00\",\n    \"2082-04-19 00:00:00\",\n    \"2082-04-25 00:00:00\",\n    \"2082-04-26 00:00:00\",\n    \"2082-05-01 00:00:00\",\n    \"2082-05-02 00:00:00\",\n    \"2082-05-03 00:00:00\",\n    \"2082-05-04 00:00:00\",\n    \"2082-05-05 00:00:00\",\n    \"2082-05-10 00:00:00\",\n    \"2082-05-16 00:00:00\",\n    \"2082-05-17 00:00:00\",\n    \"2082-05-23 00:00:00\",\n    \"2082-05-24 00:00:00\",\n    \"2082-05-30 00:00:00\",\n    \"2082-05-31 00:00:00\",\n    \"2082-06-01 00:00:00\",\n    \"2082-06-06 00:00:00\",\n    \"2082-06-07 00:00:00\",\n    \"2082-06-13 00:00:00\",\n    \"2082-06-14 00:00:00\",\n    \"2082-06-20 00:00:00\",\n    \"2082-06-21 00:00:00\",\n    \"2082-06-27 00:00:00\",\n    \"2082-06-28 00:00:00\",\n    \"2082-07-04 00:00:00\",\n    \"2082-07-05 00:00:00\",\n    \"2082-07-11 00:00:00\",\n    \"2082-07-12 00:00:00\",\n    \"2082-07-18 00:00:00\",\n    \"2082-07-19 00:00:00\",\n    \"2082-07-25 00:00:00\",\n    \"2082-07-26 00:00:00\",\n    \"2082-08-01 00:00:00\",\n    \"2082-08-02 00:00:00\",\n    \"2082-08-08 00:00:00\",\n    \"2082-08-09 00:00:00\",\n    \"2082-08-15 00:00:00\",\n    \"2082-08-16 00:00:00\",\n    \"2082-08-22 00:00:00\",\n    \"2082-08-23 00:00:00\",\n    \"2082-08-29 00:00:00\",\n    \"2082-08-30 00:00:00\",\n    \"2082-09-05 00:00:00\",\n    \"2082-09-06 00:00:00\",\n    \"2082-09-12 00:00:00\",\n    \"2082-09-13 00:00:00\",\n    \"2082-09-19 00:00:00\",\n    \"2082-09-20 00:00:00\",\n    \"2082-09-26 00:00:00\",\n    \"2082-10-01 00:00:00\",\n    \"2082-10-02 00:00:00\",\n    \"2082-10-03 00:00:00\",\n    \"2082-10-04 00:00:00\",\n    \"2082-10-05 00:00:00\",\n    \"2082-10-06 00:00:00\",\n    \"2082-10-07 00:00:00\",\n    \"2082-10-08 00:00:00\",\n    \"2082-10-11 00:00:00\",\n    \"2082-10-17 00:00:00\",\n    \"2082-10-18 00:00:00\",\n    \"2082-10-24 00:00:00\",\n    \"2082-10-25 00:00:00\",\n    \"2082-10-31 00:00:00\",\n    \"2082-11-01 00:00:00\",\n    \"2082-11-07 00:00:00\",\n    \"2082-11-08 00:00:00\",\n    \"2082-11-14 00:00:00\",\n    \"2082-11-15 00:00:00\",\n    \"2082-11-21 00:00:00\",\n    \"2082-11-22 00:00:00\",\n    \"2082-11-28 00:00:00\",\n    \"2082-11-29 00:00:00\",\n    \"2082-12-05 00:00:00\",\n    \"2082-12-06 00:00:00\",\n    \"2082-12-12 00:00:00\",\n    \"2082-12-13 00:00:00\",\n    \"2082-12-19 00:00:00\",\n    \"2082-12-20 00:00:00\",\n    \"2082-12-26 00:00:00\",\n    \"2082-12-27 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-02 00:00:00\",\n    \"2083-01-03 00:00:00\",\n    \"2083-01-09 00:00:00\",\n    \"2083-01-10 00:00:00\",\n    \"2083-01-16 00:00:00\",\n    \"2083-01-17 00:00:00\",\n    \"2083-01-23 00:00:00\",\n    \"2083-01-24 00:00:00\",\n    \"2083-01-30 00:00:00\",\n    \"2083-01-31 00:00:00\",\n    \"2083-02-06 00:00:00\",\n    \"2083-02-07 00:00:00\",\n    \"2083-02-13 00:00:00\",\n    \"2083-02-16 00:00:00\",\n    \"2083-02-17 00:00:00\",\n    \"2083-02-18 00:00:00\",\n    \"2083-02-19 00:00:00\",\n    \"2083-02-20 00:00:00\",\n    \"2083-02-21 00:00:00\",\n    \"2083-02-22 00:00:00\",\n    \"2083-02-23 00:00:00\",\n    \"2083-02-28 00:00:00\",\n    \"2083-03-06 00:00:00\",\n    \"2083-03-07 00:00:00\",\n    \"2083-03-13 00:00:00\",\n    \"2083-03-14 00:00:00\",\n    \"2083-03-20 00:00:00\",\n    \"2083-03-21 00:00:00\",\n    \"2083-03-27 00:00:00\",\n    \"2083-03-28 00:00:00\",\n    \"2083-04-03 00:00:00\",\n    \"2083-04-04 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-04-10 00:00:00\",\n    \"2083-04-11 00:00:00\",\n    \"2083-04-17 00:00:00\",\n    \"2083-04-18 00:00:00\",\n    \"2083-04-24 00:00:00\",\n    \"2083-04-25 00:00:00\",\n    \"2083-05-01 00:00:00\",\n    \"2083-05-02 00:00:00\",\n    \"2083-05-03 00:00:00\",\n    \"2083-05-04 00:00:00\",\n    \"2083-05-05 00:00:00\",\n    \"2083-05-08 00:00:00\",\n    \"2083-05-15 00:00:00\",\n    \"2083-05-16 00:00:00\",\n    \"2083-05-22 00:00:00\",\n    \"2083-05-23 00:00:00\",\n    \"2083-05-29 00:00:00\",\n    \"2083-05-30 00:00:00\",\n    \"2083-06-05 00:00:00\",\n    \"2083-06-06 00:00:00\",\n    \"2083-06-12 00:00:00\",\n    \"2083-06-13 00:00:00\",\n    \"2083-06-19 00:00:00\",\n    \"2083-06-20 00:00:00\",\n    \"2083-06-21 00:00:00\",\n    \"2083-06-26 00:00:00\",\n    \"2083-06-27 00:00:00\",\n    \"2083-07-03 00:00:00\",\n    \"2083-07-04 00:00:00\",\n    \"2083-07-10 00:00:00\",\n    \"2083-07-11 00:00:00\",\n    \"2083-07-17 00:00:00\",\n    \"2083-07-18 00:00:00\",\n    \"2083-07-24 00:00:00\",\n    \"2083-07-25 00:00:00\",\n    \"2083-07-31 00:00:00\",\n    \"2083-08-01 00:00:00\",\n    \"2083-08-07 00:00:00\",\n    \"2083-08-08 00:00:00\",\n    \"2083-08-14 00:00:00\",\n    \"2083-08-15 00:00:00\",\n    \"2083-08-21 00:00:00\",\n    \"2083-08-22 00:00:00\",\n    \"2083-08-28 00:00:00\",\n    \"2083-08-29 00:00:00\",\n    \"2083-09-04 00:00:00\",\n    \"2083-09-05 00:00:00\",\n    \"2083-09-11 00:00:00\",\n    \"2083-09-12 00:00:00\",\n    \"2083-09-18 00:00:00\",\n    \"2083-09-25 00:00:00\",\n    \"2083-09-26 00:00:00\",\n    \"2083-09-27 00:00:00\",\n    \"2083-10-01 00:00:00\",\n    \"2083-10-02 00:00:00\",\n    \"2083-10-03 00:00:00\",\n    \"2083-10-04 00:00:00\",\n    \"2083-10-05 00:00:00\",\n    \"2083-10-06 00:00:00\",\n    \"2083-10-07 00:00:00\",\n    \"2083-10-10 00:00:00\",\n    \"2083-10-16 00:00:00\",\n    \"2083-10-17 00:00:00\",\n    \"2083-10-23 00:00:00\",\n    \"2083-10-24 00:00:00\",\n    \"2083-10-30 00:00:00\",\n    \"2083-10-31 00:00:00\",\n    \"2083-11-06 00:00:00\",\n    \"2083-11-07 00:00:00\",\n    \"2083-11-13 00:00:00\",\n    \"2083-11-14 00:00:00\",\n    \"2083-11-20 00:00:00\",\n    \"2083-11-21 00:00:00\",\n    \"2083-11-27 00:00:00\",\n    \"2083-11-28 00:00:00\",\n    \"2083-12-04 00:00:00\",\n    \"2083-12-05 00:00:00\",\n    \"2083-12-11 00:00:00\",\n    \"2083-12-12 00:00:00\",\n    \"2083-12-18 00:00:00\",\n    \"2083-12-19 00:00:00\",\n    \"2083-12-25 00:00:00\",\n    \"2083-12-26 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-01-02 00:00:00\",\n    \"2084-01-03 00:00:00\",\n    \"2084-01-08 00:00:00\",\n    \"2084-01-09 00:00:00\",\n    \"2084-01-15 00:00:00\",\n    \"2084-01-16 00:00:00\",\n    \"2084-01-22 00:00:00\",\n    \"2084-01-23 00:00:00\",\n    \"2084-01-29 00:00:00\",\n    \"2084-02-05 00:00:00\",\n    \"2084-02-06 00:00:00\",\n    \"2084-02-07 00:00:00\",\n    \"2084-02-08 00:00:00\",\n    \"2084-02-09 00:00:00\",\n    \"2084-02-10 00:00:00\",\n    \"2084-02-11 00:00:00\",\n    \"2084-02-12 00:00:00\",\n    \"2084-02-19 00:00:00\",\n    \"2084-02-20 00:00:00\",\n    \"2084-02-26 00:00:00\",\n    \"2084-02-27 00:00:00\",\n    \"2084-03-04 00:00:00\",\n    \"2084-03-05 00:00:00\",\n    \"2084-03-11 00:00:00\",\n    \"2084-03-12 00:00:00\",\n    \"2084-03-18 00:00:00\",\n    \"2084-03-19 00:00:00\",\n    \"2084-03-25 00:00:00\",\n    \"2084-03-26 00:00:00\",\n    \"2084-04-01 00:00:00\",\n    \"2084-04-02 00:00:00\",\n    \"2084-04-03 00:00:00\",\n    \"2084-04-08 00:00:00\",\n    \"2084-04-09 00:00:00\",\n    \"2084-04-15 00:00:00\",\n    \"2084-04-16 00:00:00\",\n    \"2084-04-22 00:00:00\",\n    \"2084-04-23 00:00:00\",\n    \"2084-04-29 00:00:00\",\n    \"2084-04-30 00:00:00\",\n    \"2084-05-01 00:00:00\",\n    \"2084-05-02 00:00:00\",\n    \"2084-05-03 00:00:00\",\n    \"2084-05-06 00:00:00\",\n    \"2084-05-13 00:00:00\",\n    \"2084-05-14 00:00:00\",\n    \"2084-05-20 00:00:00\",\n    \"2084-05-21 00:00:00\",\n    \"2084-05-27 00:00:00\",\n    \"2084-05-28 00:00:00\",\n    \"2084-06-03 00:00:00\",\n    \"2084-06-04 00:00:00\",\n    \"2084-06-07 00:00:00\",\n    \"2084-06-10 00:00:00\",\n    \"2084-06-11 00:00:00\",\n    \"2084-06-17 00:00:00\",\n    \"2084-06-18 00:00:00\",\n    \"2084-06-24 00:00:00\",\n    \"2084-06-25 00:00:00\",\n    \"2084-07-01 00:00:00\",\n    \"2084-07-02 00:00:00\",\n    \"2084-07-08 00:00:00\",\n    \"2084-07-09 00:00:00\",\n    \"2084-07-15 00:00:00\",\n    \"2084-07-16 00:00:00\",\n    \"2084-07-22 00:00:00\",\n    \"2084-07-23 00:00:00\",\n    \"2084-07-29 00:00:00\",\n    \"2084-07-30 00:00:00\",\n    \"2084-08-05 00:00:00\",\n    \"2084-08-06 00:00:00\",\n    \"2084-08-12 00:00:00\",\n    \"2084-08-13 00:00:00\",\n    \"2084-08-19 00:00:00\",\n    \"2084-08-20 00:00:00\",\n    \"2084-08-26 00:00:00\",\n    \"2084-08-27 00:00:00\",\n    \"2084-09-02 00:00:00\",\n    \"2084-09-03 00:00:00\",\n    \"2084-09-09 00:00:00\",\n    \"2084-09-10 00:00:00\",\n    \"2084-09-14 00:00:00\",\n    \"2084-09-15 00:00:00\",\n    \"2084-09-16 00:00:00\",\n    \"2084-09-17 00:00:00\",\n    \"2084-09-23 00:00:00\",\n    \"2084-09-30 00:00:00\",\n    \"2084-10-01 00:00:00\",\n    \"2084-10-02 00:00:00\",\n    \"2084-10-03 00:00:00\",\n    \"2084-10-04 00:00:00\",\n    \"2084-10-05 00:00:00\",\n    \"2084-10-06 00:00:00\",\n    \"2084-10-07 00:00:00\",\n    \"2084-10-14 00:00:00\",\n    \"2084-10-15 00:00:00\",\n    \"2084-10-21 00:00:00\",\n    \"2084-10-22 00:00:00\",\n    \"2084-10-28 00:00:00\",\n    \"2084-10-29 00:00:00\",\n    \"2084-11-04 00:00:00\",\n    \"2084-11-05 00:00:00\",\n    \"2084-11-11 00:00:00\",\n    \"2084-11-12 00:00:00\",\n    \"2084-11-18 00:00:00\",\n    \"2084-11-19 00:00:00\",\n    \"2084-11-25 00:00:00\",\n    \"2084-11-26 00:00:00\",\n    \"2084-12-02 00:00:00\",\n    \"2084-12-03 00:00:00\",\n    \"2084-12-09 00:00:00\",\n    \"2084-12-10 00:00:00\",\n    \"2084-12-16 00:00:00\",\n    \"2084-12-17 00:00:00\",\n    \"2084-12-23 00:00:00\",\n    \"2084-12-24 00:00:00\",\n    \"2084-12-30 00:00:00\",\n    \"2084-12-31 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-06 00:00:00\",\n    \"2085-01-07 00:00:00\",\n    \"2085-01-13 00:00:00\",\n    \"2085-01-14 00:00:00\",\n    \"2085-01-20 00:00:00\",\n    \"2085-01-25 00:00:00\",\n    \"2085-01-26 00:00:00\",\n    \"2085-01-27 00:00:00\",\n    \"2085-01-28 00:00:00\",\n    \"2085-01-29 00:00:00\",\n    \"2085-01-30 00:00:00\",\n    \"2085-01-31 00:00:00\",\n    \"2085-02-01 00:00:00\",\n    \"2085-02-04 00:00:00\",\n    \"2085-02-10 00:00:00\",\n    \"2085-02-11 00:00:00\",\n    \"2085-02-17 00:00:00\",\n    \"2085-02-18 00:00:00\",\n    \"2085-02-24 00:00:00\",\n    \"2085-02-25 00:00:00\",\n    \"2085-03-03 00:00:00\",\n    \"2085-03-04 00:00:00\",\n    \"2085-03-10 00:00:00\",\n    \"2085-03-11 00:00:00\",\n    \"2085-03-17 00:00:00\",\n    \"2085-03-18 00:00:00\",\n    \"2085-03-24 00:00:00\",\n    \"2085-03-31 00:00:00\",\n    \"2085-04-01 00:00:00\",\n    \"2085-04-02 00:00:00\",\n    \"2085-04-03 00:00:00\",\n    \"2085-04-07 00:00:00\",\n    \"2085-04-08 00:00:00\",\n    \"2085-04-14 00:00:00\",\n    \"2085-04-15 00:00:00\",\n    \"2085-04-21 00:00:00\",\n    \"2085-04-22 00:00:00\",\n    \"2085-04-28 00:00:00\",\n    \"2085-04-29 00:00:00\",\n    \"2085-04-30 00:00:00\",\n    \"2085-05-01 00:00:00\",\n    \"2085-05-02 00:00:00\",\n    \"2085-05-05 00:00:00\",\n    \"2085-05-06 00:00:00\",\n    \"2085-05-12 00:00:00\",\n    \"2085-05-13 00:00:00\",\n    \"2085-05-19 00:00:00\",\n    \"2085-05-20 00:00:00\",\n    \"2085-05-26 00:00:00\",\n    \"2085-05-27 00:00:00\",\n    \"2085-05-28 00:00:00\",\n    \"2085-06-02 00:00:00\",\n    \"2085-06-03 00:00:00\",\n    \"2085-06-09 00:00:00\",\n    \"2085-06-10 00:00:00\",\n    \"2085-06-16 00:00:00\",\n    \"2085-06-17 00:00:00\",\n    \"2085-06-23 00:00:00\",\n    \"2085-06-24 00:00:00\",\n    \"2085-06-30 00:00:00\",\n    \"2085-07-01 00:00:00\",\n    \"2085-07-07 00:00:00\",\n    \"2085-07-08 00:00:00\",\n    \"2085-07-14 00:00:00\",\n    \"2085-07-15 00:00:00\",\n    \"2085-07-21 00:00:00\",\n    \"2085-07-22 00:00:00\",\n    \"2085-07-28 00:00:00\",\n    \"2085-07-29 00:00:00\",\n    \"2085-08-04 00:00:00\",\n    \"2085-08-05 00:00:00\",\n    \"2085-08-11 00:00:00\",\n    \"2085-08-12 00:00:00\",\n    \"2085-08-18 00:00:00\",\n    \"2085-08-19 00:00:00\",\n    \"2085-08-25 00:00:00\",\n    \"2085-08-26 00:00:00\",\n    \"2085-09-01 00:00:00\",\n    \"2085-09-02 00:00:00\",\n    \"2085-09-08 00:00:00\",\n    \"2085-09-09 00:00:00\",\n    \"2085-09-15 00:00:00\",\n    \"2085-09-16 00:00:00\",\n    \"2085-09-22 00:00:00\",\n    \"2085-09-23 00:00:00\",\n    \"2085-09-30 00:00:00\",\n    \"2085-10-01 00:00:00\",\n    \"2085-10-02 00:00:00\",\n    \"2085-10-03 00:00:00\",\n    \"2085-10-04 00:00:00\",\n    \"2085-10-05 00:00:00\",\n    \"2085-10-06 00:00:00\",\n    \"2085-10-07 00:00:00\",\n    \"2085-10-08 00:00:00\",\n    \"2085-10-14 00:00:00\",\n    \"2085-10-20 00:00:00\",\n    \"2085-10-21 00:00:00\",\n    \"2085-10-27 00:00:00\",\n    \"2085-10-28 00:00:00\",\n    \"2085-11-03 00:00:00\",\n    \"2085-11-04 00:00:00\",\n    \"2085-11-10 00:00:00\",\n    \"2085-11-11 00:00:00\",\n    \"2085-11-17 00:00:00\",\n    \"2085-11-18 00:00:00\",\n    \"2085-11-24 00:00:00\",\n    \"2085-11-25 00:00:00\",\n    \"2085-12-01 00:00:00\",\n    \"2085-12-02 00:00:00\",\n    \"2085-12-08 00:00:00\",\n    \"2085-12-09 00:00:00\",\n    \"2085-12-15 00:00:00\",\n    \"2085-12-16 00:00:00\",\n    \"2085-12-22 00:00:00\",\n    \"2085-12-23 00:00:00\",\n    \"2085-12-30 00:00:00\",\n    \"2085-12-31 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-05 00:00:00\",\n    \"2086-01-06 00:00:00\",\n    \"2086-01-12 00:00:00\",\n    \"2086-01-13 00:00:00\",\n    \"2086-01-19 00:00:00\",\n    \"2086-01-20 00:00:00\",\n    \"2086-01-26 00:00:00\",\n    \"2086-01-27 00:00:00\",\n    \"2086-02-02 00:00:00\",\n    \"2086-02-03 00:00:00\",\n    \"2086-02-09 00:00:00\",\n    \"2086-02-13 00:00:00\",\n    \"2086-02-14 00:00:00\",\n    \"2086-02-15 00:00:00\",\n    \"2086-02-16 00:00:00\",\n    \"2086-02-17 00:00:00\",\n    \"2086-02-18 00:00:00\",\n    \"2086-02-19 00:00:00\",\n    \"2086-02-20 00:00:00\",\n    \"2086-02-24 00:00:00\",\n    \"2086-03-02 00:00:00\",\n    \"2086-03-03 00:00:00\",\n    \"2086-03-09 00:00:00\",\n    \"2086-03-10 00:00:00\",\n    \"2086-03-16 00:00:00\",\n    \"2086-03-17 00:00:00\",\n    \"2086-03-23 00:00:00\",\n    \"2086-03-24 00:00:00\",\n    \"2086-03-30 00:00:00\",\n    \"2086-03-31 00:00:00\",\n    \"2086-04-04 00:00:00\",\n    \"2086-04-05 00:00:00\",\n    \"2086-04-06 00:00:00\",\n    \"2086-04-07 00:00:00\",\n    \"2086-04-13 00:00:00\",\n    \"2086-04-20 00:00:00\",\n    \"2086-04-21 00:00:00\",\n    \"2086-04-27 00:00:00\",\n    \"2086-04-28 00:00:00\",\n    \"2086-05-01 00:00:00\",\n    \"2086-05-02 00:00:00\",\n    \"2086-05-04 00:00:00\",\n    \"2086-05-05 00:00:00\",\n    \"2086-05-11 00:00:00\",\n    \"2086-05-12 00:00:00\",\n    \"2086-05-18 00:00:00\",\n    \"2086-05-19 00:00:00\",\n    \"2086-05-25 00:00:00\",\n    \"2086-05-26 00:00:00\",\n    \"2086-06-01 00:00:00\",\n    \"2086-06-02 00:00:00\",\n    \"2086-06-08 00:00:00\",\n    \"2086-06-09 00:00:00\",\n    \"2086-06-15 00:00:00\",\n    \"2086-06-16 00:00:00\",\n    \"2086-06-17 00:00:00\",\n    \"2086-06-22 00:00:00\",\n    \"2086-06-23 00:00:00\",\n    \"2086-06-29 00:00:00\",\n    \"2086-06-30 00:00:00\",\n    \"2086-07-06 00:00:00\",\n    \"2086-07-07 00:00:00\",\n    \"2086-07-13 00:00:00\",\n    \"2086-07-14 00:00:00\",\n    \"2086-07-20 00:00:00\",\n    \"2086-07-21 00:00:00\",\n    \"2086-07-27 00:00:00\",\n    \"2086-07-28 00:00:00\",\n    \"2086-08-03 00:00:00\",\n    \"2086-08-04 00:00:00\",\n    \"2086-08-10 00:00:00\",\n    \"2086-08-11 00:00:00\",\n    \"2086-08-17 00:00:00\",\n    \"2086-08-18 00:00:00\",\n    \"2086-08-24 00:00:00\",\n    \"2086-08-25 00:00:00\",\n    \"2086-08-31 00:00:00\",\n    \"2086-09-01 00:00:00\",\n    \"2086-09-07 00:00:00\",\n    \"2086-09-08 00:00:00\",\n    \"2086-09-14 00:00:00\",\n    \"2086-09-15 00:00:00\",\n    \"2086-09-21 00:00:00\",\n    \"2086-09-22 00:00:00\",\n    \"2086-09-23 00:00:00\",\n    \"2086-09-28 00:00:00\",\n    \"2086-10-01 00:00:00\",\n    \"2086-10-02 00:00:00\",\n    \"2086-10-03 00:00:00\",\n    \"2086-10-04 00:00:00\",\n    \"2086-10-05 00:00:00\",\n    \"2086-10-06 00:00:00\",\n    \"2086-10-07 00:00:00\",\n    \"2086-10-13 00:00:00\",\n    \"2086-10-19 00:00:00\",\n    \"2086-10-20 00:00:00\",\n    \"2086-10-26 00:00:00\",\n    \"2086-10-27 00:00:00\",\n    \"2086-11-02 00:00:00\",\n    \"2086-11-03 00:00:00\",\n    \"2086-11-09 00:00:00\",\n    \"2086-11-10 00:00:00\",\n    \"2086-11-16 00:00:00\",\n    \"2086-11-17 00:00:00\",\n    \"2086-11-23 00:00:00\",\n    \"2086-11-24 00:00:00\",\n    \"2086-11-30 00:00:00\",\n    \"2086-12-01 00:00:00\",\n    \"2086-12-07 00:00:00\",\n    \"2086-12-08 00:00:00\",\n    \"2086-12-14 00:00:00\",\n    \"2086-12-15 00:00:00\",\n    \"2086-12-21 00:00:00\",\n    \"2086-12-22 00:00:00\",\n    \"2086-12-28 00:00:00\",\n    \"2086-12-29 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-04 00:00:00\",\n    \"2087-01-05 00:00:00\",\n    \"2087-01-11 00:00:00\",\n    \"2087-01-12 00:00:00\",\n    \"2087-01-18 00:00:00\",\n    \"2087-01-19 00:00:00\",\n    \"2087-01-25 00:00:00\",\n    \"2087-01-26 00:00:00\",\n    \"2087-02-02 00:00:00\",\n    \"2087-02-03 00:00:00\",\n    \"2087-02-04 00:00:00\",\n    \"2087-02-05 00:00:00\",\n    \"2087-02-06 00:00:00\",\n    \"2087-02-07 00:00:00\",\n    \"2087-02-08 00:00:00\",\n    \"2087-02-09 00:00:00\",\n    \"2087-02-16 00:00:00\",\n    \"2087-02-22 00:00:00\",\n    \"2087-02-23 00:00:00\",\n    \"2087-03-01 00:00:00\",\n    \"2087-03-02 00:00:00\",\n    \"2087-03-08 00:00:00\",\n    \"2087-03-09 00:00:00\",\n    \"2087-03-15 00:00:00\",\n    \"2087-03-16 00:00:00\",\n    \"2087-03-22 00:00:00\",\n    \"2087-03-23 00:00:00\",\n    \"2087-03-29 00:00:00\",\n    \"2087-03-30 00:00:00\",\n    \"2087-04-04 00:00:00\",\n    \"2087-04-05 00:00:00\",\n    \"2087-04-06 00:00:00\",\n    \"2087-04-12 00:00:00\",\n    \"2087-04-13 00:00:00\",\n    \"2087-04-19 00:00:00\",\n    \"2087-04-20 00:00:00\",\n    \"2087-04-26 00:00:00\",\n    \"2087-05-01 00:00:00\",\n    \"2087-05-02 00:00:00\",\n    \"2087-05-03 00:00:00\",\n    \"2087-05-04 00:00:00\",\n    \"2087-05-05 00:00:00\",\n    \"2087-05-10 00:00:00\",\n    \"2087-05-11 00:00:00\",\n    \"2087-05-17 00:00:00\",\n    \"2087-05-18 00:00:00\",\n    \"2087-05-24 00:00:00\",\n    \"2087-05-25 00:00:00\",\n    \"2087-05-31 00:00:00\",\n    \"2087-06-01 00:00:00\",\n    \"2087-06-05 00:00:00\",\n    \"2087-06-06 00:00:00\",\n    \"2087-06-07 00:00:00\",\n    \"2087-06-08 00:00:00\",\n    \"2087-06-14 00:00:00\",\n    \"2087-06-21 00:00:00\",\n    \"2087-06-22 00:00:00\",\n    \"2087-06-28 00:00:00\",\n    \"2087-06-29 00:00:00\",\n    \"2087-07-05 00:00:00\",\n    \"2087-07-06 00:00:00\",\n    \"2087-07-12 00:00:00\",\n    \"2087-07-13 00:00:00\",\n    \"2087-07-19 00:00:00\",\n    \"2087-07-20 00:00:00\",\n    \"2087-07-26 00:00:00\",\n    \"2087-07-27 00:00:00\",\n    \"2087-08-02 00:00:00\",\n    \"2087-08-03 00:00:00\",\n    \"2087-08-09 00:00:00\",\n    \"2087-08-10 00:00:00\",\n    \"2087-08-16 00:00:00\",\n    \"2087-08-17 00:00:00\",\n    \"2087-08-23 00:00:00\",\n    \"2087-08-24 00:00:00\",\n    \"2087-08-30 00:00:00\",\n    \"2087-08-31 00:00:00\",\n    \"2087-09-06 00:00:00\",\n    \"2087-09-07 00:00:00\",\n    \"2087-09-11 00:00:00\",\n    \"2087-09-12 00:00:00\",\n    \"2087-09-13 00:00:00\",\n    \"2087-09-14 00:00:00\",\n    \"2087-09-20 00:00:00\",\n    \"2087-09-27 00:00:00\",\n    \"2087-10-01 00:00:00\",\n    \"2087-10-02 00:00:00\",\n    \"2087-10-03 00:00:00\",\n    \"2087-10-04 00:00:00\",\n    \"2087-10-05 00:00:00\",\n    \"2087-10-06 00:00:00\",\n    \"2087-10-07 00:00:00\",\n    \"2087-10-12 00:00:00\",\n    \"2087-10-18 00:00:00\",\n    \"2087-10-19 00:00:00\",\n    \"2087-10-25 00:00:00\",\n    \"2087-10-26 00:00:00\",\n    \"2087-11-01 00:00:00\",\n    \"2087-11-02 00:00:00\",\n    \"2087-11-08 00:00:00\",\n    \"2087-11-09 00:00:00\",\n    \"2087-11-15 00:00:00\",\n    \"2087-11-16 00:00:00\",\n    \"2087-11-22 00:00:00\",\n    \"2087-11-23 00:00:00\",\n    \"2087-11-29 00:00:00\",\n    \"2087-11-30 00:00:00\",\n    \"2087-12-06 00:00:00\",\n    \"2087-12-07 00:00:00\",\n    \"2087-12-13 00:00:00\",\n    \"2087-12-14 00:00:00\",\n    \"2087-12-20 00:00:00\",\n    \"2087-12-21 00:00:00\",\n    \"2087-12-27 00:00:00\",\n    \"2087-12-28 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-02 00:00:00\",\n    \"2088-01-03 00:00:00\",\n    \"2088-01-10 00:00:00\",\n    \"2088-01-11 00:00:00\",\n    \"2088-01-17 00:00:00\",\n    \"2088-01-23 00:00:00\",\n    \"2088-01-24 00:00:00\",\n    \"2088-01-25 00:00:00\",\n    \"2088-01-26 00:00:00\",\n    \"2088-01-27 00:00:00\",\n    \"2088-01-28 00:00:00\",\n    \"2088-01-29 00:00:00\",\n    \"2088-01-30 00:00:00\",\n    \"2088-02-01 00:00:00\",\n    \"2088-02-07 00:00:00\",\n    \"2088-02-08 00:00:00\",\n    \"2088-02-14 00:00:00\",\n    \"2088-02-15 00:00:00\",\n    \"2088-02-21 00:00:00\",\n    \"2088-02-22 00:00:00\",\n    \"2088-02-28 00:00:00\",\n    \"2088-02-29 00:00:00\",\n    \"2088-03-06 00:00:00\",\n    \"2088-03-07 00:00:00\",\n    \"2088-03-13 00:00:00\",\n    \"2088-03-14 00:00:00\",\n    \"2088-03-20 00:00:00\",\n    \"2088-03-21 00:00:00\",\n    \"2088-03-27 00:00:00\",\n    \"2088-03-28 00:00:00\",\n    \"2088-04-03 00:00:00\",\n    \"2088-04-04 00:00:00\",\n    \"2088-04-05 00:00:00\",\n    \"2088-04-10 00:00:00\",\n    \"2088-04-11 00:00:00\",\n    \"2088-04-17 00:00:00\",\n    \"2088-04-18 00:00:00\",\n    \"2088-04-24 00:00:00\",\n    \"2088-04-25 00:00:00\",\n    \"2088-05-01 00:00:00\",\n    \"2088-05-02 00:00:00\",\n    \"2088-05-03 00:00:00\",\n    \"2088-05-04 00:00:00\",\n    \"2088-05-05 00:00:00\",\n    \"2088-05-08 00:00:00\",\n    \"2088-05-15 00:00:00\",\n    \"2088-05-16 00:00:00\",\n    \"2088-05-22 00:00:00\",\n    \"2088-05-23 00:00:00\",\n    \"2088-05-29 00:00:00\",\n    \"2088-05-30 00:00:00\",\n    \"2088-06-05 00:00:00\",\n    \"2088-06-06 00:00:00\",\n    \"2088-06-12 00:00:00\",\n    \"2088-06-13 00:00:00\",\n    \"2088-06-19 00:00:00\",\n    \"2088-06-20 00:00:00\",\n    \"2088-06-23 00:00:00\",\n    \"2088-06-26 00:00:00\",\n    \"2088-06-27 00:00:00\",\n    \"2088-07-03 00:00:00\",\n    \"2088-07-04 00:00:00\",\n    \"2088-07-10 00:00:00\",\n    \"2088-07-11 00:00:00\",\n    \"2088-07-17 00:00:00\",\n    \"2088-07-18 00:00:00\",\n    \"2088-07-24 00:00:00\",\n    \"2088-07-25 00:00:00\",\n    \"2088-07-31 00:00:00\",\n    \"2088-08-01 00:00:00\",\n    \"2088-08-07 00:00:00\",\n    \"2088-08-08 00:00:00\",\n    \"2088-08-14 00:00:00\",\n    \"2088-08-15 00:00:00\",\n    \"2088-08-21 00:00:00\",\n    \"2088-08-22 00:00:00\",\n    \"2088-08-28 00:00:00\",\n    \"2088-08-29 00:00:00\",\n    \"2088-09-04 00:00:00\",\n    \"2088-09-05 00:00:00\",\n    \"2088-09-11 00:00:00\",\n    \"2088-09-12 00:00:00\",\n    \"2088-09-18 00:00:00\",\n    \"2088-09-19 00:00:00\",\n    \"2088-09-25 00:00:00\",\n    \"2088-09-29 00:00:00\",\n    \"2088-10-01 00:00:00\",\n    \"2088-10-02 00:00:00\",\n    \"2088-10-03 00:00:00\",\n    \"2088-10-04 00:00:00\",\n    \"2088-10-05 00:00:00\",\n    \"2088-10-06 00:00:00\",\n    \"2088-10-07 00:00:00\",\n    \"2088-10-10 00:00:00\",\n    \"2088-10-16 00:00:00\",\n    \"2088-10-17 00:00:00\",\n    \"2088-10-23 00:00:00\",\n    \"2088-10-24 00:00:00\",\n    \"2088-10-30 00:00:00\",\n    \"2088-10-31 00:00:00\",\n    \"2088-11-06 00:00:00\",\n    \"2088-11-07 00:00:00\",\n    \"2088-11-13 00:00:00\",\n    \"2088-11-14 00:00:00\",\n    \"2088-11-20 00:00:00\",\n    \"2088-11-21 00:00:00\",\n    \"2088-11-27 00:00:00\",\n    \"2088-11-28 00:00:00\",\n    \"2088-12-04 00:00:00\",\n    \"2088-12-05 00:00:00\",\n    \"2088-12-11 00:00:00\",\n    \"2088-12-12 00:00:00\",\n    \"2088-12-18 00:00:00\",\n    \"2088-12-19 00:00:00\",\n    \"2088-12-25 00:00:00\",\n    \"2088-12-26 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-01-02 00:00:00\",\n    \"2089-01-03 00:00:00\",\n    \"2089-01-08 00:00:00\",\n    \"2089-01-09 00:00:00\",\n    \"2089-01-15 00:00:00\",\n    \"2089-01-16 00:00:00\",\n    \"2089-01-22 00:00:00\",\n    \"2089-01-23 00:00:00\",\n    \"2089-01-29 00:00:00\",\n    \"2089-01-30 00:00:00\",\n    \"2089-02-05 00:00:00\",\n    \"2089-02-09 00:00:00\",\n    \"2089-02-10 00:00:00\",\n    \"2089-02-11 00:00:00\",\n    \"2089-02-12 00:00:00\",\n    \"2089-02-13 00:00:00\",\n    \"2089-02-14 00:00:00\",\n    \"2089-02-15 00:00:00\",\n    \"2089-02-16 00:00:00\",\n    \"2089-02-20 00:00:00\",\n    \"2089-02-26 00:00:00\",\n    \"2089-02-27 00:00:00\",\n    \"2089-03-05 00:00:00\",\n    \"2089-03-06 00:00:00\",\n    \"2089-03-12 00:00:00\",\n    \"2089-03-13 00:00:00\",\n    \"2089-03-19 00:00:00\",\n    \"2089-03-20 00:00:00\",\n    \"2089-03-26 00:00:00\",\n    \"2089-03-27 00:00:00\",\n    \"2089-04-02 00:00:00\",\n    \"2089-04-03 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-04-09 00:00:00\",\n    \"2089-04-10 00:00:00\",\n    \"2089-04-16 00:00:00\",\n    \"2089-04-17 00:00:00\",\n    \"2089-04-23 00:00:00\",\n    \"2089-04-24 00:00:00\",\n    \"2089-04-30 00:00:00\",\n    \"2089-05-01 00:00:00\",\n    \"2089-05-02 00:00:00\",\n    \"2089-05-03 00:00:00\",\n    \"2089-05-04 00:00:00\",\n    \"2089-05-07 00:00:00\",\n    \"2089-05-14 00:00:00\",\n    \"2089-05-15 00:00:00\",\n    \"2089-05-21 00:00:00\",\n    \"2089-05-22 00:00:00\",\n    \"2089-05-28 00:00:00\",\n    \"2089-05-29 00:00:00\",\n    \"2089-06-04 00:00:00\",\n    \"2089-06-05 00:00:00\",\n    \"2089-06-11 00:00:00\",\n    \"2089-06-12 00:00:00\",\n    \"2089-06-13 00:00:00\",\n    \"2089-06-18 00:00:00\",\n    \"2089-06-19 00:00:00\",\n    \"2089-06-25 00:00:00\",\n    \"2089-06-26 00:00:00\",\n    \"2089-07-02 00:00:00\",\n    \"2089-07-03 00:00:00\",\n    \"2089-07-09 00:00:00\",\n    \"2089-07-10 00:00:00\",\n    \"2089-07-16 00:00:00\",\n    \"2089-07-17 00:00:00\",\n    \"2089-07-23 00:00:00\",\n    \"2089-07-24 00:00:00\",\n    \"2089-07-30 00:00:00\",\n    \"2089-07-31 00:00:00\",\n    \"2089-08-06 00:00:00\",\n    \"2089-08-07 00:00:00\",\n    \"2089-08-13 00:00:00\",\n    \"2089-08-14 00:00:00\",\n    \"2089-08-20 00:00:00\",\n    \"2089-08-21 00:00:00\",\n    \"2089-08-27 00:00:00\",\n    \"2089-08-28 00:00:00\",\n    \"2089-09-03 00:00:00\",\n    \"2089-09-04 00:00:00\",\n    \"2089-09-10 00:00:00\",\n    \"2089-09-11 00:00:00\",\n    \"2089-09-17 00:00:00\",\n    \"2089-09-18 00:00:00\",\n    \"2089-09-19 00:00:00\",\n    \"2089-09-24 00:00:00\",\n    \"2089-10-01 00:00:00\",\n    \"2089-10-02 00:00:00\",\n    \"2089-10-03 00:00:00\",\n    \"2089-10-04 00:00:00\",\n    \"2089-10-05 00:00:00\",\n    \"2089-10-06 00:00:00\",\n    \"2089-10-07 00:00:00\",\n    \"2089-10-08 00:00:00\",\n    \"2089-10-15 00:00:00\",\n    \"2089-10-16 00:00:00\",\n    \"2089-10-22 00:00:00\",\n    \"2089-10-23 00:00:00\",\n    \"2089-10-29 00:00:00\",\n    \"2089-10-30 00:00:00\",\n    \"2089-11-05 00:00:00\",\n    \"2089-11-06 00:00:00\",\n    \"2089-11-12 00:00:00\",\n    \"2089-11-13 00:00:00\",\n    \"2089-11-19 00:00:00\",\n    \"2089-11-20 00:00:00\",\n    \"2089-11-26 00:00:00\",\n    \"2089-11-27 00:00:00\",\n    \"2089-12-03 00:00:00\",\n    \"2089-12-04 00:00:00\",\n    \"2089-12-10 00:00:00\",\n    \"2089-12-11 00:00:00\",\n    \"2089-12-17 00:00:00\",\n    \"2089-12-18 00:00:00\",\n    \"2089-12-24 00:00:00\",\n    \"2089-12-25 00:00:00\",\n    \"2089-12-31 00:00:00\",\n    \"2090-01-01 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-01-07 00:00:00\",\n    \"2090-01-08 00:00:00\",\n    \"2090-01-14 00:00:00\",\n    \"2090-01-15 00:00:00\",\n    \"2090-01-21 00:00:00\",\n    \"2090-01-22 00:00:00\",\n    \"2090-01-29 00:00:00\",\n    \"2090-01-30 00:00:00\",\n    \"2090-01-31 00:00:00\",\n    \"2090-02-01 00:00:00\",\n    \"2090-02-02 00:00:00\",\n    \"2090-02-03 00:00:00\",\n    \"2090-02-04 00:00:00\",\n    \"2090-02-05 00:00:00\",\n    \"2090-02-12 00:00:00\",\n    \"2090-02-18 00:00:00\",\n    \"2090-02-19 00:00:00\",\n    \"2090-02-25 00:00:00\",\n    \"2090-02-26 00:00:00\",\n    \"2090-03-04 00:00:00\",\n    \"2090-03-05 00:00:00\",\n    \"2090-03-11 00:00:00\",\n    \"2090-03-12 00:00:00\",\n    \"2090-03-18 00:00:00\",\n    \"2090-03-19 00:00:00\",\n    \"2090-03-25 00:00:00\",\n    \"2090-04-01 00:00:00\",\n    \"2090-04-02 00:00:00\",\n    \"2090-04-03 00:00:00\",\n    \"2090-04-04 00:00:00\",\n    \"2090-04-08 00:00:00\",\n    \"2090-04-09 00:00:00\",\n    \"2090-04-15 00:00:00\",\n    \"2090-04-16 00:00:00\",\n    \"2090-04-22 00:00:00\",\n    \"2090-04-23 00:00:00\",\n    \"2090-04-29 00:00:00\",\n    \"2090-04-30 00:00:00\",\n    \"2090-05-01 00:00:00\",\n    \"2090-05-02 00:00:00\",\n    \"2090-05-03 00:00:00\",\n    \"2090-05-06 00:00:00\",\n    \"2090-05-13 00:00:00\",\n    \"2090-05-14 00:00:00\",\n    \"2090-05-20 00:00:00\",\n    \"2090-05-21 00:00:00\",\n    \"2090-05-27 00:00:00\",\n    \"2090-05-28 00:00:00\",\n    \"2090-06-02 00:00:00\",\n    \"2090-06-03 00:00:00\",\n    \"2090-06-04 00:00:00\",\n    \"2090-06-10 00:00:00\",\n    \"2090-06-11 00:00:00\",\n    \"2090-06-17 00:00:00\",\n    \"2090-06-18 00:00:00\",\n    \"2090-06-24 00:00:00\",\n    \"2090-06-25 00:00:00\",\n    \"2090-07-01 00:00:00\",\n    \"2090-07-02 00:00:00\",\n    \"2090-07-08 00:00:00\",\n    \"2090-07-09 00:00:00\",\n    \"2090-07-15 00:00:00\",\n    \"2090-07-16 00:00:00\",\n    \"2090-07-22 00:00:00\",\n    \"2090-07-23 00:00:00\",\n    \"2090-07-29 00:00:00\",\n    \"2090-07-30 00:00:00\",\n    \"2090-08-05 00:00:00\",\n    \"2090-08-06 00:00:00\",\n    \"2090-08-12 00:00:00\",\n    \"2090-08-13 00:00:00\",\n    \"2090-08-19 00:00:00\",\n    \"2090-08-20 00:00:00\",\n    \"2090-08-26 00:00:00\",\n    \"2090-08-27 00:00:00\",\n    \"2090-09-02 00:00:00\",\n    \"2090-09-03 00:00:00\",\n    \"2090-09-08 00:00:00\",\n    \"2090-09-09 00:00:00\",\n    \"2090-09-10 00:00:00\",\n    \"2090-09-16 00:00:00\",\n    \"2090-09-17 00:00:00\",\n    \"2090-09-23 00:00:00\",\n    \"2090-09-30 00:00:00\",\n    \"2090-10-01 00:00:00\",\n    \"2090-10-02 00:00:00\",\n    \"2090-10-03 00:00:00\",\n    \"2090-10-04 00:00:00\",\n    \"2090-10-05 00:00:00\",\n    \"2090-10-06 00:00:00\",\n    \"2090-10-07 00:00:00\",\n    \"2090-10-14 00:00:00\",\n    \"2090-10-15 00:00:00\",\n    \"2090-10-21 00:00:00\",\n    \"2090-10-22 00:00:00\",\n    \"2090-10-28 00:00:00\",\n    \"2090-10-29 00:00:00\",\n    \"2090-11-04 00:00:00\",\n    \"2090-11-05 00:00:00\",\n    \"2090-11-11 00:00:00\",\n    \"2090-11-12 00:00:00\",\n    \"2090-11-18 00:00:00\",\n    \"2090-11-19 00:00:00\",\n    \"2090-11-25 00:00:00\",\n    \"2090-11-26 00:00:00\",\n    \"2090-12-02 00:00:00\",\n    \"2090-12-03 00:00:00\",\n    \"2090-12-09 00:00:00\",\n    \"2090-12-10 00:00:00\",\n    \"2090-12-16 00:00:00\",\n    \"2090-12-17 00:00:00\",\n    \"2090-12-23 00:00:00\",\n    \"2090-12-24 00:00:00\",\n    \"2090-12-30 00:00:00\",\n    \"2090-12-31 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-06 00:00:00\",\n    \"2091-01-07 00:00:00\",\n    \"2091-01-13 00:00:00\",\n    \"2091-01-14 00:00:00\",\n    \"2091-01-20 00:00:00\",\n    \"2091-01-21 00:00:00\",\n    \"2091-01-27 00:00:00\",\n    \"2091-01-28 00:00:00\",\n    \"2091-02-03 00:00:00\",\n    \"2091-02-04 00:00:00\",\n    \"2091-02-10 00:00:00\",\n    \"2091-02-17 00:00:00\",\n    \"2091-02-18 00:00:00\",\n    \"2091-02-19 00:00:00\",\n    \"2091-02-20 00:00:00\",\n    \"2091-02-21 00:00:00\",\n    \"2091-02-22 00:00:00\",\n    \"2091-02-23 00:00:00\",\n    \"2091-02-24 00:00:00\",\n    \"2091-03-03 00:00:00\",\n    \"2091-03-04 00:00:00\",\n    \"2091-03-10 00:00:00\",\n    \"2091-03-11 00:00:00\",\n    \"2091-03-17 00:00:00\",\n    \"2091-03-18 00:00:00\",\n    \"2091-03-24 00:00:00\",\n    \"2091-03-25 00:00:00\",\n    \"2091-03-31 00:00:00\",\n    \"2091-04-01 00:00:00\",\n    \"2091-04-04 00:00:00\",\n    \"2091-04-07 00:00:00\",\n    \"2091-04-08 00:00:00\",\n    \"2091-04-14 00:00:00\",\n    \"2091-04-15 00:00:00\",\n    \"2091-04-21 00:00:00\",\n    \"2091-04-22 00:00:00\",\n    \"2091-04-28 00:00:00\",\n    \"2091-04-29 00:00:00\",\n    \"2091-04-30 00:00:00\",\n    \"2091-05-01 00:00:00\",\n    \"2091-05-02 00:00:00\",\n    \"2091-05-05 00:00:00\",\n    \"2091-05-06 00:00:00\",\n    \"2091-05-12 00:00:00\",\n    \"2091-05-13 00:00:00\",\n    \"2091-05-19 00:00:00\",\n    \"2091-05-20 00:00:00\",\n    \"2091-05-26 00:00:00\",\n    \"2091-05-27 00:00:00\",\n    \"2091-06-02 00:00:00\",\n    \"2091-06-03 00:00:00\",\n    \"2091-06-09 00:00:00\",\n    \"2091-06-10 00:00:00\",\n    \"2091-06-16 00:00:00\",\n    \"2091-06-17 00:00:00\",\n    \"2091-06-21 00:00:00\",\n    \"2091-06-22 00:00:00\",\n    \"2091-06-23 00:00:00\",\n    \"2091-06-24 00:00:00\",\n    \"2091-06-30 00:00:00\",\n    \"2091-07-07 00:00:00\",\n    \"2091-07-08 00:00:00\",\n    \"2091-07-14 00:00:00\",\n    \"2091-07-15 00:00:00\",\n    \"2091-07-21 00:00:00\",\n    \"2091-07-22 00:00:00\",\n    \"2091-07-28 00:00:00\",\n    \"2091-07-29 00:00:00\",\n    \"2091-08-04 00:00:00\",\n    \"2091-08-05 00:00:00\",\n    \"2091-08-11 00:00:00\",\n    \"2091-08-12 00:00:00\",\n    \"2091-08-18 00:00:00\",\n    \"2091-08-19 00:00:00\",\n    \"2091-08-25 00:00:00\",\n    \"2091-08-26 00:00:00\",\n    \"2091-09-01 00:00:00\",\n    \"2091-09-02 00:00:00\",\n    \"2091-09-08 00:00:00\",\n    \"2091-09-09 00:00:00\",\n    \"2091-09-15 00:00:00\",\n    \"2091-09-16 00:00:00\",\n    \"2091-09-23 00:00:00\",\n    \"2091-09-27 00:00:00\",\n    \"2091-09-28 00:00:00\",\n    \"2091-09-29 00:00:00\",\n    \"2091-09-30 00:00:00\",\n    \"2091-10-01 00:00:00\",\n    \"2091-10-02 00:00:00\",\n    \"2091-10-03 00:00:00\",\n    \"2091-10-04 00:00:00\",\n    \"2091-10-05 00:00:00\",\n    \"2091-10-06 00:00:00\",\n    \"2091-10-07 00:00:00\",\n    \"2091-10-14 00:00:00\",\n    \"2091-10-20 00:00:00\",\n    \"2091-10-21 00:00:00\",\n    \"2091-10-27 00:00:00\",\n    \"2091-10-28 00:00:00\",\n    \"2091-11-03 00:00:00\",\n    \"2091-11-04 00:00:00\",\n    \"2091-11-10 00:00:00\",\n    \"2091-11-11 00:00:00\",\n    \"2091-11-17 00:00:00\",\n    \"2091-11-18 00:00:00\",\n    \"2091-11-24 00:00:00\",\n    \"2091-11-25 00:00:00\",\n    \"2091-12-01 00:00:00\",\n    \"2091-12-02 00:00:00\",\n    \"2091-12-08 00:00:00\",\n    \"2091-12-09 00:00:00\",\n    \"2091-12-15 00:00:00\",\n    \"2091-12-16 00:00:00\",\n    \"2091-12-22 00:00:00\",\n    \"2091-12-23 00:00:00\",\n    \"2091-12-30 00:00:00\",\n    \"2091-12-31 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-05 00:00:00\",\n    \"2092-01-06 00:00:00\",\n    \"2092-01-12 00:00:00\",\n    \"2092-01-13 00:00:00\",\n    \"2092-01-19 00:00:00\",\n    \"2092-01-20 00:00:00\",\n    \"2092-01-26 00:00:00\",\n    \"2092-01-27 00:00:00\",\n    \"2092-02-02 00:00:00\",\n    \"2092-02-06 00:00:00\",\n    \"2092-02-07 00:00:00\",\n    \"2092-02-08 00:00:00\",\n    \"2092-02-09 00:00:00\",\n    \"2092-02-10 00:00:00\",\n    \"2092-02-11 00:00:00\",\n    \"2092-02-12 00:00:00\",\n    \"2092-02-13 00:00:00\",\n    \"2092-02-17 00:00:00\",\n    \"2092-02-23 00:00:00\",\n    \"2092-02-24 00:00:00\",\n    \"2092-03-01 00:00:00\",\n    \"2092-03-02 00:00:00\",\n    \"2092-03-08 00:00:00\",\n    \"2092-03-09 00:00:00\",\n    \"2092-03-15 00:00:00\",\n    \"2092-03-16 00:00:00\",\n    \"2092-03-22 00:00:00\",\n    \"2092-03-23 00:00:00\",\n    \"2092-03-29 00:00:00\",\n    \"2092-03-30 00:00:00\",\n    \"2092-04-03 00:00:00\",\n    \"2092-04-04 00:00:00\",\n    \"2092-04-05 00:00:00\",\n    \"2092-04-06 00:00:00\",\n    \"2092-04-12 00:00:00\",\n    \"2092-04-19 00:00:00\",\n    \"2092-04-20 00:00:00\",\n    \"2092-04-26 00:00:00\",\n    \"2092-05-01 00:00:00\",\n    \"2092-05-02 00:00:00\",\n    \"2092-05-03 00:00:00\",\n    \"2092-05-04 00:00:00\",\n    \"2092-05-05 00:00:00\",\n    \"2092-05-10 00:00:00\",\n    \"2092-05-11 00:00:00\",\n    \"2092-05-17 00:00:00\",\n    \"2092-05-18 00:00:00\",\n    \"2092-05-24 00:00:00\",\n    \"2092-05-25 00:00:00\",\n    \"2092-05-31 00:00:00\",\n    \"2092-06-01 00:00:00\",\n    \"2092-06-07 00:00:00\",\n    \"2092-06-08 00:00:00\",\n    \"2092-06-09 00:00:00\",\n    \"2092-06-14 00:00:00\",\n    \"2092-06-15 00:00:00\",\n    \"2092-06-21 00:00:00\",\n    \"2092-06-22 00:00:00\",\n    \"2092-06-28 00:00:00\",\n    \"2092-06-29 00:00:00\",\n    \"2092-07-05 00:00:00\",\n    \"2092-07-06 00:00:00\",\n    \"2092-07-12 00:00:00\",\n    \"2092-07-13 00:00:00\",\n    \"2092-07-19 00:00:00\",\n    \"2092-07-20 00:00:00\",\n    \"2092-07-26 00:00:00\",\n    \"2092-07-27 00:00:00\",\n    \"2092-08-02 00:00:00\",\n    \"2092-08-03 00:00:00\",\n    \"2092-08-09 00:00:00\",\n    \"2092-08-10 00:00:00\",\n    \"2092-08-16 00:00:00\",\n    \"2092-08-17 00:00:00\",\n    \"2092-08-23 00:00:00\",\n    \"2092-08-24 00:00:00\",\n    \"2092-08-30 00:00:00\",\n    \"2092-08-31 00:00:00\",\n    \"2092-09-06 00:00:00\",\n    \"2092-09-13 00:00:00\",\n    \"2092-09-14 00:00:00\",\n    \"2092-09-15 00:00:00\",\n    \"2092-09-16 00:00:00\",\n    \"2092-09-20 00:00:00\",\n    \"2092-09-21 00:00:00\",\n    \"2092-09-27 00:00:00\",\n    \"2092-10-01 00:00:00\",\n    \"2092-10-02 00:00:00\",\n    \"2092-10-03 00:00:00\",\n    \"2092-10-04 00:00:00\",\n    \"2092-10-05 00:00:00\",\n    \"2092-10-06 00:00:00\",\n    \"2092-10-07 00:00:00\",\n    \"2092-10-12 00:00:00\",\n    \"2092-10-18 00:00:00\",\n    \"2092-10-19 00:00:00\",\n    \"2092-10-25 00:00:00\",\n    \"2092-10-26 00:00:00\",\n    \"2092-11-01 00:00:00\",\n    \"2092-11-02 00:00:00\",\n    \"2092-11-08 00:00:00\",\n    \"2092-11-09 00:00:00\",\n    \"2092-11-15 00:00:00\",\n    \"2092-11-16 00:00:00\",\n    \"2092-11-22 00:00:00\",\n    \"2092-11-23 00:00:00\",\n    \"2092-11-29 00:00:00\",\n    \"2092-11-30 00:00:00\",\n    \"2092-12-06 00:00:00\",\n    \"2092-12-07 00:00:00\",\n    \"2092-12-13 00:00:00\",\n    \"2092-12-14 00:00:00\",\n    \"2092-12-20 00:00:00\",\n    \"2092-12-21 00:00:00\",\n    \"2092-12-27 00:00:00\",\n    \"2092-12-28 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-02 00:00:00\",\n    \"2093-01-03 00:00:00\",\n    \"2093-01-10 00:00:00\",\n    \"2093-01-11 00:00:00\",\n    \"2093-01-17 00:00:00\",\n    \"2093-01-18 00:00:00\",\n    \"2093-01-25 00:00:00\",\n    \"2093-01-26 00:00:00\",\n    \"2093-01-27 00:00:00\",\n    \"2093-01-28 00:00:00\",\n    \"2093-01-29 00:00:00\",\n    \"2093-01-30 00:00:00\",\n    \"2093-01-31 00:00:00\",\n    \"2093-02-01 00:00:00\",\n    \"2093-02-02 00:00:00\",\n    \"2093-02-08 00:00:00\",\n    \"2093-02-14 00:00:00\",\n    \"2093-02-15 00:00:00\",\n    \"2093-02-21 00:00:00\",\n    \"2093-02-22 00:00:00\",\n    \"2093-02-28 00:00:00\",\n    \"2093-03-01 00:00:00\",\n    \"2093-03-07 00:00:00\",\n    \"2093-03-08 00:00:00\",\n    \"2093-03-14 00:00:00\",\n    \"2093-03-15 00:00:00\",\n    \"2093-03-21 00:00:00\",\n    \"2093-03-22 00:00:00\",\n    \"2093-03-28 00:00:00\",\n    \"2093-03-29 00:00:00\",\n    \"2093-04-03 00:00:00\",\n    \"2093-04-04 00:00:00\",\n    \"2093-04-05 00:00:00\",\n    \"2093-04-11 00:00:00\",\n    \"2093-04-12 00:00:00\",\n    \"2093-04-18 00:00:00\",\n    \"2093-04-19 00:00:00\",\n    \"2093-04-25 00:00:00\",\n    \"2093-04-26 00:00:00\",\n    \"2093-05-01 00:00:00\",\n    \"2093-05-02 00:00:00\",\n    \"2093-05-03 00:00:00\",\n    \"2093-05-04 00:00:00\",\n    \"2093-05-05 00:00:00\",\n    \"2093-05-10 00:00:00\",\n    \"2093-05-16 00:00:00\",\n    \"2093-05-17 00:00:00\",\n    \"2093-05-23 00:00:00\",\n    \"2093-05-24 00:00:00\",\n    \"2093-05-29 00:00:00\",\n    \"2093-05-30 00:00:00\",\n    \"2093-05-31 00:00:00\",\n    \"2093-06-06 00:00:00\",\n    \"2093-06-07 00:00:00\",\n    \"2093-06-13 00:00:00\",\n    \"2093-06-14 00:00:00\",\n    \"2093-06-20 00:00:00\",\n    \"2093-06-21 00:00:00\",\n    \"2093-06-27 00:00:00\",\n    \"2093-06-28 00:00:00\",\n    \"2093-07-04 00:00:00\",\n    \"2093-07-05 00:00:00\",\n    \"2093-07-11 00:00:00\",\n    \"2093-07-12 00:00:00\",\n    \"2093-07-18 00:00:00\",\n    \"2093-07-19 00:00:00\",\n    \"2093-07-25 00:00:00\",\n    \"2093-07-26 00:00:00\",\n    \"2093-08-01 00:00:00\",\n    \"2093-08-02 00:00:00\",\n    \"2093-08-08 00:00:00\",\n    \"2093-08-09 00:00:00\",\n    \"2093-08-15 00:00:00\",\n    \"2093-08-16 00:00:00\",\n    \"2093-08-22 00:00:00\",\n    \"2093-08-23 00:00:00\",\n    \"2093-08-29 00:00:00\",\n    \"2093-08-30 00:00:00\",\n    \"2093-09-05 00:00:00\",\n    \"2093-09-06 00:00:00\",\n    \"2093-09-12 00:00:00\",\n    \"2093-09-13 00:00:00\",\n    \"2093-09-19 00:00:00\",\n    \"2093-09-20 00:00:00\",\n    \"2093-09-26 00:00:00\",\n    \"2093-10-01 00:00:00\",\n    \"2093-10-02 00:00:00\",\n    \"2093-10-03 00:00:00\",\n    \"2093-10-04 00:00:00\",\n    \"2093-10-05 00:00:00\",\n    \"2093-10-06 00:00:00\",\n    \"2093-10-07 00:00:00\",\n    \"2093-10-08 00:00:00\",\n    \"2093-10-11 00:00:00\",\n    \"2093-10-17 00:00:00\",\n    \"2093-10-18 00:00:00\",\n    \"2093-10-24 00:00:00\",\n    \"2093-10-25 00:00:00\",\n    \"2093-10-31 00:00:00\",\n    \"2093-11-01 00:00:00\",\n    \"2093-11-07 00:00:00\",\n    \"2093-11-08 00:00:00\",\n    \"2093-11-14 00:00:00\",\n    \"2093-11-15 00:00:00\",\n    \"2093-11-21 00:00:00\",\n    \"2093-11-22 00:00:00\",\n    \"2093-11-28 00:00:00\",\n    \"2093-11-29 00:00:00\",\n    \"2093-12-05 00:00:00\",\n    \"2093-12-06 00:00:00\",\n    \"2093-12-12 00:00:00\",\n    \"2093-12-13 00:00:00\",\n    \"2093-12-19 00:00:00\",\n    \"2093-12-20 00:00:00\",\n    \"2093-12-26 00:00:00\",\n    \"2093-12-27 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-02 00:00:00\",\n    \"2094-01-03 00:00:00\",\n    \"2094-01-09 00:00:00\",\n    \"2094-01-10 00:00:00\",\n    \"2094-01-16 00:00:00\",\n    \"2094-01-17 00:00:00\",\n    \"2094-01-23 00:00:00\",\n    \"2094-01-24 00:00:00\",\n    \"2094-01-30 00:00:00\",\n    \"2094-01-31 00:00:00\",\n    \"2094-02-06 00:00:00\",\n    \"2094-02-07 00:00:00\",\n    \"2094-02-14 00:00:00\",\n    \"2094-02-15 00:00:00\",\n    \"2094-02-16 00:00:00\",\n    \"2094-02-17 00:00:00\",\n    \"2094-02-18 00:00:00\",\n    \"2094-02-19 00:00:00\",\n    \"2094-02-20 00:00:00\",\n    \"2094-02-21 00:00:00\",\n    \"2094-02-28 00:00:00\",\n    \"2094-03-06 00:00:00\",\n    \"2094-03-07 00:00:00\",\n    \"2094-03-13 00:00:00\",\n    \"2094-03-14 00:00:00\",\n    \"2094-03-20 00:00:00\",\n    \"2094-03-21 00:00:00\",\n    \"2094-03-27 00:00:00\",\n    \"2094-03-28 00:00:00\",\n    \"2094-04-03 00:00:00\",\n    \"2094-04-04 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-04-10 00:00:00\",\n    \"2094-04-11 00:00:00\",\n    \"2094-04-17 00:00:00\",\n    \"2094-04-18 00:00:00\",\n    \"2094-04-24 00:00:00\",\n    \"2094-04-25 00:00:00\",\n    \"2094-05-01 00:00:00\",\n    \"2094-05-02 00:00:00\",\n    \"2094-05-03 00:00:00\",\n    \"2094-05-04 00:00:00\",\n    \"2094-05-05 00:00:00\",\n    \"2094-05-08 00:00:00\",\n    \"2094-05-15 00:00:00\",\n    \"2094-05-16 00:00:00\",\n    \"2094-05-22 00:00:00\",\n    \"2094-05-23 00:00:00\",\n    \"2094-05-29 00:00:00\",\n    \"2094-05-30 00:00:00\",\n    \"2094-06-05 00:00:00\",\n    \"2094-06-06 00:00:00\",\n    \"2094-06-12 00:00:00\",\n    \"2094-06-13 00:00:00\",\n    \"2094-06-17 00:00:00\",\n    \"2094-06-18 00:00:00\",\n    \"2094-06-19 00:00:00\",\n    \"2094-06-20 00:00:00\",\n    \"2094-06-26 00:00:00\",\n    \"2094-07-03 00:00:00\",\n    \"2094-07-04 00:00:00\",\n    \"2094-07-10 00:00:00\",\n    \"2094-07-11 00:00:00\",\n    \"2094-07-17 00:00:00\",\n    \"2094-07-18 00:00:00\",\n    \"2094-07-24 00:00:00\",\n    \"2094-07-25 00:00:00\",\n    \"2094-07-31 00:00:00\",\n    \"2094-08-01 00:00:00\",\n    \"2094-08-07 00:00:00\",\n    \"2094-08-08 00:00:00\",\n    \"2094-08-14 00:00:00\",\n    \"2094-08-15 00:00:00\",\n    \"2094-08-21 00:00:00\",\n    \"2094-08-22 00:00:00\",\n    \"2094-08-28 00:00:00\",\n    \"2094-08-29 00:00:00\",\n    \"2094-09-04 00:00:00\",\n    \"2094-09-05 00:00:00\",\n    \"2094-09-11 00:00:00\",\n    \"2094-09-12 00:00:00\",\n    \"2094-09-18 00:00:00\",\n    \"2094-09-24 00:00:00\",\n    \"2094-09-25 00:00:00\",\n    \"2094-09-26 00:00:00\",\n    \"2094-10-01 00:00:00\",\n    \"2094-10-02 00:00:00\",\n    \"2094-10-03 00:00:00\",\n    \"2094-10-04 00:00:00\",\n    \"2094-10-05 00:00:00\",\n    \"2094-10-06 00:00:00\",\n    \"2094-10-07 00:00:00\",\n    \"2094-10-10 00:00:00\",\n    \"2094-10-16 00:00:00\",\n    \"2094-10-17 00:00:00\",\n    \"2094-10-23 00:00:00\",\n    \"2094-10-24 00:00:00\",\n    \"2094-10-30 00:00:00\",\n    \"2094-10-31 00:00:00\",\n    \"2094-11-06 00:00:00\",\n    \"2094-11-07 00:00:00\",\n    \"2094-11-13 00:00:00\",\n    \"2094-11-14 00:00:00\",\n    \"2094-11-20 00:00:00\",\n    \"2094-11-21 00:00:00\",\n    \"2094-11-27 00:00:00\",\n    \"2094-11-28 00:00:00\",\n    \"2094-12-04 00:00:00\",\n    \"2094-12-05 00:00:00\",\n    \"2094-12-11 00:00:00\",\n    \"2094-12-12 00:00:00\",\n    \"2094-12-18 00:00:00\",\n    \"2094-12-19 00:00:00\",\n    \"2094-12-25 00:00:00\",\n    \"2094-12-26 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-01-02 00:00:00\",\n    \"2095-01-03 00:00:00\",\n    \"2095-01-08 00:00:00\",\n    \"2095-01-09 00:00:00\",\n    \"2095-01-15 00:00:00\",\n    \"2095-01-16 00:00:00\",\n    \"2095-01-22 00:00:00\",\n    \"2095-01-23 00:00:00\",\n    \"2095-01-29 00:00:00\",\n    \"2095-02-04 00:00:00\",\n    \"2095-02-05 00:00:00\",\n    \"2095-02-06 00:00:00\",\n    \"2095-02-07 00:00:00\",\n    \"2095-02-08 00:00:00\",\n    \"2095-02-09 00:00:00\",\n    \"2095-02-10 00:00:00\",\n    \"2095-02-11 00:00:00\",\n    \"2095-02-13 00:00:00\",\n    \"2095-02-19 00:00:00\",\n    \"2095-02-20 00:00:00\",\n    \"2095-02-26 00:00:00\",\n    \"2095-02-27 00:00:00\",\n    \"2095-03-05 00:00:00\",\n    \"2095-03-06 00:00:00\",\n    \"2095-03-12 00:00:00\",\n    \"2095-03-13 00:00:00\",\n    \"2095-03-19 00:00:00\",\n    \"2095-03-20 00:00:00\",\n    \"2095-03-26 00:00:00\",\n    \"2095-03-27 00:00:00\",\n    \"2095-04-02 00:00:00\",\n    \"2095-04-03 00:00:00\",\n    \"2095-04-04 00:00:00\",\n    \"2095-04-09 00:00:00\",\n    \"2095-04-10 00:00:00\",\n    \"2095-04-16 00:00:00\",\n    \"2095-04-17 00:00:00\",\n    \"2095-04-23 00:00:00\",\n    \"2095-04-24 00:00:00\",\n    \"2095-04-30 00:00:00\",\n    \"2095-05-01 00:00:00\",\n    \"2095-05-02 00:00:00\",\n    \"2095-05-03 00:00:00\",\n    \"2095-05-04 00:00:00\",\n    \"2095-05-07 00:00:00\",\n    \"2095-05-14 00:00:00\",\n    \"2095-05-15 00:00:00\",\n    \"2095-05-21 00:00:00\",\n    \"2095-05-22 00:00:00\",\n    \"2095-05-28 00:00:00\",\n    \"2095-05-29 00:00:00\",\n    \"2095-06-04 00:00:00\",\n    \"2095-06-05 00:00:00\",\n    \"2095-06-06 00:00:00\",\n    \"2095-06-11 00:00:00\",\n    \"2095-06-12 00:00:00\",\n    \"2095-06-18 00:00:00\",\n    \"2095-06-19 00:00:00\",\n    \"2095-06-25 00:00:00\",\n    \"2095-06-26 00:00:00\",\n    \"2095-07-02 00:00:00\",\n    \"2095-07-03 00:00:00\",\n    \"2095-07-09 00:00:00\",\n    \"2095-07-10 00:00:00\",\n    \"2095-07-16 00:00:00\",\n    \"2095-07-17 00:00:00\",\n    \"2095-07-23 00:00:00\",\n    \"2095-07-24 00:00:00\",\n    \"2095-07-30 00:00:00\",\n    \"2095-07-31 00:00:00\",\n    \"2095-08-06 00:00:00\",\n    \"2095-08-07 00:00:00\",\n    \"2095-08-13 00:00:00\",\n    \"2095-08-14 00:00:00\",\n    \"2095-08-20 00:00:00\",\n    \"2095-08-21 00:00:00\",\n    \"2095-08-27 00:00:00\",\n    \"2095-08-28 00:00:00\",\n    \"2095-09-03 00:00:00\",\n    \"2095-09-10 00:00:00\",\n    \"2095-09-11 00:00:00\",\n    \"2095-09-12 00:00:00\",\n    \"2095-09-13 00:00:00\",\n    \"2095-09-17 00:00:00\",\n    \"2095-09-18 00:00:00\",\n    \"2095-09-24 00:00:00\",\n    \"2095-10-01 00:00:00\",\n    \"2095-10-02 00:00:00\",\n    \"2095-10-03 00:00:00\",\n    \"2095-10-04 00:00:00\",\n    \"2095-10-05 00:00:00\",\n    \"2095-10-06 00:00:00\",\n    \"2095-10-07 00:00:00\",\n    \"2095-10-08 00:00:00\",\n    \"2095-10-15 00:00:00\",\n    \"2095-10-16 00:00:00\",\n    \"2095-10-22 00:00:00\",\n    \"2095-10-23 00:00:00\",\n    \"2095-10-29 00:00:00\",\n    \"2095-10-30 00:00:00\",\n    \"2095-11-05 00:00:00\",\n    \"2095-11-06 00:00:00\",\n    \"2095-11-12 00:00:00\",\n    \"2095-11-13 00:00:00\",\n    \"2095-11-19 00:00:00\",\n    \"2095-11-20 00:00:00\",\n    \"2095-11-26 00:00:00\",\n    \"2095-11-27 00:00:00\",\n    \"2095-12-03 00:00:00\",\n    \"2095-12-04 00:00:00\",\n    \"2095-12-10 00:00:00\",\n    \"2095-12-11 00:00:00\",\n    \"2095-12-17 00:00:00\",\n    \"2095-12-18 00:00:00\",\n    \"2095-12-24 00:00:00\",\n    \"2095-12-25 00:00:00\",\n    \"2095-12-31 00:00:00\",\n    \"2096-01-01 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-01-07 00:00:00\",\n    \"2096-01-08 00:00:00\",\n    \"2096-01-14 00:00:00\",\n    \"2096-01-15 00:00:00\",\n    \"2096-01-21 00:00:00\",\n    \"2096-01-24 00:00:00\",\n    \"2096-01-25 00:00:00\",\n    \"2096-01-26 00:00:00\",\n    \"2096-01-27 00:00:00\",\n    \"2096-01-28 00:00:00\",\n    \"2096-01-29 00:00:00\",\n    \"2096-01-30 00:00:00\",\n    \"2096-01-31 00:00:00\",\n    \"2096-02-05 00:00:00\",\n    \"2096-02-11 00:00:00\",\n    \"2096-02-12 00:00:00\",\n    \"2096-02-18 00:00:00\",\n    \"2096-02-19 00:00:00\",\n    \"2096-02-25 00:00:00\",\n    \"2096-02-26 00:00:00\",\n    \"2096-03-03 00:00:00\",\n    \"2096-03-04 00:00:00\",\n    \"2096-03-10 00:00:00\",\n    \"2096-03-11 00:00:00\",\n    \"2096-03-17 00:00:00\",\n    \"2096-03-18 00:00:00\",\n    \"2096-03-24 00:00:00\",\n    \"2096-03-31 00:00:00\",\n    \"2096-04-01 00:00:00\",\n    \"2096-04-02 00:00:00\",\n    \"2096-04-03 00:00:00\",\n    \"2096-04-07 00:00:00\",\n    \"2096-04-08 00:00:00\",\n    \"2096-04-14 00:00:00\",\n    \"2096-04-15 00:00:00\",\n    \"2096-04-21 00:00:00\",\n    \"2096-04-22 00:00:00\",\n    \"2096-04-28 00:00:00\",\n    \"2096-04-29 00:00:00\",\n    \"2096-04-30 00:00:00\",\n    \"2096-05-01 00:00:00\",\n    \"2096-05-02 00:00:00\",\n    \"2096-05-05 00:00:00\",\n    \"2096-05-06 00:00:00\",\n    \"2096-05-12 00:00:00\",\n    \"2096-05-13 00:00:00\",\n    \"2096-05-19 00:00:00\",\n    \"2096-05-20 00:00:00\",\n    \"2096-05-26 00:00:00\",\n    \"2096-05-27 00:00:00\",\n    \"2096-06-02 00:00:00\",\n    \"2096-06-03 00:00:00\",\n    \"2096-06-09 00:00:00\",\n    \"2096-06-10 00:00:00\",\n    \"2096-06-16 00:00:00\",\n    \"2096-06-17 00:00:00\",\n    \"2096-06-23 00:00:00\",\n    \"2096-06-24 00:00:00\",\n    \"2096-06-25 00:00:00\",\n    \"2096-06-30 00:00:00\",\n    \"2096-07-01 00:00:00\",\n    \"2096-07-07 00:00:00\",\n    \"2096-07-08 00:00:00\",\n    \"2096-07-14 00:00:00\",\n    \"2096-07-15 00:00:00\",\n    \"2096-07-21 00:00:00\",\n    \"2096-07-22 00:00:00\",\n    \"2096-07-28 00:00:00\",\n    \"2096-07-29 00:00:00\",\n    \"2096-08-04 00:00:00\",\n    \"2096-08-05 00:00:00\",\n    \"2096-08-11 00:00:00\",\n    \"2096-08-12 00:00:00\",\n    \"2096-08-18 00:00:00\",\n    \"2096-08-19 00:00:00\",\n    \"2096-08-25 00:00:00\",\n    \"2096-08-26 00:00:00\",\n    \"2096-09-01 00:00:00\",\n    \"2096-09-02 00:00:00\",\n    \"2096-09-08 00:00:00\",\n    \"2096-09-09 00:00:00\",\n    \"2096-09-15 00:00:00\",\n    \"2096-09-16 00:00:00\",\n    \"2096-09-23 00:00:00\",\n    \"2096-09-29 00:00:00\",\n    \"2096-09-30 00:00:00\",\n    \"2096-10-01 00:00:00\",\n    \"2096-10-02 00:00:00\",\n    \"2096-10-03 00:00:00\",\n    \"2096-10-04 00:00:00\",\n    \"2096-10-05 00:00:00\",\n    \"2096-10-06 00:00:00\",\n    \"2096-10-07 00:00:00\",\n    \"2096-10-14 00:00:00\",\n    \"2096-10-20 00:00:00\",\n    \"2096-10-21 00:00:00\",\n    \"2096-10-27 00:00:00\",\n    \"2096-10-28 00:00:00\",\n    \"2096-11-03 00:00:00\",\n    \"2096-11-04 00:00:00\",\n    \"2096-11-10 00:00:00\",\n    \"2096-11-11 00:00:00\",\n    \"2096-11-17 00:00:00\",\n    \"2096-11-18 00:00:00\",\n    \"2096-11-24 00:00:00\",\n    \"2096-11-25 00:00:00\",\n    \"2096-12-01 00:00:00\",\n    \"2096-12-02 00:00:00\",\n    \"2096-12-08 00:00:00\",\n    \"2096-12-09 00:00:00\",\n    \"2096-12-15 00:00:00\",\n    \"2096-12-16 00:00:00\",\n    \"2096-12-22 00:00:00\",\n    \"2096-12-23 00:00:00\",\n    \"2096-12-30 00:00:00\",\n    \"2096-12-31 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-05 00:00:00\",\n    \"2097-01-06 00:00:00\",\n    \"2097-01-12 00:00:00\",\n    \"2097-01-13 00:00:00\",\n    \"2097-01-19 00:00:00\",\n    \"2097-01-20 00:00:00\",\n    \"2097-01-26 00:00:00\",\n    \"2097-01-27 00:00:00\",\n    \"2097-02-02 00:00:00\",\n    \"2097-02-03 00:00:00\",\n    \"2097-02-10 00:00:00\",\n    \"2097-02-11 00:00:00\",\n    \"2097-02-12 00:00:00\",\n    \"2097-02-13 00:00:00\",\n    \"2097-02-14 00:00:00\",\n    \"2097-02-15 00:00:00\",\n    \"2097-02-16 00:00:00\",\n    \"2097-02-17 00:00:00\",\n    \"2097-02-18 00:00:00\",\n    \"2097-02-24 00:00:00\",\n    \"2097-03-02 00:00:00\",\n    \"2097-03-03 00:00:00\",\n    \"2097-03-09 00:00:00\",\n    \"2097-03-10 00:00:00\",\n    \"2097-03-16 00:00:00\",\n    \"2097-03-17 00:00:00\",\n    \"2097-03-23 00:00:00\",\n    \"2097-03-24 00:00:00\",\n    \"2097-03-30 00:00:00\",\n    \"2097-03-31 00:00:00\",\n    \"2097-04-03 00:00:00\",\n    \"2097-04-06 00:00:00\",\n    \"2097-04-07 00:00:00\",\n    \"2097-04-13 00:00:00\",\n    \"2097-04-14 00:00:00\",\n    \"2097-04-20 00:00:00\",\n    \"2097-04-21 00:00:00\",\n    \"2097-04-27 00:00:00\",\n    \"2097-04-28 00:00:00\",\n    \"2097-05-01 00:00:00\",\n    \"2097-05-02 00:00:00\",\n    \"2097-05-04 00:00:00\",\n    \"2097-05-05 00:00:00\",\n    \"2097-05-11 00:00:00\",\n    \"2097-05-12 00:00:00\",\n    \"2097-05-18 00:00:00\",\n    \"2097-05-19 00:00:00\",\n    \"2097-05-25 00:00:00\",\n    \"2097-05-26 00:00:00\",\n    \"2097-06-01 00:00:00\",\n    \"2097-06-02 00:00:00\",\n    \"2097-06-08 00:00:00\",\n    \"2097-06-09 00:00:00\",\n    \"2097-06-14 00:00:00\",\n    \"2097-06-15 00:00:00\",\n    \"2097-06-16 00:00:00\",\n    \"2097-06-22 00:00:00\",\n    \"2097-06-23 00:00:00\",\n    \"2097-06-29 00:00:00\",\n    \"2097-06-30 00:00:00\",\n    \"2097-07-06 00:00:00\",\n    \"2097-07-07 00:00:00\",\n    \"2097-07-13 00:00:00\",\n    \"2097-07-14 00:00:00\",\n    \"2097-07-20 00:00:00\",\n    \"2097-07-21 00:00:00\",\n    \"2097-07-27 00:00:00\",\n    \"2097-07-28 00:00:00\",\n    \"2097-08-03 00:00:00\",\n    \"2097-08-04 00:00:00\",\n    \"2097-08-10 00:00:00\",\n    \"2097-08-11 00:00:00\",\n    \"2097-08-17 00:00:00\",\n    \"2097-08-18 00:00:00\",\n    \"2097-08-24 00:00:00\",\n    \"2097-08-25 00:00:00\",\n    \"2097-08-31 00:00:00\",\n    \"2097-09-01 00:00:00\",\n    \"2097-09-07 00:00:00\",\n    \"2097-09-08 00:00:00\",\n    \"2097-09-14 00:00:00\",\n    \"2097-09-15 00:00:00\",\n    \"2097-09-20 00:00:00\",\n    \"2097-09-21 00:00:00\",\n    \"2097-09-22 00:00:00\",\n    \"2097-09-28 00:00:00\",\n    \"2097-10-01 00:00:00\",\n    \"2097-10-02 00:00:00\",\n    \"2097-10-03 00:00:00\",\n    \"2097-10-04 00:00:00\",\n    \"2097-10-05 00:00:00\",\n    \"2097-10-06 00:00:00\",\n    \"2097-10-07 00:00:00\",\n    \"2097-10-13 00:00:00\",\n    \"2097-10-19 00:00:00\",\n    \"2097-10-20 00:00:00\",\n    \"2097-10-26 00:00:00\",\n    \"2097-10-27 00:00:00\",\n    \"2097-11-02 00:00:00\",\n    \"2097-11-03 00:00:00\",\n    \"2097-11-09 00:00:00\",\n    \"2097-11-10 00:00:00\",\n    \"2097-11-16 00:00:00\",\n    \"2097-11-17 00:00:00\",\n    \"2097-11-23 00:00:00\",\n    \"2097-11-24 00:00:00\",\n    \"2097-11-30 00:00:00\",\n    \"2097-12-01 00:00:00\",\n    \"2097-12-07 00:00:00\",\n    \"2097-12-08 00:00:00\",\n    \"2097-12-14 00:00:00\",\n    \"2097-12-15 00:00:00\",\n    \"2097-12-21 00:00:00\",\n    \"2097-12-22 00:00:00\",\n    \"2097-12-28 00:00:00\",\n    \"2097-12-29 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-04 00:00:00\",\n    \"2098-01-05 00:00:00\",\n    \"2098-01-11 00:00:00\",\n    \"2098-01-12 00:00:00\",\n    \"2098-01-18 00:00:00\",\n    \"2098-01-19 00:00:00\",\n    \"2098-01-25 00:00:00\",\n    \"2098-01-31 00:00:00\",\n    \"2098-02-01 00:00:00\",\n    \"2098-02-02 00:00:00\",\n    \"2098-02-03 00:00:00\",\n    \"2098-02-04 00:00:00\",\n    \"2098-02-05 00:00:00\",\n    \"2098-02-06 00:00:00\",\n    \"2098-02-07 00:00:00\",\n    \"2098-02-09 00:00:00\",\n    \"2098-02-15 00:00:00\",\n    \"2098-02-16 00:00:00\",\n    \"2098-02-22 00:00:00\",\n    \"2098-02-23 00:00:00\",\n    \"2098-03-01 00:00:00\",\n    \"2098-03-02 00:00:00\",\n    \"2098-03-08 00:00:00\",\n    \"2098-03-09 00:00:00\",\n    \"2098-03-15 00:00:00\",\n    \"2098-03-16 00:00:00\",\n    \"2098-03-22 00:00:00\",\n    \"2098-03-23 00:00:00\",\n    \"2098-03-29 00:00:00\",\n    \"2098-03-30 00:00:00\",\n    \"2098-04-04 00:00:00\",\n    \"2098-04-05 00:00:00\",\n    \"2098-04-06 00:00:00\",\n    \"2098-04-12 00:00:00\",\n    \"2098-04-13 00:00:00\",\n    \"2098-04-19 00:00:00\",\n    \"2098-04-20 00:00:00\",\n    \"2098-04-26 00:00:00\",\n    \"2098-05-01 00:00:00\",\n    \"2098-05-02 00:00:00\",\n    \"2098-05-03 00:00:00\",\n    \"2098-05-04 00:00:00\",\n    \"2098-05-05 00:00:00\",\n    \"2098-05-10 00:00:00\",\n    \"2098-05-11 00:00:00\",\n    \"2098-05-17 00:00:00\",\n    \"2098-05-18 00:00:00\",\n    \"2098-05-24 00:00:00\",\n    \"2098-05-25 00:00:00\",\n    \"2098-05-31 00:00:00\",\n    \"2098-06-01 00:00:00\",\n    \"2098-06-04 00:00:00\",\n    \"2098-06-07 00:00:00\",\n    \"2098-06-08 00:00:00\",\n    \"2098-06-14 00:00:00\",\n    \"2098-06-15 00:00:00\",\n    \"2098-06-21 00:00:00\",\n    \"2098-06-22 00:00:00\",\n    \"2098-06-28 00:00:00\",\n    \"2098-06-29 00:00:00\",\n    \"2098-07-05 00:00:00\",\n    \"2098-07-06 00:00:00\",\n    \"2098-07-12 00:00:00\",\n    \"2098-07-13 00:00:00\",\n    \"2098-07-19 00:00:00\",\n    \"2098-07-20 00:00:00\",\n    \"2098-07-26 00:00:00\",\n    \"2098-07-27 00:00:00\",\n    \"2098-08-02 00:00:00\",\n    \"2098-08-03 00:00:00\",\n    \"2098-08-09 00:00:00\",\n    \"2098-08-10 00:00:00\",\n    \"2098-08-16 00:00:00\",\n    \"2098-08-17 00:00:00\",\n    \"2098-08-23 00:00:00\",\n    \"2098-08-24 00:00:00\",\n    \"2098-08-30 00:00:00\",\n    \"2098-09-06 00:00:00\",\n    \"2098-09-07 00:00:00\",\n    \"2098-09-08 00:00:00\",\n    \"2098-09-09 00:00:00\",\n    \"2098-09-13 00:00:00\",\n    \"2098-09-14 00:00:00\",\n    \"2098-09-20 00:00:00\",\n    \"2098-09-21 00:00:00\",\n    \"2098-09-27 00:00:00\",\n    \"2098-10-01 00:00:00\",\n    \"2098-10-02 00:00:00\",\n    \"2098-10-03 00:00:00\",\n    \"2098-10-04 00:00:00\",\n    \"2098-10-05 00:00:00\",\n    \"2098-10-06 00:00:00\",\n    \"2098-10-07 00:00:00\",\n    \"2098-10-12 00:00:00\",\n    \"2098-10-18 00:00:00\",\n    \"2098-10-19 00:00:00\",\n    \"2098-10-25 00:00:00\",\n    \"2098-10-26 00:00:00\",\n    \"2098-11-01 00:00:00\",\n    \"2098-11-02 00:00:00\",\n    \"2098-11-08 00:00:00\",\n    \"2098-11-09 00:00:00\",\n    \"2098-11-15 00:00:00\",\n    \"2098-11-16 00:00:00\",\n    \"2098-11-22 00:00:00\",\n    \"2098-11-23 00:00:00\",\n    \"2098-11-29 00:00:00\",\n    \"2098-11-30 00:00:00\",\n    \"2098-12-06 00:00:00\",\n    \"2098-12-07 00:00:00\",\n    \"2098-12-13 00:00:00\",\n    \"2098-12-14 00:00:00\",\n    \"2098-12-20 00:00:00\",\n    \"2098-12-21 00:00:00\",\n    \"2098-12-27 00:00:00\",\n    \"2098-12-28 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-02 00:00:00\",\n    \"2099-01-03 00:00:00\",\n    \"2099-01-10 00:00:00\",\n    \"2099-01-11 00:00:00\",\n    \"2099-01-17 00:00:00\",\n    \"2099-01-20 00:00:00\",\n    \"2099-01-21 00:00:00\",\n    \"2099-01-22 00:00:00\",\n    \"2099-01-23 00:00:00\",\n    \"2099-01-24 00:00:00\",\n    \"2099-01-25 00:00:00\",\n    \"2099-01-26 00:00:00\",\n    \"2099-01-27 00:00:00\",\n    \"2099-02-01 00:00:00\",\n    \"2099-02-07 00:00:00\",\n    \"2099-02-08 00:00:00\",\n    \"2099-02-14 00:00:00\",\n    \"2099-02-15 00:00:00\",\n    \"2099-02-21 00:00:00\",\n    \"2099-02-22 00:00:00\",\n    \"2099-02-28 00:00:00\",\n    \"2099-03-01 00:00:00\",\n    \"2099-03-07 00:00:00\",\n    \"2099-03-08 00:00:00\",\n    \"2099-03-14 00:00:00\",\n    \"2099-03-15 00:00:00\",\n    \"2099-03-21 00:00:00\",\n    \"2099-03-22 00:00:00\",\n    \"2099-03-28 00:00:00\",\n    \"2099-03-29 00:00:00\",\n    \"2099-04-04 00:00:00\",\n    \"2099-04-05 00:00:00\",\n    \"2099-04-06 00:00:00\",\n    \"2099-04-11 00:00:00\",\n    \"2099-04-12 00:00:00\",\n    \"2099-04-18 00:00:00\",\n    \"2099-04-19 00:00:00\",\n    \"2099-04-25 00:00:00\",\n    \"2099-04-26 00:00:00\",\n    \"2099-05-01 00:00:00\",\n    \"2099-05-02 00:00:00\",\n    \"2099-05-03 00:00:00\",\n    \"2099-05-04 00:00:00\",\n    \"2099-05-05 00:00:00\",\n    \"2099-05-10 00:00:00\",\n    \"2099-05-16 00:00:00\",\n    \"2099-05-17 00:00:00\",\n    \"2099-05-23 00:00:00\",\n    \"2099-05-24 00:00:00\",\n    \"2099-05-30 00:00:00\",\n    \"2099-05-31 00:00:00\",\n    \"2099-06-06 00:00:00\",\n    \"2099-06-07 00:00:00\",\n    \"2099-06-13 00:00:00\",\n    \"2099-06-20 00:00:00\",\n    \"2099-06-21 00:00:00\",\n    \"2099-06-22 00:00:00\",\n    \"2099-06-23 00:00:00\",\n    \"2099-06-27 00:00:00\",\n    \"2099-06-28 00:00:00\",\n    \"2099-07-04 00:00:00\",\n    \"2099-07-05 00:00:00\",\n    \"2099-07-11 00:00:00\",\n    \"2099-07-12 00:00:00\",\n    \"2099-07-18 00:00:00\",\n    \"2099-07-19 00:00:00\",\n    \"2099-07-25 00:00:00\",\n    \"2099-07-26 00:00:00\",\n    \"2099-08-01 00:00:00\",\n    \"2099-08-02 00:00:00\",\n    \"2099-08-08 00:00:00\",\n    \"2099-08-09 00:00:00\",\n    \"2099-08-15 00:00:00\",\n    \"2099-08-16 00:00:00\",\n    \"2099-08-22 00:00:00\",\n    \"2099-08-23 00:00:00\",\n    \"2099-08-29 00:00:00\",\n    \"2099-08-30 00:00:00\",\n    \"2099-09-05 00:00:00\",\n    \"2099-09-06 00:00:00\",\n    \"2099-09-12 00:00:00\",\n    \"2099-09-13 00:00:00\",\n    \"2099-09-19 00:00:00\",\n    \"2099-09-26 00:00:00\",\n    \"2099-09-27 00:00:00\",\n    \"2099-09-28 00:00:00\",\n    \"2099-09-29 00:00:00\",\n    \"2099-10-01 00:00:00\",\n    \"2099-10-02 00:00:00\",\n    \"2099-10-03 00:00:00\",\n    \"2099-10-04 00:00:00\",\n    \"2099-10-05 00:00:00\",\n    \"2099-10-06 00:00:00\",\n    \"2099-10-07 00:00:00\",\n    \"2099-10-11 00:00:00\",\n    \"2099-10-17 00:00:00\",\n    \"2099-10-18 00:00:00\",\n    \"2099-10-24 00:00:00\",\n    \"2099-10-25 00:00:00\",\n    \"2099-10-31 00:00:00\",\n    \"2099-11-01 00:00:00\",\n    \"2099-11-07 00:00:00\",\n    \"2099-11-08 00:00:00\",\n    \"2099-11-14 00:00:00\",\n    \"2099-11-15 00:00:00\",\n    \"2099-11-21 00:00:00\",\n    \"2099-11-22 00:00:00\",\n    \"2099-11-28 00:00:00\",\n    \"2099-11-29 00:00:00\",\n    \"2099-12-05 00:00:00\",\n    \"2099-12-06 00:00:00\",\n    \"2099-12-12 00:00:00\",\n    \"2099-12-13 00:00:00\",\n    \"2099-12-19 00:00:00\",\n    \"2099-12-20 00:00:00\",\n    \"2099-12-26 00:00:00\",\n    \"2099-12-27 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-02 00:00:00\",\n    \"2100-01-03 00:00:00\",\n    \"2100-01-09 00:00:00\",\n    \"2100-01-10 00:00:00\",\n    \"2100-01-16 00:00:00\",\n    \"2100-01-17 00:00:00\",\n    \"2100-01-23 00:00:00\",\n    \"2100-01-24 00:00:00\",\n    \"2100-01-30 00:00:00\",\n    \"2100-01-31 00:00:00\",\n    \"2100-02-07 00:00:00\",\n    \"2100-02-08 00:00:00\",\n    \"2100-02-09 00:00:00\",\n    \"2100-02-10 00:00:00\",\n    \"2100-02-11 00:00:00\",\n    \"2100-02-12 00:00:00\",\n    \"2100-02-13 00:00:00\",\n    \"2100-02-14 00:00:00\",\n    \"2100-02-15 00:00:00\",\n    \"2100-02-21 00:00:00\",\n    \"2100-02-27 00:00:00\",\n    \"2100-02-28 00:00:00\",\n    \"2100-03-06 00:00:00\",\n    \"2100-03-07 00:00:00\",\n    \"2100-03-13 00:00:00\",\n    \"2100-03-14 00:00:00\",\n    \"2100-03-20 00:00:00\",\n    \"2100-03-21 00:00:00\",\n    \"2100-03-27 00:00:00\",\n    \"2100-03-28 00:00:00\",\n    \"2100-04-03 00:00:00\",\n    \"2100-04-04 00:00:00\",\n    \"2100-04-05 00:00:00\",\n    \"2100-04-10 00:00:00\",\n    \"2100-04-11 00:00:00\",\n    \"2100-04-17 00:00:00\",\n    \"2100-04-18 00:00:00\",\n    \"2100-04-24 00:00:00\",\n    \"2100-04-25 00:00:00\",\n    \"2100-05-01 00:00:00\",\n    \"2100-05-02 00:00:00\",\n    \"2100-05-03 00:00:00\",\n    \"2100-05-04 00:00:00\",\n    \"2100-05-05 00:00:00\",\n    \"2100-05-08 00:00:00\",\n    \"2100-05-15 00:00:00\",\n    \"2100-05-16 00:00:00\",\n    \"2100-05-22 00:00:00\",\n    \"2100-05-23 00:00:00\",\n    \"2100-05-29 00:00:00\",\n    \"2100-05-30 00:00:00\",\n    \"2100-06-05 00:00:00\",\n    \"2100-06-06 00:00:00\",\n    \"2100-06-12 00:00:00\",\n    \"2100-06-13 00:00:00\",\n    \"2100-06-14 00:00:00\",\n    \"2100-06-19 00:00:00\",\n    \"2100-06-20 00:00:00\",\n    \"2100-06-26 00:00:00\",\n    \"2100-06-27 00:00:00\",\n    \"2100-07-03 00:00:00\",\n    \"2100-07-04 00:00:00\",\n    \"2100-07-10 00:00:00\",\n    \"2100-07-11 00:00:00\",\n    \"2100-07-17 00:00:00\",\n    \"2100-07-18 00:00:00\",\n    \"2100-07-24 00:00:00\",\n    \"2100-07-25 00:00:00\",\n    \"2100-07-31 00:00:00\",\n    \"2100-08-01 00:00:00\",\n    \"2100-08-07 00:00:00\",\n    \"2100-08-08 00:00:00\",\n    \"2100-08-14 00:00:00\",\n    \"2100-08-15 00:00:00\",\n    \"2100-08-21 00:00:00\",\n    \"2100-08-22 00:00:00\",\n    \"2100-08-28 00:00:00\",\n    \"2100-08-29 00:00:00\",\n    \"2100-09-04 00:00:00\",\n    \"2100-09-05 00:00:00\",\n    \"2100-09-11 00:00:00\",\n    \"2100-09-12 00:00:00\",\n    \"2100-09-18 00:00:00\",\n    \"2100-09-19 00:00:00\",\n    \"2100-09-20 00:00:00\",\n    \"2100-09-25 00:00:00\",\n    \"2100-10-01 00:00:00\",\n    \"2100-10-02 00:00:00\",\n    \"2100-10-03 00:00:00\",\n    \"2100-10-04 00:00:00\",\n    \"2100-10-05 00:00:00\",\n    \"2100-10-06 00:00:00\",\n    \"2100-10-07 00:00:00\",\n    \"2100-10-10 00:00:00\",\n    \"2100-10-16 00:00:00\",\n    \"2100-10-17 00:00:00\",\n    \"2100-10-23 00:00:00\",\n    \"2100-10-24 00:00:00\",\n    \"2100-10-30 00:00:00\",\n    \"2100-10-31 00:00:00\",\n    \"2100-11-06 00:00:00\",\n    \"2100-11-07 00:00:00\",\n    \"2100-11-13 00:00:00\",\n    \"2100-11-14 00:00:00\",\n    \"2100-11-20 00:00:00\",\n    \"2100-11-21 00:00:00\",\n    \"2100-11-27 00:00:00\",\n    \"2100-11-28 00:00:00\",\n    \"2100-12-04 00:00:00\",\n    \"2100-12-05 00:00:00\",\n    \"2100-12-11 00:00:00\",\n    \"2100-12-12 00:00:00\",\n    \"2100-12-18 00:00:00\",\n    \"2100-12-19 00:00:00\",\n    \"2100-12-25 00:00:00\",\n    \"2100-12-26 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-01-02 00:00:00\",\n    \"2101-01-08 00:00:00\",\n    \"2101-01-09 00:00:00\",\n    \"2101-01-15 00:00:00\",\n    \"2101-01-16 00:00:00\",\n    \"2101-01-22 00:00:00\",\n    \"2101-01-23 00:00:00\",\n    \"2101-01-29 00:00:00\",\n    \"2101-01-30 00:00:00\",\n    \"2101-02-05 00:00:00\",\n    \"2101-02-06 00:00:00\",\n    \"2101-02-12 00:00:00\",\n    \"2101-02-13 00:00:00\",\n    \"2101-02-19 00:00:00\",\n    \"2101-02-20 00:00:00\",\n    \"2101-02-26 00:00:00\",\n    \"2101-02-27 00:00:00\",\n    \"2101-03-05 00:00:00\",\n    \"2101-03-06 00:00:00\",\n    \"2101-03-12 00:00:00\",\n    \"2101-03-13 00:00:00\",\n    \"2101-03-19 00:00:00\",\n    \"2101-03-20 00:00:00\",\n    \"2101-03-26 00:00:00\",\n    \"2101-03-27 00:00:00\",\n    \"2101-04-02 00:00:00\",\n    \"2101-04-03 00:00:00\",\n    \"2101-04-09 00:00:00\",\n    \"2101-04-10 00:00:00\",\n    \"2101-04-16 00:00:00\",\n    \"2101-04-17 00:00:00\",\n    \"2101-04-23 00:00:00\",\n    \"2101-04-24 00:00:00\",\n    \"2101-04-30 00:00:00\",\n    \"2101-05-01 00:00:00\",\n    \"2101-05-07 00:00:00\",\n    \"2101-05-08 00:00:00\",\n    \"2101-05-14 00:00:00\",\n    \"2101-05-15 00:00:00\",\n    \"2101-05-21 00:00:00\",\n    \"2101-05-22 00:00:00\",\n    \"2101-05-28 00:00:00\",\n    \"2101-05-29 00:00:00\",\n    \"2101-06-04 00:00:00\",\n    \"2101-06-05 00:00:00\",\n    \"2101-06-11 00:00:00\",\n    \"2101-06-12 00:00:00\",\n    \"2101-06-18 00:00:00\",\n    \"2101-06-19 00:00:00\",\n    \"2101-06-25 00:00:00\",\n    \"2101-06-26 00:00:00\",\n    \"2101-07-02 00:00:00\",\n    \"2101-07-03 00:00:00\",\n    \"2101-07-09 00:00:00\",\n    \"2101-07-10 00:00:00\",\n    \"2101-07-16 00:00:00\",\n    \"2101-07-17 00:00:00\",\n    \"2101-07-23 00:00:00\",\n    \"2101-07-24 00:00:00\",\n    \"2101-07-30 00:00:00\",\n    \"2101-07-31 00:00:00\",\n    \"2101-08-06 00:00:00\",\n    \"2101-08-07 00:00:00\",\n    \"2101-08-13 00:00:00\",\n    \"2101-08-14 00:00:00\",\n    \"2101-08-20 00:00:00\",\n    \"2101-08-21 00:00:00\",\n    \"2101-08-27 00:00:00\",\n    \"2101-08-28 00:00:00\",\n    \"2101-09-03 00:00:00\",\n    \"2101-09-04 00:00:00\",\n    \"2101-09-10 00:00:00\",\n    \"2101-09-11 00:00:00\",\n    \"2101-09-17 00:00:00\",\n    \"2101-09-18 00:00:00\",\n    \"2101-09-24 00:00:00\",\n    \"2101-09-25 00:00:00\",\n    \"2101-10-01 00:00:00\",\n    \"2101-10-02 00:00:00\",\n    \"2101-10-08 00:00:00\",\n    \"2101-10-09 00:00:00\",\n    \"2101-10-15 00:00:00\",\n    \"2101-10-16 00:00:00\",\n    \"2101-10-22 00:00:00\",\n    \"2101-10-23 00:00:00\",\n    \"2101-10-29 00:00:00\",\n    \"2101-10-30 00:00:00\",\n    \"2101-11-05 00:00:00\",\n    \"2101-11-06 00:00:00\",\n    \"2101-11-12 00:00:00\",\n    \"2101-11-13 00:00:00\",\n    \"2101-11-19 00:00:00\",\n    \"2101-11-20 00:00:00\",\n    \"2101-11-26 00:00:00\",\n    \"2101-11-27 00:00:00\",\n    \"2101-12-03 00:00:00\",\n    \"2101-12-04 00:00:00\",\n    \"2101-12-10 00:00:00\",\n    \"2101-12-11 00:00:00\",\n    \"2101-12-17 00:00:00\",\n    \"2101-12-18 00:00:00\",\n    \"2101-12-24 00:00:00\",\n    \"2101-12-25 00:00:00\",\n    \"2101-12-31 00:00:00\",\n    \"2102-01-01 00:00:00\",\n    \"2102-01-07 00:00:00\",\n    \"2102-01-08 00:00:00\",\n    \"2102-01-14 00:00:00\",\n    \"2102-01-15 00:00:00\",\n    \"2102-01-21 00:00:00\",\n    \"2102-01-22 00:00:00\",\n    \"2102-01-28 00:00:00\",\n    \"2102-01-29 00:00:00\",\n    \"2102-02-04 00:00:00\",\n    \"2102-02-05 00:00:00\",\n    \"2102-02-11 00:00:00\",\n    \"2102-02-12 00:00:00\",\n    \"2102-02-18 00:00:00\",\n    \"2102-02-19 00:00:00\",\n    \"2102-02-25 00:00:00\",\n    \"2102-02-26 00:00:00\",\n    \"2102-03-04 00:00:00\",\n    \"2102-03-05 00:00:00\",\n    \"2102-03-11 00:00:00\",\n    \"2102-03-12 00:00:00\",\n    \"2102-03-18 00:00:00\",\n    \"2102-03-19 00:00:00\",\n    \"2102-03-25 00:00:00\",\n    \"2102-03-26 00:00:00\",\n    \"2102-04-01 00:00:00\",\n    \"2102-04-02 00:00:00\",\n    \"2102-04-08 00:00:00\",\n    \"2102-04-09 00:00:00\",\n    \"2102-04-15 00:00:00\",\n    \"2102-04-16 00:00:00\",\n    \"2102-04-22 00:00:00\",\n    \"2102-04-23 00:00:00\",\n    \"2102-04-29 00:00:00\",\n    \"2102-04-30 00:00:00\",\n    \"2102-05-06 00:00:00\",\n    \"2102-05-07 00:00:00\",\n    \"2102-05-13 00:00:00\",\n    \"2102-05-14 00:00:00\",\n    \"2102-05-20 00:00:00\",\n    \"2102-05-21 00:00:00\",\n    \"2102-05-27 00:00:00\",\n    \"2102-05-28 00:00:00\",\n    \"2102-06-03 00:00:00\",\n    \"2102-06-04 00:00:00\",\n    \"2102-06-10 00:00:00\",\n    \"2102-06-11 00:00:00\",\n    \"2102-06-17 00:00:00\",\n    \"2102-06-18 00:00:00\",\n    \"2102-06-24 00:00:00\",\n    \"2102-06-25 00:00:00\",\n    \"2102-07-01 00:00:00\",\n    \"2102-07-02 00:00:00\",\n    \"2102-07-08 00:00:00\",\n    \"2102-07-09 00:00:00\",\n    \"2102-07-15 00:00:00\",\n    \"2102-07-16 00:00:00\",\n    \"2102-07-22 00:00:00\",\n    \"2102-07-23 00:00:00\",\n    \"2102-07-29 00:00:00\",\n    \"2102-07-30 00:00:00\",\n    \"2102-08-05 00:00:00\",\n    \"2102-08-06 00:00:00\",\n    \"2102-08-12 00:00:00\",\n    \"2102-08-13 00:00:00\",\n    \"2102-08-19 00:00:00\",\n    \"2102-08-20 00:00:00\",\n    \"2102-08-26 00:00:00\",\n    \"2102-08-27 00:00:00\",\n    \"2102-09-02 00:00:00\",\n    \"2102-09-03 00:00:00\",\n    \"2102-09-09 00:00:00\",\n    \"2102-09-10 00:00:00\",\n    \"2102-09-16 00:00:00\",\n    \"2102-09-17 00:00:00\",\n    \"2102-09-23 00:00:00\",\n    \"2102-09-24 00:00:00\",\n    \"2102-09-30 00:00:00\",\n    \"2102-10-01 00:00:00\",\n    \"2102-10-07 00:00:00\",\n    \"2102-10-08 00:00:00\",\n    \"2102-10-14 00:00:00\",\n    \"2102-10-15 00:00:00\",\n    \"2102-10-21 00:00:00\",\n    \"2102-10-22 00:00:00\",\n    \"2102-10-28 00:00:00\",\n    \"2102-10-29 00:00:00\",\n    \"2102-11-04 00:00:00\",\n    \"2102-11-05 00:00:00\",\n    \"2102-11-11 00:00:00\",\n    \"2102-11-12 00:00:00\",\n    \"2102-11-18 00:00:00\",\n    \"2102-11-19 00:00:00\",\n    \"2102-11-25 00:00:00\",\n    \"2102-11-26 00:00:00\",\n    \"2102-12-02 00:00:00\",\n    \"2102-12-03 00:00:00\",\n    \"2102-12-09 00:00:00\",\n    \"2102-12-10 00:00:00\",\n    \"2102-12-16 00:00:00\",\n    \"2102-12-17 00:00:00\",\n    \"2102-12-23 00:00:00\",\n    \"2102-12-24 00:00:00\",\n    \"2102-12-30 00:00:00\",\n    \"2102-12-31 00:00:00\",\n    \"2103-01-06 00:00:00\",\n    \"2103-01-07 00:00:00\",\n    \"2103-01-13 00:00:00\",\n    \"2103-01-14 00:00:00\",\n    \"2103-01-20 00:00:00\",\n    \"2103-01-21 00:00:00\",\n    \"2103-01-27 00:00:00\",\n    \"2103-01-28 00:00:00\",\n    \"2103-02-03 00:00:00\",\n    \"2103-02-04 00:00:00\",\n    \"2103-02-10 00:00:00\",\n    \"2103-02-11 00:00:00\",\n    \"2103-02-17 00:00:00\",\n    \"2103-02-18 00:00:00\",\n    \"2103-02-24 00:00:00\",\n    \"2103-02-25 00:00:00\",\n    \"2103-03-03 00:00:00\",\n    \"2103-03-04 00:00:00\",\n    \"2103-03-10 00:00:00\",\n    \"2103-03-11 00:00:00\",\n    \"2103-03-17 00:00:00\",\n    \"2103-03-18 00:00:00\",\n    \"2103-03-24 00:00:00\",\n    \"2103-03-25 00:00:00\",\n    \"2103-03-31 00:00:00\",\n    \"2103-04-01 00:00:00\",\n    \"2103-04-07 00:00:00\",\n    \"2103-04-08 00:00:00\",\n    \"2103-04-14 00:00:00\",\n    \"2103-04-15 00:00:00\",\n    \"2103-04-21 00:00:00\",\n    \"2103-04-22 00:00:00\",\n    \"2103-04-28 00:00:00\",\n    \"2103-04-29 00:00:00\",\n    \"2103-05-05 00:00:00\",\n    \"2103-05-06 00:00:00\",\n    \"2103-05-12 00:00:00\",\n    \"2103-05-13 00:00:00\",\n    \"2103-05-19 00:00:00\",\n    \"2103-05-20 00:00:00\",\n    \"2103-05-26 00:00:00\",\n    \"2103-05-27 00:00:00\",\n    \"2103-06-02 00:00:00\",\n    \"2103-06-03 00:00:00\",\n    \"2103-06-09 00:00:00\",\n    \"2103-06-10 00:00:00\",\n    \"2103-06-16 00:00:00\",\n    \"2103-06-17 00:00:00\",\n    \"2103-06-23 00:00:00\",\n    \"2103-06-24 00:00:00\",\n    \"2103-06-30 00:00:00\",\n    \"2103-07-01 00:00:00\",\n    \"2103-07-07 00:00:00\",\n    \"2103-07-08 00:00:00\",\n    \"2103-07-14 00:00:00\",\n    \"2103-07-15 00:00:00\",\n    \"2103-07-21 00:00:00\",\n    \"2103-07-22 00:00:00\",\n    \"2103-07-28 00:00:00\",\n    \"2103-07-29 00:00:00\",\n    \"2103-08-04 00:00:00\",\n    \"2103-08-05 00:00:00\",\n    \"2103-08-11 00:00:00\",\n    \"2103-08-12 00:00:00\",\n    \"2103-08-18 00:00:00\",\n    \"2103-08-19 00:00:00\",\n    \"2103-08-25 00:00:00\",\n    \"2103-08-26 00:00:00\",\n    \"2103-09-01 00:00:00\",\n    \"2103-09-02 00:00:00\",\n    \"2103-09-08 00:00:00\",\n    \"2103-09-09 00:00:00\",\n    \"2103-09-15 00:00:00\",\n    \"2103-09-16 00:00:00\",\n    \"2103-09-22 00:00:00\",\n    \"2103-09-23 00:00:00\",\n    \"2103-09-29 00:00:00\",\n    \"2103-09-30 00:00:00\",\n    \"2103-10-06 00:00:00\",\n    \"2103-10-07 00:00:00\",\n    \"2103-10-13 00:00:00\",\n    \"2103-10-14 00:00:00\",\n    \"2103-10-20 00:00:00\",\n    \"2103-10-21 00:00:00\",\n    \"2103-10-27 00:00:00\",\n    \"2103-10-28 00:00:00\",\n    \"2103-11-03 00:00:00\",\n    \"2103-11-04 00:00:00\",\n    \"2103-11-10 00:00:00\",\n    \"2103-11-11 00:00:00\",\n    \"2103-11-17 00:00:00\",\n    \"2103-11-18 00:00:00\",\n    \"2103-11-24 00:00:00\",\n    \"2103-11-25 00:00:00\",\n    \"2103-12-01 00:00:00\",\n    \"2103-12-02 00:00:00\",\n    \"2103-12-08 00:00:00\",\n    \"2103-12-09 00:00:00\",\n    \"2103-12-15 00:00:00\",\n    \"2103-12-16 00:00:00\",\n    \"2103-12-22 00:00:00\",\n    \"2103-12-23 00:00:00\",\n    \"2103-12-29 00:00:00\",\n    \"2103-12-30 00:00:00\",\n    \"2104-01-05 00:00:00\",\n    \"2104-01-06 00:00:00\",\n    \"2104-01-12 00:00:00\",\n    \"2104-01-13 00:00:00\",\n    \"2104-01-19 00:00:00\",\n    \"2104-01-20 00:00:00\",\n    \"2104-01-26 00:00:00\",\n    \"2104-01-27 00:00:00\",\n    \"2104-02-02 00:00:00\",\n    \"2104-02-03 00:00:00\",\n    \"2104-02-09 00:00:00\",\n    \"2104-02-10 00:00:00\",\n    \"2104-02-16 00:00:00\",\n    \"2104-02-17 00:00:00\",\n    \"2104-02-23 00:00:00\",\n    \"2104-02-24 00:00:00\",\n    \"2104-03-01 00:00:00\",\n    \"2104-03-02 00:00:00\",\n    \"2104-03-08 00:00:00\",\n    \"2104-03-09 00:00:00\",\n    \"2104-03-15 00:00:00\",\n    \"2104-03-16 00:00:00\",\n    \"2104-03-22 00:00:00\",\n    \"2104-03-23 00:00:00\",\n    \"2104-03-29 00:00:00\",\n    \"2104-03-30 00:00:00\",\n    \"2104-04-05 00:00:00\",\n    \"2104-04-06 00:00:00\",\n    \"2104-04-12 00:00:00\",\n    \"2104-04-13 00:00:00\",\n    \"2104-04-19 00:00:00\",\n    \"2104-04-20 00:00:00\",\n    \"2104-04-26 00:00:00\",\n    \"2104-04-27 00:00:00\",\n    \"2104-05-03 00:00:00\",\n    \"2104-05-04 00:00:00\",\n    \"2104-05-10 00:00:00\",\n    \"2104-05-11 00:00:00\",\n    \"2104-05-17 00:00:00\",\n    \"2104-05-18 00:00:00\",\n    \"2104-05-24 00:00:00\",\n    \"2104-05-25 00:00:00\",\n    \"2104-05-31 00:00:00\",\n    \"2104-06-01 00:00:00\",\n    \"2104-06-07 00:00:00\",\n    \"2104-06-08 00:00:00\",\n    \"2104-06-14 00:00:00\",\n    \"2104-06-15 00:00:00\",\n    \"2104-06-21 00:00:00\",\n    \"2104-06-22 00:00:00\",\n    \"2104-06-28 00:00:00\",\n    \"2104-06-29 00:00:00\",\n    \"2104-07-05 00:00:00\",\n    \"2104-07-06 00:00:00\",\n    \"2104-07-12 00:00:00\",\n    \"2104-07-13 00:00:00\",\n    \"2104-07-19 00:00:00\",\n    \"2104-07-20 00:00:00\",\n    \"2104-07-26 00:00:00\",\n    \"2104-07-27 00:00:00\",\n    \"2104-08-02 00:00:00\",\n    \"2104-08-03 00:00:00\",\n    \"2104-08-09 00:00:00\",\n    \"2104-08-10 00:00:00\",\n    \"2104-08-16 00:00:00\",\n    \"2104-08-17 00:00:00\",\n    \"2104-08-23 00:00:00\",\n    \"2104-08-24 00:00:00\",\n    \"2104-08-30 00:00:00\",\n    \"2104-08-31 00:00:00\",\n    \"2104-09-06 00:00:00\",\n    \"2104-09-07 00:00:00\",\n    \"2104-09-13 00:00:00\",\n    \"2104-09-14 00:00:00\",\n    \"2104-09-20 00:00:00\",\n    \"2104-09-21 00:00:00\",\n    \"2104-09-27 00:00:00\",\n    \"2104-09-28 00:00:00\",\n    \"2104-10-04 00:00:00\",\n    \"2104-10-05 00:00:00\",\n    \"2104-10-11 00:00:00\",\n    \"2104-10-12 00:00:00\",\n    \"2104-10-18 00:00:00\",\n    \"2104-10-19 00:00:00\",\n    \"2104-10-25 00:00:00\",\n    \"2104-10-26 00:00:00\",\n    \"2104-11-01 00:00:00\",\n    \"2104-11-02 00:00:00\",\n    \"2104-11-08 00:00:00\",\n    \"2104-11-09 00:00:00\",\n    \"2104-11-15 00:00:00\",\n    \"2104-11-16 00:00:00\",\n    \"2104-11-22 00:00:00\",\n    \"2104-11-23 00:00:00\",\n    \"2104-11-29 00:00:00\",\n    \"2104-11-30 00:00:00\",\n    \"2104-12-06 00:00:00\",\n    \"2104-12-07 00:00:00\",\n    \"2104-12-13 00:00:00\",\n    \"2104-12-14 00:00:00\",\n    \"2104-12-20 00:00:00\",\n    \"2104-12-21 00:00:00\",\n    \"2104-12-27 00:00:00\",\n    \"2104-12-28 00:00:00\",\n    \"2105-01-03 00:00:00\",\n    \"2105-01-04 00:00:00\",\n    \"2105-01-10 00:00:00\",\n    \"2105-01-11 00:00:00\",\n    \"2105-01-17 00:00:00\",\n    \"2105-01-18 00:00:00\",\n    \"2105-01-24 00:00:00\",\n    \"2105-01-25 00:00:00\",\n    \"2105-01-31 00:00:00\",\n    \"2105-02-01 00:00:00\",\n    \"2105-02-07 00:00:00\",\n    \"2105-02-08 00:00:00\",\n    \"2105-02-14 00:00:00\",\n    \"2105-02-15 00:00:00\",\n    \"2105-02-21 00:00:00\",\n    \"2105-02-22 00:00:00\",\n    \"2105-02-28 00:00:00\",\n    \"2105-03-01 00:00:00\",\n    \"2105-03-07 00:00:00\",\n    \"2105-03-08 00:00:00\",\n    \"2105-03-14 00:00:00\",\n    \"2105-03-15 00:00:00\",\n    \"2105-03-21 00:00:00\",\n    \"2105-03-22 00:00:00\",\n    \"2105-03-28 00:00:00\",\n    \"2105-03-29 00:00:00\",\n    \"2105-04-04 00:00:00\",\n    \"2105-04-05 00:00:00\",\n    \"2105-04-11 00:00:00\",\n    \"2105-04-12 00:00:00\",\n    \"2105-04-18 00:00:00\",\n    \"2105-04-19 00:00:00\",\n    \"2105-04-25 00:00:00\",\n    \"2105-04-26 00:00:00\",\n    \"2105-05-02 00:00:00\",\n    \"2105-05-03 00:00:00\",\n    \"2105-05-09 00:00:00\",\n    \"2105-05-10 00:00:00\",\n    \"2105-05-16 00:00:00\",\n    \"2105-05-17 00:00:00\",\n    \"2105-05-23 00:00:00\",\n    \"2105-05-24 00:00:00\",\n    \"2105-05-30 00:00:00\",\n    \"2105-05-31 00:00:00\",\n    \"2105-06-06 00:00:00\",\n    \"2105-06-07 00:00:00\",\n    \"2105-06-13 00:00:00\",\n    \"2105-06-14 00:00:00\",\n    \"2105-06-20 00:00:00\",\n    \"2105-06-21 00:00:00\",\n    \"2105-06-27 00:00:00\",\n    \"2105-06-28 00:00:00\",\n    \"2105-07-04 00:00:00\",\n    \"2105-07-05 00:00:00\",\n    \"2105-07-11 00:00:00\",\n    \"2105-07-12 00:00:00\",\n    \"2105-07-18 00:00:00\",\n    \"2105-07-19 00:00:00\",\n    \"2105-07-25 00:00:00\",\n    \"2105-07-26 00:00:00\",\n    \"2105-08-01 00:00:00\",\n    \"2105-08-02 00:00:00\",\n    \"2105-08-08 00:00:00\",\n    \"2105-08-09 00:00:00\",\n    \"2105-08-15 00:00:00\",\n    \"2105-08-16 00:00:00\",\n    \"2105-08-22 00:00:00\",\n    \"2105-08-23 00:00:00\",\n    \"2105-08-29 00:00:00\",\n    \"2105-08-30 00:00:00\",\n    \"2105-09-05 00:00:00\",\n    \"2105-09-06 00:00:00\",\n    \"2105-09-12 00:00:00\",\n    \"2105-09-13 00:00:00\",\n    \"2105-09-19 00:00:00\",\n    \"2105-09-20 00:00:00\",\n    \"2105-09-26 00:00:00\",\n    \"2105-09-27 00:00:00\",\n    \"2105-10-03 00:00:00\",\n    \"2105-10-04 00:00:00\",\n    \"2105-10-10 00:00:00\",\n    \"2105-10-11 00:00:00\",\n    \"2105-10-17 00:00:00\",\n    \"2105-10-18 00:00:00\",\n    \"2105-10-24 00:00:00\",\n    \"2105-10-25 00:00:00\",\n    \"2105-10-31 00:00:00\",\n    \"2105-11-01 00:00:00\",\n    \"2105-11-07 00:00:00\",\n    \"2105-11-08 00:00:00\",\n    \"2105-11-14 00:00:00\",\n    \"2105-11-15 00:00:00\",\n    \"2105-11-21 00:00:00\",\n    \"2105-11-22 00:00:00\",\n    \"2105-11-28 00:00:00\",\n    \"2105-11-29 00:00:00\",\n    \"2105-12-05 00:00:00\",\n    \"2105-12-06 00:00:00\",\n    \"2105-12-12 00:00:00\",\n    \"2105-12-13 00:00:00\",\n    \"2105-12-19 00:00:00\",\n    \"2105-12-20 00:00:00\",\n    \"2105-12-26 00:00:00\",\n    \"2105-12-27 00:00:00\",\n    \"2106-01-02 00:00:00\",\n    \"2106-01-03 00:00:00\",\n    \"2106-01-09 00:00:00\",\n    \"2106-01-10 00:00:00\",\n    \"2106-01-16 00:00:00\",\n    \"2106-01-17 00:00:00\",\n    \"2106-01-23 00:00:00\",\n    \"2106-01-24 00:00:00\",\n    \"2106-01-30 00:00:00\",\n    \"2106-01-31 00:00:00\",\n    \"2106-02-06 00:00:00\",\n    \"2106-02-07 00:00:00\",\n    \"2106-02-13 00:00:00\",\n    \"2106-02-14 00:00:00\",\n    \"2106-02-20 00:00:00\",\n    \"2106-02-21 00:00:00\",\n    \"2106-02-27 00:00:00\",\n    \"2106-02-28 00:00:00\",\n    \"2106-03-06 00:00:00\",\n    \"2106-03-07 00:00:00\",\n    \"2106-03-13 00:00:00\",\n    \"2106-03-14 00:00:00\",\n    \"2106-03-20 00:00:00\",\n    \"2106-03-21 00:00:00\",\n    \"2106-03-27 00:00:00\",\n    \"2106-03-28 00:00:00\",\n    \"2106-04-03 00:00:00\",\n    \"2106-04-04 00:00:00\",\n    \"2106-04-10 00:00:00\",\n    \"2106-04-11 00:00:00\",\n    \"2106-04-17 00:00:00\",\n    \"2106-04-18 00:00:00\",\n    \"2106-04-24 00:00:00\",\n    \"2106-04-25 00:00:00\",\n    \"2106-05-01 00:00:00\",\n    \"2106-05-02 00:00:00\",\n    \"2106-05-08 00:00:00\",\n    \"2106-05-09 00:00:00\",\n    \"2106-05-15 00:00:00\",\n    \"2106-05-16 00:00:00\",\n    \"2106-05-22 00:00:00\",\n    \"2106-05-23 00:00:00\",\n    \"2106-05-29 00:00:00\",\n    \"2106-05-30 00:00:00\",\n    \"2106-06-05 00:00:00\",\n    \"2106-06-06 00:00:00\",\n    \"2106-06-12 00:00:00\",\n    \"2106-06-13 00:00:00\",\n    \"2106-06-19 00:00:00\",\n    \"2106-06-20 00:00:00\",\n    \"2106-06-26 00:00:00\",\n    \"2106-06-27 00:00:00\",\n    \"2106-07-03 00:00:00\",\n    \"2106-07-04 00:00:00\",\n    \"2106-07-10 00:00:00\",\n    \"2106-07-11 00:00:00\",\n    \"2106-07-17 00:00:00\",\n    \"2106-07-18 00:00:00\",\n    \"2106-07-24 00:00:00\",\n    \"2106-07-25 00:00:00\",\n    \"2106-07-31 00:00:00\",\n    \"2106-08-01 00:00:00\",\n    \"2106-08-07 00:00:00\",\n    \"2106-08-08 00:00:00\",\n    \"2106-08-14 00:00:00\",\n    \"2106-08-15 00:00:00\",\n    \"2106-08-21 00:00:00\",\n    \"2106-08-22 00:00:00\",\n    \"2106-08-28 00:00:00\",\n    \"2106-08-29 00:00:00\",\n    \"2106-09-04 00:00:00\",\n    \"2106-09-05 00:00:00\",\n    \"2106-09-11 00:00:00\",\n    \"2106-09-12 00:00:00\",\n    \"2106-09-18 00:00:00\",\n    \"2106-09-19 00:00:00\",\n    \"2106-09-25 00:00:00\",\n    \"2106-09-26 00:00:00\",\n    \"2106-10-02 00:00:00\",\n    \"2106-10-03 00:00:00\",\n    \"2106-10-09 00:00:00\",\n    \"2106-10-10 00:00:00\",\n    \"2106-10-16 00:00:00\",\n    \"2106-10-17 00:00:00\",\n    \"2106-10-23 00:00:00\",\n    \"2106-10-24 00:00:00\",\n    \"2106-10-30 00:00:00\",\n    \"2106-10-31 00:00:00\",\n    \"2106-11-06 00:00:00\",\n    \"2106-11-07 00:00:00\",\n    \"2106-11-13 00:00:00\",\n    \"2106-11-14 00:00:00\",\n    \"2106-11-20 00:00:00\",\n    \"2106-11-21 00:00:00\",\n    \"2106-11-27 00:00:00\",\n    \"2106-11-28 00:00:00\",\n    \"2106-12-04 00:00:00\",\n    \"2106-12-05 00:00:00\",\n    \"2106-12-11 00:00:00\",\n    \"2106-12-12 00:00:00\",\n    \"2106-12-18 00:00:00\",\n    \"2106-12-19 00:00:00\",\n    \"2106-12-25 00:00:00\",\n    \"2106-12-26 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-01-02 00:00:00\",\n    \"2107-01-08 00:00:00\",\n    \"2107-01-09 00:00:00\",\n    \"2107-01-15 00:00:00\",\n    \"2107-01-16 00:00:00\",\n    \"2107-01-22 00:00:00\",\n    \"2107-01-23 00:00:00\",\n    \"2107-01-29 00:00:00\",\n    \"2107-01-30 00:00:00\",\n    \"2107-02-05 00:00:00\",\n    \"2107-02-06 00:00:00\",\n    \"2107-02-12 00:00:00\",\n    \"2107-02-13 00:00:00\",\n    \"2107-02-19 00:00:00\",\n    \"2107-02-20 00:00:00\",\n    \"2107-02-26 00:00:00\",\n    \"2107-02-27 00:00:00\",\n    \"2107-03-05 00:00:00\",\n    \"2107-03-06 00:00:00\",\n    \"2107-03-12 00:00:00\",\n    \"2107-03-13 00:00:00\",\n    \"2107-03-19 00:00:00\",\n    \"2107-03-20 00:00:00\",\n    \"2107-03-26 00:00:00\",\n    \"2107-03-27 00:00:00\",\n    \"2107-04-02 00:00:00\",\n    \"2107-04-03 00:00:00\",\n    \"2107-04-09 00:00:00\",\n    \"2107-04-10 00:00:00\",\n    \"2107-04-16 00:00:00\",\n    \"2107-04-17 00:00:00\",\n    \"2107-04-23 00:00:00\",\n    \"2107-04-24 00:00:00\",\n    \"2107-04-30 00:00:00\",\n    \"2107-05-01 00:00:00\",\n    \"2107-05-07 00:00:00\",\n    \"2107-05-08 00:00:00\",\n    \"2107-05-14 00:00:00\",\n    \"2107-05-15 00:00:00\",\n    \"2107-05-21 00:00:00\",\n    \"2107-05-22 00:00:00\",\n    \"2107-05-28 00:00:00\",\n    \"2107-05-29 00:00:00\",\n    \"2107-06-04 00:00:00\",\n    \"2107-06-05 00:00:00\",\n    \"2107-06-11 00:00:00\",\n    \"2107-06-12 00:00:00\",\n    \"2107-06-18 00:00:00\",\n    \"2107-06-19 00:00:00\",\n    \"2107-06-25 00:00:00\",\n    \"2107-06-26 00:00:00\",\n    \"2107-07-02 00:00:00\",\n    \"2107-07-03 00:00:00\",\n    \"2107-07-09 00:00:00\",\n    \"2107-07-10 00:00:00\",\n    \"2107-07-16 00:00:00\",\n    \"2107-07-17 00:00:00\",\n    \"2107-07-23 00:00:00\",\n    \"2107-07-24 00:00:00\",\n    \"2107-07-30 00:00:00\",\n    \"2107-07-31 00:00:00\",\n    \"2107-08-06 00:00:00\",\n    \"2107-08-07 00:00:00\",\n    \"2107-08-13 00:00:00\",\n    \"2107-08-14 00:00:00\",\n    \"2107-08-20 00:00:00\",\n    \"2107-08-21 00:00:00\",\n    \"2107-08-27 00:00:00\",\n    \"2107-08-28 00:00:00\",\n    \"2107-09-03 00:00:00\",\n    \"2107-09-04 00:00:00\",\n    \"2107-09-10 00:00:00\",\n    \"2107-09-11 00:00:00\",\n    \"2107-09-17 00:00:00\",\n    \"2107-09-18 00:00:00\",\n    \"2107-09-24 00:00:00\",\n    \"2107-09-25 00:00:00\",\n    \"2107-10-01 00:00:00\",\n    \"2107-10-02 00:00:00\",\n    \"2107-10-08 00:00:00\",\n    \"2107-10-09 00:00:00\",\n    \"2107-10-15 00:00:00\",\n    \"2107-10-16 00:00:00\",\n    \"2107-10-22 00:00:00\",\n    \"2107-10-23 00:00:00\",\n    \"2107-10-29 00:00:00\",\n    \"2107-10-30 00:00:00\",\n    \"2107-11-05 00:00:00\",\n    \"2107-11-06 00:00:00\",\n    \"2107-11-12 00:00:00\",\n    \"2107-11-13 00:00:00\",\n    \"2107-11-19 00:00:00\",\n    \"2107-11-20 00:00:00\",\n    \"2107-11-26 00:00:00\",\n    \"2107-11-27 00:00:00\",\n    \"2107-12-03 00:00:00\",\n    \"2107-12-04 00:00:00\",\n    \"2107-12-10 00:00:00\",\n    \"2107-12-11 00:00:00\",\n    \"2107-12-17 00:00:00\",\n    \"2107-12-18 00:00:00\",\n    \"2107-12-24 00:00:00\",\n    \"2107-12-25 00:00:00\",\n    \"2107-12-31 00:00:00\",\n    \"2108-01-01 00:00:00\",\n    \"2108-01-07 00:00:00\",\n    \"2108-01-08 00:00:00\",\n    \"2108-01-14 00:00:00\",\n    \"2108-01-15 00:00:00\",\n    \"2108-01-21 00:00:00\",\n    \"2108-01-22 00:00:00\",\n    \"2108-01-28 00:00:00\",\n    \"2108-01-29 00:00:00\",\n    \"2108-02-04 00:00:00\",\n    \"2108-02-05 00:00:00\",\n    \"2108-02-11 00:00:00\",\n    \"2108-02-12 00:00:00\",\n    \"2108-02-18 00:00:00\",\n    \"2108-02-19 00:00:00\",\n    \"2108-02-25 00:00:00\",\n    \"2108-02-26 00:00:00\",\n    \"2108-03-03 00:00:00\",\n    \"2108-03-04 00:00:00\",\n    \"2108-03-10 00:00:00\",\n    \"2108-03-11 00:00:00\",\n    \"2108-03-17 00:00:00\",\n    \"2108-03-18 00:00:00\",\n    \"2108-03-24 00:00:00\",\n    \"2108-03-25 00:00:00\",\n    \"2108-03-31 00:00:00\",\n    \"2108-04-01 00:00:00\",\n    \"2108-04-07 00:00:00\",\n    \"2108-04-08 00:00:00\",\n    \"2108-04-14 00:00:00\",\n    \"2108-04-15 00:00:00\",\n    \"2108-04-21 00:00:00\",\n    \"2108-04-22 00:00:00\",\n    \"2108-04-28 00:00:00\",\n    \"2108-04-29 00:00:00\",\n    \"2108-05-05 00:00:00\",\n    \"2108-05-06 00:00:00\",\n    \"2108-05-12 00:00:00\",\n    \"2108-05-13 00:00:00\",\n    \"2108-05-19 00:00:00\",\n    \"2108-05-20 00:00:00\",\n    \"2108-05-26 00:00:00\",\n    \"2108-05-27 00:00:00\",\n    \"2108-06-02 00:00:00\",\n    \"2108-06-03 00:00:00\",\n    \"2108-06-09 00:00:00\",\n    \"2108-06-10 00:00:00\",\n    \"2108-06-16 00:00:00\",\n    \"2108-06-17 00:00:00\",\n    \"2108-06-23 00:00:00\",\n    \"2108-06-24 00:00:00\",\n    \"2108-06-30 00:00:00\",\n    \"2108-07-01 00:00:00\",\n    \"2108-07-07 00:00:00\",\n    \"2108-07-08 00:00:00\",\n    \"2108-07-14 00:00:00\",\n    \"2108-07-15 00:00:00\",\n    \"2108-07-21 00:00:00\",\n    \"2108-07-22 00:00:00\",\n    \"2108-07-28 00:00:00\",\n    \"2108-07-29 00:00:00\",\n    \"2108-08-04 00:00:00\",\n    \"2108-08-05 00:00:00\",\n    \"2108-08-11 00:00:00\",\n    \"2108-08-12 00:00:00\",\n    \"2108-08-18 00:00:00\",\n    \"2108-08-19 00:00:00\",\n    \"2108-08-25 00:00:00\",\n    \"2108-08-26 00:00:00\",\n    \"2108-09-01 00:00:00\",\n    \"2108-09-02 00:00:00\",\n    \"2108-09-08 00:00:00\",\n    \"2108-09-09 00:00:00\",\n    \"2108-09-15 00:00:00\",\n    \"2108-09-16 00:00:00\",\n    \"2108-09-22 00:00:00\",\n    \"2108-09-23 00:00:00\",\n    \"2108-09-29 00:00:00\",\n    \"2108-09-30 00:00:00\",\n    \"2108-10-06 00:00:00\",\n    \"2108-10-07 00:00:00\",\n    \"2108-10-13 00:00:00\",\n    \"2108-10-14 00:00:00\",\n    \"2108-10-20 00:00:00\",\n    \"2108-10-21 00:00:00\",\n    \"2108-10-27 00:00:00\",\n    \"2108-10-28 00:00:00\",\n    \"2108-11-03 00:00:00\",\n    \"2108-11-04 00:00:00\",\n    \"2108-11-10 00:00:00\",\n    \"2108-11-11 00:00:00\",\n    \"2108-11-17 00:00:00\",\n    \"2108-11-18 00:00:00\",\n    \"2108-11-24 00:00:00\",\n    \"2108-11-25 00:00:00\",\n    \"2108-12-01 00:00:00\",\n    \"2108-12-02 00:00:00\",\n    \"2108-12-08 00:00:00\",\n    \"2108-12-09 00:00:00\",\n    \"2108-12-15 00:00:00\",\n    \"2108-12-16 00:00:00\",\n    \"2108-12-22 00:00:00\",\n    \"2108-12-23 00:00:00\",\n    \"2108-12-29 00:00:00\",\n    \"2108-12-30 00:00:00\",\n    \"2109-01-05 00:00:00\",\n    \"2109-01-06 00:00:00\",\n    \"2109-01-12 00:00:00\",\n    \"2109-01-13 00:00:00\",\n    \"2109-01-19 00:00:00\",\n    \"2109-01-20 00:00:00\",\n    \"2109-01-26 00:00:00\",\n    \"2109-01-27 00:00:00\",\n    \"2109-02-02 00:00:00\",\n    \"2109-02-03 00:00:00\",\n    \"2109-02-09 00:00:00\",\n    \"2109-02-10 00:00:00\",\n    \"2109-02-16 00:00:00\",\n    \"2109-02-17 00:00:00\",\n    \"2109-02-23 00:00:00\",\n    \"2109-02-24 00:00:00\",\n    \"2109-03-02 00:00:00\",\n    \"2109-03-03 00:00:00\",\n    \"2109-03-09 00:00:00\",\n    \"2109-03-10 00:00:00\",\n    \"2109-03-16 00:00:00\",\n    \"2109-03-17 00:00:00\",\n    \"2109-03-23 00:00:00\",\n    \"2109-03-24 00:00:00\",\n    \"2109-03-30 00:00:00\",\n    \"2109-03-31 00:00:00\",\n    \"2109-04-06 00:00:00\",\n    \"2109-04-07 00:00:00\",\n    \"2109-04-13 00:00:00\",\n    \"2109-04-14 00:00:00\",\n    \"2109-04-20 00:00:00\",\n    \"2109-04-21 00:00:00\",\n    \"2109-04-27 00:00:00\",\n    \"2109-04-28 00:00:00\",\n    \"2109-05-04 00:00:00\",\n    \"2109-05-05 00:00:00\",\n    \"2109-05-11 00:00:00\",\n    \"2109-05-12 00:00:00\",\n    \"2109-05-18 00:00:00\",\n    \"2109-05-19 00:00:00\",\n    \"2109-05-25 00:00:00\",\n    \"2109-05-26 00:00:00\",\n    \"2109-06-01 00:00:00\",\n    \"2109-06-02 00:00:00\",\n    \"2109-06-08 00:00:00\",\n    \"2109-06-09 00:00:00\",\n    \"2109-06-15 00:00:00\",\n    \"2109-06-16 00:00:00\",\n    \"2109-06-22 00:00:00\",\n    \"2109-06-23 00:00:00\",\n    \"2109-06-29 00:00:00\",\n    \"2109-06-30 00:00:00\",\n    \"2109-07-06 00:00:00\",\n    \"2109-07-07 00:00:00\",\n    \"2109-07-13 00:00:00\",\n    \"2109-07-14 00:00:00\",\n    \"2109-07-20 00:00:00\",\n    \"2109-07-21 00:00:00\",\n    \"2109-07-27 00:00:00\",\n    \"2109-07-28 00:00:00\",\n    \"2109-08-03 00:00:00\",\n    \"2109-08-04 00:00:00\",\n    \"2109-08-10 00:00:00\",\n    \"2109-08-11 00:00:00\",\n    \"2109-08-17 00:00:00\",\n    \"2109-08-18 00:00:00\",\n    \"2109-08-24 00:00:00\",\n    \"2109-08-25 00:00:00\",\n    \"2109-08-31 00:00:00\",\n    \"2109-09-01 00:00:00\",\n    \"2109-09-07 00:00:00\",\n    \"2109-09-08 00:00:00\",\n    \"2109-09-14 00:00:00\",\n    \"2109-09-15 00:00:00\",\n    \"2109-09-21 00:00:00\",\n    \"2109-09-22 00:00:00\",\n    \"2109-09-28 00:00:00\",\n    \"2109-09-29 00:00:00\",\n    \"2109-10-05 00:00:00\",\n    \"2109-10-06 00:00:00\",\n    \"2109-10-12 00:00:00\",\n    \"2109-10-13 00:00:00\",\n    \"2109-10-19 00:00:00\",\n    \"2109-10-20 00:00:00\",\n    \"2109-10-26 00:00:00\",\n    \"2109-10-27 00:00:00\",\n    \"2109-11-02 00:00:00\",\n    \"2109-11-03 00:00:00\",\n    \"2109-11-09 00:00:00\",\n    \"2109-11-10 00:00:00\",\n    \"2109-11-16 00:00:00\",\n    \"2109-11-17 00:00:00\",\n    \"2109-11-23 00:00:00\",\n    \"2109-11-24 00:00:00\",\n    \"2109-11-30 00:00:00\",\n    \"2109-12-01 00:00:00\",\n    \"2109-12-07 00:00:00\",\n    \"2109-12-08 00:00:00\",\n    \"2109-12-14 00:00:00\",\n    \"2109-12-15 00:00:00\",\n    \"2109-12-21 00:00:00\",\n    \"2109-12-22 00:00:00\",\n    \"2109-12-28 00:00:00\",\n    \"2109-12-29 00:00:00\",\n    \"2110-01-04 00:00:00\",\n    \"2110-01-05 00:00:00\",\n    \"2110-01-11 00:00:00\",\n    \"2110-01-12 00:00:00\",\n    \"2110-01-18 00:00:00\",\n    \"2110-01-19 00:00:00\",\n    \"2110-01-25 00:00:00\",\n    \"2110-01-26 00:00:00\",\n    \"2110-02-01 00:00:00\",\n    \"2110-02-02 00:00:00\",\n    \"2110-02-08 00:00:00\",\n    \"2110-02-09 00:00:00\",\n    \"2110-02-15 00:00:00\",\n    \"2110-02-16 00:00:00\",\n    \"2110-02-22 00:00:00\",\n    \"2110-02-23 00:00:00\",\n    \"2110-03-01 00:00:00\",\n    \"2110-03-02 00:00:00\",\n    \"2110-03-08 00:00:00\",\n    \"2110-03-09 00:00:00\",\n    \"2110-03-15 00:00:00\",\n    \"2110-03-16 00:00:00\",\n    \"2110-03-22 00:00:00\",\n    \"2110-03-23 00:00:00\",\n    \"2110-03-29 00:00:00\",\n    \"2110-03-30 00:00:00\",\n    \"2110-04-05 00:00:00\",\n    \"2110-04-06 00:00:00\",\n    \"2110-04-12 00:00:00\",\n    \"2110-04-13 00:00:00\",\n    \"2110-04-19 00:00:00\",\n    \"2110-04-20 00:00:00\",\n    \"2110-04-26 00:00:00\",\n    \"2110-04-27 00:00:00\",\n    \"2110-05-03 00:00:00\",\n    \"2110-05-04 00:00:00\",\n    \"2110-05-10 00:00:00\",\n    \"2110-05-11 00:00:00\",\n    \"2110-05-17 00:00:00\",\n    \"2110-05-18 00:00:00\",\n    \"2110-05-24 00:00:00\",\n    \"2110-05-25 00:00:00\",\n    \"2110-05-31 00:00:00\",\n    \"2110-06-01 00:00:00\",\n    \"2110-06-07 00:00:00\",\n    \"2110-06-08 00:00:00\",\n    \"2110-06-14 00:00:00\",\n    \"2110-06-15 00:00:00\",\n    \"2110-06-21 00:00:00\",\n    \"2110-06-22 00:00:00\",\n    \"2110-06-28 00:00:00\",\n    \"2110-06-29 00:00:00\",\n    \"2110-07-05 00:00:00\",\n    \"2110-07-06 00:00:00\",\n    \"2110-07-12 00:00:00\",\n    \"2110-07-13 00:00:00\",\n    \"2110-07-19 00:00:00\",\n    \"2110-07-20 00:00:00\",\n    \"2110-07-26 00:00:00\",\n    \"2110-07-27 00:00:00\",\n    \"2110-08-02 00:00:00\",\n    \"2110-08-03 00:00:00\",\n    \"2110-08-09 00:00:00\",\n    \"2110-08-10 00:00:00\",\n    \"2110-08-16 00:00:00\",\n    \"2110-08-17 00:00:00\",\n    \"2110-08-23 00:00:00\",\n    \"2110-08-24 00:00:00\",\n    \"2110-08-30 00:00:00\",\n    \"2110-08-31 00:00:00\",\n    \"2110-09-06 00:00:00\",\n    \"2110-09-07 00:00:00\",\n    \"2110-09-13 00:00:00\",\n    \"2110-09-14 00:00:00\",\n    \"2110-09-20 00:00:00\",\n    \"2110-09-21 00:00:00\",\n    \"2110-09-27 00:00:00\",\n    \"2110-09-28 00:00:00\",\n    \"2110-10-04 00:00:00\",\n    \"2110-10-05 00:00:00\",\n    \"2110-10-11 00:00:00\",\n    \"2110-10-12 00:00:00\",\n    \"2110-10-18 00:00:00\",\n    \"2110-10-19 00:00:00\",\n    \"2110-10-25 00:00:00\",\n    \"2110-10-26 00:00:00\",\n    \"2110-11-01 00:00:00\",\n    \"2110-11-02 00:00:00\",\n    \"2110-11-08 00:00:00\",\n    \"2110-11-09 00:00:00\",\n    \"2110-11-15 00:00:00\",\n    \"2110-11-16 00:00:00\",\n    \"2110-11-22 00:00:00\",\n    \"2110-11-23 00:00:00\",\n    \"2110-11-29 00:00:00\",\n    \"2110-11-30 00:00:00\",\n    \"2110-12-06 00:00:00\",\n    \"2110-12-07 00:00:00\",\n    \"2110-12-13 00:00:00\",\n    \"2110-12-14 00:00:00\",\n    \"2110-12-20 00:00:00\",\n    \"2110-12-21 00:00:00\",\n    \"2110-12-27 00:00:00\",\n    \"2110-12-28 00:00:00\",\n    \"2111-01-03 00:00:00\",\n    \"2111-01-04 00:00:00\",\n    \"2111-01-10 00:00:00\",\n    \"2111-01-11 00:00:00\",\n    \"2111-01-17 00:00:00\",\n    \"2111-01-18 00:00:00\",\n    \"2111-01-24 00:00:00\",\n    \"2111-01-25 00:00:00\",\n    \"2111-01-31 00:00:00\",\n    \"2111-02-01 00:00:00\",\n    \"2111-02-07 00:00:00\",\n    \"2111-02-08 00:00:00\",\n    \"2111-02-14 00:00:00\",\n    \"2111-02-15 00:00:00\",\n    \"2111-02-21 00:00:00\",\n    \"2111-02-22 00:00:00\",\n    \"2111-02-28 00:00:00\",\n    \"2111-03-01 00:00:00\",\n    \"2111-03-07 00:00:00\",\n    \"2111-03-08 00:00:00\",\n    \"2111-03-14 00:00:00\",\n    \"2111-03-15 00:00:00\",\n    \"2111-03-21 00:00:00\",\n    \"2111-03-22 00:00:00\",\n    \"2111-03-28 00:00:00\",\n    \"2111-03-29 00:00:00\",\n    \"2111-04-04 00:00:00\",\n    \"2111-04-05 00:00:00\",\n    \"2111-04-11 00:00:00\",\n    \"2111-04-12 00:00:00\",\n    \"2111-04-18 00:00:00\",\n    \"2111-04-19 00:00:00\",\n    \"2111-04-25 00:00:00\",\n    \"2111-04-26 00:00:00\",\n    \"2111-05-02 00:00:00\",\n    \"2111-05-03 00:00:00\",\n    \"2111-05-09 00:00:00\",\n    \"2111-05-10 00:00:00\",\n    \"2111-05-16 00:00:00\",\n    \"2111-05-17 00:00:00\",\n    \"2111-05-23 00:00:00\",\n    \"2111-05-24 00:00:00\",\n    \"2111-05-30 00:00:00\",\n    \"2111-05-31 00:00:00\",\n    \"2111-06-06 00:00:00\",\n    \"2111-06-07 00:00:00\",\n    \"2111-06-13 00:00:00\",\n    \"2111-06-14 00:00:00\",\n    \"2111-06-20 00:00:00\",\n    \"2111-06-21 00:00:00\",\n    \"2111-06-27 00:00:00\",\n    \"2111-06-28 00:00:00\",\n    \"2111-07-04 00:00:00\",\n    \"2111-07-05 00:00:00\",\n    \"2111-07-11 00:00:00\",\n    \"2111-07-12 00:00:00\",\n    \"2111-07-18 00:00:00\",\n    \"2111-07-19 00:00:00\",\n    \"2111-07-25 00:00:00\",\n    \"2111-07-26 00:00:00\",\n    \"2111-08-01 00:00:00\",\n    \"2111-08-02 00:00:00\",\n    \"2111-08-08 00:00:00\",\n    \"2111-08-09 00:00:00\",\n    \"2111-08-15 00:00:00\",\n    \"2111-08-16 00:00:00\",\n    \"2111-08-22 00:00:00\",\n    \"2111-08-23 00:00:00\",\n    \"2111-08-29 00:00:00\",\n    \"2111-08-30 00:00:00\",\n    \"2111-09-05 00:00:00\",\n    \"2111-09-06 00:00:00\",\n    \"2111-09-12 00:00:00\",\n    \"2111-09-13 00:00:00\",\n    \"2111-09-19 00:00:00\",\n    \"2111-09-20 00:00:00\",\n    \"2111-09-26 00:00:00\",\n    \"2111-09-27 00:00:00\",\n    \"2111-10-03 00:00:00\",\n    \"2111-10-04 00:00:00\",\n    \"2111-10-10 00:00:00\",\n    \"2111-10-11 00:00:00\",\n    \"2111-10-17 00:00:00\",\n    \"2111-10-18 00:00:00\",\n    \"2111-10-24 00:00:00\",\n    \"2111-10-25 00:00:00\",\n    \"2111-10-31 00:00:00\",\n    \"2111-11-01 00:00:00\",\n    \"2111-11-07 00:00:00\",\n    \"2111-11-08 00:00:00\",\n    \"2111-11-14 00:00:00\",\n    \"2111-11-15 00:00:00\",\n    \"2111-11-21 00:00:00\",\n    \"2111-11-22 00:00:00\",\n    \"2111-11-28 00:00:00\",\n    \"2111-11-29 00:00:00\",\n    \"2111-12-05 00:00:00\",\n    \"2111-12-06 00:00:00\",\n    \"2111-12-12 00:00:00\",\n    \"2111-12-13 00:00:00\",\n    \"2111-12-19 00:00:00\",\n    \"2111-12-20 00:00:00\",\n    \"2111-12-26 00:00:00\",\n    \"2111-12-27 00:00:00\",\n    \"2112-01-02 00:00:00\",\n    \"2112-01-03 00:00:00\",\n    \"2112-01-09 00:00:00\",\n    \"2112-01-10 00:00:00\",\n    \"2112-01-16 00:00:00\",\n    \"2112-01-17 00:00:00\",\n    \"2112-01-23 00:00:00\",\n    \"2112-01-24 00:00:00\",\n    \"2112-01-30 00:00:00\",\n    \"2112-01-31 00:00:00\",\n    \"2112-02-06 00:00:00\",\n    \"2112-02-07 00:00:00\",\n    \"2112-02-13 00:00:00\",\n    \"2112-02-14 00:00:00\",\n    \"2112-02-20 00:00:00\",\n    \"2112-02-21 00:00:00\",\n    \"2112-02-27 00:00:00\",\n    \"2112-02-28 00:00:00\",\n    \"2112-03-05 00:00:00\",\n    \"2112-03-06 00:00:00\",\n    \"2112-03-12 00:00:00\",\n    \"2112-03-13 00:00:00\",\n    \"2112-03-19 00:00:00\",\n    \"2112-03-20 00:00:00\",\n    \"2112-03-26 00:00:00\",\n    \"2112-03-27 00:00:00\",\n    \"2112-04-02 00:00:00\",\n    \"2112-04-03 00:00:00\",\n    \"2112-04-09 00:00:00\",\n    \"2112-04-10 00:00:00\",\n    \"2112-04-16 00:00:00\",\n    \"2112-04-17 00:00:00\",\n    \"2112-04-23 00:00:00\",\n    \"2112-04-24 00:00:00\",\n    \"2112-04-30 00:00:00\",\n    \"2112-05-01 00:00:00\",\n    \"2112-05-07 00:00:00\",\n    \"2112-05-08 00:00:00\",\n    \"2112-05-14 00:00:00\",\n    \"2112-05-15 00:00:00\",\n    \"2112-05-21 00:00:00\",\n    \"2112-05-22 00:00:00\",\n    \"2112-05-28 00:00:00\",\n    \"2112-05-29 00:00:00\",\n    \"2112-06-04 00:00:00\",\n    \"2112-06-05 00:00:00\",\n    \"2112-06-11 00:00:00\",\n    \"2112-06-12 00:00:00\",\n    \"2112-06-18 00:00:00\",\n    \"2112-06-19 00:00:00\",\n    \"2112-06-25 00:00:00\",\n    \"2112-06-26 00:00:00\",\n    \"2112-07-02 00:00:00\",\n    \"2112-07-03 00:00:00\",\n    \"2112-07-09 00:00:00\",\n    \"2112-07-10 00:00:00\",\n    \"2112-07-16 00:00:00\",\n    \"2112-07-17 00:00:00\",\n    \"2112-07-23 00:00:00\",\n    \"2112-07-24 00:00:00\",\n    \"2112-07-30 00:00:00\",\n    \"2112-07-31 00:00:00\",\n    \"2112-08-06 00:00:00\",\n    \"2112-08-07 00:00:00\",\n    \"2112-08-13 00:00:00\",\n    \"2112-08-14 00:00:00\",\n    \"2112-08-20 00:00:00\",\n    \"2112-08-21 00:00:00\",\n    \"2112-08-27 00:00:00\",\n    \"2112-08-28 00:00:00\",\n    \"2112-09-03 00:00:00\",\n    \"2112-09-04 00:00:00\",\n    \"2112-09-10 00:00:00\",\n    \"2112-09-11 00:00:00\",\n    \"2112-09-17 00:00:00\",\n    \"2112-09-18 00:00:00\",\n    \"2112-09-24 00:00:00\",\n    \"2112-09-25 00:00:00\",\n    \"2112-10-01 00:00:00\",\n    \"2112-10-02 00:00:00\",\n    \"2112-10-08 00:00:00\",\n    \"2112-10-09 00:00:00\",\n    \"2112-10-15 00:00:00\",\n    \"2112-10-16 00:00:00\",\n    \"2112-10-22 00:00:00\",\n    \"2112-10-23 00:00:00\",\n    \"2112-10-29 00:00:00\",\n    \"2112-10-30 00:00:00\",\n    \"2112-11-05 00:00:00\",\n    \"2112-11-06 00:00:00\",\n    \"2112-11-12 00:00:00\",\n    \"2112-11-13 00:00:00\",\n    \"2112-11-19 00:00:00\",\n    \"2112-11-20 00:00:00\",\n    \"2112-11-26 00:00:00\",\n    \"2112-11-27 00:00:00\",\n    \"2112-12-03 00:00:00\",\n    \"2112-12-04 00:00:00\",\n    \"2112-12-10 00:00:00\",\n    \"2112-12-11 00:00:00\",\n    \"2112-12-17 00:00:00\",\n    \"2112-12-18 00:00:00\",\n    \"2112-12-24 00:00:00\",\n    \"2112-12-25 00:00:00\",\n    \"2112-12-31 00:00:00\",\n    \"2113-01-01 00:00:00\",\n    \"2113-01-07 00:00:00\",\n    \"2113-01-08 00:00:00\",\n    \"2113-01-14 00:00:00\",\n    \"2113-01-15 00:00:00\",\n    \"2113-01-21 00:00:00\",\n    \"2113-01-22 00:00:00\",\n    \"2113-01-28 00:00:00\",\n    \"2113-01-29 00:00:00\",\n    \"2113-02-04 00:00:00\",\n    \"2113-02-05 00:00:00\",\n    \"2113-02-11 00:00:00\",\n    \"2113-02-12 00:00:00\",\n    \"2113-02-18 00:00:00\",\n    \"2113-02-19 00:00:00\",\n    \"2113-02-25 00:00:00\",\n    \"2113-02-26 00:00:00\",\n    \"2113-03-04 00:00:00\",\n    \"2113-03-05 00:00:00\",\n    \"2113-03-11 00:00:00\",\n    \"2113-03-12 00:00:00\",\n    \"2113-03-18 00:00:00\",\n    \"2113-03-19 00:00:00\",\n    \"2113-03-25 00:00:00\",\n    \"2113-03-26 00:00:00\",\n    \"2113-04-01 00:00:00\",\n    \"2113-04-02 00:00:00\",\n    \"2113-04-08 00:00:00\",\n    \"2113-04-09 00:00:00\",\n    \"2113-04-15 00:00:00\",\n    \"2113-04-16 00:00:00\",\n    \"2113-04-22 00:00:00\",\n    \"2113-04-23 00:00:00\",\n    \"2113-04-29 00:00:00\",\n    \"2113-04-30 00:00:00\",\n    \"2113-05-06 00:00:00\",\n    \"2113-05-07 00:00:00\",\n    \"2113-05-13 00:00:00\",\n    \"2113-05-14 00:00:00\",\n    \"2113-05-20 00:00:00\",\n    \"2113-05-21 00:00:00\",\n    \"2113-05-27 00:00:00\",\n    \"2113-05-28 00:00:00\",\n    \"2113-06-03 00:00:00\",\n    \"2113-06-04 00:00:00\",\n    \"2113-06-10 00:00:00\",\n    \"2113-06-11 00:00:00\",\n    \"2113-06-17 00:00:00\",\n    \"2113-06-18 00:00:00\",\n    \"2113-06-24 00:00:00\",\n    \"2113-06-25 00:00:00\",\n    \"2113-07-01 00:00:00\",\n    \"2113-07-02 00:00:00\",\n    \"2113-07-08 00:00:00\",\n    \"2113-07-09 00:00:00\",\n    \"2113-07-15 00:00:00\",\n    \"2113-07-16 00:00:00\",\n    \"2113-07-22 00:00:00\",\n    \"2113-07-23 00:00:00\",\n    \"2113-07-29 00:00:00\",\n    \"2113-07-30 00:00:00\",\n    \"2113-08-05 00:00:00\",\n    \"2113-08-06 00:00:00\",\n    \"2113-08-12 00:00:00\",\n    \"2113-08-13 00:00:00\",\n    \"2113-08-19 00:00:00\",\n    \"2113-08-20 00:00:00\",\n    \"2113-08-26 00:00:00\",\n    \"2113-08-27 00:00:00\",\n    \"2113-09-02 00:00:00\",\n    \"2113-09-03 00:00:00\",\n    \"2113-09-09 00:00:00\",\n    \"2113-09-10 00:00:00\",\n    \"2113-09-16 00:00:00\",\n    \"2113-09-17 00:00:00\",\n    \"2113-09-23 00:00:00\",\n    \"2113-09-24 00:00:00\",\n    \"2113-09-30 00:00:00\",\n    \"2113-10-01 00:00:00\",\n    \"2113-10-07 00:00:00\",\n    \"2113-10-08 00:00:00\",\n    \"2113-10-14 00:00:00\",\n    \"2113-10-15 00:00:00\",\n    \"2113-10-21 00:00:00\",\n    \"2113-10-22 00:00:00\",\n    \"2113-10-28 00:00:00\",\n    \"2113-10-29 00:00:00\",\n    \"2113-11-04 00:00:00\",\n    \"2113-11-05 00:00:00\",\n    \"2113-11-11 00:00:00\",\n    \"2113-11-12 00:00:00\",\n    \"2113-11-18 00:00:00\",\n    \"2113-11-19 00:00:00\",\n    \"2113-11-25 00:00:00\",\n    \"2113-11-26 00:00:00\",\n    \"2113-12-02 00:00:00\",\n    \"2113-12-03 00:00:00\",\n    \"2113-12-09 00:00:00\",\n    \"2113-12-10 00:00:00\",\n    \"2113-12-16 00:00:00\",\n    \"2113-12-17 00:00:00\",\n    \"2113-12-23 00:00:00\",\n    \"2113-12-24 00:00:00\",\n    \"2113-12-30 00:00:00\",\n    \"2113-12-31 00:00:00\",\n    \"2114-01-06 00:00:00\",\n    \"2114-01-07 00:00:00\",\n    \"2114-01-13 00:00:00\",\n    \"2114-01-14 00:00:00\",\n    \"2114-01-20 00:00:00\",\n    \"2114-01-21 00:00:00\",\n    \"2114-01-27 00:00:00\",\n    \"2114-01-28 00:00:00\",\n    \"2114-02-03 00:00:00\",\n    \"2114-02-04 00:00:00\",\n    \"2114-02-10 00:00:00\",\n    \"2114-02-11 00:00:00\",\n    \"2114-02-17 00:00:00\",\n    \"2114-02-18 00:00:00\",\n    \"2114-02-24 00:00:00\",\n    \"2114-02-25 00:00:00\",\n    \"2114-03-03 00:00:00\",\n    \"2114-03-04 00:00:00\",\n    \"2114-03-10 00:00:00\",\n    \"2114-03-11 00:00:00\",\n    \"2114-03-17 00:00:00\",\n    \"2114-03-18 00:00:00\",\n    \"2114-03-24 00:00:00\",\n    \"2114-03-25 00:00:00\",\n    \"2114-03-31 00:00:00\",\n    \"2114-04-01 00:00:00\",\n    \"2114-04-07 00:00:00\",\n    \"2114-04-08 00:00:00\",\n    \"2114-04-14 00:00:00\",\n    \"2114-04-15 00:00:00\",\n    \"2114-04-21 00:00:00\",\n    \"2114-04-22 00:00:00\",\n    \"2114-04-28 00:00:00\",\n    \"2114-04-29 00:00:00\",\n    \"2114-05-05 00:00:00\",\n    \"2114-05-06 00:00:00\",\n    \"2114-05-12 00:00:00\",\n    \"2114-05-13 00:00:00\",\n    \"2114-05-19 00:00:00\",\n    \"2114-05-20 00:00:00\",\n    \"2114-05-26 00:00:00\",\n    \"2114-05-27 00:00:00\",\n    \"2114-06-02 00:00:00\",\n    \"2114-06-03 00:00:00\",\n    \"2114-06-09 00:00:00\",\n    \"2114-06-10 00:00:00\",\n    \"2114-06-16 00:00:00\",\n    \"2114-06-17 00:00:00\",\n    \"2114-06-23 00:00:00\",\n    \"2114-06-24 00:00:00\",\n    \"2114-06-30 00:00:00\",\n    \"2114-07-01 00:00:00\",\n    \"2114-07-07 00:00:00\",\n    \"2114-07-08 00:00:00\",\n    \"2114-07-14 00:00:00\",\n    \"2114-07-15 00:00:00\",\n    \"2114-07-21 00:00:00\",\n    \"2114-07-22 00:00:00\",\n    \"2114-07-28 00:00:00\",\n    \"2114-07-29 00:00:00\",\n    \"2114-08-04 00:00:00\",\n    \"2114-08-05 00:00:00\",\n    \"2114-08-11 00:00:00\",\n    \"2114-08-12 00:00:00\",\n    \"2114-08-18 00:00:00\",\n    \"2114-08-19 00:00:00\",\n    \"2114-08-25 00:00:00\",\n    \"2114-08-26 00:00:00\",\n    \"2114-09-01 00:00:00\",\n    \"2114-09-02 00:00:00\",\n    \"2114-09-08 00:00:00\",\n    \"2114-09-09 00:00:00\",\n    \"2114-09-15 00:00:00\",\n    \"2114-09-16 00:00:00\",\n    \"2114-09-22 00:00:00\",\n    \"2114-09-23 00:00:00\",\n    \"2114-09-29 00:00:00\",\n    \"2114-09-30 00:00:00\",\n    \"2114-10-06 00:00:00\",\n    \"2114-10-07 00:00:00\",\n    \"2114-10-13 00:00:00\",\n    \"2114-10-14 00:00:00\",\n    \"2114-10-20 00:00:00\",\n    \"2114-10-21 00:00:00\",\n    \"2114-10-27 00:00:00\",\n    \"2114-10-28 00:00:00\",\n    \"2114-11-03 00:00:00\",\n    \"2114-11-04 00:00:00\",\n    \"2114-11-10 00:00:00\",\n    \"2114-11-11 00:00:00\",\n    \"2114-11-17 00:00:00\",\n    \"2114-11-18 00:00:00\",\n    \"2114-11-24 00:00:00\",\n    \"2114-11-25 00:00:00\",\n    \"2114-12-01 00:00:00\",\n    \"2114-12-02 00:00:00\",\n    \"2114-12-08 00:00:00\",\n    \"2114-12-09 00:00:00\",\n    \"2114-12-15 00:00:00\",\n    \"2114-12-16 00:00:00\",\n    \"2114-12-22 00:00:00\",\n    \"2114-12-23 00:00:00\",\n    \"2114-12-29 00:00:00\",\n    \"2114-12-30 00:00:00\",\n    \"2115-01-05 00:00:00\",\n    \"2115-01-06 00:00:00\",\n    \"2115-01-12 00:00:00\",\n    \"2115-01-13 00:00:00\",\n    \"2115-01-19 00:00:00\",\n    \"2115-01-20 00:00:00\",\n    \"2115-01-26 00:00:00\",\n    \"2115-01-27 00:00:00\",\n    \"2115-02-02 00:00:00\",\n    \"2115-02-03 00:00:00\",\n    \"2115-02-09 00:00:00\",\n    \"2115-02-10 00:00:00\",\n    \"2115-02-16 00:00:00\",\n    \"2115-02-17 00:00:00\",\n    \"2115-02-23 00:00:00\",\n    \"2115-02-24 00:00:00\",\n    \"2115-03-02 00:00:00\",\n    \"2115-03-03 00:00:00\",\n    \"2115-03-09 00:00:00\",\n    \"2115-03-10 00:00:00\",\n    \"2115-03-16 00:00:00\",\n    \"2115-03-17 00:00:00\",\n    \"2115-03-23 00:00:00\",\n    \"2115-03-24 00:00:00\",\n    \"2115-03-30 00:00:00\",\n    \"2115-03-31 00:00:00\",\n    \"2115-04-06 00:00:00\",\n    \"2115-04-07 00:00:00\",\n    \"2115-04-13 00:00:00\",\n    \"2115-04-14 00:00:00\",\n    \"2115-04-20 00:00:00\",\n    \"2115-04-21 00:00:00\",\n    \"2115-04-27 00:00:00\",\n    \"2115-04-28 00:00:00\",\n    \"2115-05-04 00:00:00\",\n    \"2115-05-05 00:00:00\",\n    \"2115-05-11 00:00:00\",\n    \"2115-05-12 00:00:00\",\n    \"2115-05-18 00:00:00\",\n    \"2115-05-19 00:00:00\",\n    \"2115-05-25 00:00:00\",\n    \"2115-05-26 00:00:00\",\n    \"2115-06-01 00:00:00\",\n    \"2115-06-02 00:00:00\",\n    \"2115-06-08 00:00:00\",\n    \"2115-06-09 00:00:00\",\n    \"2115-06-15 00:00:00\",\n    \"2115-06-16 00:00:00\",\n    \"2115-06-22 00:00:00\",\n    \"2115-06-23 00:00:00\",\n    \"2115-06-29 00:00:00\",\n    \"2115-06-30 00:00:00\",\n    \"2115-07-06 00:00:00\",\n    \"2115-07-07 00:00:00\",\n    \"2115-07-13 00:00:00\",\n    \"2115-07-14 00:00:00\",\n    \"2115-07-20 00:00:00\",\n    \"2115-07-21 00:00:00\",\n    \"2115-07-27 00:00:00\",\n    \"2115-07-28 00:00:00\",\n    \"2115-08-03 00:00:00\",\n    \"2115-08-04 00:00:00\",\n    \"2115-08-10 00:00:00\",\n    \"2115-08-11 00:00:00\",\n    \"2115-08-17 00:00:00\",\n    \"2115-08-18 00:00:00\",\n    \"2115-08-24 00:00:00\",\n    \"2115-08-25 00:00:00\",\n    \"2115-08-31 00:00:00\",\n    \"2115-09-01 00:00:00\",\n    \"2115-09-07 00:00:00\",\n    \"2115-09-08 00:00:00\",\n    \"2115-09-14 00:00:00\",\n    \"2115-09-15 00:00:00\",\n    \"2115-09-21 00:00:00\",\n    \"2115-09-22 00:00:00\",\n    \"2115-09-28 00:00:00\",\n    \"2115-09-29 00:00:00\",\n    \"2115-10-05 00:00:00\",\n    \"2115-10-06 00:00:00\",\n    \"2115-10-12 00:00:00\",\n    \"2115-10-13 00:00:00\",\n    \"2115-10-19 00:00:00\",\n    \"2115-10-20 00:00:00\",\n    \"2115-10-26 00:00:00\",\n    \"2115-10-27 00:00:00\",\n    \"2115-11-02 00:00:00\",\n    \"2115-11-03 00:00:00\",\n    \"2115-11-09 00:00:00\",\n    \"2115-11-10 00:00:00\",\n    \"2115-11-16 00:00:00\",\n    \"2115-11-17 00:00:00\",\n    \"2115-11-23 00:00:00\",\n    \"2115-11-24 00:00:00\",\n    \"2115-11-30 00:00:00\",\n    \"2115-12-01 00:00:00\",\n    \"2115-12-07 00:00:00\",\n    \"2115-12-08 00:00:00\",\n    \"2115-12-14 00:00:00\",\n    \"2115-12-15 00:00:00\",\n    \"2115-12-21 00:00:00\",\n    \"2115-12-22 00:00:00\",\n    \"2115-12-28 00:00:00\",\n    \"2115-12-29 00:00:00\",\n    \"2116-01-04 00:00:00\",\n    \"2116-01-05 00:00:00\",\n    \"2116-01-11 00:00:00\",\n    \"2116-01-12 00:00:00\",\n    \"2116-01-18 00:00:00\",\n    \"2116-01-19 00:00:00\",\n    \"2116-01-25 00:00:00\",\n    \"2116-01-26 00:00:00\",\n    \"2116-02-01 00:00:00\",\n    \"2116-02-02 00:00:00\",\n    \"2116-02-08 00:00:00\",\n    \"2116-02-09 00:00:00\",\n    \"2116-02-15 00:00:00\",\n    \"2116-02-16 00:00:00\",\n    \"2116-02-22 00:00:00\",\n    \"2116-02-23 00:00:00\",\n    \"2116-02-29 00:00:00\",\n    \"2116-03-01 00:00:00\",\n    \"2116-03-07 00:00:00\",\n    \"2116-03-08 00:00:00\",\n    \"2116-03-14 00:00:00\",\n    \"2116-03-15 00:00:00\",\n    \"2116-03-21 00:00:00\",\n    \"2116-03-22 00:00:00\",\n    \"2116-03-28 00:00:00\",\n    \"2116-03-29 00:00:00\",\n    \"2116-04-04 00:00:00\",\n    \"2116-04-05 00:00:00\",\n    \"2116-04-11 00:00:00\",\n    \"2116-04-12 00:00:00\",\n    \"2116-04-18 00:00:00\",\n    \"2116-04-19 00:00:00\",\n    \"2116-04-25 00:00:00\",\n    \"2116-04-26 00:00:00\",\n    \"2116-05-02 00:00:00\",\n    \"2116-05-03 00:00:00\",\n    \"2116-05-09 00:00:00\",\n    \"2116-05-10 00:00:00\",\n    \"2116-05-16 00:00:00\",\n    \"2116-05-17 00:00:00\",\n    \"2116-05-23 00:00:00\",\n    \"2116-05-24 00:00:00\",\n    \"2116-05-30 00:00:00\",\n    \"2116-05-31 00:00:00\",\n    \"2116-06-06 00:00:00\",\n    \"2116-06-07 00:00:00\",\n    \"2116-06-13 00:00:00\",\n    \"2116-06-14 00:00:00\",\n    \"2116-06-20 00:00:00\",\n    \"2116-06-21 00:00:00\",\n    \"2116-06-27 00:00:00\",\n    \"2116-06-28 00:00:00\",\n    \"2116-07-04 00:00:00\",\n    \"2116-07-05 00:00:00\",\n    \"2116-07-11 00:00:00\",\n    \"2116-07-12 00:00:00\",\n    \"2116-07-18 00:00:00\",\n    \"2116-07-19 00:00:00\",\n    \"2116-07-25 00:00:00\",\n    \"2116-07-26 00:00:00\",\n    \"2116-08-01 00:00:00\",\n    \"2116-08-02 00:00:00\",\n    \"2116-08-08 00:00:00\",\n    \"2116-08-09 00:00:00\",\n    \"2116-08-15 00:00:00\",\n    \"2116-08-16 00:00:00\",\n    \"2116-08-22 00:00:00\",\n    \"2116-08-23 00:00:00\",\n    \"2116-08-29 00:00:00\",\n    \"2116-08-30 00:00:00\",\n    \"2116-09-05 00:00:00\",\n    \"2116-09-06 00:00:00\",\n    \"2116-09-12 00:00:00\",\n    \"2116-09-13 00:00:00\",\n    \"2116-09-19 00:00:00\",\n    \"2116-09-20 00:00:00\",\n    \"2116-09-26 00:00:00\",\n    \"2116-09-27 00:00:00\",\n    \"2116-10-03 00:00:00\",\n    \"2116-10-04 00:00:00\",\n    \"2116-10-10 00:00:00\",\n    \"2116-10-11 00:00:00\",\n    \"2116-10-17 00:00:00\",\n    \"2116-10-18 00:00:00\",\n    \"2116-10-24 00:00:00\",\n    \"2116-10-25 00:00:00\",\n    \"2116-10-31 00:00:00\",\n    \"2116-11-01 00:00:00\",\n    \"2116-11-07 00:00:00\",\n    \"2116-11-08 00:00:00\",\n    \"2116-11-14 00:00:00\",\n    \"2116-11-15 00:00:00\",\n    \"2116-11-21 00:00:00\",\n    \"2116-11-22 00:00:00\",\n    \"2116-11-28 00:00:00\",\n    \"2116-11-29 00:00:00\",\n    \"2116-12-05 00:00:00\",\n    \"2116-12-06 00:00:00\",\n    \"2116-12-12 00:00:00\",\n    \"2116-12-13 00:00:00\",\n    \"2116-12-19 00:00:00\",\n    \"2116-12-20 00:00:00\",\n    \"2116-12-26 00:00:00\",\n    \"2116-12-27 00:00:00\",\n    \"2117-01-02 00:00:00\",\n    \"2117-01-03 00:00:00\",\n    \"2117-01-09 00:00:00\",\n    \"2117-01-10 00:00:00\",\n    \"2117-01-16 00:00:00\",\n    \"2117-01-17 00:00:00\",\n    \"2117-01-23 00:00:00\",\n    \"2117-01-24 00:00:00\",\n    \"2117-01-30 00:00:00\",\n    \"2117-01-31 00:00:00\",\n    \"2117-02-06 00:00:00\",\n    \"2117-02-07 00:00:00\",\n    \"2117-02-13 00:00:00\",\n    \"2117-02-14 00:00:00\",\n    \"2117-02-20 00:00:00\",\n    \"2117-02-21 00:00:00\",\n    \"2117-02-27 00:00:00\",\n    \"2117-02-28 00:00:00\",\n    \"2117-03-06 00:00:00\",\n    \"2117-03-07 00:00:00\",\n    \"2117-03-13 00:00:00\",\n    \"2117-03-14 00:00:00\",\n    \"2117-03-20 00:00:00\",\n    \"2117-03-21 00:00:00\",\n    \"2117-03-27 00:00:00\",\n    \"2117-03-28 00:00:00\",\n    \"2117-04-03 00:00:00\",\n    \"2117-04-04 00:00:00\",\n    \"2117-04-10 00:00:00\",\n    \"2117-04-11 00:00:00\",\n    \"2117-04-17 00:00:00\",\n    \"2117-04-18 00:00:00\",\n    \"2117-04-24 00:00:00\",\n    \"2117-04-25 00:00:00\",\n    \"2117-05-01 00:00:00\",\n    \"2117-05-02 00:00:00\",\n    \"2117-05-08 00:00:00\",\n    \"2117-05-09 00:00:00\",\n    \"2117-05-15 00:00:00\",\n    \"2117-05-16 00:00:00\",\n    \"2117-05-22 00:00:00\",\n    \"2117-05-23 00:00:00\",\n    \"2117-05-29 00:00:00\",\n    \"2117-05-30 00:00:00\",\n    \"2117-06-05 00:00:00\",\n    \"2117-06-06 00:00:00\",\n    \"2117-06-12 00:00:00\",\n    \"2117-06-13 00:00:00\",\n    \"2117-06-19 00:00:00\",\n    \"2117-06-20 00:00:00\",\n    \"2117-06-26 00:00:00\",\n    \"2117-06-27 00:00:00\",\n    \"2117-07-03 00:00:00\",\n    \"2117-07-04 00:00:00\",\n    \"2117-07-10 00:00:00\",\n    \"2117-07-11 00:00:00\",\n    \"2117-07-17 00:00:00\",\n    \"2117-07-18 00:00:00\",\n    \"2117-07-24 00:00:00\",\n    \"2117-07-25 00:00:00\",\n    \"2117-07-31 00:00:00\",\n    \"2117-08-01 00:00:00\",\n    \"2117-08-07 00:00:00\",\n    \"2117-08-08 00:00:00\",\n    \"2117-08-14 00:00:00\",\n    \"2117-08-15 00:00:00\",\n    \"2117-08-21 00:00:00\",\n    \"2117-08-22 00:00:00\",\n    \"2117-08-28 00:00:00\",\n    \"2117-08-29 00:00:00\",\n    \"2117-09-04 00:00:00\",\n    \"2117-09-05 00:00:00\",\n    \"2117-09-11 00:00:00\",\n    \"2117-09-12 00:00:00\",\n    \"2117-09-18 00:00:00\",\n    \"2117-09-19 00:00:00\",\n    \"2117-09-25 00:00:00\",\n    \"2117-09-26 00:00:00\",\n    \"2117-10-02 00:00:00\",\n    \"2117-10-03 00:00:00\",\n    \"2117-10-09 00:00:00\",\n    \"2117-10-10 00:00:00\",\n    \"2117-10-16 00:00:00\",\n    \"2117-10-17 00:00:00\",\n    \"2117-10-23 00:00:00\",\n    \"2117-10-24 00:00:00\",\n    \"2117-10-30 00:00:00\",\n    \"2117-10-31 00:00:00\",\n    \"2117-11-06 00:00:00\",\n    \"2117-11-07 00:00:00\",\n    \"2117-11-13 00:00:00\",\n    \"2117-11-14 00:00:00\",\n    \"2117-11-20 00:00:00\",\n    \"2117-11-21 00:00:00\",\n    \"2117-11-27 00:00:00\",\n    \"2117-11-28 00:00:00\",\n    \"2117-12-04 00:00:00\",\n    \"2117-12-05 00:00:00\",\n    \"2117-12-11 00:00:00\",\n    \"2117-12-12 00:00:00\",\n    \"2117-12-18 00:00:00\",\n    \"2117-12-19 00:00:00\",\n    \"2117-12-25 00:00:00\",\n    \"2117-12-26 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-01-02 00:00:00\",\n    \"2118-01-08 00:00:00\",\n    \"2118-01-09 00:00:00\",\n    \"2118-01-15 00:00:00\",\n    \"2118-01-16 00:00:00\",\n    \"2118-01-22 00:00:00\",\n    \"2118-01-23 00:00:00\",\n    \"2118-01-29 00:00:00\",\n    \"2118-01-30 00:00:00\",\n    \"2118-02-05 00:00:00\",\n    \"2118-02-06 00:00:00\",\n    \"2118-02-12 00:00:00\",\n    \"2118-02-13 00:00:00\",\n    \"2118-02-19 00:00:00\",\n    \"2118-02-20 00:00:00\",\n    \"2118-02-26 00:00:00\",\n    \"2118-02-27 00:00:00\",\n    \"2118-03-05 00:00:00\",\n    \"2118-03-06 00:00:00\",\n    \"2118-03-12 00:00:00\",\n    \"2118-03-13 00:00:00\",\n    \"2118-03-19 00:00:00\",\n    \"2118-03-20 00:00:00\",\n    \"2118-03-26 00:00:00\",\n    \"2118-03-27 00:00:00\",\n    \"2118-04-02 00:00:00\",\n    \"2118-04-03 00:00:00\",\n    \"2118-04-09 00:00:00\",\n    \"2118-04-10 00:00:00\",\n    \"2118-04-16 00:00:00\",\n    \"2118-04-17 00:00:00\",\n    \"2118-04-23 00:00:00\",\n    \"2118-04-24 00:00:00\",\n    \"2118-04-30 00:00:00\",\n    \"2118-05-01 00:00:00\",\n    \"2118-05-07 00:00:00\",\n    \"2118-05-08 00:00:00\",\n    \"2118-05-14 00:00:00\",\n    \"2118-05-15 00:00:00\",\n    \"2118-05-21 00:00:00\",\n    \"2118-05-22 00:00:00\",\n    \"2118-05-28 00:00:00\",\n    \"2118-05-29 00:00:00\",\n    \"2118-06-04 00:00:00\",\n    \"2118-06-05 00:00:00\",\n    \"2118-06-11 00:00:00\",\n    \"2118-06-12 00:00:00\",\n    \"2118-06-18 00:00:00\",\n    \"2118-06-19 00:00:00\",\n    \"2118-06-25 00:00:00\",\n    \"2118-06-26 00:00:00\",\n    \"2118-07-02 00:00:00\",\n    \"2118-07-03 00:00:00\",\n    \"2118-07-09 00:00:00\",\n    \"2118-07-10 00:00:00\",\n    \"2118-07-16 00:00:00\",\n    \"2118-07-17 00:00:00\",\n    \"2118-07-23 00:00:00\",\n    \"2118-07-24 00:00:00\",\n    \"2118-07-30 00:00:00\",\n    \"2118-07-31 00:00:00\",\n    \"2118-08-06 00:00:00\",\n    \"2118-08-07 00:00:00\",\n    \"2118-08-13 00:00:00\",\n    \"2118-08-14 00:00:00\",\n    \"2118-08-20 00:00:00\",\n    \"2118-08-21 00:00:00\",\n    \"2118-08-27 00:00:00\",\n    \"2118-08-28 00:00:00\",\n    \"2118-09-03 00:00:00\",\n    \"2118-09-04 00:00:00\",\n    \"2118-09-10 00:00:00\",\n    \"2118-09-11 00:00:00\",\n    \"2118-09-17 00:00:00\",\n    \"2118-09-18 00:00:00\",\n    \"2118-09-24 00:00:00\",\n    \"2118-09-25 00:00:00\",\n    \"2118-10-01 00:00:00\",\n    \"2118-10-02 00:00:00\",\n    \"2118-10-08 00:00:00\",\n    \"2118-10-09 00:00:00\",\n    \"2118-10-15 00:00:00\",\n    \"2118-10-16 00:00:00\",\n    \"2118-10-22 00:00:00\",\n    \"2118-10-23 00:00:00\",\n    \"2118-10-29 00:00:00\",\n    \"2118-10-30 00:00:00\",\n    \"2118-11-05 00:00:00\",\n    \"2118-11-06 00:00:00\",\n    \"2118-11-12 00:00:00\",\n    \"2118-11-13 00:00:00\",\n    \"2118-11-19 00:00:00\",\n    \"2118-11-20 00:00:00\",\n    \"2118-11-26 00:00:00\",\n    \"2118-11-27 00:00:00\",\n    \"2118-12-03 00:00:00\",\n    \"2118-12-04 00:00:00\",\n    \"2118-12-10 00:00:00\",\n    \"2118-12-11 00:00:00\",\n    \"2118-12-17 00:00:00\",\n    \"2118-12-18 00:00:00\",\n    \"2118-12-24 00:00:00\",\n    \"2118-12-25 00:00:00\",\n    \"2118-12-31 00:00:00\",\n    \"2119-01-01 00:00:00\",\n    \"2119-01-07 00:00:00\",\n    \"2119-01-08 00:00:00\",\n    \"2119-01-14 00:00:00\",\n    \"2119-01-15 00:00:00\",\n    \"2119-01-21 00:00:00\",\n    \"2119-01-22 00:00:00\",\n    \"2119-01-28 00:00:00\",\n    \"2119-01-29 00:00:00\",\n    \"2119-02-04 00:00:00\",\n    \"2119-02-05 00:00:00\",\n    \"2119-02-11 00:00:00\",\n    \"2119-02-12 00:00:00\",\n    \"2119-02-18 00:00:00\",\n    \"2119-02-19 00:00:00\",\n    \"2119-02-25 00:00:00\",\n    \"2119-02-26 00:00:00\",\n    \"2119-03-04 00:00:00\",\n    \"2119-03-05 00:00:00\",\n    \"2119-03-11 00:00:00\",\n    \"2119-03-12 00:00:00\",\n    \"2119-03-18 00:00:00\",\n    \"2119-03-19 00:00:00\",\n    \"2119-03-25 00:00:00\",\n    \"2119-03-26 00:00:00\",\n    \"2119-04-01 00:00:00\",\n    \"2119-04-02 00:00:00\",\n    \"2119-04-08 00:00:00\",\n    \"2119-04-09 00:00:00\",\n    \"2119-04-15 00:00:00\",\n    \"2119-04-16 00:00:00\",\n    \"2119-04-22 00:00:00\",\n    \"2119-04-23 00:00:00\",\n    \"2119-04-29 00:00:00\",\n    \"2119-04-30 00:00:00\",\n    \"2119-05-06 00:00:00\",\n    \"2119-05-07 00:00:00\",\n    \"2119-05-13 00:00:00\",\n    \"2119-05-14 00:00:00\",\n    \"2119-05-20 00:00:00\",\n    \"2119-05-21 00:00:00\",\n    \"2119-05-27 00:00:00\",\n    \"2119-05-28 00:00:00\",\n    \"2119-06-03 00:00:00\",\n    \"2119-06-04 00:00:00\",\n    \"2119-06-10 00:00:00\",\n    \"2119-06-11 00:00:00\",\n    \"2119-06-17 00:00:00\",\n    \"2119-06-18 00:00:00\",\n    \"2119-06-24 00:00:00\",\n    \"2119-06-25 00:00:00\",\n    \"2119-07-01 00:00:00\",\n    \"2119-07-02 00:00:00\",\n    \"2119-07-08 00:00:00\",\n    \"2119-07-09 00:00:00\",\n    \"2119-07-15 00:00:00\",\n    \"2119-07-16 00:00:00\",\n    \"2119-07-22 00:00:00\",\n    \"2119-07-23 00:00:00\",\n    \"2119-07-29 00:00:00\",\n    \"2119-07-30 00:00:00\",\n    \"2119-08-05 00:00:00\",\n    \"2119-08-06 00:00:00\",\n    \"2119-08-12 00:00:00\",\n    \"2119-08-13 00:00:00\",\n    \"2119-08-19 00:00:00\",\n    \"2119-08-20 00:00:00\",\n    \"2119-08-26 00:00:00\",\n    \"2119-08-27 00:00:00\",\n    \"2119-09-02 00:00:00\",\n    \"2119-09-03 00:00:00\",\n    \"2119-09-09 00:00:00\",\n    \"2119-09-10 00:00:00\",\n    \"2119-09-16 00:00:00\",\n    \"2119-09-17 00:00:00\",\n    \"2119-09-23 00:00:00\",\n    \"2119-09-24 00:00:00\",\n    \"2119-09-30 00:00:00\",\n    \"2119-10-01 00:00:00\",\n    \"2119-10-07 00:00:00\",\n    \"2119-10-08 00:00:00\",\n    \"2119-10-14 00:00:00\",\n    \"2119-10-15 00:00:00\",\n    \"2119-10-21 00:00:00\",\n    \"2119-10-22 00:00:00\",\n    \"2119-10-28 00:00:00\",\n    \"2119-10-29 00:00:00\",\n    \"2119-11-04 00:00:00\",\n    \"2119-11-05 00:00:00\",\n    \"2119-11-11 00:00:00\",\n    \"2119-11-12 00:00:00\",\n    \"2119-11-18 00:00:00\",\n    \"2119-11-19 00:00:00\",\n    \"2119-11-25 00:00:00\",\n    \"2119-11-26 00:00:00\",\n    \"2119-12-02 00:00:00\",\n    \"2119-12-03 00:00:00\",\n    \"2119-12-09 00:00:00\",\n    \"2119-12-10 00:00:00\",\n    \"2119-12-16 00:00:00\",\n    \"2119-12-17 00:00:00\",\n    \"2119-12-23 00:00:00\",\n    \"2119-12-24 00:00:00\",\n    \"2119-12-30 00:00:00\",\n    \"2119-12-31 00:00:00\",\n    \"2120-01-06 00:00:00\",\n    \"2120-01-07 00:00:00\",\n    \"2120-01-13 00:00:00\",\n    \"2120-01-14 00:00:00\",\n    \"2120-01-20 00:00:00\",\n    \"2120-01-21 00:00:00\",\n    \"2120-01-27 00:00:00\",\n    \"2120-01-28 00:00:00\",\n    \"2120-02-03 00:00:00\",\n    \"2120-02-04 00:00:00\",\n    \"2120-02-10 00:00:00\",\n    \"2120-02-11 00:00:00\",\n    \"2120-02-17 00:00:00\",\n    \"2120-02-18 00:00:00\",\n    \"2120-02-24 00:00:00\",\n    \"2120-02-25 00:00:00\",\n    \"2120-03-02 00:00:00\",\n    \"2120-03-03 00:00:00\",\n    \"2120-03-09 00:00:00\",\n    \"2120-03-10 00:00:00\",\n    \"2120-03-16 00:00:00\",\n    \"2120-03-17 00:00:00\",\n    \"2120-03-23 00:00:00\",\n    \"2120-03-24 00:00:00\",\n    \"2120-03-30 00:00:00\",\n    \"2120-03-31 00:00:00\",\n    \"2120-04-06 00:00:00\",\n    \"2120-04-07 00:00:00\",\n    \"2120-04-13 00:00:00\",\n    \"2120-04-14 00:00:00\",\n    \"2120-04-20 00:00:00\",\n    \"2120-04-21 00:00:00\",\n    \"2120-04-27 00:00:00\",\n    \"2120-04-28 00:00:00\",\n    \"2120-05-04 00:00:00\",\n    \"2120-05-05 00:00:00\",\n    \"2120-05-11 00:00:00\",\n    \"2120-05-12 00:00:00\",\n    \"2120-05-18 00:00:00\",\n    \"2120-05-19 00:00:00\",\n    \"2120-05-25 00:00:00\",\n    \"2120-05-26 00:00:00\",\n    \"2120-06-01 00:00:00\",\n    \"2120-06-02 00:00:00\",\n    \"2120-06-08 00:00:00\",\n    \"2120-06-09 00:00:00\",\n    \"2120-06-15 00:00:00\",\n    \"2120-06-16 00:00:00\",\n    \"2120-06-22 00:00:00\",\n    \"2120-06-23 00:00:00\",\n    \"2120-06-29 00:00:00\",\n    \"2120-06-30 00:00:00\",\n    \"2120-07-06 00:00:00\",\n    \"2120-07-07 00:00:00\",\n    \"2120-07-13 00:00:00\",\n    \"2120-07-14 00:00:00\",\n    \"2120-07-20 00:00:00\",\n    \"2120-07-21 00:00:00\",\n    \"2120-07-27 00:00:00\",\n    \"2120-07-28 00:00:00\",\n    \"2120-08-03 00:00:00\",\n    \"2120-08-04 00:00:00\",\n    \"2120-08-10 00:00:00\",\n    \"2120-08-11 00:00:00\",\n    \"2120-08-17 00:00:00\",\n    \"2120-08-18 00:00:00\",\n    \"2120-08-24 00:00:00\",\n    \"2120-08-25 00:00:00\",\n    \"2120-08-31 00:00:00\",\n    \"2120-09-01 00:00:00\",\n    \"2120-09-07 00:00:00\",\n    \"2120-09-08 00:00:00\",\n    \"2120-09-14 00:00:00\",\n    \"2120-09-15 00:00:00\",\n    \"2120-09-21 00:00:00\",\n    \"2120-09-22 00:00:00\",\n    \"2120-09-28 00:00:00\",\n    \"2120-09-29 00:00:00\",\n    \"2120-10-05 00:00:00\",\n    \"2120-10-06 00:00:00\",\n    \"2120-10-12 00:00:00\",\n    \"2120-10-13 00:00:00\",\n    \"2120-10-19 00:00:00\",\n    \"2120-10-20 00:00:00\",\n    \"2120-10-26 00:00:00\",\n    \"2120-10-27 00:00:00\",\n    \"2120-11-02 00:00:00\",\n    \"2120-11-03 00:00:00\",\n    \"2120-11-09 00:00:00\",\n    \"2120-11-10 00:00:00\",\n    \"2120-11-16 00:00:00\",\n    \"2120-11-17 00:00:00\",\n    \"2120-11-23 00:00:00\",\n    \"2120-11-24 00:00:00\",\n    \"2120-11-30 00:00:00\",\n    \"2120-12-01 00:00:00\",\n    \"2120-12-07 00:00:00\",\n    \"2120-12-08 00:00:00\",\n    \"2120-12-14 00:00:00\",\n    \"2120-12-15 00:00:00\",\n    \"2120-12-21 00:00:00\",\n    \"2120-12-22 00:00:00\",\n    \"2120-12-28 00:00:00\",\n    \"2120-12-29 00:00:00\",\n    \"2121-01-04 00:00:00\",\n    \"2121-01-05 00:00:00\",\n    \"2121-01-11 00:00:00\",\n    \"2121-01-12 00:00:00\",\n    \"2121-01-18 00:00:00\",\n    \"2121-01-19 00:00:00\",\n    \"2121-01-25 00:00:00\",\n    \"2121-01-26 00:00:00\",\n    \"2121-02-01 00:00:00\",\n    \"2121-02-02 00:00:00\",\n    \"2121-02-08 00:00:00\",\n    \"2121-02-09 00:00:00\",\n    \"2121-02-15 00:00:00\",\n    \"2121-02-16 00:00:00\",\n    \"2121-02-22 00:00:00\",\n    \"2121-02-23 00:00:00\",\n    \"2121-03-01 00:00:00\",\n    \"2121-03-02 00:00:00\",\n    \"2121-03-08 00:00:00\",\n    \"2121-03-09 00:00:00\",\n    \"2121-03-15 00:00:00\",\n    \"2121-03-16 00:00:00\",\n    \"2121-03-22 00:00:00\",\n    \"2121-03-23 00:00:00\",\n    \"2121-03-29 00:00:00\",\n    \"2121-03-30 00:00:00\",\n    \"2121-04-05 00:00:00\",\n    \"2121-04-06 00:00:00\",\n    \"2121-04-12 00:00:00\",\n    \"2121-04-13 00:00:00\",\n    \"2121-04-19 00:00:00\",\n    \"2121-04-20 00:00:00\",\n    \"2121-04-26 00:00:00\",\n    \"2121-04-27 00:00:00\",\n    \"2121-05-03 00:00:00\",\n    \"2121-05-04 00:00:00\",\n    \"2121-05-10 00:00:00\",\n    \"2121-05-11 00:00:00\",\n    \"2121-05-17 00:00:00\",\n    \"2121-05-18 00:00:00\",\n    \"2121-05-24 00:00:00\",\n    \"2121-05-25 00:00:00\",\n    \"2121-05-31 00:00:00\",\n    \"2121-06-01 00:00:00\",\n    \"2121-06-07 00:00:00\",\n    \"2121-06-08 00:00:00\",\n    \"2121-06-14 00:00:00\",\n    \"2121-06-15 00:00:00\",\n    \"2121-06-21 00:00:00\",\n    \"2121-06-22 00:00:00\",\n    \"2121-06-28 00:00:00\",\n    \"2121-06-29 00:00:00\",\n    \"2121-07-05 00:00:00\",\n    \"2121-07-06 00:00:00\",\n    \"2121-07-12 00:00:00\",\n    \"2121-07-13 00:00:00\",\n    \"2121-07-19 00:00:00\",\n    \"2121-07-20 00:00:00\",\n    \"2121-07-26 00:00:00\",\n    \"2121-07-27 00:00:00\",\n    \"2121-08-02 00:00:00\",\n    \"2121-08-03 00:00:00\",\n    \"2121-08-09 00:00:00\",\n    \"2121-08-10 00:00:00\",\n    \"2121-08-16 00:00:00\",\n    \"2121-08-17 00:00:00\",\n    \"2121-08-23 00:00:00\",\n    \"2121-08-24 00:00:00\",\n    \"2121-08-30 00:00:00\",\n    \"2121-08-31 00:00:00\",\n    \"2121-09-06 00:00:00\",\n    \"2121-09-07 00:00:00\",\n    \"2121-09-13 00:00:00\",\n    \"2121-09-14 00:00:00\",\n    \"2121-09-20 00:00:00\",\n    \"2121-09-21 00:00:00\",\n    \"2121-09-27 00:00:00\",\n    \"2121-09-28 00:00:00\",\n    \"2121-10-04 00:00:00\",\n    \"2121-10-05 00:00:00\",\n    \"2121-10-11 00:00:00\",\n    \"2121-10-12 00:00:00\",\n    \"2121-10-18 00:00:00\",\n    \"2121-10-19 00:00:00\",\n    \"2121-10-25 00:00:00\",\n    \"2121-10-26 00:00:00\",\n    \"2121-11-01 00:00:00\",\n    \"2121-11-02 00:00:00\",\n    \"2121-11-08 00:00:00\",\n    \"2121-11-09 00:00:00\",\n    \"2121-11-15 00:00:00\",\n    \"2121-11-16 00:00:00\",\n    \"2121-11-22 00:00:00\",\n    \"2121-11-23 00:00:00\",\n    \"2121-11-29 00:00:00\",\n    \"2121-11-30 00:00:00\",\n    \"2121-12-06 00:00:00\",\n    \"2121-12-07 00:00:00\",\n    \"2121-12-13 00:00:00\",\n    \"2121-12-14 00:00:00\",\n    \"2121-12-20 00:00:00\",\n    \"2121-12-21 00:00:00\",\n    \"2121-12-27 00:00:00\",\n    \"2121-12-28 00:00:00\",\n    \"2122-01-03 00:00:00\",\n    \"2122-01-04 00:00:00\",\n    \"2122-01-10 00:00:00\",\n    \"2122-01-11 00:00:00\",\n    \"2122-01-17 00:00:00\",\n    \"2122-01-18 00:00:00\",\n    \"2122-01-24 00:00:00\",\n    \"2122-01-25 00:00:00\",\n    \"2122-01-31 00:00:00\",\n    \"2122-02-01 00:00:00\",\n    \"2122-02-07 00:00:00\",\n    \"2122-02-08 00:00:00\",\n    \"2122-02-14 00:00:00\",\n    \"2122-02-15 00:00:00\",\n    \"2122-02-21 00:00:00\",\n    \"2122-02-22 00:00:00\",\n    \"2122-02-28 00:00:00\",\n    \"2122-03-01 00:00:00\",\n    \"2122-03-07 00:00:00\",\n    \"2122-03-08 00:00:00\",\n    \"2122-03-14 00:00:00\",\n    \"2122-03-15 00:00:00\",\n    \"2122-03-21 00:00:00\",\n    \"2122-03-22 00:00:00\",\n    \"2122-03-28 00:00:00\",\n    \"2122-03-29 00:00:00\",\n    \"2122-04-04 00:00:00\",\n    \"2122-04-05 00:00:00\",\n    \"2122-04-11 00:00:00\",\n    \"2122-04-12 00:00:00\",\n    \"2122-04-18 00:00:00\",\n    \"2122-04-19 00:00:00\",\n    \"2122-04-25 00:00:00\",\n    \"2122-04-26 00:00:00\",\n    \"2122-05-02 00:00:00\",\n    \"2122-05-03 00:00:00\",\n    \"2122-05-09 00:00:00\",\n    \"2122-05-10 00:00:00\",\n    \"2122-05-16 00:00:00\",\n    \"2122-05-17 00:00:00\",\n    \"2122-05-23 00:00:00\",\n    \"2122-05-24 00:00:00\",\n    \"2122-05-30 00:00:00\",\n    \"2122-05-31 00:00:00\",\n    \"2122-06-06 00:00:00\",\n    \"2122-06-07 00:00:00\",\n    \"2122-06-13 00:00:00\",\n    \"2122-06-14 00:00:00\",\n    \"2122-06-20 00:00:00\",\n    \"2122-06-21 00:00:00\",\n    \"2122-06-27 00:00:00\",\n    \"2122-06-28 00:00:00\",\n    \"2122-07-04 00:00:00\",\n    \"2122-07-05 00:00:00\",\n    \"2122-07-11 00:00:00\",\n    \"2122-07-12 00:00:00\",\n    \"2122-07-18 00:00:00\",\n    \"2122-07-19 00:00:00\",\n    \"2122-07-25 00:00:00\",\n    \"2122-07-26 00:00:00\",\n    \"2122-08-01 00:00:00\",\n    \"2122-08-02 00:00:00\",\n    \"2122-08-08 00:00:00\",\n    \"2122-08-09 00:00:00\",\n    \"2122-08-15 00:00:00\",\n    \"2122-08-16 00:00:00\",\n    \"2122-08-22 00:00:00\",\n    \"2122-08-23 00:00:00\",\n    \"2122-08-29 00:00:00\",\n    \"2122-08-30 00:00:00\",\n    \"2122-09-05 00:00:00\",\n    \"2122-09-06 00:00:00\",\n    \"2122-09-12 00:00:00\",\n    \"2122-09-13 00:00:00\",\n    \"2122-09-19 00:00:00\",\n    \"2122-09-20 00:00:00\",\n    \"2122-09-26 00:00:00\",\n    \"2122-09-27 00:00:00\",\n    \"2122-10-03 00:00:00\",\n    \"2122-10-04 00:00:00\",\n    \"2122-10-10 00:00:00\",\n    \"2122-10-11 00:00:00\",\n    \"2122-10-17 00:00:00\",\n    \"2122-10-18 00:00:00\",\n    \"2122-10-24 00:00:00\",\n    \"2122-10-25 00:00:00\",\n    \"2122-10-31 00:00:00\",\n    \"2122-11-01 00:00:00\",\n    \"2122-11-07 00:00:00\",\n    \"2122-11-08 00:00:00\",\n    \"2122-11-14 00:00:00\",\n    \"2122-11-15 00:00:00\",\n    \"2122-11-21 00:00:00\",\n    \"2122-11-22 00:00:00\",\n    \"2122-11-28 00:00:00\",\n    \"2122-11-29 00:00:00\",\n    \"2122-12-05 00:00:00\",\n    \"2122-12-06 00:00:00\",\n    \"2122-12-12 00:00:00\",\n    \"2122-12-13 00:00:00\",\n    \"2122-12-19 00:00:00\",\n    \"2122-12-20 00:00:00\",\n    \"2122-12-26 00:00:00\",\n    \"2122-12-27 00:00:00\",\n    \"2123-01-02 00:00:00\",\n    \"2123-01-03 00:00:00\",\n    \"2123-01-09 00:00:00\",\n    \"2123-01-10 00:00:00\",\n    \"2123-01-16 00:00:00\",\n    \"2123-01-17 00:00:00\",\n    \"2123-01-23 00:00:00\",\n    \"2123-01-24 00:00:00\",\n    \"2123-01-30 00:00:00\",\n    \"2123-01-31 00:00:00\",\n    \"2123-02-06 00:00:00\",\n    \"2123-02-07 00:00:00\",\n    \"2123-02-13 00:00:00\",\n    \"2123-02-14 00:00:00\",\n    \"2123-02-20 00:00:00\",\n    \"2123-02-21 00:00:00\",\n    \"2123-02-27 00:00:00\",\n    \"2123-02-28 00:00:00\",\n    \"2123-03-06 00:00:00\",\n    \"2123-03-07 00:00:00\",\n    \"2123-03-13 00:00:00\",\n    \"2123-03-14 00:00:00\",\n    \"2123-03-20 00:00:00\",\n    \"2123-03-21 00:00:00\",\n    \"2123-03-27 00:00:00\",\n    \"2123-03-28 00:00:00\",\n    \"2123-04-03 00:00:00\",\n    \"2123-04-04 00:00:00\",\n    \"2123-04-10 00:00:00\",\n    \"2123-04-11 00:00:00\",\n    \"2123-04-17 00:00:00\",\n    \"2123-04-18 00:00:00\",\n    \"2123-04-24 00:00:00\",\n    \"2123-04-25 00:00:00\",\n    \"2123-05-01 00:00:00\",\n    \"2123-05-02 00:00:00\",\n    \"2123-05-08 00:00:00\",\n    \"2123-05-09 00:00:00\",\n    \"2123-05-15 00:00:00\",\n    \"2123-05-16 00:00:00\",\n    \"2123-05-22 00:00:00\",\n    \"2123-05-23 00:00:00\",\n    \"2123-05-29 00:00:00\",\n    \"2123-05-30 00:00:00\",\n    \"2123-06-05 00:00:00\",\n    \"2123-06-06 00:00:00\",\n    \"2123-06-12 00:00:00\",\n    \"2123-06-13 00:00:00\",\n    \"2123-06-19 00:00:00\",\n    \"2123-06-20 00:00:00\",\n    \"2123-06-26 00:00:00\",\n    \"2123-06-27 00:00:00\",\n    \"2123-07-03 00:00:00\",\n    \"2123-07-04 00:00:00\",\n    \"2123-07-10 00:00:00\",\n    \"2123-07-11 00:00:00\",\n    \"2123-07-17 00:00:00\",\n    \"2123-07-18 00:00:00\",\n    \"2123-07-24 00:00:00\",\n    \"2123-07-25 00:00:00\",\n    \"2123-07-31 00:00:00\",\n    \"2123-08-01 00:00:00\",\n    \"2123-08-07 00:00:00\",\n    \"2123-08-08 00:00:00\",\n    \"2123-08-14 00:00:00\",\n    \"2123-08-15 00:00:00\",\n    \"2123-08-21 00:00:00\",\n    \"2123-08-22 00:00:00\",\n    \"2123-08-28 00:00:00\",\n    \"2123-08-29 00:00:00\",\n    \"2123-09-04 00:00:00\",\n    \"2123-09-05 00:00:00\",\n    \"2123-09-11 00:00:00\",\n    \"2123-09-12 00:00:00\",\n    \"2123-09-18 00:00:00\",\n    \"2123-09-19 00:00:00\",\n    \"2123-09-25 00:00:00\",\n    \"2123-09-26 00:00:00\",\n    \"2123-10-02 00:00:00\",\n    \"2123-10-03 00:00:00\",\n    \"2123-10-09 00:00:00\",\n    \"2123-10-10 00:00:00\",\n    \"2123-10-16 00:00:00\",\n    \"2123-10-17 00:00:00\",\n    \"2123-10-23 00:00:00\",\n    \"2123-10-24 00:00:00\",\n    \"2123-10-30 00:00:00\",\n    \"2123-10-31 00:00:00\",\n    \"2123-11-06 00:00:00\",\n    \"2123-11-07 00:00:00\",\n    \"2123-11-13 00:00:00\",\n    \"2123-11-14 00:00:00\",\n    \"2123-11-20 00:00:00\",\n    \"2123-11-21 00:00:00\",\n    \"2123-11-27 00:00:00\",\n    \"2123-11-28 00:00:00\",\n    \"2123-12-04 00:00:00\",\n    \"2123-12-05 00:00:00\",\n    \"2123-12-11 00:00:00\",\n    \"2123-12-12 00:00:00\",\n    \"2123-12-18 00:00:00\",\n    \"2123-12-19 00:00:00\",\n    \"2123-12-25 00:00:00\",\n    \"2123-12-26 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-01-02 00:00:00\",\n    \"2124-01-08 00:00:00\",\n    \"2124-01-09 00:00:00\",\n    \"2124-01-15 00:00:00\",\n    \"2124-01-16 00:00:00\",\n    \"2124-01-22 00:00:00\",\n    \"2124-01-23 00:00:00\",\n    \"2124-01-29 00:00:00\",\n    \"2124-01-30 00:00:00\",\n    \"2124-02-05 00:00:00\",\n    \"2124-02-06 00:00:00\",\n    \"2124-02-12 00:00:00\",\n    \"2124-02-13 00:00:00\",\n    \"2124-02-19 00:00:00\",\n    \"2124-02-20 00:00:00\",\n    \"2124-02-26 00:00:00\",\n    \"2124-02-27 00:00:00\",\n    \"2124-03-04 00:00:00\",\n    \"2124-03-05 00:00:00\",\n    \"2124-03-11 00:00:00\",\n    \"2124-03-12 00:00:00\",\n    \"2124-03-18 00:00:00\",\n    \"2124-03-19 00:00:00\",\n    \"2124-03-25 00:00:00\",\n    \"2124-03-26 00:00:00\",\n    \"2124-04-01 00:00:00\",\n    \"2124-04-02 00:00:00\",\n    \"2124-04-08 00:00:00\",\n    \"2124-04-09 00:00:00\",\n    \"2124-04-15 00:00:00\",\n    \"2124-04-16 00:00:00\",\n    \"2124-04-22 00:00:00\",\n    \"2124-04-23 00:00:00\",\n    \"2124-04-29 00:00:00\",\n    \"2124-04-30 00:00:00\",\n    \"2124-05-06 00:00:00\",\n    \"2124-05-07 00:00:00\",\n    \"2124-05-13 00:00:00\",\n    \"2124-05-14 00:00:00\",\n    \"2124-05-20 00:00:00\",\n    \"2124-05-21 00:00:00\",\n    \"2124-05-27 00:00:00\",\n    \"2124-05-28 00:00:00\",\n    \"2124-06-03 00:00:00\",\n    \"2124-06-04 00:00:00\",\n    \"2124-06-10 00:00:00\",\n    \"2124-06-11 00:00:00\",\n    \"2124-06-17 00:00:00\",\n    \"2124-06-18 00:00:00\",\n    \"2124-06-24 00:00:00\",\n    \"2124-06-25 00:00:00\",\n    \"2124-07-01 00:00:00\",\n    \"2124-07-02 00:00:00\",\n    \"2124-07-08 00:00:00\",\n    \"2124-07-09 00:00:00\",\n    \"2124-07-15 00:00:00\",\n    \"2124-07-16 00:00:00\",\n    \"2124-07-22 00:00:00\",\n    \"2124-07-23 00:00:00\",\n    \"2124-07-29 00:00:00\",\n    \"2124-07-30 00:00:00\",\n    \"2124-08-05 00:00:00\",\n    \"2124-08-06 00:00:00\",\n    \"2124-08-12 00:00:00\",\n    \"2124-08-13 00:00:00\",\n    \"2124-08-19 00:00:00\",\n    \"2124-08-20 00:00:00\",\n    \"2124-08-26 00:00:00\",\n    \"2124-08-27 00:00:00\",\n    \"2124-09-02 00:00:00\",\n    \"2124-09-03 00:00:00\",\n    \"2124-09-09 00:00:00\",\n    \"2124-09-10 00:00:00\",\n    \"2124-09-16 00:00:00\",\n    \"2124-09-17 00:00:00\",\n    \"2124-09-23 00:00:00\",\n    \"2124-09-24 00:00:00\",\n    \"2124-09-30 00:00:00\",\n    \"2124-10-01 00:00:00\",\n    \"2124-10-07 00:00:00\",\n    \"2124-10-08 00:00:00\",\n    \"2124-10-14 00:00:00\",\n    \"2124-10-15 00:00:00\",\n    \"2124-10-21 00:00:00\",\n    \"2124-10-22 00:00:00\",\n    \"2124-10-28 00:00:00\",\n    \"2124-10-29 00:00:00\",\n    \"2124-11-04 00:00:00\",\n    \"2124-11-05 00:00:00\",\n    \"2124-11-11 00:00:00\",\n    \"2124-11-12 00:00:00\",\n    \"2124-11-18 00:00:00\",\n    \"2124-11-19 00:00:00\",\n    \"2124-11-25 00:00:00\",\n    \"2124-11-26 00:00:00\",\n    \"2124-12-02 00:00:00\",\n    \"2124-12-03 00:00:00\",\n    \"2124-12-09 00:00:00\",\n    \"2124-12-10 00:00:00\",\n    \"2124-12-16 00:00:00\",\n    \"2124-12-17 00:00:00\",\n    \"2124-12-23 00:00:00\",\n    \"2124-12-24 00:00:00\",\n    \"2124-12-30 00:00:00\",\n    \"2124-12-31 00:00:00\",\n    \"2125-01-06 00:00:00\",\n    \"2125-01-07 00:00:00\",\n    \"2125-01-13 00:00:00\",\n    \"2125-01-14 00:00:00\",\n    \"2125-01-20 00:00:00\",\n    \"2125-01-21 00:00:00\",\n    \"2125-01-27 00:00:00\",\n    \"2125-01-28 00:00:00\",\n    \"2125-02-03 00:00:00\",\n    \"2125-02-04 00:00:00\",\n    \"2125-02-10 00:00:00\",\n    \"2125-02-11 00:00:00\",\n    \"2125-02-17 00:00:00\",\n    \"2125-02-18 00:00:00\",\n    \"2125-02-24 00:00:00\",\n    \"2125-02-25 00:00:00\",\n    \"2125-03-03 00:00:00\",\n    \"2125-03-04 00:00:00\",\n    \"2125-03-10 00:00:00\",\n    \"2125-03-11 00:00:00\",\n    \"2125-03-17 00:00:00\",\n    \"2125-03-18 00:00:00\",\n    \"2125-03-24 00:00:00\",\n    \"2125-03-25 00:00:00\",\n    \"2125-03-31 00:00:00\",\n    \"2125-04-01 00:00:00\",\n    \"2125-04-07 00:00:00\",\n    \"2125-04-08 00:00:00\",\n    \"2125-04-14 00:00:00\",\n    \"2125-04-15 00:00:00\",\n    \"2125-04-21 00:00:00\",\n    \"2125-04-22 00:00:00\",\n    \"2125-04-28 00:00:00\",\n    \"2125-04-29 00:00:00\",\n    \"2125-05-05 00:00:00\",\n    \"2125-05-06 00:00:00\",\n    \"2125-05-12 00:00:00\",\n    \"2125-05-13 00:00:00\",\n    \"2125-05-19 00:00:00\",\n    \"2125-05-20 00:00:00\",\n    \"2125-05-26 00:00:00\",\n    \"2125-05-27 00:00:00\",\n    \"2125-06-02 00:00:00\",\n    \"2125-06-03 00:00:00\",\n    \"2125-06-09 00:00:00\",\n    \"2125-06-10 00:00:00\",\n    \"2125-06-16 00:00:00\",\n    \"2125-06-17 00:00:00\",\n    \"2125-06-23 00:00:00\",\n    \"2125-06-24 00:00:00\",\n    \"2125-06-30 00:00:00\",\n    \"2125-07-01 00:00:00\",\n    \"2125-07-07 00:00:00\",\n    \"2125-07-08 00:00:00\",\n    \"2125-07-14 00:00:00\",\n    \"2125-07-15 00:00:00\",\n    \"2125-07-21 00:00:00\",\n    \"2125-07-22 00:00:00\",\n    \"2125-07-28 00:00:00\",\n    \"2125-07-29 00:00:00\",\n    \"2125-08-04 00:00:00\",\n    \"2125-08-05 00:00:00\",\n    \"2125-08-11 00:00:00\",\n    \"2125-08-12 00:00:00\",\n    \"2125-08-18 00:00:00\",\n    \"2125-08-19 00:00:00\",\n    \"2125-08-25 00:00:00\",\n    \"2125-08-26 00:00:00\",\n    \"2125-09-01 00:00:00\",\n    \"2125-09-02 00:00:00\",\n    \"2125-09-08 00:00:00\",\n    \"2125-09-09 00:00:00\",\n    \"2125-09-15 00:00:00\",\n    \"2125-09-16 00:00:00\",\n    \"2125-09-22 00:00:00\",\n    \"2125-09-23 00:00:00\",\n    \"2125-09-29 00:00:00\",\n    \"2125-09-30 00:00:00\",\n    \"2125-10-06 00:00:00\",\n    \"2125-10-07 00:00:00\",\n    \"2125-10-13 00:00:00\",\n    \"2125-10-14 00:00:00\",\n    \"2125-10-20 00:00:00\",\n    \"2125-10-21 00:00:00\",\n    \"2125-10-27 00:00:00\",\n    \"2125-10-28 00:00:00\",\n    \"2125-11-03 00:00:00\",\n    \"2125-11-04 00:00:00\",\n    \"2125-11-10 00:00:00\",\n    \"2125-11-11 00:00:00\",\n    \"2125-11-17 00:00:00\",\n    \"2125-11-18 00:00:00\",\n    \"2125-11-24 00:00:00\",\n    \"2125-11-25 00:00:00\",\n    \"2125-12-01 00:00:00\",\n    \"2125-12-02 00:00:00\",\n    \"2125-12-08 00:00:00\",\n    \"2125-12-09 00:00:00\",\n    \"2125-12-15 00:00:00\",\n    \"2125-12-16 00:00:00\",\n    \"2125-12-22 00:00:00\",\n    \"2125-12-23 00:00:00\",\n    \"2125-12-29 00:00:00\",\n    \"2125-12-30 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/bjs_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime, timedelta\n\nimport pandas as pd\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay\n\n\"\"\"\nThe Chinese holiday system is quite complex. This script focuses on 2025 and later.\n\nMost holidays are defined relative to lunar or solar events whose dates must be known or tabulated\nin advance. Some of the data here is collected versus external, cited sources.\n\nA system of bridging and repaying holidays is in force meaning generic week masks cannot be\napplied becuase some Saturdays or Sundays will be official work days.\n\nRegulation in 2025 aimed to reduce the number of \"owed\" days but these still exist throughout the\nyear.\n\nFor each holiday this script aims to return a list of holiday dates and a list of compensation\ndays, if required.\n\n\"\"\"\n\n\ndef weekday_mask(\n    weekdays: list[int], years: tuple[int, int], exclude: list[datetime]\n) -> list[datetime]:\n    iterate = datetime(years[0], month=1, day=1)\n    end = datetime(years[1], month=12, day=31)\n    holidays = []\n    while iterate < end:\n        if iterate.weekday() in weekdays and iterate not in exclude:\n            holidays.append(\n                Holiday(\n                    f\"Weekday Mask {iterate.strftime('yymmdd')}\",\n                    year=iterate.year,\n                    month=iterate.month,\n                    day=iterate.day,\n                )\n            )\n        iterate = iterate + timedelta(days=1)\n    return holidays\n\n\ndef new_years_holidays(year: int) -> list[datetime]:\n    dt = datetime(year=year, month=1, day=1)\n    if dt.weekday() in [3, 4, 5]:\n        # Th, Fr, Sa roll forwards\n        idx = 0\n    elif dt.weekday() in [0, 1]:\n        # Mo, Tu roll backwards\n        idx = -2\n    elif dt.weekday() in [2]:\n        # We is single day\n        return [dt]\n    elif dt.weekday() in [6]:\n        # Su roll backwards\n        idx = -1\n    return [dt + timedelta(days=i) for i in range(idx, idx + 3)]\n\n\ndef new_years_compensations(year: int) -> list[datetime]:\n    dt = datetime(year=year, month=1, day=1)\n    if dt.weekday() in [0, 2, 4, 5, 6]:\n        return []\n    elif dt.weekday() in [1]:\n        return [datetime(year=year - 1, month=12, day=29)]\n    else:\n        return [datetime(year=year, month=1, day=4)]\n\n\n# parsed with re: from https://taiwan-database.net/PDFs/WTFpdf23.pdf\nlunar_new_year_dates = [\n    \"1800-01-25\",\n    \"1801-02-13\",\n    \"1802-02-03\",\n    \"1803-01-23\",\n    \"1804-02-11\",\n    \"1805-01-31\",\n    \"1806-02-18\",\n    \"1807-02-07\",\n    \"1808-01-28\",\n    \"1809-02-14\",\n    \"1810-02-04\",\n    \"1811-01-25\",\n    \"1812-02-13\",\n    \"1813-02-01\",\n    \"1814-01-21\",\n    \"1815-02-09\",\n    \"1816-01-29\",\n    \"1817-02-16\",\n    \"1818-02-05\",\n    \"1819-01-26\",\n    \"1820-02-14\",\n    \"1821-02-03\",\n    \"1822-01-23\",\n    \"1823-02-11\",\n    \"1824-01-31\",\n    \"1825-02-18\",\n    \"1826-02-07\",\n    \"1827-01-27\",\n    \"1828-02-15\",\n    \"1829-02-04\",\n    \"1830-01-25\",\n    \"1831-02-13\",\n    \"1832-02-02\",\n    \"1833-02-20\",\n    \"1834-02-09\",\n    \"1835-01-29\",\n    \"1836-02-17\",\n    \"1837-02-05\",\n    \"1838-01-26\",\n    \"1839-02-14\",\n    \"1840-02-03\",\n    \"1841-01-23\",\n    \"1842-02-10\",\n    \"1843-01-30\",\n    \"1844-02-18\",\n    \"1845-02-07\",\n    \"1846-01-27\",\n    \"1847-02-15\",\n    \"1848-02-05\",\n    \"1849-01-24\",\n    \"1850-02-12\",\n    \"1851-02-01\",\n    \"1852-02-20\",\n    \"1853-02-08\",\n    \"1854-01-29\",\n    \"1855-02-17\",\n    \"1856-02-06\",\n    \"1857-01-26\",\n    \"1858-02-14\",\n    \"1859-02-03\",\n    \"1860-01-23\",\n    \"1861-02-10\",\n    \"1862-01-30\",\n    \"1863-02-18\",\n    \"1864-02-08\",\n    \"1865-01-27\",\n    \"1866-02-15\",\n    \"1867-02-05\",\n    \"1868-01-25\",\n    \"1869-02-11\",\n    \"1870-01-31\",\n    \"1871-02-19\",\n    \"1872-02-09\",\n    \"1873-01-29\",\n    \"1874-02-17\",\n    \"1875-02-06\",\n    \"1876-01-26\",\n    \"1877-02-13\",\n    \"1878-02-02\",\n    \"1879-01-22\",\n    \"1880-02-10\",\n    \"1881-01-30\",\n    \"1882-02-18\",\n    \"1883-02-08\",\n    \"1884-01-28\",\n    \"1885-02-15\",\n    \"1886-02-04\",\n    \"1887-01-24\",\n    \"1888-02-12\",\n    \"1889-01-31\",\n    \"1890-01-21\",\n    \"1891-02-09\",\n    \"1892-01-30\",\n    \"1893-02-17\",\n    \"1894-02-06\",\n    \"1895-01-26\",\n    \"1896-02-13\",\n    \"1897-02-02\",\n    \"1898-01-22\",\n    \"1899-02-10\",\n    \"1900-01-31\",\n    \"1901-02-19\",\n    \"1902-02-08\",\n    \"1903-01-29\",\n    \"1904-02-16\",\n    \"1905-02-04\",\n    \"1906-01-25\",\n    \"1907-02-13\",\n    \"1908-02-02\",\n    \"1909-01-22\",\n    \"1910-02-10\",\n    \"1911-01-30\",\n    \"1912-02-18\",\n    \"1913-02-06\",\n    \"1914-01-26\",\n    \"1915-02-14\",\n    \"1916-02-03\",\n    \"1917-01-23\",\n    \"1918-02-11\",\n    \"1919-02-01\",\n    \"1920-02-20\",\n    \"1921-02-08\",\n    \"1922-01-28\",\n    \"1923-02-16\",\n    \"1924-02-05\",\n    \"1925-01-24\",\n    \"1926-02-13\",\n    \"1927-02-02\",\n    \"1928-01-23\",\n    \"1929-02-10\",\n    \"1930-01-30\",\n    \"1931-02-17\",\n    \"1932-02-06\",\n    \"1933-01-26\",\n    \"1934-02-14\",\n    \"1935-02-04\",\n    \"1936-01-24\",\n    \"1937-02-11\",\n    \"1938-01-31\",\n    \"1939-02-19\",\n    \"1940-02-08\",\n    \"1941-01-27\",\n    \"1942-02-15\",\n    \"1943-02-05\",\n    \"1944-01-25\",\n    \"1945-02-13\",\n    \"1946-02-02\",\n    \"1947-01-22\",\n    \"1948-02-10\",\n    \"1949-01-29\",\n    \"1950-02-17\",\n    \"1951-02-06\",\n    \"1952-01-27\",\n    \"1953-02-14\",\n    \"1954-02-03\",\n    \"1955-01-24\",\n    \"1956-02-12\",\n    \"1957-01-31\",\n    \"1958-02-18\",\n    \"1959-02-08\",\n    \"1960-01-28\",\n    \"1961-02-15\",\n    \"1962-02-05\",\n    \"1963-01-25\",\n    \"1964-02-13\",\n    \"1965-02-02\",\n    \"1966-01-21\",\n    \"1967-02-09\",\n    \"1968-01-30\",\n    \"1969-02-17\",\n    \"1970-02-06\",\n    \"1971-01-27\",\n    \"1972-02-15\",\n    \"1973-02-03\",\n    \"1974-01-23\",\n    \"1975-02-11\",\n    \"1976-01-31\",\n    \"1977-02-18\",\n    \"1978-02-07\",\n    \"1979-01-28\",\n    \"1980-02-16\",\n    \"1981-02-05\",\n    \"1982-01-25\",\n    \"1983-02-13\",\n    \"1984-02-02\",\n    \"1985-02-20\",\n    \"1986-02-09\",\n    \"1987-01-29\",\n    \"1988-02-17\",\n    \"1989-02-06\",\n    \"1990-01-27\",\n    \"1991-02-15\",\n    \"1992-02-04\",\n    \"1993-01-23\",\n    \"1994-02-10\",\n    \"1995-01-31\",\n    \"1996-02-19\",\n    \"1997-02-07\",\n    \"1998-01-28\",\n    \"1999-02-16\",\n    \"2000-02-05\",\n    \"2001-01-24\",\n    \"2002-02-12\",\n    \"2003-02-01\",\n    \"2004-01-22\",\n    \"2005-02-09\",\n    \"2006-01-29\",\n    \"2007-02-18\",\n    \"2008-02-07\",\n    \"2009-01-26\",\n    \"2010-02-14\",\n    \"2011-02-03\",\n    \"2012-01-23\",\n    \"2013-02-10\",\n    \"2014-01-31\",\n    \"2015-02-19\",\n    \"2016-02-08\",\n    \"2017-01-28\",\n    \"2018-02-16\",\n    \"2019-02-05\",\n    \"2020-01-25\",\n    \"2021-02-12\",\n    \"2022-02-01\",\n    \"2023-01-22\",\n    \"2024-02-10\",\n    \"2025-01-29\",\n    \"2026-02-17\",\n    \"2027-02-06\",\n    \"2028-01-26\",\n    \"2029-02-13\",\n    \"2030-02-02\",\n    \"2031-01-23\",\n    \"2032-02-11\",\n    \"2033-01-31\",\n    \"2034-02-19\",\n    \"2035-02-08\",\n    \"2036-01-28\",\n    \"2037-02-15\",\n    \"2038-02-04\",\n    \"2039-01-24\",\n    \"2040-02-12\",\n    \"2041-02-01\",\n    \"2042-01-22\",\n    \"2043-02-10\",\n    \"2044-01-30\",\n    \"2045-02-17\",\n    \"2046-02-06\",\n    \"2047-01-26\",\n    \"2048-02-14\",\n    \"2049-02-02\",\n    \"2050-01-23\",\n    \"2051-02-11\",\n    \"2052-02-01\",\n    \"2053-02-19\",\n    \"2054-02-08\",\n    \"2055-01-28\",\n    \"2056-02-15\",\n    \"2057-02-04\",\n    \"2058-01-24\",\n    \"2059-02-12\",\n    \"2060-02-02\",\n    \"2061-01-21\",\n    \"2062-02-09\",\n    \"2063-01-29\",\n    \"2064-02-17\",\n    \"2065-02-05\",\n    \"2066-01-26\",\n    \"2067-02-14\",\n    \"2068-02-03\",\n    \"2069-01-23\",\n    \"2070-02-11\",\n    \"2071-01-31\",\n    \"2072-02-19\",\n    \"2073-02-07\",\n    \"2074-01-27\",\n    \"2075-02-15\",\n    \"2076-02-05\",\n    \"2077-01-24\",\n    \"2078-02-12\",\n    \"2079-02-02\",\n    \"2080-01-22\",\n    \"2081-02-09\",\n    \"2082-01-29\",\n    \"2083-02-17\",\n    \"2084-02-06\",\n    \"2085-01-26\",\n    \"2086-02-14\",\n    \"2087-02-03\",\n    \"2088-01-24\",\n    \"2089-02-10\",\n    \"2090-01-30\",\n    \"2091-02-18\",\n    \"2092-02-07\",\n    \"2093-01-27\",\n    \"2094-02-15\",\n    \"2095-02-05\",\n    \"2096-01-25\",\n    \"2097-02-12\",\n    \"2098-02-01\",\n    \"2099-01-21\",\n    \"2100-02-09\",\n]\nlunar_new_year_dict = {\n    k + 1800: datetime.strptime(v, \"%Y-%m-%d\") for k, v in enumerate(lunar_new_year_dates)\n}\n\n\ndef lunar_new_year_holidays(year: int) -> list[datetime]:\n    try:\n        dt = lunar_new_year_dict[year]\n    except KeyError:\n        return []\n\n    if dt.weekday() in [0, 2, 3, 4, 5, 6]:\n        idx = (-1, 7)\n    elif dt.weekday() in [1]:\n        idx = (-2, 7)\n\n    return [dt + timedelta(days=i) for i in range(*idx)]\n\n\ndef lunar_new_year_compensations(year: int) -> list[datetime]:\n    try:\n        dt = lunar_new_year_dict[year]\n    except KeyError:\n        return []\n\n    if dt.weekday() in [0]:\n        # previous and post saturday\n        return [dt + timedelta(days=-2), dt + timedelta(days=12)]\n    elif dt.weekday() in [1]:\n        # previous and post saturday\n        return [dt + timedelta(days=-3), dt + timedelta(days=11)]\n    elif dt.weekday() in [2]:\n        # previous Sunday and post saturday\n        return [dt + timedelta(days=-3), dt + timedelta(days=10)]\n    elif dt.weekday() in [3]:\n        # previous Sunday and post saturday\n        return [dt + timedelta(days=-4), dt + timedelta(days=9)]\n    elif dt.weekday() in [4]:\n        return [dt + timedelta(days=-5), dt + timedelta(days=8)]\n    elif dt.weekday() in [5]:\n        return [dt + timedelta(days=-6), dt + timedelta(days=7)]\n    elif dt.weekday() in [6]:\n        return [dt + timedelta(days=-7), dt + timedelta(days=7)]\n\n\n# parsed with re: from https://taiwan-database.net/PDFs/WTFpdf23.pdf\ndragon_boat_days = [\n    \"1900-06-01\",\n    \"1901-06-20\",\n    \"1902-06-10\",\n    \"1903-05-31\",\n    \"1904-06-18\",\n    \"1905-06-07\",\n    \"1906-06-26\",\n    \"1907-06-15\",\n    \"1908-06-03\",\n    \"1909-06-22\",\n    \"1910-06-11\",\n    \"1911-06-01\",\n    \"1912-06-19\",\n    \"1913-06-09\",\n    \"1914-05-29\",\n    \"1915-06-17\",\n    \"1916-06-05\",\n    \"1917-06-23\",\n    \"1918-06-13\",\n    \"1919-06-02\",\n    \"1920-06-20\",\n    \"1921-06-10\",\n    \"1922-05-31\",\n    \"1923-06-18\",\n    \"1924-06-06\",\n    \"1925-06-25\",\n    \"1926-06-14\",\n    \"1927-06-04\",\n    \"1928-06-22\",\n    \"1929-06-11\",\n    \"1930-06-01\",\n    \"1931-06-20\",\n    \"1932-06-08\",\n    \"1933-05-28\",\n    \"1934-06-16\",\n    \"1935-06-05\",\n    \"1936-06-23\",\n    \"1937-06-12\",\n    \"1938-06-02\",\n    \"1939-06-21\",\n    \"1940-06-10\",\n    \"1941-05-30\",\n    \"1942-06-18\",\n    \"1943-06-07\",\n    \"1944-06-25\",\n    \"1945-06-14\",\n    \"1946-06-04\",\n    \"1947-06-23\",\n    \"1948-06-11\",\n    \"1949-06-01\",\n    \"1950-06-19\",\n    \"1951-06-09\",\n    \"1952-05-28\",\n    \"1953-06-15\",\n    \"1954-06-05\",\n    \"1955-06-24\",\n    \"1956-06-13\",\n    \"1957-06-02\",\n    \"1958-06-21\",\n    \"1959-06-10\",\n    \"1960-05-29\",\n    \"1961-06-17\",\n    \"1962-06-06\",\n    \"1963-06-25\",\n    \"1964-06-14\",\n    \"1965-06-04\",\n    \"1966-06-23\",\n    \"1967-06-12\",\n    \"1968-05-31\",\n    \"1969-06-19\",\n    \"1970-06-08\",\n    \"1971-05-28\",\n    \"1972-06-15\",\n    \"1973-06-05\",\n    \"1974-06-24\",\n    \"1975-06-14\",\n    \"1976-06-02\",\n    \"1977-06-21\",\n    \"1978-06-10\",\n    \"1979-05-30\",\n    \"1980-06-17\",\n    \"1981-06-06\",\n    \"1982-06-25\",\n    \"1983-06-15\",\n    \"1984-06-04\",\n    \"1985-06-22\",\n    \"1986-06-11\",\n    \"1987-05-31\",\n    \"1988-06-18\",\n    \"1989-06-08\",\n    \"1990-05-28\",\n    \"1991-06-16\",\n    \"1992-06-05\",\n    \"1993-06-24\",\n    \"1994-06-13\",\n    \"1995-06-02\",\n    \"1996-06-20\",\n    \"1997-06-09\",\n    \"1998-05-30\",\n    \"1999-06-18\",\n    \"2000-06-06\",\n    \"2001-06-25\",\n    \"2002-06-15\",\n    \"2003-06-04\",\n    \"2004-06-22\",\n    \"2005-06-11\",\n    \"2006-05-31\",\n    \"2007-06-19\",\n    \"2008-06-08\",\n    \"2009-05-28\",\n    \"2010-06-16\",\n    \"2011-06-06\",\n    \"2012-06-23\",\n    \"2013-06-13\",\n    \"2014-06-02\",\n    \"2015-06-20\",\n    \"2016-06-09\",\n    \"2017-05-30\",\n    \"2018-06-18\",\n    \"2019-06-07\",\n    \"2020-06-25\",\n    \"2021-06-14\",\n    \"2022-06-03\",\n    \"2023-06-22\",\n    \"2024-06-10\",\n    \"2025-05-31\",\n    \"2026-06-19\",\n    \"2027-06-09\",\n    \"2028-05-28\",\n    \"2029-06-16\",\n    \"2030-06-05\",\n    \"2031-06-24\",\n    \"2032-06-12\",\n    \"2033-06-01\",\n    \"2034-06-20\",\n    \"2035-06-10\",\n    \"2036-05-30\",\n    \"2037-06-18\",\n    \"2038-06-07\",\n    \"2039-05-27\",\n    \"2040-06-14\",\n    \"2041-06-03\",\n    \"2042-06-22\",\n    \"2043-06-11\",\n    \"2044-05-31\",\n    \"2045-06-19\",\n    \"2046-06-08\",\n    \"2047-05-29\",\n    \"2048-06-15\",\n    \"2049-06-04\",\n    \"2050-06-23\",\n    \"2051-06-13\",\n    \"2052-06-01\",\n    \"2053-06-20\",\n    \"2054-06-10\",\n    \"2055-05-30\",\n    \"2056-06-17\",\n    \"2057-06-06\",\n    \"2058-06-25\",\n    \"2059-06-14\",\n    \"2060-06-03\",\n    \"2061-06-22\",\n    \"2062-06-11\",\n    \"2063-06-01\",\n    \"2064-06-19\",\n    \"2065-06-08\",\n    \"2066-05-28\",\n    \"2067-06-16\",\n    \"2068-06-04\",\n    \"2069-06-23\",\n    \"2070-06-13\",\n    \"2071-06-02\",\n    \"2072-06-20\",\n    \"2073-06-10\",\n    \"2074-05-30\",\n    \"2075-06-17\",\n    \"2076-06-06\",\n    \"2077-06-24\",\n    \"2078-06-14\",\n    \"2079-06-04\",\n    \"2080-06-22\",\n    \"2081-06-11\",\n    \"2082-06-01\",\n    \"2083-06-19\",\n    \"2084-06-07\",\n    \"2085-05-27\",\n    \"2086-06-15\",\n    \"2087-06-05\",\n    \"2088-06-23\",\n    \"2089-06-13\",\n    \"2090-06-02\",\n    \"2091-06-21\",\n    \"2092-06-09\",\n    \"2093-05-29\",\n    \"2094-06-17\",\n    \"2095-06-06\",\n    \"2096-06-24\",\n    \"2097-06-14\",\n    \"2098-06-04\",\n    \"2099-06-23\",\n    \"2100-06-12\",\n]\ndragon_boat_dict = {\n    k + 1900: datetime.strptime(v, \"%Y-%m-%d\") for k, v in enumerate(dragon_boat_days)\n}\n\n\ndef three_day_holidays(dt: datetime) -> list[datetime]:\n    if dt.weekday() in [3, 4]:\n        # holidays on the weekend after\n        return [dt + timedelta(days=i) for i in range(3)]\n    elif dt.weekday() in [0, 1]:\n        # holidays on the weekend before\n        return [dt + timedelta(days=i) for i in range(-2, 1)]\n    elif dt.weekday() in [2]:\n        # only one mid-week holiday day\n        return [dt + timedelta(days=i) for i in range(1)]\n    elif dt.weekday() in [5]:\n        # sat, sun and mon\n        start = 0\n    elif dt.weekday() in [6]:\n        # sat, sun, mon\n        start = -1\n    return [dt + timedelta(days=i) for i in range(start, start + 3)]\n\n\ndef three_day_compensations(dt: datetime) -> list[datetime]:\n    if dt.weekday() in [1]:\n        # compensate by preceding Su\n        return [dt + timedelta(days=-9)]\n    if dt.weekday() in [3]:\n        # following Su\n        return [dt + timedelta(days=10)]\n    else:\n        return []\n\n\ndef dragon_boat_holidays(year: int) -> list[datetime]:\n    try:\n        dt = dragon_boat_dict[year]\n    except KeyError:\n        return []\n\n    return three_day_holidays(dt)\n\n\ndef dragon_boat_compensations(year: int) -> list[datetime]:\n    try:\n        dt = dragon_boat_dict[year]\n    except KeyError:\n        return []\n\n    return three_day_compensations(dt)\n\n\nvernal_equinox_date = [\n    \"1970-03-21\",\n    \"1971-03-21\",\n    \"1972-03-20\",\n    \"1973-03-20\",\n    \"1974-03-21\",\n    \"1975-03-21\",\n    \"1976-03-20\",\n    \"1977-03-20\",\n    \"1978-03-20\",\n    \"1979-03-21\",\n    \"1980-03-20\",\n    \"1981-03-20\",\n    \"1982-03-20\",\n    \"1983-03-21\",\n    \"1984-03-20\",\n    \"1985-03-20\",\n    \"1986-03-20\",\n    \"1987-03-21\",\n    \"1988-03-20\",\n    \"1989-03-20\",\n    \"1990-03-20\",\n    \"1991-03-21\",\n    \"1992-03-20\",\n    \"1993-03-20\",\n    \"1994-03-20\",\n    \"1995-03-21\",\n    \"1996-03-20\",\n    \"1997-03-20\",\n    \"1998-03-20\",\n    \"1999-03-21\",\n    \"2000-03-20\",\n    \"2001-03-20\",\n    \"2002-03-20\",\n    \"2003-03-21\",\n    \"2004-03-20\",\n    \"2005-03-20\",\n    \"2006-03-20\",\n    \"2007-03-21\",\n    \"2008-03-20\",\n    \"2009-03-20\",\n    \"2010-03-20\",\n    \"2011-03-20\",\n    \"2012-03-20\",\n    \"2013-03-20\",\n    \"2014-03-20\",\n    \"2015-03-20\",\n    \"2016-03-21\",  # Manual edit\n    \"2017-03-20\",\n    \"2018-03-21\",  # Manual edit\n    \"2019-03-20\",\n    \"2020-03-20\",\n    \"2021-03-21\",  # Manual edit\n    \"2022-03-21\",  # Manual edit\n    \"2023-03-21\",  # Manual edit\n    \"2024-03-20\",\n    \"2025-03-20\",\n    \"2026-03-20\",\n    \"2027-03-20\",\n    \"2028-03-20\",\n    \"2029-03-20\",\n    \"2030-03-20\",\n    \"2031-03-20\",\n    \"2032-03-20\",\n    \"2033-03-20\",\n    \"2034-03-20\",\n    \"2035-03-20\",\n    \"2036-03-20\",\n    \"2037-03-20\",\n    \"2038-03-20\",\n    \"2039-03-20\",\n    \"2040-03-20\",\n    \"2041-03-20\",\n    \"2042-03-20\",\n    \"2043-03-20\",\n    \"2044-03-19\",\n    \"2045-03-20\",\n    \"2046-03-20\",\n    \"2047-03-20\",\n    \"2048-03-19\",\n    \"2049-03-20\",\n    \"2050-03-20\",\n    \"2051-03-20\",\n    \"2052-03-19\",\n    \"2053-03-20\",\n    \"2054-03-20\",\n    \"2055-03-20\",\n    \"2056-03-19\",\n    \"2057-03-20\",\n    \"2058-03-20\",\n    \"2059-03-20\",\n    \"2060-03-19\",\n    \"2061-03-20\",\n    \"2062-03-20\",\n    \"2063-03-20\",\n    \"2064-03-19\",\n    \"2065-03-20\",\n    \"2066-03-20\",\n    \"2067-03-20\",\n    \"2068-03-19\",\n    \"2069-03-20\",\n    \"2070-03-20\",\n    \"2071-03-20\",\n    \"2072-03-19\",\n    \"2073-03-20\",\n    \"2074-03-20\",\n    \"2075-03-20\",\n    \"2076-03-19\",\n    \"2077-03-19\",\n    \"2078-03-20\",\n    \"2079-03-20\",\n    \"2080-03-19\",\n    \"2081-03-19\",\n    \"2082-03-20\",\n    \"2083-03-20\",\n    \"2084-03-19\",\n    \"2085-03-19\",\n    \"2086-03-20\",\n    \"2087-03-20\",\n    \"2088-03-19\",\n    \"2089-03-19\",\n    \"2090-03-20\",\n    \"2091-03-20\",\n    \"2092-03-19\",\n    \"2093-03-19\",\n    \"2094-03-20\",\n    \"2095-03-20\",\n    \"2096-03-19\",\n    \"2097-03-19\",\n    \"2098-03-20\",\n    \"2099-03-20\",\n    \"2100-03-20\",\n    \"2101-03-20\",\n    \"2102-03-21\",\n    \"2103-03-21\",\n    \"2104-03-20\",\n    \"2105-03-20\",\n    \"2106-03-21\",\n    \"2107-03-21\",\n    \"2108-03-20\",\n    \"2109-03-20\",\n    \"2110-03-20\",\n    \"2111-03-21\",\n    \"2112-03-20\",\n    \"2113-03-20\",\n    \"2114-03-20\",\n    \"2115-03-21\",\n    \"2116-03-20\",\n    \"2117-03-20\",\n    \"2118-03-20\",\n    \"2119-03-21\",\n    \"2120-03-20\",\n    \"2121-03-20\",\n    \"2122-03-20\",\n    \"2123-03-21\",\n    \"2124-03-20\",\n    \"2125-03-20\",\n    \"2126-03-20\",\n    \"2127-03-21\",\n    \"2128-03-20\",\n    \"2129-03-20\",\n    \"2130-03-20\",\n    \"2131-03-21\",\n    \"2132-03-20\",\n    \"2133-03-20\",\n    \"2134-03-20\",\n    \"2135-03-21\",\n    \"2136-03-20\",\n    \"2137-03-20\",\n    \"2138-03-20\",\n    \"2139-03-20\",\n    \"2140-03-20\",\n    \"2141-03-20\",\n    \"2142-03-20\",\n    \"2143-03-20\",\n    \"2144-03-20\",\n    \"2145-03-20\",\n    \"2146-03-20\",\n    \"2147-03-20\",\n    \"2148-03-20\",\n    \"2149-03-20\",\n    \"2150-03-20\",\n    \"2151-03-20\",\n    \"2152-03-20\",\n    \"2153-03-20\",\n    \"2154-03-20\",\n    \"2155-03-20\",\n    \"2156-03-20\",\n    \"2157-03-20\",\n    \"2158-03-20\",\n    \"2159-03-20\",\n    \"2160-03-20\",\n    \"2161-03-20\",\n    \"2162-03-20\",\n    \"2163-03-20\",\n    \"2164-03-20\",\n    \"2165-03-20\",\n    \"2166-03-20\",\n    \"2167-03-20\",\n    \"2168-03-20\",\n    \"2169-03-20\",\n    \"2170-03-20\",\n    \"2171-03-20\",\n    \"2172-03-19\",\n    \"2173-03-20\",\n    \"2174-03-20\",\n    \"2175-03-20\",\n    \"2176-03-19\",\n    \"2177-03-20\",\n    \"2178-03-20\",\n    \"2179-03-20\",\n    \"2180-03-19\",\n    \"2181-03-20\",\n    \"2182-03-20\",\n    \"2183-03-20\",\n    \"2184-03-19\",\n    \"2185-03-20\",\n    \"2186-03-20\",\n    \"2187-03-20\",\n    \"2188-03-19\",\n    \"2189-03-20\",\n    \"2190-03-20\",\n    \"2191-03-20\",\n    \"2192-03-19\",\n    \"2193-03-20\",\n    \"2194-03-20\",\n    \"2195-03-20\",\n    \"2196-03-19\",\n    \"2197-03-20\",\n    \"2198-03-20\",\n    \"2199-03-20\",\n    \"2200-03-20\",\n]\nvernal_equinox_dict = {\n    k + 1970: datetime.strptime(v, \"%Y-%m-%d\") for k, v in enumerate(vernal_equinox_date)\n}\n\nmid_autumn_festival_dates = [\n    \"1900-09-08\",\n    \"1901-09-27\",\n    \"1902-09-16\",\n    \"1903-10-05\",\n    \"1904-09-24\",\n    \"1905-09-13\",\n    \"1906-10-02\",\n    \"1907-09-22\",\n    \"1908-09-10\",\n    \"1909-09-28\",\n    \"1910-09-18\",\n    \"1911-10-06\",\n    \"1912-09-25\",\n    \"1913-09-15\",\n    \"1914-10-04\",\n    \"1915-09-23\",\n    \"1916-09-12\",\n    \"1917-09-30\",\n    \"1918-09-19\",\n    \"1919-10-08\",\n    \"1920-09-26\",\n    \"1921-09-16\",\n    \"1922-10-05\",\n    \"1923-09-25\",\n    \"1924-09-13\",\n    \"1925-10-02\",\n    \"1926-09-21\",\n    \"1927-09-10\",\n    \"1928-09-28\",\n    \"1929-09-17\",\n    \"1930-10-06\",\n    \"1931-09-26\",\n    \"1932-09-15\",\n    \"1933-10-04\",\n    \"1934-09-23\",\n    \"1935-09-12\",\n    \"1936-09-30\",\n    \"1937-09-18\",\n    \"1938-10-08\",\n    \"1939-09-27\",\n    \"1940-09-16\",\n    \"1941-10-05\",\n    \"1942-09-24\",\n    \"1943-09-14\",\n    \"1944-10-01\",\n    \"1945-09-20\",\n    \"1946-09-10\",\n    \"1947-09-29\",\n    \"1948-09-17\",\n    \"1949-10-06\",\n    \"1950-09-26\",\n    \"1951-09-15\",\n    \"1952-10-03\",\n    \"1953-09-22\",\n    \"1954-09-11\",\n    \"1955-09-30\",\n    \"1956-09-19\",\n    \"1957-09-08\",\n    \"1958-09-27\",\n    \"1959-09-17\",\n    \"1960-10-05\",\n    \"1961-09-24\",\n    \"1962-09-13\",\n    \"1963-10-02\",\n    \"1964-09-20\",\n    \"1965-09-10\",\n    \"1966-09-29\",\n    \"1967-09-18\",\n    \"1968-10-06\",\n    \"1969-09-26\",\n    \"1970-09-15\",\n    \"1971-10-03\",\n    \"1972-09-22\",\n    \"1973-09-11\",\n    \"1974-09-30\",\n    \"1975-09-20\",\n    \"1976-09-08\",\n    \"1977-09-27\",\n    \"1978-09-17\",\n    \"1979-10-05\",\n    \"1980-09-23\",\n    \"1981-09-12\",\n    \"1982-10-01\",\n    \"1983-09-21\",\n    \"1984-09-10\",\n    \"1985-09-29\",\n    \"1986-09-18\",\n    \"1987-10-07\",\n    \"1988-09-25\",\n    \"1989-09-14\",\n    \"1990-10-03\",\n    \"1991-09-22\",\n    \"1992-09-11\",\n    \"1993-09-30\",\n    \"1994-09-20\",\n    \"1995-09-09\",\n    \"1996-09-27\",\n    \"1997-09-16\",\n    \"1998-10-05\",\n    \"1999-09-24\",\n    \"2000-09-12\",\n    \"2001-10-01\",\n    \"2002-09-21\",\n    \"2003-09-11\",\n    \"2004-09-28\",\n    \"2005-09-18\",\n    \"2006-10-06\",\n    \"2007-09-25\",\n    \"2008-09-14\",\n    \"2009-10-03\",\n    \"2010-09-22\",\n    \"2011-09-12\",\n    \"2012-09-30\",\n    \"2013-09-19\",\n    \"2014-09-08\",\n    \"2015-09-27\",\n    \"2016-09-15\",\n    \"2017-10-04\",\n    \"2018-09-24\",\n    \"2019-09-13\",\n    \"2020-10-01\",\n    \"2021-09-21\",\n    \"2022-09-10\",\n    \"2023-09-29\",\n    \"2024-09-17\",\n    \"2025-10-06\",\n    \"2026-09-25\",\n    \"2027-09-15\",\n    \"2028-10-03\",\n    \"2029-09-22\",\n    \"2030-09-12\",\n    \"2031-10-01\",\n    \"2032-09-19\",\n    \"2033-09-08\",\n    \"2034-09-27\",\n    \"2035-09-16\",\n    \"2036-10-04\",\n    \"2037-09-24\",\n    \"2038-09-13\",\n    \"2039-10-02\",\n    \"2040-09-20\",\n    \"2041-09-10\",\n    \"2042-09-28\",\n    \"2043-09-17\",\n    \"2044-10-05\",\n    \"2045-09-25\",\n    \"2046-09-15\",\n    \"2047-10-04\",\n    \"2048-09-22\",\n    \"2049-09-11\",\n    \"2050-09-30\",\n    \"2051-09-19\",\n    \"2052-09-07\",\n    \"2053-09-26\",\n    \"2054-09-16\",\n    \"2055-10-05\",\n    \"2056-09-24\",\n    \"2057-09-13\",\n    \"2058-10-02\",\n    \"2059-09-21\",\n    \"2060-09-09\",\n    \"2061-09-28\",\n    \"2062-09-17\",\n    \"2063-10-06\",\n    \"2064-09-25\",\n    \"2065-09-15\",\n    \"2066-10-03\",\n    \"2067-09-23\",\n    \"2068-09-11\",\n    \"2069-09-29\",\n    \"2070-09-19\",\n    \"2071-09-08\",\n    \"2072-09-26\",\n    \"2073-09-16\",\n    \"2074-10-05\",\n    \"2075-09-24\",\n    \"2076-09-12\",\n    \"2077-10-01\",\n    \"2078-09-20\",\n    \"2079-09-10\",\n    \"2080-09-28\",\n    \"2081-09-17\",\n    \"2082-10-06\",\n    \"2083-09-26\",\n    \"2084-09-14\",\n    \"2085-10-03\",\n    \"2086-09-22\",\n    \"2087-09-11\",\n    \"2088-09-29\",\n    \"2089-09-18\",\n    \"2090-09-08\",\n    \"2091-09-27\",\n    \"2092-09-16\",\n    \"2093-10-05\",\n    \"2094-09-24\",\n    \"2095-09-13\",\n    \"2096-09-30\",\n    \"2097-09-20\",\n    \"2098-09-09\",\n    \"2099-09-29\",\n    \"2100-09-18\",\n]\nmid_autumn_dict = {\n    k + 1900: datetime.strptime(v, \"%Y-%m-%d\") for k, v in enumerate(mid_autumn_festival_dates)\n}\n\n\ndef tomb_sweeping_holidays(year: int) -> list[datetime]:\n    try:\n        dt = vernal_equinox_dict[year]\n    except KeyError:\n        return []\n    # add 15 days to get to the holiday\n    return three_day_holidays(dt + timedelta(days=15))\n\n\ndef tomb_sweeping_compensations(year: int) -> list[datetime]:\n    try:\n        dt = vernal_equinox_dict[year]\n    except KeyError:\n        return []\n    # add 15 days to get to the holiday\n    return three_day_compensations(dt + timedelta(days=15))\n\n\ndef mid_autumn_holidays(year: int) -> list[datetime]:\n    try:\n        dt = mid_autumn_dict[year]\n    except KeyError:\n        return []\n\n    return three_day_holidays(dt)\n\n\ndef mid_autumn_compensations(year: int) -> list[datetime]:\n    try:\n        dt = mid_autumn_dict[year]\n    except KeyError:\n        return []\n\n    return three_day_compensations(dt)\n\n\ndef labour_day_holidays(year: int) -> list[datetime]:\n    # golden 5 day holiday\n    dt = datetime(year=year, month=5, day=1)\n\n    if dt.weekday() == 0:\n        idx = (-2, 3)\n    elif dt.weekday() == 1:\n        idx = (-3, 2)\n    elif dt.weekday() == 2:\n        idx = (0, 2)\n    elif dt.weekday() == 3 or dt.weekday() == 4 or dt.weekday() == 5:\n        idx = (0, 5)\n    elif dt.weekday() == 6:\n        idx = (-1, 4)\n\n    return [dt + timedelta(days=i) for i in range(*idx)]\n\n\ndef labour_day_compensations(year: int) -> list[datetime]:\n    dt = datetime(year=year, month=5, day=1)\n\n    if dt.weekday() == 0:\n        # following Su\n        return [dt + timedelta(days=6)]\n    elif dt.weekday() == 1:\n        # Previous Sa\n        return [dt + timedelta(days=-3)]\n    elif dt.weekday() in [2]:\n        # no compensation for Wednesday\n        return []\n    elif dt.weekday() in [3]:\n        # Previous Su\n        return [dt + timedelta(days=-4)]\n    elif dt.weekday() in [4]:\n        # Following Sa\n        return [dt + timedelta(days=8)]\n    elif dt.weekday() in [5]:\n        # Following Su\n        return [dt + timedelta(days=8)]\n    elif dt.weekday() in [6]:\n        # Following Su\n        return [dt + timedelta(days=7)]\n\n\ndef national_day_holidays(year: int) -> list[datetime]:\n    return [datetime(year=year, month=10, day=1) + timedelta(days=i) for i in range(7)]\n\n\ndef national_day_compensations(year: int) -> list[datetime]:\n    dt = datetime(year=year, month=10, day=1)\n\n    if dt.weekday() == 0:\n        return [dt + timedelta(days=-2), dt + timedelta(days=12)]\n    if dt.weekday() == 1:\n        return [dt + timedelta(days=-2), dt + timedelta(days=11)]\n    if dt.weekday() == 2:\n        return [dt + timedelta(days=-3), dt + timedelta(days=10)]\n    if dt.weekday() == 3:\n        return [dt + timedelta(days=-4), dt + timedelta(days=9)]\n    if dt.weekday() == 4:\n        return [dt + timedelta(days=-5), dt + timedelta(days=8)]\n    if dt.weekday() == 5:\n        return [dt + timedelta(days=-6), dt + timedelta(days=8)]\n    if dt.weekday() == 6:\n        return [dt + timedelta(days=-7), dt + timedelta(days=7)]\n\n\ndef national_day_and_mid_autumn_holidays(year: int) -> list[datetime]:\n    # broad stroke approximations to merge National holiday and Mid Autumn overlaps\n    mu_holidays = mid_autumn_holidays(year)\n    nat_holidays = national_day_holidays(year)\n\n    if mu_holidays[-1] <= nat_holidays[-1] and mu_holidays[0] >= nat_holidays[0]:\n        # then mu holidays are contained within nat holidays so extend\n        return nat_holidays + [datetime(year=year, month=10, day=8)]\n    else:\n        return mu_holidays + nat_holidays\n\n\ndef national_day_and_mid_autumn_compensations(year: int) -> list[datetime]:\n    # broad stroke approximations to merge National holiday and Mid Autumn overlaps\n    mu_holidays = mid_autumn_holidays(year)\n    nat_holidays = national_day_holidays(year)\n    mu_compensations = mid_autumn_compensations(year)  # can only be at most 1 date\n    nat_compensations = national_day_compensations(year)  # will be 2 dates\n\n    if len(mu_compensations) > 0 and mu_compensations in nat_holidays:\n        mu_compensations = [mu_compensations[0] - timedelta(days=7)]\n\n    if nat_compensations[0] in mu_holidays:\n        nat_compensations[0] = nat_compensations[0] - timedelta(days=7)\n\n    return mu_compensations + nat_compensations\n\n\ndef apply_years(years: tuple[int, int], func) -> list[datetime]:\n    h = []\n    for year in range(years[0], years[1] + 1):\n        h.extend(func(year))\n    return h\n\n\ndef apply_years_H(years: tuple[int, int], func) -> list[Holiday]:\n    return [\n        Holiday(\"Date: {_.strftime('%Y-%m-%d')}\", year=_.year, month=_.month, day=_.day)\n        for _ in apply_years(years, func)\n    ]\n\n\nCOMPENSATIONS = [\n    *apply_years((2025, 2100), new_years_compensations),\n    *apply_years((2025, 2100), lunar_new_year_compensations),\n    *apply_years((2025, 2100), dragon_boat_compensations),\n    *apply_years((2025, 2100), tomb_sweeping_compensations),\n    *apply_years((2025, 2100), labour_day_compensations),\n    *apply_years((2025, 2100), national_day_and_mid_autumn_compensations),\n]\n\nRULES = [\n    # these provide a custom saturday sunday weekmask but add back specific trading days at weekend\n    *weekday_mask(weekdays=[5, 6], years=(1970, 2125), exclude=COMPENSATIONS),\n    *apply_years_H((2025, 2100), new_years_holidays),\n    *apply_years_H((2025, 2100), lunar_new_year_holidays),\n    *apply_years_H((2025, 2100), dragon_boat_holidays),\n    *apply_years_H((2025, 2100), tomb_sweeping_holidays),\n    *apply_years_H((2025, 2100), labour_day_holidays),\n    *apply_years_H((2025, 2100), national_day_and_mid_autumn_holidays),\n]\n\nCALENDAR = CustomBusinessDay(  # type: ignore[call-arg]\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri Sat Sun\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(list(dict.fromkeys(strings)))\nprint(line)\n\n\n# [\n#             datetime(2022, 1, 29),\n#             datetime(2022, 1, 30),\n#             datetime(2022, 4, 2),\n#             datetime(2022, 4, 24),\n#             datetime(2022, 5, 7),\n#             datetime(2022, 10, 8),\n#             datetime(2022, 10, 9),\n#             datetime(2022, 12, 31),\n#             datetime(2023, 1, 28),\n#             datetime(2023, 1, 29),\n#             datetime(2023, 4, 23),\n#             datetime(2023, 5, 6),\n#             datetime(2023, 6, 25),\n#             datetime(2023, 10, 7),\n#             datetime(2023, 10, 8),\n#             datetime(2023, 12, 31),\n#             datetime(2024, 2, 4),\n#             datetime(2024, 2, 18),\n#             datetime(2024, 4, 7),\n#             datetime(2024, 4, 28),\n#             datetime(2024, 5, 11),\n#             datetime(2024, 9, 14),\n#             datetime(2024, 9, 29),\n#             datetime(2024, 10, 12),\n#             datetime(2025, 1, 26),\n#             datetime(2025, 2, 8),\n#             datetime(2025, 4, 27),\n#             datetime(2025, 9, 28),\n#             datetime(2025, 10, 11),\n#             datetime(2026, 1, 4),\n#             datetime(2026, 2, 14),\n#             datetime(2026, 2, 28),\n#             datetime(2026, 5, 9),\n#             datetime(2026, 9, 20),\n#             datetime(2026, 10, 10),\n#         ],\n"
  },
  {
    "path": "rust/scheduling/calendars/named/bus.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a generic Western business weekday calendar without any specific holidays.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[];\n\npub const HOLIDAYS: &[&str] = &[]; // no specific holidays\n"
  },
  {
    "path": "rust/scheduling/calendars/named/fed.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a New York business day calendar, aligned with SOFR publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1: Sun->Mon (New Year)\",\n//     \"Jan 3rd Mon (Martin Luther King Jr.)\",\n//     \"Feb 3rd Mon (President's)\",\n//     \"May last Mon (Memorial)\",\n//     \"Jun 19: Sun->Mon (Juneteenth)\",\n//     \"Jul 4: Sat->Fri, Sun->Mon (Independence)\",\n//     \"Sep 1st Mon (Labour)\",\n//     \"Oct 2nd Mon (Columbus)\",\n//     \"Nov 11: Sun->Mon (Veteran's)\",\n//     \"Nov 4th Thu (Thanksgiving)\",\n//     \"Dec 25: Sat->Fri,Sun->Mon (Christmas)\",\n//     \"Note: Special additional dates.\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-02-16 00:00:00\",\n    \"1970-05-25 00:00:00\",\n    \"1970-07-04 00:00:00\",\n    \"1970-09-07 00:00:00\",\n    \"1970-10-12 00:00:00\",\n    \"1970-11-11 00:00:00\",\n    \"1970-11-26 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-02-15 00:00:00\",\n    \"1971-05-31 00:00:00\",\n    \"1971-07-05 00:00:00\",\n    \"1971-09-06 00:00:00\",\n    \"1971-10-11 00:00:00\",\n    \"1971-11-11 00:00:00\",\n    \"1971-11-25 00:00:00\",\n    \"1971-12-25 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-02-21 00:00:00\",\n    \"1972-05-29 00:00:00\",\n    \"1972-07-04 00:00:00\",\n    \"1972-09-04 00:00:00\",\n    \"1972-10-09 00:00:00\",\n    \"1972-11-11 00:00:00\",\n    \"1972-11-23 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-02-19 00:00:00\",\n    \"1973-05-28 00:00:00\",\n    \"1973-07-04 00:00:00\",\n    \"1973-09-03 00:00:00\",\n    \"1973-10-08 00:00:00\",\n    \"1973-11-12 00:00:00\",\n    \"1973-11-22 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-02-18 00:00:00\",\n    \"1974-05-27 00:00:00\",\n    \"1974-07-04 00:00:00\",\n    \"1974-09-02 00:00:00\",\n    \"1974-10-14 00:00:00\",\n    \"1974-11-11 00:00:00\",\n    \"1974-11-28 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-02-17 00:00:00\",\n    \"1975-05-26 00:00:00\",\n    \"1975-07-04 00:00:00\",\n    \"1975-09-01 00:00:00\",\n    \"1975-10-13 00:00:00\",\n    \"1975-11-11 00:00:00\",\n    \"1975-11-27 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-02-16 00:00:00\",\n    \"1976-05-31 00:00:00\",\n    \"1976-07-05 00:00:00\",\n    \"1976-09-06 00:00:00\",\n    \"1976-10-11 00:00:00\",\n    \"1976-11-11 00:00:00\",\n    \"1976-11-25 00:00:00\",\n    \"1976-12-25 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-02-21 00:00:00\",\n    \"1977-05-30 00:00:00\",\n    \"1977-07-04 00:00:00\",\n    \"1977-09-05 00:00:00\",\n    \"1977-10-10 00:00:00\",\n    \"1977-11-11 00:00:00\",\n    \"1977-11-24 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-02-20 00:00:00\",\n    \"1978-05-29 00:00:00\",\n    \"1978-07-04 00:00:00\",\n    \"1978-09-04 00:00:00\",\n    \"1978-10-09 00:00:00\",\n    \"1978-11-11 00:00:00\",\n    \"1978-11-23 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-02-19 00:00:00\",\n    \"1979-05-28 00:00:00\",\n    \"1979-07-04 00:00:00\",\n    \"1979-09-03 00:00:00\",\n    \"1979-10-08 00:00:00\",\n    \"1979-11-12 00:00:00\",\n    \"1979-11-22 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-02-18 00:00:00\",\n    \"1980-05-26 00:00:00\",\n    \"1980-07-04 00:00:00\",\n    \"1980-09-01 00:00:00\",\n    \"1980-10-13 00:00:00\",\n    \"1980-11-11 00:00:00\",\n    \"1980-11-27 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-02-16 00:00:00\",\n    \"1981-05-25 00:00:00\",\n    \"1981-07-04 00:00:00\",\n    \"1981-09-07 00:00:00\",\n    \"1981-10-12 00:00:00\",\n    \"1981-11-11 00:00:00\",\n    \"1981-11-26 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-02-15 00:00:00\",\n    \"1982-05-31 00:00:00\",\n    \"1982-07-05 00:00:00\",\n    \"1982-09-06 00:00:00\",\n    \"1982-10-11 00:00:00\",\n    \"1982-11-11 00:00:00\",\n    \"1982-11-25 00:00:00\",\n    \"1982-12-25 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-02-21 00:00:00\",\n    \"1983-05-30 00:00:00\",\n    \"1983-07-04 00:00:00\",\n    \"1983-09-05 00:00:00\",\n    \"1983-10-10 00:00:00\",\n    \"1983-11-11 00:00:00\",\n    \"1983-11-24 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-02-20 00:00:00\",\n    \"1984-05-28 00:00:00\",\n    \"1984-07-04 00:00:00\",\n    \"1984-09-03 00:00:00\",\n    \"1984-10-08 00:00:00\",\n    \"1984-11-12 00:00:00\",\n    \"1984-11-22 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-02-18 00:00:00\",\n    \"1985-05-27 00:00:00\",\n    \"1985-07-04 00:00:00\",\n    \"1985-09-02 00:00:00\",\n    \"1985-10-14 00:00:00\",\n    \"1985-11-11 00:00:00\",\n    \"1985-11-28 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-01-20 00:00:00\",\n    \"1986-02-17 00:00:00\",\n    \"1986-05-26 00:00:00\",\n    \"1986-07-04 00:00:00\",\n    \"1986-09-01 00:00:00\",\n    \"1986-10-13 00:00:00\",\n    \"1986-11-11 00:00:00\",\n    \"1986-11-27 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-01-19 00:00:00\",\n    \"1987-02-16 00:00:00\",\n    \"1987-05-25 00:00:00\",\n    \"1987-07-04 00:00:00\",\n    \"1987-09-07 00:00:00\",\n    \"1987-10-12 00:00:00\",\n    \"1987-11-11 00:00:00\",\n    \"1987-11-26 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-01-18 00:00:00\",\n    \"1988-02-15 00:00:00\",\n    \"1988-05-30 00:00:00\",\n    \"1988-07-04 00:00:00\",\n    \"1988-09-05 00:00:00\",\n    \"1988-10-10 00:00:00\",\n    \"1988-11-11 00:00:00\",\n    \"1988-11-24 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-01-16 00:00:00\",\n    \"1989-02-20 00:00:00\",\n    \"1989-05-29 00:00:00\",\n    \"1989-07-04 00:00:00\",\n    \"1989-09-04 00:00:00\",\n    \"1989-10-09 00:00:00\",\n    \"1989-11-11 00:00:00\",\n    \"1989-11-23 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-01-15 00:00:00\",\n    \"1990-02-19 00:00:00\",\n    \"1990-05-28 00:00:00\",\n    \"1990-07-04 00:00:00\",\n    \"1990-09-03 00:00:00\",\n    \"1990-10-08 00:00:00\",\n    \"1990-11-12 00:00:00\",\n    \"1990-11-22 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-01-21 00:00:00\",\n    \"1991-02-18 00:00:00\",\n    \"1991-05-27 00:00:00\",\n    \"1991-07-04 00:00:00\",\n    \"1991-09-02 00:00:00\",\n    \"1991-10-14 00:00:00\",\n    \"1991-11-11 00:00:00\",\n    \"1991-11-28 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-01-20 00:00:00\",\n    \"1992-02-17 00:00:00\",\n    \"1992-05-25 00:00:00\",\n    \"1992-07-04 00:00:00\",\n    \"1992-09-07 00:00:00\",\n    \"1992-10-12 00:00:00\",\n    \"1992-11-11 00:00:00\",\n    \"1992-11-26 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-01-18 00:00:00\",\n    \"1993-02-15 00:00:00\",\n    \"1993-05-31 00:00:00\",\n    \"1993-07-05 00:00:00\",\n    \"1993-09-06 00:00:00\",\n    \"1993-10-11 00:00:00\",\n    \"1993-11-11 00:00:00\",\n    \"1993-11-25 00:00:00\",\n    \"1993-12-25 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-01-17 00:00:00\",\n    \"1994-02-21 00:00:00\",\n    \"1994-05-30 00:00:00\",\n    \"1994-07-04 00:00:00\",\n    \"1994-09-05 00:00:00\",\n    \"1994-10-10 00:00:00\",\n    \"1994-11-11 00:00:00\",\n    \"1994-11-24 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-01-16 00:00:00\",\n    \"1995-02-20 00:00:00\",\n    \"1995-05-29 00:00:00\",\n    \"1995-07-04 00:00:00\",\n    \"1995-09-04 00:00:00\",\n    \"1995-10-09 00:00:00\",\n    \"1995-11-11 00:00:00\",\n    \"1995-11-23 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-01-15 00:00:00\",\n    \"1996-02-19 00:00:00\",\n    \"1996-05-27 00:00:00\",\n    \"1996-07-04 00:00:00\",\n    \"1996-09-02 00:00:00\",\n    \"1996-10-14 00:00:00\",\n    \"1996-11-11 00:00:00\",\n    \"1996-11-28 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-01-20 00:00:00\",\n    \"1997-02-17 00:00:00\",\n    \"1997-05-26 00:00:00\",\n    \"1997-07-04 00:00:00\",\n    \"1997-09-01 00:00:00\",\n    \"1997-10-13 00:00:00\",\n    \"1997-11-11 00:00:00\",\n    \"1997-11-27 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-01-19 00:00:00\",\n    \"1998-02-16 00:00:00\",\n    \"1998-05-25 00:00:00\",\n    \"1998-07-04 00:00:00\",\n    \"1998-09-07 00:00:00\",\n    \"1998-10-12 00:00:00\",\n    \"1998-11-11 00:00:00\",\n    \"1998-11-26 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-01-18 00:00:00\",\n    \"1999-02-15 00:00:00\",\n    \"1999-05-31 00:00:00\",\n    \"1999-07-05 00:00:00\",\n    \"1999-09-06 00:00:00\",\n    \"1999-10-11 00:00:00\",\n    \"1999-11-11 00:00:00\",\n    \"1999-11-25 00:00:00\",\n    \"1999-12-25 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-01-17 00:00:00\",\n    \"2000-02-21 00:00:00\",\n    \"2000-05-29 00:00:00\",\n    \"2000-07-04 00:00:00\",\n    \"2000-09-04 00:00:00\",\n    \"2000-10-09 00:00:00\",\n    \"2000-11-11 00:00:00\",\n    \"2000-11-23 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-01-15 00:00:00\",\n    \"2001-02-19 00:00:00\",\n    \"2001-05-28 00:00:00\",\n    \"2001-07-04 00:00:00\",\n    \"2001-09-03 00:00:00\",\n    \"2001-10-08 00:00:00\",\n    \"2001-11-12 00:00:00\",\n    \"2001-11-22 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-01-21 00:00:00\",\n    \"2002-02-18 00:00:00\",\n    \"2002-05-27 00:00:00\",\n    \"2002-07-04 00:00:00\",\n    \"2002-09-02 00:00:00\",\n    \"2002-10-14 00:00:00\",\n    \"2002-11-11 00:00:00\",\n    \"2002-11-28 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-01-20 00:00:00\",\n    \"2003-02-17 00:00:00\",\n    \"2003-05-26 00:00:00\",\n    \"2003-07-04 00:00:00\",\n    \"2003-09-01 00:00:00\",\n    \"2003-10-13 00:00:00\",\n    \"2003-11-11 00:00:00\",\n    \"2003-11-27 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-01-19 00:00:00\",\n    \"2004-02-16 00:00:00\",\n    \"2004-05-31 00:00:00\",\n    \"2004-07-05 00:00:00\",\n    \"2004-09-06 00:00:00\",\n    \"2004-10-11 00:00:00\",\n    \"2004-11-11 00:00:00\",\n    \"2004-11-25 00:00:00\",\n    \"2004-12-25 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-01-17 00:00:00\",\n    \"2005-02-21 00:00:00\",\n    \"2005-05-30 00:00:00\",\n    \"2005-07-04 00:00:00\",\n    \"2005-09-05 00:00:00\",\n    \"2005-10-10 00:00:00\",\n    \"2005-11-11 00:00:00\",\n    \"2005-11-24 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-01-16 00:00:00\",\n    \"2006-02-20 00:00:00\",\n    \"2006-05-29 00:00:00\",\n    \"2006-07-04 00:00:00\",\n    \"2006-09-04 00:00:00\",\n    \"2006-10-09 00:00:00\",\n    \"2006-11-11 00:00:00\",\n    \"2006-11-23 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-01-15 00:00:00\",\n    \"2007-02-19 00:00:00\",\n    \"2007-05-28 00:00:00\",\n    \"2007-07-04 00:00:00\",\n    \"2007-09-03 00:00:00\",\n    \"2007-10-08 00:00:00\",\n    \"2007-11-12 00:00:00\",\n    \"2007-11-22 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-01-21 00:00:00\",\n    \"2008-02-18 00:00:00\",\n    \"2008-05-26 00:00:00\",\n    \"2008-07-04 00:00:00\",\n    \"2008-09-01 00:00:00\",\n    \"2008-10-13 00:00:00\",\n    \"2008-11-11 00:00:00\",\n    \"2008-11-27 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-01-19 00:00:00\",\n    \"2009-02-16 00:00:00\",\n    \"2009-05-25 00:00:00\",\n    \"2009-07-04 00:00:00\",\n    \"2009-09-07 00:00:00\",\n    \"2009-10-12 00:00:00\",\n    \"2009-11-11 00:00:00\",\n    \"2009-11-26 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-01-18 00:00:00\",\n    \"2010-02-15 00:00:00\",\n    \"2010-05-31 00:00:00\",\n    \"2010-07-05 00:00:00\",\n    \"2010-09-06 00:00:00\",\n    \"2010-10-11 00:00:00\",\n    \"2010-11-11 00:00:00\",\n    \"2010-11-25 00:00:00\",\n    \"2010-12-25 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-01-17 00:00:00\",\n    \"2011-02-21 00:00:00\",\n    \"2011-05-30 00:00:00\",\n    \"2011-07-04 00:00:00\",\n    \"2011-09-05 00:00:00\",\n    \"2011-10-10 00:00:00\",\n    \"2011-11-11 00:00:00\",\n    \"2011-11-24 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-01-16 00:00:00\",\n    \"2012-02-20 00:00:00\",\n    \"2012-05-28 00:00:00\",\n    \"2012-07-04 00:00:00\",\n    \"2012-09-03 00:00:00\",\n    \"2012-10-08 00:00:00\",\n    \"2012-11-12 00:00:00\",\n    \"2012-11-22 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-01-21 00:00:00\",\n    \"2013-02-18 00:00:00\",\n    \"2013-05-27 00:00:00\",\n    \"2013-07-04 00:00:00\",\n    \"2013-09-02 00:00:00\",\n    \"2013-10-14 00:00:00\",\n    \"2013-11-11 00:00:00\",\n    \"2013-11-28 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-01-20 00:00:00\",\n    \"2014-02-17 00:00:00\",\n    \"2014-05-26 00:00:00\",\n    \"2014-07-04 00:00:00\",\n    \"2014-09-01 00:00:00\",\n    \"2014-10-13 00:00:00\",\n    \"2014-11-11 00:00:00\",\n    \"2014-11-27 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-01-19 00:00:00\",\n    \"2015-02-16 00:00:00\",\n    \"2015-05-25 00:00:00\",\n    \"2015-07-04 00:00:00\",\n    \"2015-09-07 00:00:00\",\n    \"2015-10-12 00:00:00\",\n    \"2015-11-11 00:00:00\",\n    \"2015-11-26 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-01-18 00:00:00\",\n    \"2016-02-15 00:00:00\",\n    \"2016-05-30 00:00:00\",\n    \"2016-07-04 00:00:00\",\n    \"2016-09-05 00:00:00\",\n    \"2016-10-10 00:00:00\",\n    \"2016-11-11 00:00:00\",\n    \"2016-11-24 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-01-16 00:00:00\",\n    \"2017-02-20 00:00:00\",\n    \"2017-05-29 00:00:00\",\n    \"2017-07-04 00:00:00\",\n    \"2017-09-04 00:00:00\",\n    \"2017-10-09 00:00:00\",\n    \"2017-11-11 00:00:00\",\n    \"2017-11-23 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-01-15 00:00:00\",\n    \"2018-02-19 00:00:00\",\n    \"2018-05-28 00:00:00\",\n    \"2018-07-04 00:00:00\",\n    \"2018-09-03 00:00:00\",\n    \"2018-10-08 00:00:00\",\n    \"2018-11-12 00:00:00\",\n    \"2018-11-22 00:00:00\",\n    \"2018-12-05 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-01-21 00:00:00\",\n    \"2019-02-18 00:00:00\",\n    \"2019-05-27 00:00:00\",\n    \"2019-07-04 00:00:00\",\n    \"2019-09-02 00:00:00\",\n    \"2019-10-14 00:00:00\",\n    \"2019-11-11 00:00:00\",\n    \"2019-11-28 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-01-20 00:00:00\",\n    \"2020-02-17 00:00:00\",\n    \"2020-05-25 00:00:00\",\n    \"2020-07-04 00:00:00\",\n    \"2020-09-07 00:00:00\",\n    \"2020-10-12 00:00:00\",\n    \"2020-11-11 00:00:00\",\n    \"2020-11-26 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-01-18 00:00:00\",\n    \"2021-02-15 00:00:00\",\n    \"2021-05-31 00:00:00\",\n    \"2021-07-05 00:00:00\",\n    \"2021-09-06 00:00:00\",\n    \"2021-10-11 00:00:00\",\n    \"2021-11-11 00:00:00\",\n    \"2021-11-25 00:00:00\",\n    \"2021-12-25 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-01-17 00:00:00\",\n    \"2022-02-21 00:00:00\",\n    \"2022-05-30 00:00:00\",\n    \"2022-06-20 00:00:00\",\n    \"2022-07-04 00:00:00\",\n    \"2022-09-05 00:00:00\",\n    \"2022-10-10 00:00:00\",\n    \"2022-11-11 00:00:00\",\n    \"2022-11-24 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-01-16 00:00:00\",\n    \"2023-02-20 00:00:00\",\n    \"2023-05-29 00:00:00\",\n    \"2023-06-19 00:00:00\",\n    \"2023-07-04 00:00:00\",\n    \"2023-09-04 00:00:00\",\n    \"2023-10-09 00:00:00\",\n    \"2023-11-11 00:00:00\",\n    \"2023-11-23 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-01-15 00:00:00\",\n    \"2024-02-19 00:00:00\",\n    \"2024-05-27 00:00:00\",\n    \"2024-06-19 00:00:00\",\n    \"2024-07-04 00:00:00\",\n    \"2024-09-02 00:00:00\",\n    \"2024-10-14 00:00:00\",\n    \"2024-11-11 00:00:00\",\n    \"2024-11-28 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-20 00:00:00\",\n    \"2025-02-17 00:00:00\",\n    \"2025-05-26 00:00:00\",\n    \"2025-06-19 00:00:00\",\n    \"2025-07-04 00:00:00\",\n    \"2025-09-01 00:00:00\",\n    \"2025-10-13 00:00:00\",\n    \"2025-11-11 00:00:00\",\n    \"2025-11-27 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-19 00:00:00\",\n    \"2026-02-16 00:00:00\",\n    \"2026-05-25 00:00:00\",\n    \"2026-06-19 00:00:00\",\n    \"2026-07-04 00:00:00\",\n    \"2026-09-07 00:00:00\",\n    \"2026-10-12 00:00:00\",\n    \"2026-11-11 00:00:00\",\n    \"2026-11-26 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-18 00:00:00\",\n    \"2027-02-15 00:00:00\",\n    \"2027-05-31 00:00:00\",\n    \"2027-06-19 00:00:00\",\n    \"2027-07-05 00:00:00\",\n    \"2027-09-06 00:00:00\",\n    \"2027-10-11 00:00:00\",\n    \"2027-11-11 00:00:00\",\n    \"2027-11-25 00:00:00\",\n    \"2027-12-25 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-01-17 00:00:00\",\n    \"2028-02-21 00:00:00\",\n    \"2028-05-29 00:00:00\",\n    \"2028-06-19 00:00:00\",\n    \"2028-07-04 00:00:00\",\n    \"2028-09-04 00:00:00\",\n    \"2028-10-09 00:00:00\",\n    \"2028-11-11 00:00:00\",\n    \"2028-11-23 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-15 00:00:00\",\n    \"2029-02-19 00:00:00\",\n    \"2029-05-28 00:00:00\",\n    \"2029-06-19 00:00:00\",\n    \"2029-07-04 00:00:00\",\n    \"2029-09-03 00:00:00\",\n    \"2029-10-08 00:00:00\",\n    \"2029-11-12 00:00:00\",\n    \"2029-11-22 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-21 00:00:00\",\n    \"2030-02-18 00:00:00\",\n    \"2030-05-27 00:00:00\",\n    \"2030-06-19 00:00:00\",\n    \"2030-07-04 00:00:00\",\n    \"2030-09-02 00:00:00\",\n    \"2030-10-14 00:00:00\",\n    \"2030-11-11 00:00:00\",\n    \"2030-11-28 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-20 00:00:00\",\n    \"2031-02-17 00:00:00\",\n    \"2031-05-26 00:00:00\",\n    \"2031-06-19 00:00:00\",\n    \"2031-07-04 00:00:00\",\n    \"2031-09-01 00:00:00\",\n    \"2031-10-13 00:00:00\",\n    \"2031-11-11 00:00:00\",\n    \"2031-11-27 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-19 00:00:00\",\n    \"2032-02-16 00:00:00\",\n    \"2032-05-31 00:00:00\",\n    \"2032-06-19 00:00:00\",\n    \"2032-07-05 00:00:00\",\n    \"2032-09-06 00:00:00\",\n    \"2032-10-11 00:00:00\",\n    \"2032-11-11 00:00:00\",\n    \"2032-11-25 00:00:00\",\n    \"2032-12-25 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-01-17 00:00:00\",\n    \"2033-02-21 00:00:00\",\n    \"2033-05-30 00:00:00\",\n    \"2033-06-20 00:00:00\",\n    \"2033-07-04 00:00:00\",\n    \"2033-09-05 00:00:00\",\n    \"2033-10-10 00:00:00\",\n    \"2033-11-11 00:00:00\",\n    \"2033-11-24 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-01-16 00:00:00\",\n    \"2034-02-20 00:00:00\",\n    \"2034-05-29 00:00:00\",\n    \"2034-06-19 00:00:00\",\n    \"2034-07-04 00:00:00\",\n    \"2034-09-04 00:00:00\",\n    \"2034-10-09 00:00:00\",\n    \"2034-11-11 00:00:00\",\n    \"2034-11-23 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-15 00:00:00\",\n    \"2035-02-19 00:00:00\",\n    \"2035-05-28 00:00:00\",\n    \"2035-06-19 00:00:00\",\n    \"2035-07-04 00:00:00\",\n    \"2035-09-03 00:00:00\",\n    \"2035-10-08 00:00:00\",\n    \"2035-11-12 00:00:00\",\n    \"2035-11-22 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-21 00:00:00\",\n    \"2036-02-18 00:00:00\",\n    \"2036-05-26 00:00:00\",\n    \"2036-06-19 00:00:00\",\n    \"2036-07-04 00:00:00\",\n    \"2036-09-01 00:00:00\",\n    \"2036-10-13 00:00:00\",\n    \"2036-11-11 00:00:00\",\n    \"2036-11-27 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-19 00:00:00\",\n    \"2037-02-16 00:00:00\",\n    \"2037-05-25 00:00:00\",\n    \"2037-06-19 00:00:00\",\n    \"2037-07-04 00:00:00\",\n    \"2037-09-07 00:00:00\",\n    \"2037-10-12 00:00:00\",\n    \"2037-11-11 00:00:00\",\n    \"2037-11-26 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-18 00:00:00\",\n    \"2038-02-15 00:00:00\",\n    \"2038-05-31 00:00:00\",\n    \"2038-06-19 00:00:00\",\n    \"2038-07-05 00:00:00\",\n    \"2038-09-06 00:00:00\",\n    \"2038-10-11 00:00:00\",\n    \"2038-11-11 00:00:00\",\n    \"2038-11-25 00:00:00\",\n    \"2038-12-25 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-01-17 00:00:00\",\n    \"2039-02-21 00:00:00\",\n    \"2039-05-30 00:00:00\",\n    \"2039-06-20 00:00:00\",\n    \"2039-07-04 00:00:00\",\n    \"2039-09-05 00:00:00\",\n    \"2039-10-10 00:00:00\",\n    \"2039-11-11 00:00:00\",\n    \"2039-11-24 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-01-16 00:00:00\",\n    \"2040-02-20 00:00:00\",\n    \"2040-05-28 00:00:00\",\n    \"2040-06-19 00:00:00\",\n    \"2040-07-04 00:00:00\",\n    \"2040-09-03 00:00:00\",\n    \"2040-10-08 00:00:00\",\n    \"2040-11-12 00:00:00\",\n    \"2040-11-22 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-21 00:00:00\",\n    \"2041-02-18 00:00:00\",\n    \"2041-05-27 00:00:00\",\n    \"2041-06-19 00:00:00\",\n    \"2041-07-04 00:00:00\",\n    \"2041-09-02 00:00:00\",\n    \"2041-10-14 00:00:00\",\n    \"2041-11-11 00:00:00\",\n    \"2041-11-28 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-20 00:00:00\",\n    \"2042-02-17 00:00:00\",\n    \"2042-05-26 00:00:00\",\n    \"2042-06-19 00:00:00\",\n    \"2042-07-04 00:00:00\",\n    \"2042-09-01 00:00:00\",\n    \"2042-10-13 00:00:00\",\n    \"2042-11-11 00:00:00\",\n    \"2042-11-27 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-19 00:00:00\",\n    \"2043-02-16 00:00:00\",\n    \"2043-05-25 00:00:00\",\n    \"2043-06-19 00:00:00\",\n    \"2043-07-04 00:00:00\",\n    \"2043-09-07 00:00:00\",\n    \"2043-10-12 00:00:00\",\n    \"2043-11-11 00:00:00\",\n    \"2043-11-26 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-18 00:00:00\",\n    \"2044-02-15 00:00:00\",\n    \"2044-05-30 00:00:00\",\n    \"2044-06-20 00:00:00\",\n    \"2044-07-04 00:00:00\",\n    \"2044-09-05 00:00:00\",\n    \"2044-10-10 00:00:00\",\n    \"2044-11-11 00:00:00\",\n    \"2044-11-24 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-01-16 00:00:00\",\n    \"2045-02-20 00:00:00\",\n    \"2045-05-29 00:00:00\",\n    \"2045-06-19 00:00:00\",\n    \"2045-07-04 00:00:00\",\n    \"2045-09-04 00:00:00\",\n    \"2045-10-09 00:00:00\",\n    \"2045-11-11 00:00:00\",\n    \"2045-11-23 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-15 00:00:00\",\n    \"2046-02-19 00:00:00\",\n    \"2046-05-28 00:00:00\",\n    \"2046-06-19 00:00:00\",\n    \"2046-07-04 00:00:00\",\n    \"2046-09-03 00:00:00\",\n    \"2046-10-08 00:00:00\",\n    \"2046-11-12 00:00:00\",\n    \"2046-11-22 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-21 00:00:00\",\n    \"2047-02-18 00:00:00\",\n    \"2047-05-27 00:00:00\",\n    \"2047-06-19 00:00:00\",\n    \"2047-07-04 00:00:00\",\n    \"2047-09-02 00:00:00\",\n    \"2047-10-14 00:00:00\",\n    \"2047-11-11 00:00:00\",\n    \"2047-11-28 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-20 00:00:00\",\n    \"2048-02-17 00:00:00\",\n    \"2048-05-25 00:00:00\",\n    \"2048-06-19 00:00:00\",\n    \"2048-07-04 00:00:00\",\n    \"2048-09-07 00:00:00\",\n    \"2048-10-12 00:00:00\",\n    \"2048-11-11 00:00:00\",\n    \"2048-11-26 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-18 00:00:00\",\n    \"2049-02-15 00:00:00\",\n    \"2049-05-31 00:00:00\",\n    \"2049-06-19 00:00:00\",\n    \"2049-07-05 00:00:00\",\n    \"2049-09-06 00:00:00\",\n    \"2049-10-11 00:00:00\",\n    \"2049-11-11 00:00:00\",\n    \"2049-11-25 00:00:00\",\n    \"2049-12-25 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-01-17 00:00:00\",\n    \"2050-02-21 00:00:00\",\n    \"2050-05-30 00:00:00\",\n    \"2050-06-20 00:00:00\",\n    \"2050-07-04 00:00:00\",\n    \"2050-09-05 00:00:00\",\n    \"2050-10-10 00:00:00\",\n    \"2050-11-11 00:00:00\",\n    \"2050-11-24 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-01-16 00:00:00\",\n    \"2051-02-20 00:00:00\",\n    \"2051-05-29 00:00:00\",\n    \"2051-06-19 00:00:00\",\n    \"2051-07-04 00:00:00\",\n    \"2051-09-04 00:00:00\",\n    \"2051-10-09 00:00:00\",\n    \"2051-11-11 00:00:00\",\n    \"2051-11-23 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-15 00:00:00\",\n    \"2052-02-19 00:00:00\",\n    \"2052-05-27 00:00:00\",\n    \"2052-06-19 00:00:00\",\n    \"2052-07-04 00:00:00\",\n    \"2052-09-02 00:00:00\",\n    \"2052-10-14 00:00:00\",\n    \"2052-11-11 00:00:00\",\n    \"2052-11-28 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-20 00:00:00\",\n    \"2053-02-17 00:00:00\",\n    \"2053-05-26 00:00:00\",\n    \"2053-06-19 00:00:00\",\n    \"2053-07-04 00:00:00\",\n    \"2053-09-01 00:00:00\",\n    \"2053-10-13 00:00:00\",\n    \"2053-11-11 00:00:00\",\n    \"2053-11-27 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-19 00:00:00\",\n    \"2054-02-16 00:00:00\",\n    \"2054-05-25 00:00:00\",\n    \"2054-06-19 00:00:00\",\n    \"2054-07-04 00:00:00\",\n    \"2054-09-07 00:00:00\",\n    \"2054-10-12 00:00:00\",\n    \"2054-11-11 00:00:00\",\n    \"2054-11-26 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-18 00:00:00\",\n    \"2055-02-15 00:00:00\",\n    \"2055-05-31 00:00:00\",\n    \"2055-06-19 00:00:00\",\n    \"2055-07-05 00:00:00\",\n    \"2055-09-06 00:00:00\",\n    \"2055-10-11 00:00:00\",\n    \"2055-11-11 00:00:00\",\n    \"2055-11-25 00:00:00\",\n    \"2055-12-25 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-01-17 00:00:00\",\n    \"2056-02-21 00:00:00\",\n    \"2056-05-29 00:00:00\",\n    \"2056-06-19 00:00:00\",\n    \"2056-07-04 00:00:00\",\n    \"2056-09-04 00:00:00\",\n    \"2056-10-09 00:00:00\",\n    \"2056-11-11 00:00:00\",\n    \"2056-11-23 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-15 00:00:00\",\n    \"2057-02-19 00:00:00\",\n    \"2057-05-28 00:00:00\",\n    \"2057-06-19 00:00:00\",\n    \"2057-07-04 00:00:00\",\n    \"2057-09-03 00:00:00\",\n    \"2057-10-08 00:00:00\",\n    \"2057-11-12 00:00:00\",\n    \"2057-11-22 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-21 00:00:00\",\n    \"2058-02-18 00:00:00\",\n    \"2058-05-27 00:00:00\",\n    \"2058-06-19 00:00:00\",\n    \"2058-07-04 00:00:00\",\n    \"2058-09-02 00:00:00\",\n    \"2058-10-14 00:00:00\",\n    \"2058-11-11 00:00:00\",\n    \"2058-11-28 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-20 00:00:00\",\n    \"2059-02-17 00:00:00\",\n    \"2059-05-26 00:00:00\",\n    \"2059-06-19 00:00:00\",\n    \"2059-07-04 00:00:00\",\n    \"2059-09-01 00:00:00\",\n    \"2059-10-13 00:00:00\",\n    \"2059-11-11 00:00:00\",\n    \"2059-11-27 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-19 00:00:00\",\n    \"2060-02-16 00:00:00\",\n    \"2060-05-31 00:00:00\",\n    \"2060-06-19 00:00:00\",\n    \"2060-07-05 00:00:00\",\n    \"2060-09-06 00:00:00\",\n    \"2060-10-11 00:00:00\",\n    \"2060-11-11 00:00:00\",\n    \"2060-11-25 00:00:00\",\n    \"2060-12-25 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-01-17 00:00:00\",\n    \"2061-02-21 00:00:00\",\n    \"2061-05-30 00:00:00\",\n    \"2061-06-20 00:00:00\",\n    \"2061-07-04 00:00:00\",\n    \"2061-09-05 00:00:00\",\n    \"2061-10-10 00:00:00\",\n    \"2061-11-11 00:00:00\",\n    \"2061-11-24 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-01-16 00:00:00\",\n    \"2062-02-20 00:00:00\",\n    \"2062-05-29 00:00:00\",\n    \"2062-06-19 00:00:00\",\n    \"2062-07-04 00:00:00\",\n    \"2062-09-04 00:00:00\",\n    \"2062-10-09 00:00:00\",\n    \"2062-11-11 00:00:00\",\n    \"2062-11-23 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-15 00:00:00\",\n    \"2063-02-19 00:00:00\",\n    \"2063-05-28 00:00:00\",\n    \"2063-06-19 00:00:00\",\n    \"2063-07-04 00:00:00\",\n    \"2063-09-03 00:00:00\",\n    \"2063-10-08 00:00:00\",\n    \"2063-11-12 00:00:00\",\n    \"2063-11-22 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-21 00:00:00\",\n    \"2064-02-18 00:00:00\",\n    \"2064-05-26 00:00:00\",\n    \"2064-06-19 00:00:00\",\n    \"2064-07-04 00:00:00\",\n    \"2064-09-01 00:00:00\",\n    \"2064-10-13 00:00:00\",\n    \"2064-11-11 00:00:00\",\n    \"2064-11-27 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-19 00:00:00\",\n    \"2065-02-16 00:00:00\",\n    \"2065-05-25 00:00:00\",\n    \"2065-06-19 00:00:00\",\n    \"2065-07-04 00:00:00\",\n    \"2065-09-07 00:00:00\",\n    \"2065-10-12 00:00:00\",\n    \"2065-11-11 00:00:00\",\n    \"2065-11-26 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-18 00:00:00\",\n    \"2066-02-15 00:00:00\",\n    \"2066-05-31 00:00:00\",\n    \"2066-06-19 00:00:00\",\n    \"2066-07-05 00:00:00\",\n    \"2066-09-06 00:00:00\",\n    \"2066-10-11 00:00:00\",\n    \"2066-11-11 00:00:00\",\n    \"2066-11-25 00:00:00\",\n    \"2066-12-25 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-01-17 00:00:00\",\n    \"2067-02-21 00:00:00\",\n    \"2067-05-30 00:00:00\",\n    \"2067-06-20 00:00:00\",\n    \"2067-07-04 00:00:00\",\n    \"2067-09-05 00:00:00\",\n    \"2067-10-10 00:00:00\",\n    \"2067-11-11 00:00:00\",\n    \"2067-11-24 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-01-16 00:00:00\",\n    \"2068-02-20 00:00:00\",\n    \"2068-05-28 00:00:00\",\n    \"2068-06-19 00:00:00\",\n    \"2068-07-04 00:00:00\",\n    \"2068-09-03 00:00:00\",\n    \"2068-10-08 00:00:00\",\n    \"2068-11-12 00:00:00\",\n    \"2068-11-22 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-21 00:00:00\",\n    \"2069-02-18 00:00:00\",\n    \"2069-05-27 00:00:00\",\n    \"2069-06-19 00:00:00\",\n    \"2069-07-04 00:00:00\",\n    \"2069-09-02 00:00:00\",\n    \"2069-10-14 00:00:00\",\n    \"2069-11-11 00:00:00\",\n    \"2069-11-28 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-20 00:00:00\",\n    \"2070-02-17 00:00:00\",\n    \"2070-05-26 00:00:00\",\n    \"2070-06-19 00:00:00\",\n    \"2070-07-04 00:00:00\",\n    \"2070-09-01 00:00:00\",\n    \"2070-10-13 00:00:00\",\n    \"2070-11-11 00:00:00\",\n    \"2070-11-27 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-19 00:00:00\",\n    \"2071-02-16 00:00:00\",\n    \"2071-05-25 00:00:00\",\n    \"2071-06-19 00:00:00\",\n    \"2071-07-04 00:00:00\",\n    \"2071-09-07 00:00:00\",\n    \"2071-10-12 00:00:00\",\n    \"2071-11-11 00:00:00\",\n    \"2071-11-26 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-18 00:00:00\",\n    \"2072-02-15 00:00:00\",\n    \"2072-05-30 00:00:00\",\n    \"2072-06-20 00:00:00\",\n    \"2072-07-04 00:00:00\",\n    \"2072-09-05 00:00:00\",\n    \"2072-10-10 00:00:00\",\n    \"2072-11-11 00:00:00\",\n    \"2072-11-24 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-01-16 00:00:00\",\n    \"2073-02-20 00:00:00\",\n    \"2073-05-29 00:00:00\",\n    \"2073-06-19 00:00:00\",\n    \"2073-07-04 00:00:00\",\n    \"2073-09-04 00:00:00\",\n    \"2073-10-09 00:00:00\",\n    \"2073-11-11 00:00:00\",\n    \"2073-11-23 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-15 00:00:00\",\n    \"2074-02-19 00:00:00\",\n    \"2074-05-28 00:00:00\",\n    \"2074-06-19 00:00:00\",\n    \"2074-07-04 00:00:00\",\n    \"2074-09-03 00:00:00\",\n    \"2074-10-08 00:00:00\",\n    \"2074-11-12 00:00:00\",\n    \"2074-11-22 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-21 00:00:00\",\n    \"2075-02-18 00:00:00\",\n    \"2075-05-27 00:00:00\",\n    \"2075-06-19 00:00:00\",\n    \"2075-07-04 00:00:00\",\n    \"2075-09-02 00:00:00\",\n    \"2075-10-14 00:00:00\",\n    \"2075-11-11 00:00:00\",\n    \"2075-11-28 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-20 00:00:00\",\n    \"2076-02-17 00:00:00\",\n    \"2076-05-25 00:00:00\",\n    \"2076-06-19 00:00:00\",\n    \"2076-07-04 00:00:00\",\n    \"2076-09-07 00:00:00\",\n    \"2076-10-12 00:00:00\",\n    \"2076-11-11 00:00:00\",\n    \"2076-11-26 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-18 00:00:00\",\n    \"2077-02-15 00:00:00\",\n    \"2077-05-31 00:00:00\",\n    \"2077-06-19 00:00:00\",\n    \"2077-07-05 00:00:00\",\n    \"2077-09-06 00:00:00\",\n    \"2077-10-11 00:00:00\",\n    \"2077-11-11 00:00:00\",\n    \"2077-11-25 00:00:00\",\n    \"2077-12-25 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-01-17 00:00:00\",\n    \"2078-02-21 00:00:00\",\n    \"2078-05-30 00:00:00\",\n    \"2078-06-20 00:00:00\",\n    \"2078-07-04 00:00:00\",\n    \"2078-09-05 00:00:00\",\n    \"2078-10-10 00:00:00\",\n    \"2078-11-11 00:00:00\",\n    \"2078-11-24 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-01-16 00:00:00\",\n    \"2079-02-20 00:00:00\",\n    \"2079-05-29 00:00:00\",\n    \"2079-06-19 00:00:00\",\n    \"2079-07-04 00:00:00\",\n    \"2079-09-04 00:00:00\",\n    \"2079-10-09 00:00:00\",\n    \"2079-11-11 00:00:00\",\n    \"2079-11-23 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-15 00:00:00\",\n    \"2080-02-19 00:00:00\",\n    \"2080-05-27 00:00:00\",\n    \"2080-06-19 00:00:00\",\n    \"2080-07-04 00:00:00\",\n    \"2080-09-02 00:00:00\",\n    \"2080-10-14 00:00:00\",\n    \"2080-11-11 00:00:00\",\n    \"2080-11-28 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-20 00:00:00\",\n    \"2081-02-17 00:00:00\",\n    \"2081-05-26 00:00:00\",\n    \"2081-06-19 00:00:00\",\n    \"2081-07-04 00:00:00\",\n    \"2081-09-01 00:00:00\",\n    \"2081-10-13 00:00:00\",\n    \"2081-11-11 00:00:00\",\n    \"2081-11-27 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-19 00:00:00\",\n    \"2082-02-16 00:00:00\",\n    \"2082-05-25 00:00:00\",\n    \"2082-06-19 00:00:00\",\n    \"2082-07-04 00:00:00\",\n    \"2082-09-07 00:00:00\",\n    \"2082-10-12 00:00:00\",\n    \"2082-11-11 00:00:00\",\n    \"2082-11-26 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-18 00:00:00\",\n    \"2083-02-15 00:00:00\",\n    \"2083-05-31 00:00:00\",\n    \"2083-06-19 00:00:00\",\n    \"2083-07-05 00:00:00\",\n    \"2083-09-06 00:00:00\",\n    \"2083-10-11 00:00:00\",\n    \"2083-11-11 00:00:00\",\n    \"2083-11-25 00:00:00\",\n    \"2083-12-25 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-01-17 00:00:00\",\n    \"2084-02-21 00:00:00\",\n    \"2084-05-29 00:00:00\",\n    \"2084-06-19 00:00:00\",\n    \"2084-07-04 00:00:00\",\n    \"2084-09-04 00:00:00\",\n    \"2084-10-09 00:00:00\",\n    \"2084-11-11 00:00:00\",\n    \"2084-11-23 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-15 00:00:00\",\n    \"2085-02-19 00:00:00\",\n    \"2085-05-28 00:00:00\",\n    \"2085-06-19 00:00:00\",\n    \"2085-07-04 00:00:00\",\n    \"2085-09-03 00:00:00\",\n    \"2085-10-08 00:00:00\",\n    \"2085-11-12 00:00:00\",\n    \"2085-11-22 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-21 00:00:00\",\n    \"2086-02-18 00:00:00\",\n    \"2086-05-27 00:00:00\",\n    \"2086-06-19 00:00:00\",\n    \"2086-07-04 00:00:00\",\n    \"2086-09-02 00:00:00\",\n    \"2086-10-14 00:00:00\",\n    \"2086-11-11 00:00:00\",\n    \"2086-11-28 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-20 00:00:00\",\n    \"2087-02-17 00:00:00\",\n    \"2087-05-26 00:00:00\",\n    \"2087-06-19 00:00:00\",\n    \"2087-07-04 00:00:00\",\n    \"2087-09-01 00:00:00\",\n    \"2087-10-13 00:00:00\",\n    \"2087-11-11 00:00:00\",\n    \"2087-11-27 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-19 00:00:00\",\n    \"2088-02-16 00:00:00\",\n    \"2088-05-31 00:00:00\",\n    \"2088-06-19 00:00:00\",\n    \"2088-07-05 00:00:00\",\n    \"2088-09-06 00:00:00\",\n    \"2088-10-11 00:00:00\",\n    \"2088-11-11 00:00:00\",\n    \"2088-11-25 00:00:00\",\n    \"2088-12-25 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-01-17 00:00:00\",\n    \"2089-02-21 00:00:00\",\n    \"2089-05-30 00:00:00\",\n    \"2089-06-20 00:00:00\",\n    \"2089-07-04 00:00:00\",\n    \"2089-09-05 00:00:00\",\n    \"2089-10-10 00:00:00\",\n    \"2089-11-11 00:00:00\",\n    \"2089-11-24 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-01-16 00:00:00\",\n    \"2090-02-20 00:00:00\",\n    \"2090-05-29 00:00:00\",\n    \"2090-06-19 00:00:00\",\n    \"2090-07-04 00:00:00\",\n    \"2090-09-04 00:00:00\",\n    \"2090-10-09 00:00:00\",\n    \"2090-11-11 00:00:00\",\n    \"2090-11-23 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-15 00:00:00\",\n    \"2091-02-19 00:00:00\",\n    \"2091-05-28 00:00:00\",\n    \"2091-06-19 00:00:00\",\n    \"2091-07-04 00:00:00\",\n    \"2091-09-03 00:00:00\",\n    \"2091-10-08 00:00:00\",\n    \"2091-11-12 00:00:00\",\n    \"2091-11-22 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-21 00:00:00\",\n    \"2092-02-18 00:00:00\",\n    \"2092-05-26 00:00:00\",\n    \"2092-06-19 00:00:00\",\n    \"2092-07-04 00:00:00\",\n    \"2092-09-01 00:00:00\",\n    \"2092-10-13 00:00:00\",\n    \"2092-11-11 00:00:00\",\n    \"2092-11-27 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-19 00:00:00\",\n    \"2093-02-16 00:00:00\",\n    \"2093-05-25 00:00:00\",\n    \"2093-06-19 00:00:00\",\n    \"2093-07-04 00:00:00\",\n    \"2093-09-07 00:00:00\",\n    \"2093-10-12 00:00:00\",\n    \"2093-11-11 00:00:00\",\n    \"2093-11-26 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-18 00:00:00\",\n    \"2094-02-15 00:00:00\",\n    \"2094-05-31 00:00:00\",\n    \"2094-06-19 00:00:00\",\n    \"2094-07-05 00:00:00\",\n    \"2094-09-06 00:00:00\",\n    \"2094-10-11 00:00:00\",\n    \"2094-11-11 00:00:00\",\n    \"2094-11-25 00:00:00\",\n    \"2094-12-25 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-01-17 00:00:00\",\n    \"2095-02-21 00:00:00\",\n    \"2095-05-30 00:00:00\",\n    \"2095-06-20 00:00:00\",\n    \"2095-07-04 00:00:00\",\n    \"2095-09-05 00:00:00\",\n    \"2095-10-10 00:00:00\",\n    \"2095-11-11 00:00:00\",\n    \"2095-11-24 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-01-16 00:00:00\",\n    \"2096-02-20 00:00:00\",\n    \"2096-05-28 00:00:00\",\n    \"2096-06-19 00:00:00\",\n    \"2096-07-04 00:00:00\",\n    \"2096-09-03 00:00:00\",\n    \"2096-10-08 00:00:00\",\n    \"2096-11-12 00:00:00\",\n    \"2096-11-22 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-21 00:00:00\",\n    \"2097-02-18 00:00:00\",\n    \"2097-05-27 00:00:00\",\n    \"2097-06-19 00:00:00\",\n    \"2097-07-04 00:00:00\",\n    \"2097-09-02 00:00:00\",\n    \"2097-10-14 00:00:00\",\n    \"2097-11-11 00:00:00\",\n    \"2097-11-28 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-20 00:00:00\",\n    \"2098-02-17 00:00:00\",\n    \"2098-05-26 00:00:00\",\n    \"2098-06-19 00:00:00\",\n    \"2098-07-04 00:00:00\",\n    \"2098-09-01 00:00:00\",\n    \"2098-10-13 00:00:00\",\n    \"2098-11-11 00:00:00\",\n    \"2098-11-27 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-19 00:00:00\",\n    \"2099-02-16 00:00:00\",\n    \"2099-05-25 00:00:00\",\n    \"2099-06-19 00:00:00\",\n    \"2099-07-04 00:00:00\",\n    \"2099-09-07 00:00:00\",\n    \"2099-10-12 00:00:00\",\n    \"2099-11-11 00:00:00\",\n    \"2099-11-26 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-18 00:00:00\",\n    \"2100-02-15 00:00:00\",\n    \"2100-05-31 00:00:00\",\n    \"2100-06-19 00:00:00\",\n    \"2100-07-05 00:00:00\",\n    \"2100-09-06 00:00:00\",\n    \"2100-10-11 00:00:00\",\n    \"2100-11-11 00:00:00\",\n    \"2100-11-25 00:00:00\",\n    \"2100-12-25 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-01-17 00:00:00\",\n    \"2101-02-21 00:00:00\",\n    \"2101-05-30 00:00:00\",\n    \"2101-06-20 00:00:00\",\n    \"2101-07-04 00:00:00\",\n    \"2101-09-05 00:00:00\",\n    \"2101-10-10 00:00:00\",\n    \"2101-11-11 00:00:00\",\n    \"2101-11-24 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-01-16 00:00:00\",\n    \"2102-02-20 00:00:00\",\n    \"2102-05-29 00:00:00\",\n    \"2102-06-19 00:00:00\",\n    \"2102-07-04 00:00:00\",\n    \"2102-09-04 00:00:00\",\n    \"2102-10-09 00:00:00\",\n    \"2102-11-11 00:00:00\",\n    \"2102-11-23 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-01-15 00:00:00\",\n    \"2103-02-19 00:00:00\",\n    \"2103-05-28 00:00:00\",\n    \"2103-06-19 00:00:00\",\n    \"2103-07-04 00:00:00\",\n    \"2103-09-03 00:00:00\",\n    \"2103-10-08 00:00:00\",\n    \"2103-11-12 00:00:00\",\n    \"2103-11-22 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-01-21 00:00:00\",\n    \"2104-02-18 00:00:00\",\n    \"2104-05-26 00:00:00\",\n    \"2104-06-19 00:00:00\",\n    \"2104-07-04 00:00:00\",\n    \"2104-09-01 00:00:00\",\n    \"2104-10-13 00:00:00\",\n    \"2104-11-11 00:00:00\",\n    \"2104-11-27 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-01-19 00:00:00\",\n    \"2105-02-16 00:00:00\",\n    \"2105-05-25 00:00:00\",\n    \"2105-06-19 00:00:00\",\n    \"2105-07-04 00:00:00\",\n    \"2105-09-07 00:00:00\",\n    \"2105-10-12 00:00:00\",\n    \"2105-11-11 00:00:00\",\n    \"2105-11-26 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-01-18 00:00:00\",\n    \"2106-02-15 00:00:00\",\n    \"2106-05-31 00:00:00\",\n    \"2106-06-19 00:00:00\",\n    \"2106-07-05 00:00:00\",\n    \"2106-09-06 00:00:00\",\n    \"2106-10-11 00:00:00\",\n    \"2106-11-11 00:00:00\",\n    \"2106-11-25 00:00:00\",\n    \"2106-12-25 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-01-17 00:00:00\",\n    \"2107-02-21 00:00:00\",\n    \"2107-05-30 00:00:00\",\n    \"2107-06-20 00:00:00\",\n    \"2107-07-04 00:00:00\",\n    \"2107-09-05 00:00:00\",\n    \"2107-10-10 00:00:00\",\n    \"2107-11-11 00:00:00\",\n    \"2107-11-24 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-01-16 00:00:00\",\n    \"2108-02-20 00:00:00\",\n    \"2108-05-28 00:00:00\",\n    \"2108-06-19 00:00:00\",\n    \"2108-07-04 00:00:00\",\n    \"2108-09-03 00:00:00\",\n    \"2108-10-08 00:00:00\",\n    \"2108-11-12 00:00:00\",\n    \"2108-11-22 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-01-21 00:00:00\",\n    \"2109-02-18 00:00:00\",\n    \"2109-05-27 00:00:00\",\n    \"2109-06-19 00:00:00\",\n    \"2109-07-04 00:00:00\",\n    \"2109-09-02 00:00:00\",\n    \"2109-10-14 00:00:00\",\n    \"2109-11-11 00:00:00\",\n    \"2109-11-28 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-01-20 00:00:00\",\n    \"2110-02-17 00:00:00\",\n    \"2110-05-26 00:00:00\",\n    \"2110-06-19 00:00:00\",\n    \"2110-07-04 00:00:00\",\n    \"2110-09-01 00:00:00\",\n    \"2110-10-13 00:00:00\",\n    \"2110-11-11 00:00:00\",\n    \"2110-11-27 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-01-19 00:00:00\",\n    \"2111-02-16 00:00:00\",\n    \"2111-05-25 00:00:00\",\n    \"2111-06-19 00:00:00\",\n    \"2111-07-04 00:00:00\",\n    \"2111-09-07 00:00:00\",\n    \"2111-10-12 00:00:00\",\n    \"2111-11-11 00:00:00\",\n    \"2111-11-26 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-01-18 00:00:00\",\n    \"2112-02-15 00:00:00\",\n    \"2112-05-30 00:00:00\",\n    \"2112-06-20 00:00:00\",\n    \"2112-07-04 00:00:00\",\n    \"2112-09-05 00:00:00\",\n    \"2112-10-10 00:00:00\",\n    \"2112-11-11 00:00:00\",\n    \"2112-11-24 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-01-16 00:00:00\",\n    \"2113-02-20 00:00:00\",\n    \"2113-05-29 00:00:00\",\n    \"2113-06-19 00:00:00\",\n    \"2113-07-04 00:00:00\",\n    \"2113-09-04 00:00:00\",\n    \"2113-10-09 00:00:00\",\n    \"2113-11-11 00:00:00\",\n    \"2113-11-23 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-01-15 00:00:00\",\n    \"2114-02-19 00:00:00\",\n    \"2114-05-28 00:00:00\",\n    \"2114-06-19 00:00:00\",\n    \"2114-07-04 00:00:00\",\n    \"2114-09-03 00:00:00\",\n    \"2114-10-08 00:00:00\",\n    \"2114-11-12 00:00:00\",\n    \"2114-11-22 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-01-21 00:00:00\",\n    \"2115-02-18 00:00:00\",\n    \"2115-05-27 00:00:00\",\n    \"2115-06-19 00:00:00\",\n    \"2115-07-04 00:00:00\",\n    \"2115-09-02 00:00:00\",\n    \"2115-10-14 00:00:00\",\n    \"2115-11-11 00:00:00\",\n    \"2115-11-28 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-01-20 00:00:00\",\n    \"2116-02-17 00:00:00\",\n    \"2116-05-25 00:00:00\",\n    \"2116-06-19 00:00:00\",\n    \"2116-07-04 00:00:00\",\n    \"2116-09-07 00:00:00\",\n    \"2116-10-12 00:00:00\",\n    \"2116-11-11 00:00:00\",\n    \"2116-11-26 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-01-18 00:00:00\",\n    \"2117-02-15 00:00:00\",\n    \"2117-05-31 00:00:00\",\n    \"2117-06-19 00:00:00\",\n    \"2117-07-05 00:00:00\",\n    \"2117-09-06 00:00:00\",\n    \"2117-10-11 00:00:00\",\n    \"2117-11-11 00:00:00\",\n    \"2117-11-25 00:00:00\",\n    \"2117-12-25 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-01-17 00:00:00\",\n    \"2118-02-21 00:00:00\",\n    \"2118-05-30 00:00:00\",\n    \"2118-06-20 00:00:00\",\n    \"2118-07-04 00:00:00\",\n    \"2118-09-05 00:00:00\",\n    \"2118-10-10 00:00:00\",\n    \"2118-11-11 00:00:00\",\n    \"2118-11-24 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-01-16 00:00:00\",\n    \"2119-02-20 00:00:00\",\n    \"2119-05-29 00:00:00\",\n    \"2119-06-19 00:00:00\",\n    \"2119-07-04 00:00:00\",\n    \"2119-09-04 00:00:00\",\n    \"2119-10-09 00:00:00\",\n    \"2119-11-11 00:00:00\",\n    \"2119-11-23 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-01-15 00:00:00\",\n    \"2120-02-19 00:00:00\",\n    \"2120-05-27 00:00:00\",\n    \"2120-06-19 00:00:00\",\n    \"2120-07-04 00:00:00\",\n    \"2120-09-02 00:00:00\",\n    \"2120-10-14 00:00:00\",\n    \"2120-11-11 00:00:00\",\n    \"2120-11-28 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-01-20 00:00:00\",\n    \"2121-02-17 00:00:00\",\n    \"2121-05-26 00:00:00\",\n    \"2121-06-19 00:00:00\",\n    \"2121-07-04 00:00:00\",\n    \"2121-09-01 00:00:00\",\n    \"2121-10-13 00:00:00\",\n    \"2121-11-11 00:00:00\",\n    \"2121-11-27 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-01-19 00:00:00\",\n    \"2122-02-16 00:00:00\",\n    \"2122-05-25 00:00:00\",\n    \"2122-06-19 00:00:00\",\n    \"2122-07-04 00:00:00\",\n    \"2122-09-07 00:00:00\",\n    \"2122-10-12 00:00:00\",\n    \"2122-11-11 00:00:00\",\n    \"2122-11-26 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-01-18 00:00:00\",\n    \"2123-02-15 00:00:00\",\n    \"2123-05-31 00:00:00\",\n    \"2123-06-19 00:00:00\",\n    \"2123-07-05 00:00:00\",\n    \"2123-09-06 00:00:00\",\n    \"2123-10-11 00:00:00\",\n    \"2123-11-11 00:00:00\",\n    \"2123-11-25 00:00:00\",\n    \"2123-12-25 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-01-17 00:00:00\",\n    \"2124-02-21 00:00:00\",\n    \"2124-05-29 00:00:00\",\n    \"2124-06-19 00:00:00\",\n    \"2124-07-04 00:00:00\",\n    \"2124-09-04 00:00:00\",\n    \"2124-10-09 00:00:00\",\n    \"2124-11-11 00:00:00\",\n    \"2124-11-23 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-01-15 00:00:00\",\n    \"2125-02-19 00:00:00\",\n    \"2125-05-28 00:00:00\",\n    \"2125-06-19 00:00:00\",\n    \"2125-07-04 00:00:00\",\n    \"2125-09-03 00:00:00\",\n    \"2125-10-08 00:00:00\",\n    \"2125-11-12 00:00:00\",\n    \"2125-11-22 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-01-21 00:00:00\",\n    \"2126-02-18 00:00:00\",\n    \"2126-05-27 00:00:00\",\n    \"2126-06-19 00:00:00\",\n    \"2126-07-04 00:00:00\",\n    \"2126-09-02 00:00:00\",\n    \"2126-10-14 00:00:00\",\n    \"2126-11-11 00:00:00\",\n    \"2126-11-28 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-01-20 00:00:00\",\n    \"2127-02-17 00:00:00\",\n    \"2127-05-26 00:00:00\",\n    \"2127-06-19 00:00:00\",\n    \"2127-07-04 00:00:00\",\n    \"2127-09-01 00:00:00\",\n    \"2127-10-13 00:00:00\",\n    \"2127-11-11 00:00:00\",\n    \"2127-11-27 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-01-19 00:00:00\",\n    \"2128-02-16 00:00:00\",\n    \"2128-05-31 00:00:00\",\n    \"2128-06-19 00:00:00\",\n    \"2128-07-05 00:00:00\",\n    \"2128-09-06 00:00:00\",\n    \"2128-10-11 00:00:00\",\n    \"2128-11-11 00:00:00\",\n    \"2128-11-25 00:00:00\",\n    \"2128-12-25 00:00:00\",\n    \"2129-01-01 00:00:00\",\n    \"2129-01-17 00:00:00\",\n    \"2129-02-21 00:00:00\",\n    \"2129-05-30 00:00:00\",\n    \"2129-06-20 00:00:00\",\n    \"2129-07-04 00:00:00\",\n    \"2129-09-05 00:00:00\",\n    \"2129-10-10 00:00:00\",\n    \"2129-11-11 00:00:00\",\n    \"2129-11-24 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-01-16 00:00:00\",\n    \"2130-02-20 00:00:00\",\n    \"2130-05-29 00:00:00\",\n    \"2130-06-19 00:00:00\",\n    \"2130-07-04 00:00:00\",\n    \"2130-09-04 00:00:00\",\n    \"2130-10-09 00:00:00\",\n    \"2130-11-11 00:00:00\",\n    \"2130-11-23 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-01-15 00:00:00\",\n    \"2131-02-19 00:00:00\",\n    \"2131-05-28 00:00:00\",\n    \"2131-06-19 00:00:00\",\n    \"2131-07-04 00:00:00\",\n    \"2131-09-03 00:00:00\",\n    \"2131-10-08 00:00:00\",\n    \"2131-11-12 00:00:00\",\n    \"2131-11-22 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-01-21 00:00:00\",\n    \"2132-02-18 00:00:00\",\n    \"2132-05-26 00:00:00\",\n    \"2132-06-19 00:00:00\",\n    \"2132-07-04 00:00:00\",\n    \"2132-09-01 00:00:00\",\n    \"2132-10-13 00:00:00\",\n    \"2132-11-11 00:00:00\",\n    \"2132-11-27 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-01-19 00:00:00\",\n    \"2133-02-16 00:00:00\",\n    \"2133-05-25 00:00:00\",\n    \"2133-06-19 00:00:00\",\n    \"2133-07-04 00:00:00\",\n    \"2133-09-07 00:00:00\",\n    \"2133-10-12 00:00:00\",\n    \"2133-11-11 00:00:00\",\n    \"2133-11-26 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-01-18 00:00:00\",\n    \"2134-02-15 00:00:00\",\n    \"2134-05-31 00:00:00\",\n    \"2134-06-19 00:00:00\",\n    \"2134-07-05 00:00:00\",\n    \"2134-09-06 00:00:00\",\n    \"2134-10-11 00:00:00\",\n    \"2134-11-11 00:00:00\",\n    \"2134-11-25 00:00:00\",\n    \"2134-12-25 00:00:00\",\n    \"2135-01-01 00:00:00\",\n    \"2135-01-17 00:00:00\",\n    \"2135-02-21 00:00:00\",\n    \"2135-05-30 00:00:00\",\n    \"2135-06-20 00:00:00\",\n    \"2135-07-04 00:00:00\",\n    \"2135-09-05 00:00:00\",\n    \"2135-10-10 00:00:00\",\n    \"2135-11-11 00:00:00\",\n    \"2135-11-24 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-01-16 00:00:00\",\n    \"2136-02-20 00:00:00\",\n    \"2136-05-28 00:00:00\",\n    \"2136-06-19 00:00:00\",\n    \"2136-07-04 00:00:00\",\n    \"2136-09-03 00:00:00\",\n    \"2136-10-08 00:00:00\",\n    \"2136-11-12 00:00:00\",\n    \"2136-11-22 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-01-21 00:00:00\",\n    \"2137-02-18 00:00:00\",\n    \"2137-05-27 00:00:00\",\n    \"2137-06-19 00:00:00\",\n    \"2137-07-04 00:00:00\",\n    \"2137-09-02 00:00:00\",\n    \"2137-10-14 00:00:00\",\n    \"2137-11-11 00:00:00\",\n    \"2137-11-28 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-01-20 00:00:00\",\n    \"2138-02-17 00:00:00\",\n    \"2138-05-26 00:00:00\",\n    \"2138-06-19 00:00:00\",\n    \"2138-07-04 00:00:00\",\n    \"2138-09-01 00:00:00\",\n    \"2138-10-13 00:00:00\",\n    \"2138-11-11 00:00:00\",\n    \"2138-11-27 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-01-19 00:00:00\",\n    \"2139-02-16 00:00:00\",\n    \"2139-05-25 00:00:00\",\n    \"2139-06-19 00:00:00\",\n    \"2139-07-04 00:00:00\",\n    \"2139-09-07 00:00:00\",\n    \"2139-10-12 00:00:00\",\n    \"2139-11-11 00:00:00\",\n    \"2139-11-26 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-01-18 00:00:00\",\n    \"2140-02-15 00:00:00\",\n    \"2140-05-30 00:00:00\",\n    \"2140-06-20 00:00:00\",\n    \"2140-07-04 00:00:00\",\n    \"2140-09-05 00:00:00\",\n    \"2140-10-10 00:00:00\",\n    \"2140-11-11 00:00:00\",\n    \"2140-11-24 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-01-16 00:00:00\",\n    \"2141-02-20 00:00:00\",\n    \"2141-05-29 00:00:00\",\n    \"2141-06-19 00:00:00\",\n    \"2141-07-04 00:00:00\",\n    \"2141-09-04 00:00:00\",\n    \"2141-10-09 00:00:00\",\n    \"2141-11-11 00:00:00\",\n    \"2141-11-23 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-01-15 00:00:00\",\n    \"2142-02-19 00:00:00\",\n    \"2142-05-28 00:00:00\",\n    \"2142-06-19 00:00:00\",\n    \"2142-07-04 00:00:00\",\n    \"2142-09-03 00:00:00\",\n    \"2142-10-08 00:00:00\",\n    \"2142-11-12 00:00:00\",\n    \"2142-11-22 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-01-21 00:00:00\",\n    \"2143-02-18 00:00:00\",\n    \"2143-05-27 00:00:00\",\n    \"2143-06-19 00:00:00\",\n    \"2143-07-04 00:00:00\",\n    \"2143-09-02 00:00:00\",\n    \"2143-10-14 00:00:00\",\n    \"2143-11-11 00:00:00\",\n    \"2143-11-28 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-01-20 00:00:00\",\n    \"2144-02-17 00:00:00\",\n    \"2144-05-25 00:00:00\",\n    \"2144-06-19 00:00:00\",\n    \"2144-07-04 00:00:00\",\n    \"2144-09-07 00:00:00\",\n    \"2144-10-12 00:00:00\",\n    \"2144-11-11 00:00:00\",\n    \"2144-11-26 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-01-18 00:00:00\",\n    \"2145-02-15 00:00:00\",\n    \"2145-05-31 00:00:00\",\n    \"2145-06-19 00:00:00\",\n    \"2145-07-05 00:00:00\",\n    \"2145-09-06 00:00:00\",\n    \"2145-10-11 00:00:00\",\n    \"2145-11-11 00:00:00\",\n    \"2145-11-25 00:00:00\",\n    \"2145-12-25 00:00:00\",\n    \"2146-01-01 00:00:00\",\n    \"2146-01-17 00:00:00\",\n    \"2146-02-21 00:00:00\",\n    \"2146-05-30 00:00:00\",\n    \"2146-06-20 00:00:00\",\n    \"2146-07-04 00:00:00\",\n    \"2146-09-05 00:00:00\",\n    \"2146-10-10 00:00:00\",\n    \"2146-11-11 00:00:00\",\n    \"2146-11-24 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-01-16 00:00:00\",\n    \"2147-02-20 00:00:00\",\n    \"2147-05-29 00:00:00\",\n    \"2147-06-19 00:00:00\",\n    \"2147-07-04 00:00:00\",\n    \"2147-09-04 00:00:00\",\n    \"2147-10-09 00:00:00\",\n    \"2147-11-11 00:00:00\",\n    \"2147-11-23 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-01-15 00:00:00\",\n    \"2148-02-19 00:00:00\",\n    \"2148-05-27 00:00:00\",\n    \"2148-06-19 00:00:00\",\n    \"2148-07-04 00:00:00\",\n    \"2148-09-02 00:00:00\",\n    \"2148-10-14 00:00:00\",\n    \"2148-11-11 00:00:00\",\n    \"2148-11-28 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-01-20 00:00:00\",\n    \"2149-02-17 00:00:00\",\n    \"2149-05-26 00:00:00\",\n    \"2149-06-19 00:00:00\",\n    \"2149-07-04 00:00:00\",\n    \"2149-09-01 00:00:00\",\n    \"2149-10-13 00:00:00\",\n    \"2149-11-11 00:00:00\",\n    \"2149-11-27 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-01-19 00:00:00\",\n    \"2150-02-16 00:00:00\",\n    \"2150-05-25 00:00:00\",\n    \"2150-06-19 00:00:00\",\n    \"2150-07-04 00:00:00\",\n    \"2150-09-07 00:00:00\",\n    \"2150-10-12 00:00:00\",\n    \"2150-11-11 00:00:00\",\n    \"2150-11-26 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-01-18 00:00:00\",\n    \"2151-02-15 00:00:00\",\n    \"2151-05-31 00:00:00\",\n    \"2151-06-19 00:00:00\",\n    \"2151-07-05 00:00:00\",\n    \"2151-09-06 00:00:00\",\n    \"2151-10-11 00:00:00\",\n    \"2151-11-11 00:00:00\",\n    \"2151-11-25 00:00:00\",\n    \"2151-12-25 00:00:00\",\n    \"2152-01-01 00:00:00\",\n    \"2152-01-17 00:00:00\",\n    \"2152-02-21 00:00:00\",\n    \"2152-05-29 00:00:00\",\n    \"2152-06-19 00:00:00\",\n    \"2152-07-04 00:00:00\",\n    \"2152-09-04 00:00:00\",\n    \"2152-10-09 00:00:00\",\n    \"2152-11-11 00:00:00\",\n    \"2152-11-23 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-01-15 00:00:00\",\n    \"2153-02-19 00:00:00\",\n    \"2153-05-28 00:00:00\",\n    \"2153-06-19 00:00:00\",\n    \"2153-07-04 00:00:00\",\n    \"2153-09-03 00:00:00\",\n    \"2153-10-08 00:00:00\",\n    \"2153-11-12 00:00:00\",\n    \"2153-11-22 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-01-21 00:00:00\",\n    \"2154-02-18 00:00:00\",\n    \"2154-05-27 00:00:00\",\n    \"2154-06-19 00:00:00\",\n    \"2154-07-04 00:00:00\",\n    \"2154-09-02 00:00:00\",\n    \"2154-10-14 00:00:00\",\n    \"2154-11-11 00:00:00\",\n    \"2154-11-28 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-01-20 00:00:00\",\n    \"2155-02-17 00:00:00\",\n    \"2155-05-26 00:00:00\",\n    \"2155-06-19 00:00:00\",\n    \"2155-07-04 00:00:00\",\n    \"2155-09-01 00:00:00\",\n    \"2155-10-13 00:00:00\",\n    \"2155-11-11 00:00:00\",\n    \"2155-11-27 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-01-19 00:00:00\",\n    \"2156-02-16 00:00:00\",\n    \"2156-05-31 00:00:00\",\n    \"2156-06-19 00:00:00\",\n    \"2156-07-05 00:00:00\",\n    \"2156-09-06 00:00:00\",\n    \"2156-10-11 00:00:00\",\n    \"2156-11-11 00:00:00\",\n    \"2156-11-25 00:00:00\",\n    \"2156-12-25 00:00:00\",\n    \"2157-01-01 00:00:00\",\n    \"2157-01-17 00:00:00\",\n    \"2157-02-21 00:00:00\",\n    \"2157-05-30 00:00:00\",\n    \"2157-06-20 00:00:00\",\n    \"2157-07-04 00:00:00\",\n    \"2157-09-05 00:00:00\",\n    \"2157-10-10 00:00:00\",\n    \"2157-11-11 00:00:00\",\n    \"2157-11-24 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-01-16 00:00:00\",\n    \"2158-02-20 00:00:00\",\n    \"2158-05-29 00:00:00\",\n    \"2158-06-19 00:00:00\",\n    \"2158-07-04 00:00:00\",\n    \"2158-09-04 00:00:00\",\n    \"2158-10-09 00:00:00\",\n    \"2158-11-11 00:00:00\",\n    \"2158-11-23 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-01-15 00:00:00\",\n    \"2159-02-19 00:00:00\",\n    \"2159-05-28 00:00:00\",\n    \"2159-06-19 00:00:00\",\n    \"2159-07-04 00:00:00\",\n    \"2159-09-03 00:00:00\",\n    \"2159-10-08 00:00:00\",\n    \"2159-11-12 00:00:00\",\n    \"2159-11-22 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-01-21 00:00:00\",\n    \"2160-02-18 00:00:00\",\n    \"2160-05-26 00:00:00\",\n    \"2160-06-19 00:00:00\",\n    \"2160-07-04 00:00:00\",\n    \"2160-09-01 00:00:00\",\n    \"2160-10-13 00:00:00\",\n    \"2160-11-11 00:00:00\",\n    \"2160-11-27 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-01-19 00:00:00\",\n    \"2161-02-16 00:00:00\",\n    \"2161-05-25 00:00:00\",\n    \"2161-06-19 00:00:00\",\n    \"2161-07-04 00:00:00\",\n    \"2161-09-07 00:00:00\",\n    \"2161-10-12 00:00:00\",\n    \"2161-11-11 00:00:00\",\n    \"2161-11-26 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-01-18 00:00:00\",\n    \"2162-02-15 00:00:00\",\n    \"2162-05-31 00:00:00\",\n    \"2162-06-19 00:00:00\",\n    \"2162-07-05 00:00:00\",\n    \"2162-09-06 00:00:00\",\n    \"2162-10-11 00:00:00\",\n    \"2162-11-11 00:00:00\",\n    \"2162-11-25 00:00:00\",\n    \"2162-12-25 00:00:00\",\n    \"2163-01-01 00:00:00\",\n    \"2163-01-17 00:00:00\",\n    \"2163-02-21 00:00:00\",\n    \"2163-05-30 00:00:00\",\n    \"2163-06-20 00:00:00\",\n    \"2163-07-04 00:00:00\",\n    \"2163-09-05 00:00:00\",\n    \"2163-10-10 00:00:00\",\n    \"2163-11-11 00:00:00\",\n    \"2163-11-24 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-01-16 00:00:00\",\n    \"2164-02-20 00:00:00\",\n    \"2164-05-28 00:00:00\",\n    \"2164-06-19 00:00:00\",\n    \"2164-07-04 00:00:00\",\n    \"2164-09-03 00:00:00\",\n    \"2164-10-08 00:00:00\",\n    \"2164-11-12 00:00:00\",\n    \"2164-11-22 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-01-21 00:00:00\",\n    \"2165-02-18 00:00:00\",\n    \"2165-05-27 00:00:00\",\n    \"2165-06-19 00:00:00\",\n    \"2165-07-04 00:00:00\",\n    \"2165-09-02 00:00:00\",\n    \"2165-10-14 00:00:00\",\n    \"2165-11-11 00:00:00\",\n    \"2165-11-28 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-01-20 00:00:00\",\n    \"2166-02-17 00:00:00\",\n    \"2166-05-26 00:00:00\",\n    \"2166-06-19 00:00:00\",\n    \"2166-07-04 00:00:00\",\n    \"2166-09-01 00:00:00\",\n    \"2166-10-13 00:00:00\",\n    \"2166-11-11 00:00:00\",\n    \"2166-11-27 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-01-19 00:00:00\",\n    \"2167-02-16 00:00:00\",\n    \"2167-05-25 00:00:00\",\n    \"2167-06-19 00:00:00\",\n    \"2167-07-04 00:00:00\",\n    \"2167-09-07 00:00:00\",\n    \"2167-10-12 00:00:00\",\n    \"2167-11-11 00:00:00\",\n    \"2167-11-26 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-01-18 00:00:00\",\n    \"2168-02-15 00:00:00\",\n    \"2168-05-30 00:00:00\",\n    \"2168-06-20 00:00:00\",\n    \"2168-07-04 00:00:00\",\n    \"2168-09-05 00:00:00\",\n    \"2168-10-10 00:00:00\",\n    \"2168-11-11 00:00:00\",\n    \"2168-11-24 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-01-16 00:00:00\",\n    \"2169-02-20 00:00:00\",\n    \"2169-05-29 00:00:00\",\n    \"2169-06-19 00:00:00\",\n    \"2169-07-04 00:00:00\",\n    \"2169-09-04 00:00:00\",\n    \"2169-10-09 00:00:00\",\n    \"2169-11-11 00:00:00\",\n    \"2169-11-23 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-01-15 00:00:00\",\n    \"2170-02-19 00:00:00\",\n    \"2170-05-28 00:00:00\",\n    \"2170-06-19 00:00:00\",\n    \"2170-07-04 00:00:00\",\n    \"2170-09-03 00:00:00\",\n    \"2170-10-08 00:00:00\",\n    \"2170-11-12 00:00:00\",\n    \"2170-11-22 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-01-21 00:00:00\",\n    \"2171-02-18 00:00:00\",\n    \"2171-05-27 00:00:00\",\n    \"2171-06-19 00:00:00\",\n    \"2171-07-04 00:00:00\",\n    \"2171-09-02 00:00:00\",\n    \"2171-10-14 00:00:00\",\n    \"2171-11-11 00:00:00\",\n    \"2171-11-28 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-01-20 00:00:00\",\n    \"2172-02-17 00:00:00\",\n    \"2172-05-25 00:00:00\",\n    \"2172-06-19 00:00:00\",\n    \"2172-07-04 00:00:00\",\n    \"2172-09-07 00:00:00\",\n    \"2172-10-12 00:00:00\",\n    \"2172-11-11 00:00:00\",\n    \"2172-11-26 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-01-18 00:00:00\",\n    \"2173-02-15 00:00:00\",\n    \"2173-05-31 00:00:00\",\n    \"2173-06-19 00:00:00\",\n    \"2173-07-05 00:00:00\",\n    \"2173-09-06 00:00:00\",\n    \"2173-10-11 00:00:00\",\n    \"2173-11-11 00:00:00\",\n    \"2173-11-25 00:00:00\",\n    \"2173-12-25 00:00:00\",\n    \"2174-01-01 00:00:00\",\n    \"2174-01-17 00:00:00\",\n    \"2174-02-21 00:00:00\",\n    \"2174-05-30 00:00:00\",\n    \"2174-06-20 00:00:00\",\n    \"2174-07-04 00:00:00\",\n    \"2174-09-05 00:00:00\",\n    \"2174-10-10 00:00:00\",\n    \"2174-11-11 00:00:00\",\n    \"2174-11-24 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-01-16 00:00:00\",\n    \"2175-02-20 00:00:00\",\n    \"2175-05-29 00:00:00\",\n    \"2175-06-19 00:00:00\",\n    \"2175-07-04 00:00:00\",\n    \"2175-09-04 00:00:00\",\n    \"2175-10-09 00:00:00\",\n    \"2175-11-11 00:00:00\",\n    \"2175-11-23 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-01-15 00:00:00\",\n    \"2176-02-19 00:00:00\",\n    \"2176-05-27 00:00:00\",\n    \"2176-06-19 00:00:00\",\n    \"2176-07-04 00:00:00\",\n    \"2176-09-02 00:00:00\",\n    \"2176-10-14 00:00:00\",\n    \"2176-11-11 00:00:00\",\n    \"2176-11-28 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-01-20 00:00:00\",\n    \"2177-02-17 00:00:00\",\n    \"2177-05-26 00:00:00\",\n    \"2177-06-19 00:00:00\",\n    \"2177-07-04 00:00:00\",\n    \"2177-09-01 00:00:00\",\n    \"2177-10-13 00:00:00\",\n    \"2177-11-11 00:00:00\",\n    \"2177-11-27 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-01-19 00:00:00\",\n    \"2178-02-16 00:00:00\",\n    \"2178-05-25 00:00:00\",\n    \"2178-06-19 00:00:00\",\n    \"2178-07-04 00:00:00\",\n    \"2178-09-07 00:00:00\",\n    \"2178-10-12 00:00:00\",\n    \"2178-11-11 00:00:00\",\n    \"2178-11-26 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-01-18 00:00:00\",\n    \"2179-02-15 00:00:00\",\n    \"2179-05-31 00:00:00\",\n    \"2179-06-19 00:00:00\",\n    \"2179-07-05 00:00:00\",\n    \"2179-09-06 00:00:00\",\n    \"2179-10-11 00:00:00\",\n    \"2179-11-11 00:00:00\",\n    \"2179-11-25 00:00:00\",\n    \"2179-12-25 00:00:00\",\n    \"2180-01-01 00:00:00\",\n    \"2180-01-17 00:00:00\",\n    \"2180-02-21 00:00:00\",\n    \"2180-05-29 00:00:00\",\n    \"2180-06-19 00:00:00\",\n    \"2180-07-04 00:00:00\",\n    \"2180-09-04 00:00:00\",\n    \"2180-10-09 00:00:00\",\n    \"2180-11-11 00:00:00\",\n    \"2180-11-23 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-01-15 00:00:00\",\n    \"2181-02-19 00:00:00\",\n    \"2181-05-28 00:00:00\",\n    \"2181-06-19 00:00:00\",\n    \"2181-07-04 00:00:00\",\n    \"2181-09-03 00:00:00\",\n    \"2181-10-08 00:00:00\",\n    \"2181-11-12 00:00:00\",\n    \"2181-11-22 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-01-21 00:00:00\",\n    \"2182-02-18 00:00:00\",\n    \"2182-05-27 00:00:00\",\n    \"2182-06-19 00:00:00\",\n    \"2182-07-04 00:00:00\",\n    \"2182-09-02 00:00:00\",\n    \"2182-10-14 00:00:00\",\n    \"2182-11-11 00:00:00\",\n    \"2182-11-28 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-01-20 00:00:00\",\n    \"2183-02-17 00:00:00\",\n    \"2183-05-26 00:00:00\",\n    \"2183-06-19 00:00:00\",\n    \"2183-07-04 00:00:00\",\n    \"2183-09-01 00:00:00\",\n    \"2183-10-13 00:00:00\",\n    \"2183-11-11 00:00:00\",\n    \"2183-11-27 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-01-19 00:00:00\",\n    \"2184-02-16 00:00:00\",\n    \"2184-05-31 00:00:00\",\n    \"2184-06-19 00:00:00\",\n    \"2184-07-05 00:00:00\",\n    \"2184-09-06 00:00:00\",\n    \"2184-10-11 00:00:00\",\n    \"2184-11-11 00:00:00\",\n    \"2184-11-25 00:00:00\",\n    \"2184-12-25 00:00:00\",\n    \"2185-01-01 00:00:00\",\n    \"2185-01-17 00:00:00\",\n    \"2185-02-21 00:00:00\",\n    \"2185-05-30 00:00:00\",\n    \"2185-06-20 00:00:00\",\n    \"2185-07-04 00:00:00\",\n    \"2185-09-05 00:00:00\",\n    \"2185-10-10 00:00:00\",\n    \"2185-11-11 00:00:00\",\n    \"2185-11-24 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-01-16 00:00:00\",\n    \"2186-02-20 00:00:00\",\n    \"2186-05-29 00:00:00\",\n    \"2186-06-19 00:00:00\",\n    \"2186-07-04 00:00:00\",\n    \"2186-09-04 00:00:00\",\n    \"2186-10-09 00:00:00\",\n    \"2186-11-11 00:00:00\",\n    \"2186-11-23 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-01-15 00:00:00\",\n    \"2187-02-19 00:00:00\",\n    \"2187-05-28 00:00:00\",\n    \"2187-06-19 00:00:00\",\n    \"2187-07-04 00:00:00\",\n    \"2187-09-03 00:00:00\",\n    \"2187-10-08 00:00:00\",\n    \"2187-11-12 00:00:00\",\n    \"2187-11-22 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-01-21 00:00:00\",\n    \"2188-02-18 00:00:00\",\n    \"2188-05-26 00:00:00\",\n    \"2188-06-19 00:00:00\",\n    \"2188-07-04 00:00:00\",\n    \"2188-09-01 00:00:00\",\n    \"2188-10-13 00:00:00\",\n    \"2188-11-11 00:00:00\",\n    \"2188-11-27 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-01-19 00:00:00\",\n    \"2189-02-16 00:00:00\",\n    \"2189-05-25 00:00:00\",\n    \"2189-06-19 00:00:00\",\n    \"2189-07-04 00:00:00\",\n    \"2189-09-07 00:00:00\",\n    \"2189-10-12 00:00:00\",\n    \"2189-11-11 00:00:00\",\n    \"2189-11-26 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-01-18 00:00:00\",\n    \"2190-02-15 00:00:00\",\n    \"2190-05-31 00:00:00\",\n    \"2190-06-19 00:00:00\",\n    \"2190-07-05 00:00:00\",\n    \"2190-09-06 00:00:00\",\n    \"2190-10-11 00:00:00\",\n    \"2190-11-11 00:00:00\",\n    \"2190-11-25 00:00:00\",\n    \"2190-12-25 00:00:00\",\n    \"2191-01-01 00:00:00\",\n    \"2191-01-17 00:00:00\",\n    \"2191-02-21 00:00:00\",\n    \"2191-05-30 00:00:00\",\n    \"2191-06-20 00:00:00\",\n    \"2191-07-04 00:00:00\",\n    \"2191-09-05 00:00:00\",\n    \"2191-10-10 00:00:00\",\n    \"2191-11-11 00:00:00\",\n    \"2191-11-24 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-01-16 00:00:00\",\n    \"2192-02-20 00:00:00\",\n    \"2192-05-28 00:00:00\",\n    \"2192-06-19 00:00:00\",\n    \"2192-07-04 00:00:00\",\n    \"2192-09-03 00:00:00\",\n    \"2192-10-08 00:00:00\",\n    \"2192-11-12 00:00:00\",\n    \"2192-11-22 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-01-21 00:00:00\",\n    \"2193-02-18 00:00:00\",\n    \"2193-05-27 00:00:00\",\n    \"2193-06-19 00:00:00\",\n    \"2193-07-04 00:00:00\",\n    \"2193-09-02 00:00:00\",\n    \"2193-10-14 00:00:00\",\n    \"2193-11-11 00:00:00\",\n    \"2193-11-28 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-01-20 00:00:00\",\n    \"2194-02-17 00:00:00\",\n    \"2194-05-26 00:00:00\",\n    \"2194-06-19 00:00:00\",\n    \"2194-07-04 00:00:00\",\n    \"2194-09-01 00:00:00\",\n    \"2194-10-13 00:00:00\",\n    \"2194-11-11 00:00:00\",\n    \"2194-11-27 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-01-19 00:00:00\",\n    \"2195-02-16 00:00:00\",\n    \"2195-05-25 00:00:00\",\n    \"2195-06-19 00:00:00\",\n    \"2195-07-04 00:00:00\",\n    \"2195-09-07 00:00:00\",\n    \"2195-10-12 00:00:00\",\n    \"2195-11-11 00:00:00\",\n    \"2195-11-26 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-01-18 00:00:00\",\n    \"2196-02-15 00:00:00\",\n    \"2196-05-30 00:00:00\",\n    \"2196-06-20 00:00:00\",\n    \"2196-07-04 00:00:00\",\n    \"2196-09-05 00:00:00\",\n    \"2196-10-10 00:00:00\",\n    \"2196-11-11 00:00:00\",\n    \"2196-11-24 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-01-16 00:00:00\",\n    \"2197-02-20 00:00:00\",\n    \"2197-05-29 00:00:00\",\n    \"2197-06-19 00:00:00\",\n    \"2197-07-04 00:00:00\",\n    \"2197-09-04 00:00:00\",\n    \"2197-10-09 00:00:00\",\n    \"2197-11-11 00:00:00\",\n    \"2197-11-23 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-01-15 00:00:00\",\n    \"2198-02-19 00:00:00\",\n    \"2198-05-28 00:00:00\",\n    \"2198-06-19 00:00:00\",\n    \"2198-07-04 00:00:00\",\n    \"2198-09-03 00:00:00\",\n    \"2198-10-08 00:00:00\",\n    \"2198-11-12 00:00:00\",\n    \"2198-11-22 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-01-21 00:00:00\",\n    \"2199-02-18 00:00:00\",\n    \"2199-05-27 00:00:00\",\n    \"2199-06-19 00:00:00\",\n    \"2199-07-04 00:00:00\",\n    \"2199-09-02 00:00:00\",\n    \"2199-10-14 00:00:00\",\n    \"2199-11-11 00:00:00\",\n    \"2199-11-28 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-01-20 00:00:00\",\n    \"2200-02-17 00:00:00\",\n    \"2200-05-26 00:00:00\",\n    \"2200-06-19 00:00:00\",\n    \"2200-07-04 00:00:00\",\n    \"2200-09-01 00:00:00\",\n    \"2200-10-13 00:00:00\",\n    \"2200-11-11 00:00:00\",\n    \"2200-11-27 00:00:00\",\n    \"2200-12-25 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/fed_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime\n\nimport pandas as pd\nfrom dateutil.relativedelta import MO, TH\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n    sunday_to_monday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, DateOffset\n\nRULES = [\n    Holiday(\"New Year's Day Holiday\", month=1, day=1, observance=sunday_to_monday),\n    Holiday(\n        \"Dr. Martin Luther King Jr.\",\n        start_date=datetime(1986, 1, 1),\n        month=1,\n        day=1,\n        offset=DateOffset(weekday=MO(3)),\n    ),\n    Holiday(\"US Presidents Day\", month=2, day=1, offset=DateOffset(weekday=MO(3))),\n    # Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"US Memorial Day\", month=5, day=31, offset=DateOffset(weekday=MO(-1))),\n    Holiday(\n        \"Juneteenth Independence Day\",\n        start_date=datetime(2022, 1, 1),\n        month=6,\n        day=19,\n        observance=sunday_to_monday,\n    ),\n    Holiday(\"US Independence Day\", month=7, day=4, observance=sunday_to_monday),\n    Holiday(\"US Labour Day\", month=9, day=1, offset=DateOffset(weekday=MO(1))),\n    Holiday(\"US Columbus Day\", month=10, day=1, offset=DateOffset(weekday=MO(2))),\n    Holiday(\"Veterans Day\", month=11, day=11, observance=sunday_to_monday),\n    Holiday(\"US Thanksgiving\", month=11, day=1, offset=DateOffset(weekday=TH(4))),\n    Holiday(\"Christmas Day Sunday Holiday\", month=12, day=25, observance=sunday_to_monday),\n    Holiday(\"GHW Bush Funeral\", year=2018, month=12, day=5),\n]\n\nCALENDAR = CustomBusinessDay(\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/ldn.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a London business day holiday calendar, aligned with SONIA publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1: Sat,Sun->Mon (New Year)\",\n//     \"Fri before Easter (Easter Friday)\",\n//     \"Mon after Easter (Easter Monday)\",\n//     \"May 1st Mon (May Bank)\",\n//     \"July last Mon (Jul Bank)\",\n//     \"Aug last Mon (Aug Bank)\",\n//     \"Dec 25: Sat,Sun->Mon (Christmas)\",\n//     \"Dec 26: Sat->Mon,Sun->Tue (Boxing Day)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-03-30 00:00:00\",\n    \"1970-05-04 00:00:00\",\n    \"1970-05-25 00:00:00\",\n    \"1970-08-31 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-28 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-12 00:00:00\",\n    \"1971-05-03 00:00:00\",\n    \"1971-05-31 00:00:00\",\n    \"1971-08-30 00:00:00\",\n    \"1971-12-27 00:00:00\",\n    \"1971-12-28 00:00:00\",\n    \"1972-01-03 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-03 00:00:00\",\n    \"1972-05-01 00:00:00\",\n    \"1972-05-29 00:00:00\",\n    \"1972-08-28 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-04-23 00:00:00\",\n    \"1973-05-07 00:00:00\",\n    \"1973-05-28 00:00:00\",\n    \"1973-08-27 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-15 00:00:00\",\n    \"1974-05-06 00:00:00\",\n    \"1974-05-27 00:00:00\",\n    \"1974-08-26 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-03-31 00:00:00\",\n    \"1975-05-05 00:00:00\",\n    \"1975-05-26 00:00:00\",\n    \"1975-08-25 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-04-19 00:00:00\",\n    \"1976-05-03 00:00:00\",\n    \"1976-05-31 00:00:00\",\n    \"1976-08-30 00:00:00\",\n    \"1976-12-27 00:00:00\",\n    \"1976-12-28 00:00:00\",\n    \"1977-01-03 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-11 00:00:00\",\n    \"1977-05-02 00:00:00\",\n    \"1977-05-30 00:00:00\",\n    \"1977-08-29 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1977-12-27 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-03-27 00:00:00\",\n    \"1978-05-01 00:00:00\",\n    \"1978-05-29 00:00:00\",\n    \"1978-08-28 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-16 00:00:00\",\n    \"1979-05-07 00:00:00\",\n    \"1979-05-28 00:00:00\",\n    \"1979-08-27 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-07 00:00:00\",\n    \"1980-05-05 00:00:00\",\n    \"1980-05-26 00:00:00\",\n    \"1980-08-25 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-04-20 00:00:00\",\n    \"1981-05-04 00:00:00\",\n    \"1981-05-25 00:00:00\",\n    \"1981-08-31 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-28 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-12 00:00:00\",\n    \"1982-05-03 00:00:00\",\n    \"1982-05-31 00:00:00\",\n    \"1982-08-30 00:00:00\",\n    \"1982-12-27 00:00:00\",\n    \"1982-12-28 00:00:00\",\n    \"1983-01-03 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-04 00:00:00\",\n    \"1983-05-02 00:00:00\",\n    \"1983-05-30 00:00:00\",\n    \"1983-08-29 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1983-12-27 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-04-23 00:00:00\",\n    \"1984-05-07 00:00:00\",\n    \"1984-05-28 00:00:00\",\n    \"1984-08-27 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-08 00:00:00\",\n    \"1985-05-06 00:00:00\",\n    \"1985-05-27 00:00:00\",\n    \"1985-08-26 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-03-31 00:00:00\",\n    \"1986-05-05 00:00:00\",\n    \"1986-05-26 00:00:00\",\n    \"1986-08-25 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-04-20 00:00:00\",\n    \"1987-05-04 00:00:00\",\n    \"1987-05-25 00:00:00\",\n    \"1987-08-31 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-28 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-04 00:00:00\",\n    \"1988-05-02 00:00:00\",\n    \"1988-05-30 00:00:00\",\n    \"1988-08-29 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1988-12-27 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-03-27 00:00:00\",\n    \"1989-05-01 00:00:00\",\n    \"1989-05-29 00:00:00\",\n    \"1989-08-28 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-16 00:00:00\",\n    \"1990-05-07 00:00:00\",\n    \"1990-05-28 00:00:00\",\n    \"1990-08-27 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-01 00:00:00\",\n    \"1991-05-06 00:00:00\",\n    \"1991-05-27 00:00:00\",\n    \"1991-08-26 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-04-20 00:00:00\",\n    \"1992-05-04 00:00:00\",\n    \"1992-05-25 00:00:00\",\n    \"1992-08-31 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-28 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-12 00:00:00\",\n    \"1993-05-03 00:00:00\",\n    \"1993-05-31 00:00:00\",\n    \"1993-08-30 00:00:00\",\n    \"1993-12-27 00:00:00\",\n    \"1993-12-28 00:00:00\",\n    \"1994-01-03 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-04 00:00:00\",\n    \"1994-05-02 00:00:00\",\n    \"1994-05-30 00:00:00\",\n    \"1994-08-29 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1994-12-27 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-17 00:00:00\",\n    \"1995-05-01 00:00:00\",\n    \"1995-05-29 00:00:00\",\n    \"1995-08-28 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-08 00:00:00\",\n    \"1996-05-06 00:00:00\",\n    \"1996-05-27 00:00:00\",\n    \"1996-08-26 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-03-31 00:00:00\",\n    \"1997-05-05 00:00:00\",\n    \"1997-05-26 00:00:00\",\n    \"1997-08-25 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-13 00:00:00\",\n    \"1998-05-04 00:00:00\",\n    \"1998-05-25 00:00:00\",\n    \"1998-08-31 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-28 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-05 00:00:00\",\n    \"1999-05-03 00:00:00\",\n    \"1999-05-31 00:00:00\",\n    \"1999-08-30 00:00:00\",\n    \"1999-12-27 00:00:00\",\n    \"1999-12-28 00:00:00\",\n    \"2000-01-03 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-04-24 00:00:00\",\n    \"2000-05-01 00:00:00\",\n    \"2000-05-29 00:00:00\",\n    \"2000-08-28 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-16 00:00:00\",\n    \"2001-05-07 00:00:00\",\n    \"2001-05-28 00:00:00\",\n    \"2001-08-27 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-01 00:00:00\",\n    \"2002-05-06 00:00:00\",\n    \"2002-05-27 00:00:00\",\n    \"2002-08-26 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-04-21 00:00:00\",\n    \"2003-05-05 00:00:00\",\n    \"2003-05-26 00:00:00\",\n    \"2003-08-25 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-12 00:00:00\",\n    \"2004-05-03 00:00:00\",\n    \"2004-05-31 00:00:00\",\n    \"2004-08-30 00:00:00\",\n    \"2004-12-27 00:00:00\",\n    \"2004-12-28 00:00:00\",\n    \"2005-01-03 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-03-28 00:00:00\",\n    \"2005-05-02 00:00:00\",\n    \"2005-05-30 00:00:00\",\n    \"2005-08-29 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2005-12-27 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-17 00:00:00\",\n    \"2006-05-01 00:00:00\",\n    \"2006-05-29 00:00:00\",\n    \"2006-08-28 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-09 00:00:00\",\n    \"2007-05-07 00:00:00\",\n    \"2007-05-28 00:00:00\",\n    \"2007-08-27 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-03-24 00:00:00\",\n    \"2008-05-05 00:00:00\",\n    \"2008-05-26 00:00:00\",\n    \"2008-08-25 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-13 00:00:00\",\n    \"2009-05-04 00:00:00\",\n    \"2009-05-25 00:00:00\",\n    \"2009-08-31 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-28 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-05 00:00:00\",\n    \"2010-05-03 00:00:00\",\n    \"2010-05-31 00:00:00\",\n    \"2010-08-30 00:00:00\",\n    \"2010-12-27 00:00:00\",\n    \"2010-12-28 00:00:00\",\n    \"2011-01-03 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-05-02 00:00:00\",\n    \"2011-05-30 00:00:00\",\n    \"2011-08-29 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2011-12-27 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-09 00:00:00\",\n    \"2012-05-07 00:00:00\",\n    \"2012-05-28 00:00:00\",\n    \"2012-08-27 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-01 00:00:00\",\n    \"2013-05-06 00:00:00\",\n    \"2013-05-27 00:00:00\",\n    \"2013-08-26 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-04-21 00:00:00\",\n    \"2014-05-05 00:00:00\",\n    \"2014-05-26 00:00:00\",\n    \"2014-08-25 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-06 00:00:00\",\n    \"2015-05-04 00:00:00\",\n    \"2015-05-25 00:00:00\",\n    \"2015-08-31 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-28 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-03-28 00:00:00\",\n    \"2016-05-02 00:00:00\",\n    \"2016-05-30 00:00:00\",\n    \"2016-08-29 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2016-12-27 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-17 00:00:00\",\n    \"2017-05-01 00:00:00\",\n    \"2017-05-29 00:00:00\",\n    \"2017-08-28 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-02 00:00:00\",\n    \"2018-05-07 00:00:00\",\n    \"2018-05-28 00:00:00\",\n    \"2018-08-27 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-22 00:00:00\",\n    \"2019-05-06 00:00:00\",\n    \"2019-05-27 00:00:00\",\n    \"2019-08-26 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-13 00:00:00\",\n    \"2020-05-08 00:00:00\",\n    \"2020-05-25 00:00:00\",\n    \"2020-08-31 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-28 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-05 00:00:00\",\n    \"2021-05-03 00:00:00\",\n    \"2021-05-31 00:00:00\",\n    \"2021-08-30 00:00:00\",\n    \"2021-12-27 00:00:00\",\n    \"2021-12-28 00:00:00\",\n    \"2022-01-03 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-04-18 00:00:00\",\n    \"2022-05-02 00:00:00\",\n    \"2022-06-02 00:00:00\",\n    \"2022-06-03 00:00:00\",\n    \"2022-08-29 00:00:00\",\n    \"2022-09-19 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2022-12-27 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-10 00:00:00\",\n    \"2023-05-01 00:00:00\",\n    \"2023-05-08 00:00:00\",\n    \"2023-05-29 00:00:00\",\n    \"2023-08-28 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-05-06 00:00:00\",\n    \"2024-05-27 00:00:00\",\n    \"2024-08-26 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-04-21 00:00:00\",\n    \"2025-05-05 00:00:00\",\n    \"2025-05-26 00:00:00\",\n    \"2025-08-25 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-05-04 00:00:00\",\n    \"2026-05-25 00:00:00\",\n    \"2026-08-31 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-28 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-03-29 00:00:00\",\n    \"2027-05-03 00:00:00\",\n    \"2027-05-31 00:00:00\",\n    \"2027-08-30 00:00:00\",\n    \"2027-12-27 00:00:00\",\n    \"2027-12-28 00:00:00\",\n    \"2028-01-03 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-17 00:00:00\",\n    \"2028-05-01 00:00:00\",\n    \"2028-05-29 00:00:00\",\n    \"2028-08-28 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-02 00:00:00\",\n    \"2029-05-07 00:00:00\",\n    \"2029-05-28 00:00:00\",\n    \"2029-08-27 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-04-22 00:00:00\",\n    \"2030-05-06 00:00:00\",\n    \"2030-05-27 00:00:00\",\n    \"2030-08-26 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-05-05 00:00:00\",\n    \"2031-05-26 00:00:00\",\n    \"2031-08-25 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-03-29 00:00:00\",\n    \"2032-05-03 00:00:00\",\n    \"2032-05-31 00:00:00\",\n    \"2032-08-30 00:00:00\",\n    \"2032-12-27 00:00:00\",\n    \"2032-12-28 00:00:00\",\n    \"2033-01-03 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-04-18 00:00:00\",\n    \"2033-05-02 00:00:00\",\n    \"2033-05-30 00:00:00\",\n    \"2033-08-29 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2033-12-27 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-10 00:00:00\",\n    \"2034-05-01 00:00:00\",\n    \"2034-05-29 00:00:00\",\n    \"2034-08-28 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-03-26 00:00:00\",\n    \"2035-05-07 00:00:00\",\n    \"2035-05-28 00:00:00\",\n    \"2035-08-27 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-05-05 00:00:00\",\n    \"2036-05-26 00:00:00\",\n    \"2036-08-25 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-05-04 00:00:00\",\n    \"2037-05-25 00:00:00\",\n    \"2037-08-31 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-28 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-05-03 00:00:00\",\n    \"2038-05-31 00:00:00\",\n    \"2038-08-30 00:00:00\",\n    \"2038-12-27 00:00:00\",\n    \"2038-12-28 00:00:00\",\n    \"2039-01-03 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-11 00:00:00\",\n    \"2039-05-02 00:00:00\",\n    \"2039-05-30 00:00:00\",\n    \"2039-08-29 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2039-12-27 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-02 00:00:00\",\n    \"2040-05-07 00:00:00\",\n    \"2040-05-28 00:00:00\",\n    \"2040-08-27 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-04-22 00:00:00\",\n    \"2041-05-06 00:00:00\",\n    \"2041-05-27 00:00:00\",\n    \"2041-08-26 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-07 00:00:00\",\n    \"2042-05-05 00:00:00\",\n    \"2042-05-26 00:00:00\",\n    \"2042-08-25 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-03-30 00:00:00\",\n    \"2043-05-04 00:00:00\",\n    \"2043-05-25 00:00:00\",\n    \"2043-08-31 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-28 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-04-18 00:00:00\",\n    \"2044-05-02 00:00:00\",\n    \"2044-05-30 00:00:00\",\n    \"2044-08-29 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2044-12-27 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-10 00:00:00\",\n    \"2045-05-01 00:00:00\",\n    \"2045-05-29 00:00:00\",\n    \"2045-08-28 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-03-26 00:00:00\",\n    \"2046-05-07 00:00:00\",\n    \"2046-05-28 00:00:00\",\n    \"2046-08-27 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-15 00:00:00\",\n    \"2047-05-06 00:00:00\",\n    \"2047-05-27 00:00:00\",\n    \"2047-08-26 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-06 00:00:00\",\n    \"2048-05-04 00:00:00\",\n    \"2048-05-25 00:00:00\",\n    \"2048-08-31 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-28 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-04-19 00:00:00\",\n    \"2049-05-03 00:00:00\",\n    \"2049-05-31 00:00:00\",\n    \"2049-08-30 00:00:00\",\n    \"2049-12-27 00:00:00\",\n    \"2049-12-28 00:00:00\",\n    \"2050-01-03 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-11 00:00:00\",\n    \"2050-05-02 00:00:00\",\n    \"2050-05-30 00:00:00\",\n    \"2050-08-29 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2050-12-27 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-05-01 00:00:00\",\n    \"2051-05-29 00:00:00\",\n    \"2051-08-28 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-04-22 00:00:00\",\n    \"2052-05-06 00:00:00\",\n    \"2052-05-27 00:00:00\",\n    \"2052-08-26 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-07 00:00:00\",\n    \"2053-05-05 00:00:00\",\n    \"2053-05-26 00:00:00\",\n    \"2053-08-25 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-03-30 00:00:00\",\n    \"2054-05-04 00:00:00\",\n    \"2054-05-25 00:00:00\",\n    \"2054-08-31 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-28 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-04-19 00:00:00\",\n    \"2055-05-03 00:00:00\",\n    \"2055-05-31 00:00:00\",\n    \"2055-08-30 00:00:00\",\n    \"2055-12-27 00:00:00\",\n    \"2055-12-28 00:00:00\",\n    \"2056-01-03 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-05-01 00:00:00\",\n    \"2056-05-29 00:00:00\",\n    \"2056-08-28 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-04-23 00:00:00\",\n    \"2057-05-07 00:00:00\",\n    \"2057-05-28 00:00:00\",\n    \"2057-08-27 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-15 00:00:00\",\n    \"2058-05-06 00:00:00\",\n    \"2058-05-27 00:00:00\",\n    \"2058-08-26 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-03-31 00:00:00\",\n    \"2059-05-05 00:00:00\",\n    \"2059-05-26 00:00:00\",\n    \"2059-08-25 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-04-19 00:00:00\",\n    \"2060-05-03 00:00:00\",\n    \"2060-05-31 00:00:00\",\n    \"2060-08-30 00:00:00\",\n    \"2060-12-27 00:00:00\",\n    \"2060-12-28 00:00:00\",\n    \"2061-01-03 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-11 00:00:00\",\n    \"2061-05-02 00:00:00\",\n    \"2061-05-30 00:00:00\",\n    \"2061-08-29 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2061-12-27 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-03-27 00:00:00\",\n    \"2062-05-01 00:00:00\",\n    \"2062-05-29 00:00:00\",\n    \"2062-08-28 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-16 00:00:00\",\n    \"2063-05-07 00:00:00\",\n    \"2063-05-28 00:00:00\",\n    \"2063-08-27 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-07 00:00:00\",\n    \"2064-05-05 00:00:00\",\n    \"2064-05-26 00:00:00\",\n    \"2064-08-25 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-03-30 00:00:00\",\n    \"2065-05-04 00:00:00\",\n    \"2065-05-25 00:00:00\",\n    \"2065-08-31 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-28 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-12 00:00:00\",\n    \"2066-05-03 00:00:00\",\n    \"2066-05-31 00:00:00\",\n    \"2066-08-30 00:00:00\",\n    \"2066-12-27 00:00:00\",\n    \"2066-12-28 00:00:00\",\n    \"2067-01-03 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-05-02 00:00:00\",\n    \"2067-05-30 00:00:00\",\n    \"2067-08-29 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2067-12-27 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-04-23 00:00:00\",\n    \"2068-05-07 00:00:00\",\n    \"2068-05-28 00:00:00\",\n    \"2068-08-27 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-15 00:00:00\",\n    \"2069-05-06 00:00:00\",\n    \"2069-05-27 00:00:00\",\n    \"2069-08-26 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-03-31 00:00:00\",\n    \"2070-05-05 00:00:00\",\n    \"2070-05-26 00:00:00\",\n    \"2070-08-25 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-04-20 00:00:00\",\n    \"2071-05-04 00:00:00\",\n    \"2071-05-25 00:00:00\",\n    \"2071-08-31 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-28 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-11 00:00:00\",\n    \"2072-05-02 00:00:00\",\n    \"2072-05-30 00:00:00\",\n    \"2072-08-29 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2072-12-27 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-03-27 00:00:00\",\n    \"2073-05-01 00:00:00\",\n    \"2073-05-29 00:00:00\",\n    \"2073-08-28 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-16 00:00:00\",\n    \"2074-05-07 00:00:00\",\n    \"2074-05-28 00:00:00\",\n    \"2074-08-27 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-08 00:00:00\",\n    \"2075-05-06 00:00:00\",\n    \"2075-05-27 00:00:00\",\n    \"2075-08-26 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-04-20 00:00:00\",\n    \"2076-05-04 00:00:00\",\n    \"2076-05-25 00:00:00\",\n    \"2076-08-31 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-28 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-12 00:00:00\",\n    \"2077-05-03 00:00:00\",\n    \"2077-05-31 00:00:00\",\n    \"2077-08-30 00:00:00\",\n    \"2077-12-27 00:00:00\",\n    \"2077-12-28 00:00:00\",\n    \"2078-01-03 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-05-02 00:00:00\",\n    \"2078-05-30 00:00:00\",\n    \"2078-08-29 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2078-12-27 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-04-24 00:00:00\",\n    \"2079-05-01 00:00:00\",\n    \"2079-05-29 00:00:00\",\n    \"2079-08-28 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-08 00:00:00\",\n    \"2080-05-06 00:00:00\",\n    \"2080-05-27 00:00:00\",\n    \"2080-08-26 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-03-31 00:00:00\",\n    \"2081-05-05 00:00:00\",\n    \"2081-05-26 00:00:00\",\n    \"2081-08-25 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-04-20 00:00:00\",\n    \"2082-05-04 00:00:00\",\n    \"2082-05-25 00:00:00\",\n    \"2082-08-31 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-28 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-05-03 00:00:00\",\n    \"2083-05-31 00:00:00\",\n    \"2083-08-30 00:00:00\",\n    \"2083-12-27 00:00:00\",\n    \"2083-12-28 00:00:00\",\n    \"2084-01-03 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-03-27 00:00:00\",\n    \"2084-05-01 00:00:00\",\n    \"2084-05-29 00:00:00\",\n    \"2084-08-28 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-16 00:00:00\",\n    \"2085-05-07 00:00:00\",\n    \"2085-05-28 00:00:00\",\n    \"2085-08-27 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-01 00:00:00\",\n    \"2086-05-06 00:00:00\",\n    \"2086-05-27 00:00:00\",\n    \"2086-08-26 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-04-21 00:00:00\",\n    \"2087-05-05 00:00:00\",\n    \"2087-05-26 00:00:00\",\n    \"2087-08-25 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-12 00:00:00\",\n    \"2088-05-03 00:00:00\",\n    \"2088-05-31 00:00:00\",\n    \"2088-08-30 00:00:00\",\n    \"2088-12-27 00:00:00\",\n    \"2088-12-28 00:00:00\",\n    \"2089-01-03 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-05-02 00:00:00\",\n    \"2089-05-30 00:00:00\",\n    \"2089-08-29 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2089-12-27 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-17 00:00:00\",\n    \"2090-05-01 00:00:00\",\n    \"2090-05-29 00:00:00\",\n    \"2090-08-28 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-09 00:00:00\",\n    \"2091-05-07 00:00:00\",\n    \"2091-05-28 00:00:00\",\n    \"2091-08-27 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-03-31 00:00:00\",\n    \"2092-05-05 00:00:00\",\n    \"2092-05-26 00:00:00\",\n    \"2092-08-25 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-13 00:00:00\",\n    \"2093-05-04 00:00:00\",\n    \"2093-05-25 00:00:00\",\n    \"2093-08-31 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-28 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-05-03 00:00:00\",\n    \"2094-05-31 00:00:00\",\n    \"2094-08-30 00:00:00\",\n    \"2094-12-27 00:00:00\",\n    \"2094-12-28 00:00:00\",\n    \"2095-01-03 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-05-02 00:00:00\",\n    \"2095-05-30 00:00:00\",\n    \"2095-08-29 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2095-12-27 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-16 00:00:00\",\n    \"2096-05-07 00:00:00\",\n    \"2096-05-28 00:00:00\",\n    \"2096-08-27 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-01 00:00:00\",\n    \"2097-05-06 00:00:00\",\n    \"2097-05-27 00:00:00\",\n    \"2097-08-26 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-04-21 00:00:00\",\n    \"2098-05-05 00:00:00\",\n    \"2098-05-26 00:00:00\",\n    \"2098-08-25 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-13 00:00:00\",\n    \"2099-05-04 00:00:00\",\n    \"2099-05-25 00:00:00\",\n    \"2099-08-31 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-28 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-03-29 00:00:00\",\n    \"2100-05-03 00:00:00\",\n    \"2100-05-31 00:00:00\",\n    \"2100-08-30 00:00:00\",\n    \"2100-12-27 00:00:00\",\n    \"2100-12-28 00:00:00\",\n    \"2101-01-03 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-04-18 00:00:00\",\n    \"2101-05-02 00:00:00\",\n    \"2101-05-30 00:00:00\",\n    \"2101-08-29 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2101-12-27 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-10 00:00:00\",\n    \"2102-05-01 00:00:00\",\n    \"2102-05-29 00:00:00\",\n    \"2102-08-28 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-03-26 00:00:00\",\n    \"2103-05-07 00:00:00\",\n    \"2103-05-28 00:00:00\",\n    \"2103-08-27 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-05-05 00:00:00\",\n    \"2104-05-26 00:00:00\",\n    \"2104-08-25 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-06 00:00:00\",\n    \"2105-05-04 00:00:00\",\n    \"2105-05-25 00:00:00\",\n    \"2105-08-31 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-28 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-04-19 00:00:00\",\n    \"2106-05-03 00:00:00\",\n    \"2106-05-31 00:00:00\",\n    \"2106-08-30 00:00:00\",\n    \"2106-12-27 00:00:00\",\n    \"2106-12-28 00:00:00\",\n    \"2107-01-03 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-11 00:00:00\",\n    \"2107-05-02 00:00:00\",\n    \"2107-05-30 00:00:00\",\n    \"2107-08-29 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2107-12-27 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-02 00:00:00\",\n    \"2108-05-07 00:00:00\",\n    \"2108-05-28 00:00:00\",\n    \"2108-08-27 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-04-22 00:00:00\",\n    \"2109-05-06 00:00:00\",\n    \"2109-05-27 00:00:00\",\n    \"2109-08-26 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-07 00:00:00\",\n    \"2110-05-05 00:00:00\",\n    \"2110-05-26 00:00:00\",\n    \"2110-08-25 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-03-30 00:00:00\",\n    \"2111-05-04 00:00:00\",\n    \"2111-05-25 00:00:00\",\n    \"2111-08-31 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-28 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-04-18 00:00:00\",\n    \"2112-05-02 00:00:00\",\n    \"2112-05-30 00:00:00\",\n    \"2112-08-29 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2112-12-27 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-03 00:00:00\",\n    \"2113-05-01 00:00:00\",\n    \"2113-05-29 00:00:00\",\n    \"2113-08-28 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-04-23 00:00:00\",\n    \"2114-05-07 00:00:00\",\n    \"2114-05-28 00:00:00\",\n    \"2114-08-27 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-15 00:00:00\",\n    \"2115-05-06 00:00:00\",\n    \"2115-05-27 00:00:00\",\n    \"2115-08-26 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-03-30 00:00:00\",\n    \"2116-05-04 00:00:00\",\n    \"2116-05-25 00:00:00\",\n    \"2116-08-31 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-28 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-04-19 00:00:00\",\n    \"2117-05-03 00:00:00\",\n    \"2117-05-31 00:00:00\",\n    \"2117-08-30 00:00:00\",\n    \"2117-12-27 00:00:00\",\n    \"2117-12-28 00:00:00\",\n    \"2118-01-03 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-11 00:00:00\",\n    \"2118-05-02 00:00:00\",\n    \"2118-05-30 00:00:00\",\n    \"2118-08-29 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2118-12-27 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-03-27 00:00:00\",\n    \"2119-05-01 00:00:00\",\n    \"2119-05-29 00:00:00\",\n    \"2119-08-28 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-15 00:00:00\",\n    \"2120-05-06 00:00:00\",\n    \"2120-05-27 00:00:00\",\n    \"2120-08-26 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-07 00:00:00\",\n    \"2121-05-05 00:00:00\",\n    \"2121-05-26 00:00:00\",\n    \"2121-08-25 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-03-30 00:00:00\",\n    \"2122-05-04 00:00:00\",\n    \"2122-05-25 00:00:00\",\n    \"2122-08-31 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-28 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-12 00:00:00\",\n    \"2123-05-03 00:00:00\",\n    \"2123-05-31 00:00:00\",\n    \"2123-08-30 00:00:00\",\n    \"2123-12-27 00:00:00\",\n    \"2123-12-28 00:00:00\",\n    \"2124-01-03 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-03 00:00:00\",\n    \"2124-05-01 00:00:00\",\n    \"2124-05-29 00:00:00\",\n    \"2124-08-28 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-04-23 00:00:00\",\n    \"2125-05-07 00:00:00\",\n    \"2125-05-28 00:00:00\",\n    \"2125-08-27 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-15 00:00:00\",\n    \"2126-05-06 00:00:00\",\n    \"2126-05-27 00:00:00\",\n    \"2126-08-26 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-03-31 00:00:00\",\n    \"2127-05-05 00:00:00\",\n    \"2127-05-26 00:00:00\",\n    \"2127-08-25 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-04-19 00:00:00\",\n    \"2128-05-03 00:00:00\",\n    \"2128-05-31 00:00:00\",\n    \"2128-08-30 00:00:00\",\n    \"2128-12-27 00:00:00\",\n    \"2128-12-28 00:00:00\",\n    \"2129-01-03 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-11 00:00:00\",\n    \"2129-05-02 00:00:00\",\n    \"2129-05-30 00:00:00\",\n    \"2129-08-29 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2129-12-27 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-03-27 00:00:00\",\n    \"2130-05-01 00:00:00\",\n    \"2130-05-29 00:00:00\",\n    \"2130-08-28 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-16 00:00:00\",\n    \"2131-05-07 00:00:00\",\n    \"2131-05-28 00:00:00\",\n    \"2131-08-27 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-07 00:00:00\",\n    \"2132-05-05 00:00:00\",\n    \"2132-05-26 00:00:00\",\n    \"2132-08-25 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-04-20 00:00:00\",\n    \"2133-05-04 00:00:00\",\n    \"2133-05-25 00:00:00\",\n    \"2133-08-31 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-28 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-12 00:00:00\",\n    \"2134-05-03 00:00:00\",\n    \"2134-05-31 00:00:00\",\n    \"2134-08-30 00:00:00\",\n    \"2134-12-27 00:00:00\",\n    \"2134-12-28 00:00:00\",\n    \"2135-01-03 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-04 00:00:00\",\n    \"2135-05-02 00:00:00\",\n    \"2135-05-30 00:00:00\",\n    \"2135-08-29 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2135-12-27 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-04-23 00:00:00\",\n    \"2136-05-07 00:00:00\",\n    \"2136-05-28 00:00:00\",\n    \"2136-08-27 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-08 00:00:00\",\n    \"2137-05-06 00:00:00\",\n    \"2137-05-27 00:00:00\",\n    \"2137-08-26 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-03-31 00:00:00\",\n    \"2138-05-05 00:00:00\",\n    \"2138-05-26 00:00:00\",\n    \"2138-08-25 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-04-20 00:00:00\",\n    \"2139-05-04 00:00:00\",\n    \"2139-05-25 00:00:00\",\n    \"2139-08-31 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-28 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-04 00:00:00\",\n    \"2140-05-02 00:00:00\",\n    \"2140-05-30 00:00:00\",\n    \"2140-08-29 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2140-12-27 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-03-27 00:00:00\",\n    \"2141-05-01 00:00:00\",\n    \"2141-05-29 00:00:00\",\n    \"2141-08-28 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-16 00:00:00\",\n    \"2142-05-07 00:00:00\",\n    \"2142-05-28 00:00:00\",\n    \"2142-08-27 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-01 00:00:00\",\n    \"2143-05-06 00:00:00\",\n    \"2143-05-27 00:00:00\",\n    \"2143-08-26 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-04-20 00:00:00\",\n    \"2144-05-04 00:00:00\",\n    \"2144-05-25 00:00:00\",\n    \"2144-08-31 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-28 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-12 00:00:00\",\n    \"2145-05-03 00:00:00\",\n    \"2145-05-31 00:00:00\",\n    \"2145-08-30 00:00:00\",\n    \"2145-12-27 00:00:00\",\n    \"2145-12-28 00:00:00\",\n    \"2146-01-03 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-04 00:00:00\",\n    \"2146-05-02 00:00:00\",\n    \"2146-05-30 00:00:00\",\n    \"2146-08-29 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2146-12-27 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-17 00:00:00\",\n    \"2147-05-01 00:00:00\",\n    \"2147-05-29 00:00:00\",\n    \"2147-08-28 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-08 00:00:00\",\n    \"2148-05-06 00:00:00\",\n    \"2148-05-27 00:00:00\",\n    \"2148-08-26 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-03-31 00:00:00\",\n    \"2149-05-05 00:00:00\",\n    \"2149-05-26 00:00:00\",\n    \"2149-08-25 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-13 00:00:00\",\n    \"2150-05-04 00:00:00\",\n    \"2150-05-25 00:00:00\",\n    \"2150-08-31 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-28 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-05 00:00:00\",\n    \"2151-05-03 00:00:00\",\n    \"2151-05-31 00:00:00\",\n    \"2151-08-30 00:00:00\",\n    \"2151-12-27 00:00:00\",\n    \"2151-12-28 00:00:00\",\n    \"2152-01-03 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-04-24 00:00:00\",\n    \"2152-05-01 00:00:00\",\n    \"2152-05-29 00:00:00\",\n    \"2152-08-28 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-16 00:00:00\",\n    \"2153-05-07 00:00:00\",\n    \"2153-05-28 00:00:00\",\n    \"2153-08-27 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-01 00:00:00\",\n    \"2154-05-06 00:00:00\",\n    \"2154-05-27 00:00:00\",\n    \"2154-08-26 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-04-21 00:00:00\",\n    \"2155-05-05 00:00:00\",\n    \"2155-05-26 00:00:00\",\n    \"2155-08-25 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-12 00:00:00\",\n    \"2156-05-03 00:00:00\",\n    \"2156-05-31 00:00:00\",\n    \"2156-08-30 00:00:00\",\n    \"2156-12-27 00:00:00\",\n    \"2156-12-28 00:00:00\",\n    \"2157-01-03 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-03-28 00:00:00\",\n    \"2157-05-02 00:00:00\",\n    \"2157-05-30 00:00:00\",\n    \"2157-08-29 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2157-12-27 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-17 00:00:00\",\n    \"2158-05-01 00:00:00\",\n    \"2158-05-29 00:00:00\",\n    \"2158-08-28 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-09 00:00:00\",\n    \"2159-05-07 00:00:00\",\n    \"2159-05-28 00:00:00\",\n    \"2159-08-27 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-03-24 00:00:00\",\n    \"2160-05-05 00:00:00\",\n    \"2160-05-26 00:00:00\",\n    \"2160-08-25 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-13 00:00:00\",\n    \"2161-05-04 00:00:00\",\n    \"2161-05-25 00:00:00\",\n    \"2161-08-31 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-28 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-05 00:00:00\",\n    \"2162-05-03 00:00:00\",\n    \"2162-05-31 00:00:00\",\n    \"2162-08-30 00:00:00\",\n    \"2162-12-27 00:00:00\",\n    \"2162-12-28 00:00:00\",\n    \"2163-01-03 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-05-02 00:00:00\",\n    \"2163-05-30 00:00:00\",\n    \"2163-08-29 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2163-12-27 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-09 00:00:00\",\n    \"2164-05-07 00:00:00\",\n    \"2164-05-28 00:00:00\",\n    \"2164-08-27 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-01 00:00:00\",\n    \"2165-05-06 00:00:00\",\n    \"2165-05-27 00:00:00\",\n    \"2165-08-26 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-04-21 00:00:00\",\n    \"2166-05-05 00:00:00\",\n    \"2166-05-26 00:00:00\",\n    \"2166-08-25 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-06 00:00:00\",\n    \"2167-05-04 00:00:00\",\n    \"2167-05-25 00:00:00\",\n    \"2167-08-31 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-28 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-03-28 00:00:00\",\n    \"2168-05-02 00:00:00\",\n    \"2168-05-30 00:00:00\",\n    \"2168-08-29 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2168-12-27 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-17 00:00:00\",\n    \"2169-05-01 00:00:00\",\n    \"2169-05-29 00:00:00\",\n    \"2169-08-28 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-02 00:00:00\",\n    \"2170-05-07 00:00:00\",\n    \"2170-05-28 00:00:00\",\n    \"2170-08-27 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-04-22 00:00:00\",\n    \"2171-05-06 00:00:00\",\n    \"2171-05-27 00:00:00\",\n    \"2171-08-26 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-13 00:00:00\",\n    \"2172-05-04 00:00:00\",\n    \"2172-05-25 00:00:00\",\n    \"2172-08-31 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-28 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-05 00:00:00\",\n    \"2173-05-03 00:00:00\",\n    \"2173-05-31 00:00:00\",\n    \"2173-08-30 00:00:00\",\n    \"2173-12-27 00:00:00\",\n    \"2173-12-28 00:00:00\",\n    \"2174-01-03 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-04-18 00:00:00\",\n    \"2174-05-02 00:00:00\",\n    \"2174-05-30 00:00:00\",\n    \"2174-08-29 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2174-12-27 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-10 00:00:00\",\n    \"2175-05-01 00:00:00\",\n    \"2175-05-29 00:00:00\",\n    \"2175-08-28 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-01 00:00:00\",\n    \"2176-05-06 00:00:00\",\n    \"2176-05-27 00:00:00\",\n    \"2176-08-26 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-04-21 00:00:00\",\n    \"2177-05-05 00:00:00\",\n    \"2177-05-26 00:00:00\",\n    \"2177-08-25 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-06 00:00:00\",\n    \"2178-05-04 00:00:00\",\n    \"2178-05-25 00:00:00\",\n    \"2178-08-31 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-28 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-03-29 00:00:00\",\n    \"2179-05-03 00:00:00\",\n    \"2179-05-31 00:00:00\",\n    \"2179-08-30 00:00:00\",\n    \"2179-12-27 00:00:00\",\n    \"2179-12-28 00:00:00\",\n    \"2180-01-03 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-17 00:00:00\",\n    \"2180-05-01 00:00:00\",\n    \"2180-05-29 00:00:00\",\n    \"2180-08-28 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-02 00:00:00\",\n    \"2181-05-07 00:00:00\",\n    \"2181-05-28 00:00:00\",\n    \"2181-08-27 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-04-22 00:00:00\",\n    \"2182-05-06 00:00:00\",\n    \"2182-05-27 00:00:00\",\n    \"2182-08-26 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-05-05 00:00:00\",\n    \"2183-05-26 00:00:00\",\n    \"2183-08-25 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-03-29 00:00:00\",\n    \"2184-05-03 00:00:00\",\n    \"2184-05-31 00:00:00\",\n    \"2184-08-30 00:00:00\",\n    \"2184-12-27 00:00:00\",\n    \"2184-12-28 00:00:00\",\n    \"2185-01-03 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-04-18 00:00:00\",\n    \"2185-05-02 00:00:00\",\n    \"2185-05-30 00:00:00\",\n    \"2185-08-29 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2185-12-27 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-10 00:00:00\",\n    \"2186-05-01 00:00:00\",\n    \"2186-05-29 00:00:00\",\n    \"2186-08-28 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-03-26 00:00:00\",\n    \"2187-05-07 00:00:00\",\n    \"2187-05-28 00:00:00\",\n    \"2187-08-27 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-05-05 00:00:00\",\n    \"2188-05-26 00:00:00\",\n    \"2188-08-25 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-06 00:00:00\",\n    \"2189-05-04 00:00:00\",\n    \"2189-05-25 00:00:00\",\n    \"2189-08-31 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-28 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-05-03 00:00:00\",\n    \"2190-05-31 00:00:00\",\n    \"2190-08-30 00:00:00\",\n    \"2190-12-27 00:00:00\",\n    \"2190-12-28 00:00:00\",\n    \"2191-01-03 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-11 00:00:00\",\n    \"2191-05-02 00:00:00\",\n    \"2191-05-30 00:00:00\",\n    \"2191-08-29 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2191-12-27 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-02 00:00:00\",\n    \"2192-05-07 00:00:00\",\n    \"2192-05-28 00:00:00\",\n    \"2192-08-27 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-04-22 00:00:00\",\n    \"2193-05-06 00:00:00\",\n    \"2193-05-27 00:00:00\",\n    \"2193-08-26 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-07 00:00:00\",\n    \"2194-05-05 00:00:00\",\n    \"2194-05-26 00:00:00\",\n    \"2194-08-25 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-03-30 00:00:00\",\n    \"2195-05-04 00:00:00\",\n    \"2195-05-25 00:00:00\",\n    \"2195-08-31 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-28 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-04-18 00:00:00\",\n    \"2196-05-02 00:00:00\",\n    \"2196-05-30 00:00:00\",\n    \"2196-08-29 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2196-12-27 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-10 00:00:00\",\n    \"2197-05-01 00:00:00\",\n    \"2197-05-29 00:00:00\",\n    \"2197-08-28 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-03-26 00:00:00\",\n    \"2198-05-07 00:00:00\",\n    \"2198-05-28 00:00:00\",\n    \"2198-08-27 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-15 00:00:00\",\n    \"2199-05-06 00:00:00\",\n    \"2199-05-27 00:00:00\",\n    \"2199-08-26 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-07 00:00:00\",\n    \"2200-05-05 00:00:00\",\n    \"2200-05-26 00:00:00\",\n    \"2200-08-25 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/ldn_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime\n\nimport pandas as pd\nfrom dateutil.relativedelta import MO\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n    next_monday,\n    next_monday_or_tuesday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, DateOffset, Day, Easter\n\nRULES = [\n    Holiday(\"New Year's Day Holiday\", month=1, day=1, observance=next_monday),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Easter Monday\", month=1, day=1, offset=[Easter(), Day(1)]),\n    Holiday(\n        \"UK Early May Bank Holiday Pre 2020\",\n        month=5,\n        day=1,\n        offset=DateOffset(weekday=MO(1)),\n        end_date=datetime(2020, 1, 1),\n    ),\n    Holiday(\"UK Early May Bank Holiday Rearranged 2020\", year=2020, month=5, day=8),\n    Holiday(\n        \"UK Early May Bank Holiday Post 2020 \",\n        month=5,\n        day=1,\n        offset=DateOffset(weekday=MO(1)),\n        start_date=datetime(2021, 1, 1),\n    ),\n    Holiday(\n        \"UK Spring Bank Holiday pre 2022\",\n        end_date=datetime(2022, 5, 1),\n        month=5,\n        day=31,\n        offset=DateOffset(weekday=MO(-1)),\n    ),\n    Holiday(\n        \"UK Spring Bank Holiday post 2022\",\n        start_date=datetime(2022, 7, 1),\n        month=5,\n        day=31,\n        offset=DateOffset(weekday=MO(-1)),\n    ),\n    Holiday(\"Queen Elizabeth II Jubilee Thu\", year=2022, month=6, day=2),\n    Holiday(\"Queen Elizabeth II Jubilee Fri\", year=2022, month=6, day=3),\n    Holiday(\"Queen Elizabeth II Funeral\", year=2022, month=9, day=19),\n    Holiday(\"King Charles III Coronation\", year=2023, month=5, day=8),\n    Holiday(\"UK Summer Bank Holiday\", month=8, day=31, offset=DateOffset(weekday=MO(-1))),\n    Holiday(\"Christmas Day Holiday\", month=12, day=25, observance=next_monday),\n    Holiday(\"Boxing Day Holiday\", month=12, day=26, observance=next_monday_or_tuesday),\n]\n\nCALENDAR = CustomBusinessDay(  # type: ignore[call-arg]\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/mex.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Mexico business day holiday calendar\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Feb 1st Mon (Constitution)\",\n//     \"Mar 3rd Mon (Benito Juarez)\",\n//     \"Thu before Easter (Maundy Thursday)\",\n//     \"Fri before Easter (Easter Friday)\",\n//     \"May 1 (Labor Day)\",\n//     \"Sep 16 (Independence Day)\",\n//     \"Nov 2 (All Souls)\",\n//     \"Nov 3rd Mon (Revolution)\",\n//     \"Dec 12 (Virgin of Guadalupe)\",\n//     \"Dec 25 (Christmas)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-02-02 00:00:00\",\n    \"1970-03-16 00:00:00\",\n    \"1970-03-26 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-05-01 00:00:00\",\n    \"1970-09-16 00:00:00\",\n    \"1970-11-02 00:00:00\",\n    \"1970-11-16 00:00:00\",\n    \"1970-12-12 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-02-01 00:00:00\",\n    \"1971-03-15 00:00:00\",\n    \"1971-04-08 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-05-01 00:00:00\",\n    \"1971-09-16 00:00:00\",\n    \"1971-11-02 00:00:00\",\n    \"1971-11-15 00:00:00\",\n    \"1971-12-12 00:00:00\",\n    \"1971-12-25 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-02-07 00:00:00\",\n    \"1972-03-20 00:00:00\",\n    \"1972-03-30 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-05-01 00:00:00\",\n    \"1972-09-16 00:00:00\",\n    \"1972-11-02 00:00:00\",\n    \"1972-11-20 00:00:00\",\n    \"1972-12-12 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-02-05 00:00:00\",\n    \"1973-03-19 00:00:00\",\n    \"1973-04-19 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-05-01 00:00:00\",\n    \"1973-09-16 00:00:00\",\n    \"1973-11-02 00:00:00\",\n    \"1973-11-19 00:00:00\",\n    \"1973-12-12 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-02-04 00:00:00\",\n    \"1974-03-18 00:00:00\",\n    \"1974-04-11 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-05-01 00:00:00\",\n    \"1974-09-16 00:00:00\",\n    \"1974-11-02 00:00:00\",\n    \"1974-11-18 00:00:00\",\n    \"1974-12-12 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-02-03 00:00:00\",\n    \"1975-03-17 00:00:00\",\n    \"1975-03-27 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-05-01 00:00:00\",\n    \"1975-09-16 00:00:00\",\n    \"1975-11-02 00:00:00\",\n    \"1975-11-17 00:00:00\",\n    \"1975-12-12 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-02-02 00:00:00\",\n    \"1976-03-15 00:00:00\",\n    \"1976-04-15 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-05-01 00:00:00\",\n    \"1976-09-16 00:00:00\",\n    \"1976-11-02 00:00:00\",\n    \"1976-11-15 00:00:00\",\n    \"1976-12-12 00:00:00\",\n    \"1976-12-25 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-02-07 00:00:00\",\n    \"1977-03-21 00:00:00\",\n    \"1977-04-07 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-05-01 00:00:00\",\n    \"1977-09-16 00:00:00\",\n    \"1977-11-02 00:00:00\",\n    \"1977-11-21 00:00:00\",\n    \"1977-12-12 00:00:00\",\n    \"1977-12-25 00:00:00\",\n    \"1978-01-01 00:00:00\",\n    \"1978-02-06 00:00:00\",\n    \"1978-03-20 00:00:00\",\n    \"1978-03-23 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-05-01 00:00:00\",\n    \"1978-09-16 00:00:00\",\n    \"1978-11-02 00:00:00\",\n    \"1978-11-20 00:00:00\",\n    \"1978-12-12 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-02-05 00:00:00\",\n    \"1979-03-19 00:00:00\",\n    \"1979-04-12 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-05-01 00:00:00\",\n    \"1979-09-16 00:00:00\",\n    \"1979-11-02 00:00:00\",\n    \"1979-11-19 00:00:00\",\n    \"1979-12-12 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-02-04 00:00:00\",\n    \"1980-03-17 00:00:00\",\n    \"1980-04-03 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-05-01 00:00:00\",\n    \"1980-09-16 00:00:00\",\n    \"1980-11-02 00:00:00\",\n    \"1980-11-17 00:00:00\",\n    \"1980-12-12 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-02-02 00:00:00\",\n    \"1981-03-16 00:00:00\",\n    \"1981-04-16 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-05-01 00:00:00\",\n    \"1981-09-16 00:00:00\",\n    \"1981-11-02 00:00:00\",\n    \"1981-11-16 00:00:00\",\n    \"1981-12-12 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-02-01 00:00:00\",\n    \"1982-03-15 00:00:00\",\n    \"1982-04-08 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-05-01 00:00:00\",\n    \"1982-09-16 00:00:00\",\n    \"1982-11-02 00:00:00\",\n    \"1982-11-15 00:00:00\",\n    \"1982-12-12 00:00:00\",\n    \"1982-12-25 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-02-07 00:00:00\",\n    \"1983-03-21 00:00:00\",\n    \"1983-03-31 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-05-01 00:00:00\",\n    \"1983-09-16 00:00:00\",\n    \"1983-11-02 00:00:00\",\n    \"1983-11-21 00:00:00\",\n    \"1983-12-12 00:00:00\",\n    \"1983-12-25 00:00:00\",\n    \"1984-01-01 00:00:00\",\n    \"1984-02-06 00:00:00\",\n    \"1984-03-19 00:00:00\",\n    \"1984-04-19 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-05-01 00:00:00\",\n    \"1984-09-16 00:00:00\",\n    \"1984-11-02 00:00:00\",\n    \"1984-11-19 00:00:00\",\n    \"1984-12-12 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-02-04 00:00:00\",\n    \"1985-03-18 00:00:00\",\n    \"1985-04-04 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-05-01 00:00:00\",\n    \"1985-09-16 00:00:00\",\n    \"1985-11-02 00:00:00\",\n    \"1985-11-18 00:00:00\",\n    \"1985-12-12 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-02-03 00:00:00\",\n    \"1986-03-17 00:00:00\",\n    \"1986-03-27 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-05-01 00:00:00\",\n    \"1986-09-16 00:00:00\",\n    \"1986-11-02 00:00:00\",\n    \"1986-11-17 00:00:00\",\n    \"1986-12-12 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-02-02 00:00:00\",\n    \"1987-03-16 00:00:00\",\n    \"1987-04-16 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-05-01 00:00:00\",\n    \"1987-09-16 00:00:00\",\n    \"1987-11-02 00:00:00\",\n    \"1987-11-16 00:00:00\",\n    \"1987-12-12 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-02-01 00:00:00\",\n    \"1988-03-21 00:00:00\",\n    \"1988-03-31 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-05-01 00:00:00\",\n    \"1988-09-16 00:00:00\",\n    \"1988-11-02 00:00:00\",\n    \"1988-11-21 00:00:00\",\n    \"1988-12-12 00:00:00\",\n    \"1988-12-25 00:00:00\",\n    \"1989-01-01 00:00:00\",\n    \"1989-02-06 00:00:00\",\n    \"1989-03-20 00:00:00\",\n    \"1989-03-23 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-05-01 00:00:00\",\n    \"1989-09-16 00:00:00\",\n    \"1989-11-02 00:00:00\",\n    \"1989-11-20 00:00:00\",\n    \"1989-12-12 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-02-05 00:00:00\",\n    \"1990-03-19 00:00:00\",\n    \"1990-04-12 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-05-01 00:00:00\",\n    \"1990-09-16 00:00:00\",\n    \"1990-11-02 00:00:00\",\n    \"1990-11-19 00:00:00\",\n    \"1990-12-12 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-02-04 00:00:00\",\n    \"1991-03-18 00:00:00\",\n    \"1991-03-28 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-05-01 00:00:00\",\n    \"1991-09-16 00:00:00\",\n    \"1991-11-02 00:00:00\",\n    \"1991-11-18 00:00:00\",\n    \"1991-12-12 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-02-03 00:00:00\",\n    \"1992-03-16 00:00:00\",\n    \"1992-04-16 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-05-01 00:00:00\",\n    \"1992-09-16 00:00:00\",\n    \"1992-11-02 00:00:00\",\n    \"1992-11-16 00:00:00\",\n    \"1992-12-12 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-02-01 00:00:00\",\n    \"1993-03-15 00:00:00\",\n    \"1993-04-08 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-05-01 00:00:00\",\n    \"1993-09-16 00:00:00\",\n    \"1993-11-02 00:00:00\",\n    \"1993-11-15 00:00:00\",\n    \"1993-12-12 00:00:00\",\n    \"1993-12-25 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-02-07 00:00:00\",\n    \"1994-03-21 00:00:00\",\n    \"1994-03-31 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-05-01 00:00:00\",\n    \"1994-09-16 00:00:00\",\n    \"1994-11-02 00:00:00\",\n    \"1994-11-21 00:00:00\",\n    \"1994-12-12 00:00:00\",\n    \"1994-12-25 00:00:00\",\n    \"1995-01-01 00:00:00\",\n    \"1995-02-06 00:00:00\",\n    \"1995-03-20 00:00:00\",\n    \"1995-04-13 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-05-01 00:00:00\",\n    \"1995-09-16 00:00:00\",\n    \"1995-11-02 00:00:00\",\n    \"1995-11-20 00:00:00\",\n    \"1995-12-12 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-02-05 00:00:00\",\n    \"1996-03-18 00:00:00\",\n    \"1996-04-04 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-05-01 00:00:00\",\n    \"1996-09-16 00:00:00\",\n    \"1996-11-02 00:00:00\",\n    \"1996-11-18 00:00:00\",\n    \"1996-12-12 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-02-03 00:00:00\",\n    \"1997-03-17 00:00:00\",\n    \"1997-03-27 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-05-01 00:00:00\",\n    \"1997-09-16 00:00:00\",\n    \"1997-11-02 00:00:00\",\n    \"1997-11-17 00:00:00\",\n    \"1997-12-12 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-02-02 00:00:00\",\n    \"1998-03-16 00:00:00\",\n    \"1998-04-09 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-05-01 00:00:00\",\n    \"1998-09-16 00:00:00\",\n    \"1998-11-02 00:00:00\",\n    \"1998-11-16 00:00:00\",\n    \"1998-12-12 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-02-01 00:00:00\",\n    \"1999-03-15 00:00:00\",\n    \"1999-04-01 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-05-01 00:00:00\",\n    \"1999-09-16 00:00:00\",\n    \"1999-11-02 00:00:00\",\n    \"1999-11-15 00:00:00\",\n    \"1999-12-12 00:00:00\",\n    \"1999-12-25 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-02-07 00:00:00\",\n    \"2000-03-20 00:00:00\",\n    \"2000-04-20 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-05-01 00:00:00\",\n    \"2000-09-16 00:00:00\",\n    \"2000-11-02 00:00:00\",\n    \"2000-11-20 00:00:00\",\n    \"2000-12-12 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-02-05 00:00:00\",\n    \"2001-03-19 00:00:00\",\n    \"2001-04-12 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-05-01 00:00:00\",\n    \"2001-09-16 00:00:00\",\n    \"2001-11-02 00:00:00\",\n    \"2001-11-19 00:00:00\",\n    \"2001-12-12 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-02-04 00:00:00\",\n    \"2002-03-18 00:00:00\",\n    \"2002-03-28 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-05-01 00:00:00\",\n    \"2002-09-16 00:00:00\",\n    \"2002-11-02 00:00:00\",\n    \"2002-11-18 00:00:00\",\n    \"2002-12-12 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-02-03 00:00:00\",\n    \"2003-03-17 00:00:00\",\n    \"2003-04-17 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-05-01 00:00:00\",\n    \"2003-09-16 00:00:00\",\n    \"2003-11-02 00:00:00\",\n    \"2003-11-17 00:00:00\",\n    \"2003-12-12 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-02-02 00:00:00\",\n    \"2004-03-15 00:00:00\",\n    \"2004-04-08 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-05-01 00:00:00\",\n    \"2004-09-16 00:00:00\",\n    \"2004-11-02 00:00:00\",\n    \"2004-11-15 00:00:00\",\n    \"2004-12-12 00:00:00\",\n    \"2004-12-25 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-02-07 00:00:00\",\n    \"2005-03-21 00:00:00\",\n    \"2005-03-24 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-05-01 00:00:00\",\n    \"2005-09-16 00:00:00\",\n    \"2005-11-02 00:00:00\",\n    \"2005-11-21 00:00:00\",\n    \"2005-12-12 00:00:00\",\n    \"2005-12-25 00:00:00\",\n    \"2006-01-01 00:00:00\",\n    \"2006-02-06 00:00:00\",\n    \"2006-03-20 00:00:00\",\n    \"2006-04-13 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-05-01 00:00:00\",\n    \"2006-09-16 00:00:00\",\n    \"2006-11-02 00:00:00\",\n    \"2006-11-20 00:00:00\",\n    \"2006-12-12 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-02-05 00:00:00\",\n    \"2007-03-19 00:00:00\",\n    \"2007-04-05 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-05-01 00:00:00\",\n    \"2007-09-16 00:00:00\",\n    \"2007-11-02 00:00:00\",\n    \"2007-11-19 00:00:00\",\n    \"2007-12-12 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-02-04 00:00:00\",\n    \"2008-03-17 00:00:00\",\n    \"2008-03-20 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-09-16 00:00:00\",\n    \"2008-11-02 00:00:00\",\n    \"2008-11-17 00:00:00\",\n    \"2008-12-12 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-02-02 00:00:00\",\n    \"2009-03-16 00:00:00\",\n    \"2009-04-09 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-05-01 00:00:00\",\n    \"2009-09-16 00:00:00\",\n    \"2009-11-02 00:00:00\",\n    \"2009-11-16 00:00:00\",\n    \"2009-12-12 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-02-01 00:00:00\",\n    \"2010-03-15 00:00:00\",\n    \"2010-04-01 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-05-01 00:00:00\",\n    \"2010-09-16 00:00:00\",\n    \"2010-11-02 00:00:00\",\n    \"2010-11-15 00:00:00\",\n    \"2010-12-12 00:00:00\",\n    \"2010-12-25 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-02-07 00:00:00\",\n    \"2011-03-21 00:00:00\",\n    \"2011-04-21 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-05-01 00:00:00\",\n    \"2011-09-16 00:00:00\",\n    \"2011-11-02 00:00:00\",\n    \"2011-11-21 00:00:00\",\n    \"2011-12-12 00:00:00\",\n    \"2011-12-25 00:00:00\",\n    \"2012-01-01 00:00:00\",\n    \"2012-02-06 00:00:00\",\n    \"2012-03-19 00:00:00\",\n    \"2012-04-05 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-05-01 00:00:00\",\n    \"2012-09-16 00:00:00\",\n    \"2012-11-02 00:00:00\",\n    \"2012-11-19 00:00:00\",\n    \"2012-12-12 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-02-04 00:00:00\",\n    \"2013-03-18 00:00:00\",\n    \"2013-03-28 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-05-01 00:00:00\",\n    \"2013-09-16 00:00:00\",\n    \"2013-11-02 00:00:00\",\n    \"2013-11-18 00:00:00\",\n    \"2013-12-12 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-02-03 00:00:00\",\n    \"2014-03-17 00:00:00\",\n    \"2014-04-17 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-05-01 00:00:00\",\n    \"2014-09-16 00:00:00\",\n    \"2014-11-02 00:00:00\",\n    \"2014-11-17 00:00:00\",\n    \"2014-12-12 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-02-02 00:00:00\",\n    \"2015-03-16 00:00:00\",\n    \"2015-04-02 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-05-01 00:00:00\",\n    \"2015-09-16 00:00:00\",\n    \"2015-11-02 00:00:00\",\n    \"2015-11-16 00:00:00\",\n    \"2015-12-12 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-02-01 00:00:00\",\n    \"2016-03-21 00:00:00\",\n    \"2016-03-24 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-05-01 00:00:00\",\n    \"2016-09-16 00:00:00\",\n    \"2016-11-02 00:00:00\",\n    \"2016-11-21 00:00:00\",\n    \"2016-12-12 00:00:00\",\n    \"2016-12-25 00:00:00\",\n    \"2017-01-01 00:00:00\",\n    \"2017-02-06 00:00:00\",\n    \"2017-03-20 00:00:00\",\n    \"2017-04-13 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-05-01 00:00:00\",\n    \"2017-09-16 00:00:00\",\n    \"2017-11-02 00:00:00\",\n    \"2017-11-20 00:00:00\",\n    \"2017-12-12 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-02-05 00:00:00\",\n    \"2018-03-19 00:00:00\",\n    \"2018-03-29 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-05-01 00:00:00\",\n    \"2018-09-16 00:00:00\",\n    \"2018-11-02 00:00:00\",\n    \"2018-11-19 00:00:00\",\n    \"2018-12-12 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-02-04 00:00:00\",\n    \"2019-03-18 00:00:00\",\n    \"2019-04-18 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-05-01 00:00:00\",\n    \"2019-09-16 00:00:00\",\n    \"2019-11-02 00:00:00\",\n    \"2019-11-18 00:00:00\",\n    \"2019-12-12 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-02-03 00:00:00\",\n    \"2020-03-16 00:00:00\",\n    \"2020-04-09 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-05-01 00:00:00\",\n    \"2020-09-16 00:00:00\",\n    \"2020-11-02 00:00:00\",\n    \"2020-11-16 00:00:00\",\n    \"2020-12-12 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-02-01 00:00:00\",\n    \"2021-03-15 00:00:00\",\n    \"2021-04-01 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-05-01 00:00:00\",\n    \"2021-09-16 00:00:00\",\n    \"2021-11-02 00:00:00\",\n    \"2021-11-15 00:00:00\",\n    \"2021-12-12 00:00:00\",\n    \"2021-12-25 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-02-07 00:00:00\",\n    \"2022-03-21 00:00:00\",\n    \"2022-04-14 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-05-01 00:00:00\",\n    \"2022-09-16 00:00:00\",\n    \"2022-11-02 00:00:00\",\n    \"2022-11-21 00:00:00\",\n    \"2022-12-12 00:00:00\",\n    \"2022-12-25 00:00:00\",\n    \"2023-01-01 00:00:00\",\n    \"2023-02-06 00:00:00\",\n    \"2023-03-20 00:00:00\",\n    \"2023-04-06 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-05-01 00:00:00\",\n    \"2023-09-16 00:00:00\",\n    \"2023-11-02 00:00:00\",\n    \"2023-11-20 00:00:00\",\n    \"2023-12-12 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-02-05 00:00:00\",\n    \"2024-03-18 00:00:00\",\n    \"2024-03-28 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-05-01 00:00:00\",\n    \"2024-09-16 00:00:00\",\n    \"2024-10-01 00:00:00\",\n    \"2024-11-02 00:00:00\",\n    \"2024-11-18 00:00:00\",\n    \"2024-12-12 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-02-03 00:00:00\",\n    \"2025-03-17 00:00:00\",\n    \"2025-04-17 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-05-01 00:00:00\",\n    \"2025-09-16 00:00:00\",\n    \"2025-11-02 00:00:00\",\n    \"2025-11-17 00:00:00\",\n    \"2025-12-12 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-02-02 00:00:00\",\n    \"2026-03-16 00:00:00\",\n    \"2026-04-02 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-05-01 00:00:00\",\n    \"2026-09-16 00:00:00\",\n    \"2026-11-02 00:00:00\",\n    \"2026-11-16 00:00:00\",\n    \"2026-12-12 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-02-01 00:00:00\",\n    \"2027-03-15 00:00:00\",\n    \"2027-03-25 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-05-01 00:00:00\",\n    \"2027-09-16 00:00:00\",\n    \"2027-11-02 00:00:00\",\n    \"2027-11-15 00:00:00\",\n    \"2027-12-12 00:00:00\",\n    \"2027-12-25 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-02-07 00:00:00\",\n    \"2028-03-20 00:00:00\",\n    \"2028-04-13 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-05-01 00:00:00\",\n    \"2028-09-16 00:00:00\",\n    \"2028-11-02 00:00:00\",\n    \"2028-11-20 00:00:00\",\n    \"2028-12-12 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-02-05 00:00:00\",\n    \"2029-03-19 00:00:00\",\n    \"2029-03-29 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-05-01 00:00:00\",\n    \"2029-09-16 00:00:00\",\n    \"2029-11-02 00:00:00\",\n    \"2029-11-19 00:00:00\",\n    \"2029-12-12 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-02-04 00:00:00\",\n    \"2030-03-18 00:00:00\",\n    \"2030-04-18 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-05-01 00:00:00\",\n    \"2030-09-16 00:00:00\",\n    \"2030-10-01 00:00:00\",\n    \"2030-11-02 00:00:00\",\n    \"2030-11-18 00:00:00\",\n    \"2030-12-12 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-02-03 00:00:00\",\n    \"2031-03-17 00:00:00\",\n    \"2031-04-10 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-05-01 00:00:00\",\n    \"2031-09-16 00:00:00\",\n    \"2031-11-02 00:00:00\",\n    \"2031-11-17 00:00:00\",\n    \"2031-12-12 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-02-02 00:00:00\",\n    \"2032-03-15 00:00:00\",\n    \"2032-03-25 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-05-01 00:00:00\",\n    \"2032-09-16 00:00:00\",\n    \"2032-11-02 00:00:00\",\n    \"2032-11-15 00:00:00\",\n    \"2032-12-12 00:00:00\",\n    \"2032-12-25 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-02-07 00:00:00\",\n    \"2033-03-21 00:00:00\",\n    \"2033-04-14 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-05-01 00:00:00\",\n    \"2033-09-16 00:00:00\",\n    \"2033-11-02 00:00:00\",\n    \"2033-11-21 00:00:00\",\n    \"2033-12-12 00:00:00\",\n    \"2033-12-25 00:00:00\",\n    \"2034-01-01 00:00:00\",\n    \"2034-02-06 00:00:00\",\n    \"2034-03-20 00:00:00\",\n    \"2034-04-06 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-05-01 00:00:00\",\n    \"2034-09-16 00:00:00\",\n    \"2034-11-02 00:00:00\",\n    \"2034-11-20 00:00:00\",\n    \"2034-12-12 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-02-05 00:00:00\",\n    \"2035-03-19 00:00:00\",\n    \"2035-03-22 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-05-01 00:00:00\",\n    \"2035-09-16 00:00:00\",\n    \"2035-11-02 00:00:00\",\n    \"2035-11-19 00:00:00\",\n    \"2035-12-12 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-02-04 00:00:00\",\n    \"2036-03-17 00:00:00\",\n    \"2036-04-10 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-05-01 00:00:00\",\n    \"2036-09-16 00:00:00\",\n    \"2036-10-01 00:00:00\",\n    \"2036-11-02 00:00:00\",\n    \"2036-11-17 00:00:00\",\n    \"2036-12-12 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-02-02 00:00:00\",\n    \"2037-03-16 00:00:00\",\n    \"2037-04-02 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-05-01 00:00:00\",\n    \"2037-09-16 00:00:00\",\n    \"2037-11-02 00:00:00\",\n    \"2037-11-16 00:00:00\",\n    \"2037-12-12 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-02-01 00:00:00\",\n    \"2038-03-15 00:00:00\",\n    \"2038-04-22 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-05-01 00:00:00\",\n    \"2038-09-16 00:00:00\",\n    \"2038-11-02 00:00:00\",\n    \"2038-11-15 00:00:00\",\n    \"2038-12-12 00:00:00\",\n    \"2038-12-25 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-02-07 00:00:00\",\n    \"2039-03-21 00:00:00\",\n    \"2039-04-07 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-05-01 00:00:00\",\n    \"2039-09-16 00:00:00\",\n    \"2039-11-02 00:00:00\",\n    \"2039-11-21 00:00:00\",\n    \"2039-12-12 00:00:00\",\n    \"2039-12-25 00:00:00\",\n    \"2040-01-01 00:00:00\",\n    \"2040-02-06 00:00:00\",\n    \"2040-03-19 00:00:00\",\n    \"2040-03-29 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-05-01 00:00:00\",\n    \"2040-09-16 00:00:00\",\n    \"2040-11-02 00:00:00\",\n    \"2040-11-19 00:00:00\",\n    \"2040-12-12 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-02-04 00:00:00\",\n    \"2041-03-18 00:00:00\",\n    \"2041-04-18 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-05-01 00:00:00\",\n    \"2041-09-16 00:00:00\",\n    \"2041-11-02 00:00:00\",\n    \"2041-11-18 00:00:00\",\n    \"2041-12-12 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-02-03 00:00:00\",\n    \"2042-03-17 00:00:00\",\n    \"2042-04-03 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-05-01 00:00:00\",\n    \"2042-09-16 00:00:00\",\n    \"2042-10-01 00:00:00\",\n    \"2042-11-02 00:00:00\",\n    \"2042-11-17 00:00:00\",\n    \"2042-12-12 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-02-02 00:00:00\",\n    \"2043-03-16 00:00:00\",\n    \"2043-03-26 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-05-01 00:00:00\",\n    \"2043-09-16 00:00:00\",\n    \"2043-11-02 00:00:00\",\n    \"2043-11-16 00:00:00\",\n    \"2043-12-12 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-02-01 00:00:00\",\n    \"2044-03-21 00:00:00\",\n    \"2044-04-14 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-05-01 00:00:00\",\n    \"2044-09-16 00:00:00\",\n    \"2044-11-02 00:00:00\",\n    \"2044-11-21 00:00:00\",\n    \"2044-12-12 00:00:00\",\n    \"2044-12-25 00:00:00\",\n    \"2045-01-01 00:00:00\",\n    \"2045-02-06 00:00:00\",\n    \"2045-03-20 00:00:00\",\n    \"2045-04-06 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-05-01 00:00:00\",\n    \"2045-09-16 00:00:00\",\n    \"2045-11-02 00:00:00\",\n    \"2045-11-20 00:00:00\",\n    \"2045-12-12 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-02-05 00:00:00\",\n    \"2046-03-19 00:00:00\",\n    \"2046-03-22 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-05-01 00:00:00\",\n    \"2046-09-16 00:00:00\",\n    \"2046-11-02 00:00:00\",\n    \"2046-11-19 00:00:00\",\n    \"2046-12-12 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-02-04 00:00:00\",\n    \"2047-03-18 00:00:00\",\n    \"2047-04-11 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-05-01 00:00:00\",\n    \"2047-09-16 00:00:00\",\n    \"2047-11-02 00:00:00\",\n    \"2047-11-18 00:00:00\",\n    \"2047-12-12 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-02-03 00:00:00\",\n    \"2048-03-16 00:00:00\",\n    \"2048-04-02 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-05-01 00:00:00\",\n    \"2048-09-16 00:00:00\",\n    \"2048-10-01 00:00:00\",\n    \"2048-11-02 00:00:00\",\n    \"2048-11-16 00:00:00\",\n    \"2048-12-12 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-02-01 00:00:00\",\n    \"2049-03-15 00:00:00\",\n    \"2049-04-15 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-05-01 00:00:00\",\n    \"2049-09-16 00:00:00\",\n    \"2049-11-02 00:00:00\",\n    \"2049-11-15 00:00:00\",\n    \"2049-12-12 00:00:00\",\n    \"2049-12-25 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-02-07 00:00:00\",\n    \"2050-03-21 00:00:00\",\n    \"2050-04-07 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-05-01 00:00:00\",\n    \"2050-09-16 00:00:00\",\n    \"2050-11-02 00:00:00\",\n    \"2050-11-21 00:00:00\",\n    \"2050-12-12 00:00:00\",\n    \"2050-12-25 00:00:00\",\n    \"2051-01-01 00:00:00\",\n    \"2051-02-06 00:00:00\",\n    \"2051-03-20 00:00:00\",\n    \"2051-03-30 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-05-01 00:00:00\",\n    \"2051-09-16 00:00:00\",\n    \"2051-11-02 00:00:00\",\n    \"2051-11-20 00:00:00\",\n    \"2051-12-12 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-02-05 00:00:00\",\n    \"2052-03-18 00:00:00\",\n    \"2052-04-18 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-05-01 00:00:00\",\n    \"2052-09-16 00:00:00\",\n    \"2052-11-02 00:00:00\",\n    \"2052-11-18 00:00:00\",\n    \"2052-12-12 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-02-03 00:00:00\",\n    \"2053-03-17 00:00:00\",\n    \"2053-04-03 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-05-01 00:00:00\",\n    \"2053-09-16 00:00:00\",\n    \"2053-11-02 00:00:00\",\n    \"2053-11-17 00:00:00\",\n    \"2053-12-12 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-02-02 00:00:00\",\n    \"2054-03-16 00:00:00\",\n    \"2054-03-26 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-05-01 00:00:00\",\n    \"2054-09-16 00:00:00\",\n    \"2054-10-01 00:00:00\",\n    \"2054-11-02 00:00:00\",\n    \"2054-11-16 00:00:00\",\n    \"2054-12-12 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-02-01 00:00:00\",\n    \"2055-03-15 00:00:00\",\n    \"2055-04-15 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-05-01 00:00:00\",\n    \"2055-09-16 00:00:00\",\n    \"2055-11-02 00:00:00\",\n    \"2055-11-15 00:00:00\",\n    \"2055-12-12 00:00:00\",\n    \"2055-12-25 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-02-07 00:00:00\",\n    \"2056-03-20 00:00:00\",\n    \"2056-03-30 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-05-01 00:00:00\",\n    \"2056-09-16 00:00:00\",\n    \"2056-11-02 00:00:00\",\n    \"2056-11-20 00:00:00\",\n    \"2056-12-12 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-02-05 00:00:00\",\n    \"2057-03-19 00:00:00\",\n    \"2057-04-19 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-05-01 00:00:00\",\n    \"2057-09-16 00:00:00\",\n    \"2057-11-02 00:00:00\",\n    \"2057-11-19 00:00:00\",\n    \"2057-12-12 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-02-04 00:00:00\",\n    \"2058-03-18 00:00:00\",\n    \"2058-04-11 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-05-01 00:00:00\",\n    \"2058-09-16 00:00:00\",\n    \"2058-11-02 00:00:00\",\n    \"2058-11-18 00:00:00\",\n    \"2058-12-12 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-02-03 00:00:00\",\n    \"2059-03-17 00:00:00\",\n    \"2059-03-27 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-05-01 00:00:00\",\n    \"2059-09-16 00:00:00\",\n    \"2059-11-02 00:00:00\",\n    \"2059-11-17 00:00:00\",\n    \"2059-12-12 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-02-02 00:00:00\",\n    \"2060-03-15 00:00:00\",\n    \"2060-04-15 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-05-01 00:00:00\",\n    \"2060-09-16 00:00:00\",\n    \"2060-10-01 00:00:00\",\n    \"2060-11-02 00:00:00\",\n    \"2060-11-15 00:00:00\",\n    \"2060-12-12 00:00:00\",\n    \"2060-12-25 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-02-07 00:00:00\",\n    \"2061-03-21 00:00:00\",\n    \"2061-04-07 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-05-01 00:00:00\",\n    \"2061-09-16 00:00:00\",\n    \"2061-11-02 00:00:00\",\n    \"2061-11-21 00:00:00\",\n    \"2061-12-12 00:00:00\",\n    \"2061-12-25 00:00:00\",\n    \"2062-01-01 00:00:00\",\n    \"2062-02-06 00:00:00\",\n    \"2062-03-20 00:00:00\",\n    \"2062-03-23 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-05-01 00:00:00\",\n    \"2062-09-16 00:00:00\",\n    \"2062-11-02 00:00:00\",\n    \"2062-11-20 00:00:00\",\n    \"2062-12-12 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-02-05 00:00:00\",\n    \"2063-03-19 00:00:00\",\n    \"2063-04-12 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-05-01 00:00:00\",\n    \"2063-09-16 00:00:00\",\n    \"2063-11-02 00:00:00\",\n    \"2063-11-19 00:00:00\",\n    \"2063-12-12 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-02-04 00:00:00\",\n    \"2064-03-17 00:00:00\",\n    \"2064-04-03 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-05-01 00:00:00\",\n    \"2064-09-16 00:00:00\",\n    \"2064-11-02 00:00:00\",\n    \"2064-11-17 00:00:00\",\n    \"2064-12-12 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-02-02 00:00:00\",\n    \"2065-03-16 00:00:00\",\n    \"2065-03-26 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-05-01 00:00:00\",\n    \"2065-09-16 00:00:00\",\n    \"2065-11-02 00:00:00\",\n    \"2065-11-16 00:00:00\",\n    \"2065-12-12 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-02-01 00:00:00\",\n    \"2066-03-15 00:00:00\",\n    \"2066-04-08 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-05-01 00:00:00\",\n    \"2066-09-16 00:00:00\",\n    \"2066-10-01 00:00:00\",\n    \"2066-11-02 00:00:00\",\n    \"2066-11-15 00:00:00\",\n    \"2066-12-12 00:00:00\",\n    \"2066-12-25 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-02-07 00:00:00\",\n    \"2067-03-21 00:00:00\",\n    \"2067-03-31 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-05-01 00:00:00\",\n    \"2067-09-16 00:00:00\",\n    \"2067-11-02 00:00:00\",\n    \"2067-11-21 00:00:00\",\n    \"2067-12-12 00:00:00\",\n    \"2067-12-25 00:00:00\",\n    \"2068-01-01 00:00:00\",\n    \"2068-02-06 00:00:00\",\n    \"2068-03-19 00:00:00\",\n    \"2068-04-19 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-05-01 00:00:00\",\n    \"2068-09-16 00:00:00\",\n    \"2068-11-02 00:00:00\",\n    \"2068-11-19 00:00:00\",\n    \"2068-12-12 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-02-04 00:00:00\",\n    \"2069-03-18 00:00:00\",\n    \"2069-04-11 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-05-01 00:00:00\",\n    \"2069-09-16 00:00:00\",\n    \"2069-11-02 00:00:00\",\n    \"2069-11-18 00:00:00\",\n    \"2069-12-12 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-02-03 00:00:00\",\n    \"2070-03-17 00:00:00\",\n    \"2070-03-27 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-05-01 00:00:00\",\n    \"2070-09-16 00:00:00\",\n    \"2070-11-02 00:00:00\",\n    \"2070-11-17 00:00:00\",\n    \"2070-12-12 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-02-02 00:00:00\",\n    \"2071-03-16 00:00:00\",\n    \"2071-04-16 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-05-01 00:00:00\",\n    \"2071-09-16 00:00:00\",\n    \"2071-11-02 00:00:00\",\n    \"2071-11-16 00:00:00\",\n    \"2071-12-12 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-02-01 00:00:00\",\n    \"2072-03-21 00:00:00\",\n    \"2072-04-07 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-05-01 00:00:00\",\n    \"2072-09-16 00:00:00\",\n    \"2072-10-01 00:00:00\",\n    \"2072-11-02 00:00:00\",\n    \"2072-11-21 00:00:00\",\n    \"2072-12-12 00:00:00\",\n    \"2072-12-25 00:00:00\",\n    \"2073-01-01 00:00:00\",\n    \"2073-02-06 00:00:00\",\n    \"2073-03-20 00:00:00\",\n    \"2073-03-23 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-05-01 00:00:00\",\n    \"2073-09-16 00:00:00\",\n    \"2073-11-02 00:00:00\",\n    \"2073-11-20 00:00:00\",\n    \"2073-12-12 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-02-05 00:00:00\",\n    \"2074-03-19 00:00:00\",\n    \"2074-04-12 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-05-01 00:00:00\",\n    \"2074-09-16 00:00:00\",\n    \"2074-11-02 00:00:00\",\n    \"2074-11-19 00:00:00\",\n    \"2074-12-12 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-02-04 00:00:00\",\n    \"2075-03-18 00:00:00\",\n    \"2075-04-04 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-05-01 00:00:00\",\n    \"2075-09-16 00:00:00\",\n    \"2075-11-02 00:00:00\",\n    \"2075-11-18 00:00:00\",\n    \"2075-12-12 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-02-03 00:00:00\",\n    \"2076-03-16 00:00:00\",\n    \"2076-04-16 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-05-01 00:00:00\",\n    \"2076-09-16 00:00:00\",\n    \"2076-11-02 00:00:00\",\n    \"2076-11-16 00:00:00\",\n    \"2076-12-12 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-02-01 00:00:00\",\n    \"2077-03-15 00:00:00\",\n    \"2077-04-08 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-05-01 00:00:00\",\n    \"2077-09-16 00:00:00\",\n    \"2077-11-02 00:00:00\",\n    \"2077-11-15 00:00:00\",\n    \"2077-12-12 00:00:00\",\n    \"2077-12-25 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-02-07 00:00:00\",\n    \"2078-03-21 00:00:00\",\n    \"2078-03-31 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-05-01 00:00:00\",\n    \"2078-09-16 00:00:00\",\n    \"2078-11-02 00:00:00\",\n    \"2078-11-21 00:00:00\",\n    \"2078-12-12 00:00:00\",\n    \"2078-12-25 00:00:00\",\n    \"2079-01-01 00:00:00\",\n    \"2079-02-06 00:00:00\",\n    \"2079-03-20 00:00:00\",\n    \"2079-04-20 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-05-01 00:00:00\",\n    \"2079-09-16 00:00:00\",\n    \"2079-11-02 00:00:00\",\n    \"2079-11-20 00:00:00\",\n    \"2079-12-12 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-02-05 00:00:00\",\n    \"2080-03-18 00:00:00\",\n    \"2080-04-04 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-05-01 00:00:00\",\n    \"2080-09-16 00:00:00\",\n    \"2080-11-02 00:00:00\",\n    \"2080-11-18 00:00:00\",\n    \"2080-12-12 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-02-03 00:00:00\",\n    \"2081-03-17 00:00:00\",\n    \"2081-03-27 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-05-01 00:00:00\",\n    \"2081-09-16 00:00:00\",\n    \"2081-11-02 00:00:00\",\n    \"2081-11-17 00:00:00\",\n    \"2081-12-12 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-02-02 00:00:00\",\n    \"2082-03-16 00:00:00\",\n    \"2082-04-16 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-05-01 00:00:00\",\n    \"2082-09-16 00:00:00\",\n    \"2082-11-02 00:00:00\",\n    \"2082-11-16 00:00:00\",\n    \"2082-12-12 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-02-01 00:00:00\",\n    \"2083-03-15 00:00:00\",\n    \"2083-04-01 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-05-01 00:00:00\",\n    \"2083-09-16 00:00:00\",\n    \"2083-11-02 00:00:00\",\n    \"2083-11-15 00:00:00\",\n    \"2083-12-12 00:00:00\",\n    \"2083-12-25 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-02-07 00:00:00\",\n    \"2084-03-20 00:00:00\",\n    \"2084-03-23 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-05-01 00:00:00\",\n    \"2084-09-16 00:00:00\",\n    \"2084-11-02 00:00:00\",\n    \"2084-11-20 00:00:00\",\n    \"2084-12-12 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-02-05 00:00:00\",\n    \"2085-03-19 00:00:00\",\n    \"2085-04-12 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-05-01 00:00:00\",\n    \"2085-09-16 00:00:00\",\n    \"2085-11-02 00:00:00\",\n    \"2085-11-19 00:00:00\",\n    \"2085-12-12 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-02-04 00:00:00\",\n    \"2086-03-18 00:00:00\",\n    \"2086-03-28 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-05-01 00:00:00\",\n    \"2086-09-16 00:00:00\",\n    \"2086-11-02 00:00:00\",\n    \"2086-11-18 00:00:00\",\n    \"2086-12-12 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-02-03 00:00:00\",\n    \"2087-03-17 00:00:00\",\n    \"2087-04-17 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-05-01 00:00:00\",\n    \"2087-09-16 00:00:00\",\n    \"2087-11-02 00:00:00\",\n    \"2087-11-17 00:00:00\",\n    \"2087-12-12 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-02-02 00:00:00\",\n    \"2088-03-15 00:00:00\",\n    \"2088-04-08 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-05-01 00:00:00\",\n    \"2088-09-16 00:00:00\",\n    \"2088-11-02 00:00:00\",\n    \"2088-11-15 00:00:00\",\n    \"2088-12-12 00:00:00\",\n    \"2088-12-25 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-02-07 00:00:00\",\n    \"2089-03-21 00:00:00\",\n    \"2089-03-31 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-05-01 00:00:00\",\n    \"2089-09-16 00:00:00\",\n    \"2089-11-02 00:00:00\",\n    \"2089-11-21 00:00:00\",\n    \"2089-12-12 00:00:00\",\n    \"2089-12-25 00:00:00\",\n    \"2090-01-01 00:00:00\",\n    \"2090-02-06 00:00:00\",\n    \"2090-03-20 00:00:00\",\n    \"2090-04-13 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-05-01 00:00:00\",\n    \"2090-09-16 00:00:00\",\n    \"2090-11-02 00:00:00\",\n    \"2090-11-20 00:00:00\",\n    \"2090-12-12 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-02-05 00:00:00\",\n    \"2091-03-19 00:00:00\",\n    \"2091-04-05 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-05-01 00:00:00\",\n    \"2091-09-16 00:00:00\",\n    \"2091-11-02 00:00:00\",\n    \"2091-11-19 00:00:00\",\n    \"2091-12-12 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-02-04 00:00:00\",\n    \"2092-03-17 00:00:00\",\n    \"2092-03-27 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-05-01 00:00:00\",\n    \"2092-09-16 00:00:00\",\n    \"2092-11-02 00:00:00\",\n    \"2092-11-17 00:00:00\",\n    \"2092-12-12 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-02-02 00:00:00\",\n    \"2093-03-16 00:00:00\",\n    \"2093-04-09 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-05-01 00:00:00\",\n    \"2093-09-16 00:00:00\",\n    \"2093-11-02 00:00:00\",\n    \"2093-11-16 00:00:00\",\n    \"2093-12-12 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-02-01 00:00:00\",\n    \"2094-03-15 00:00:00\",\n    \"2094-04-01 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-05-01 00:00:00\",\n    \"2094-09-16 00:00:00\",\n    \"2094-11-02 00:00:00\",\n    \"2094-11-15 00:00:00\",\n    \"2094-12-12 00:00:00\",\n    \"2094-12-25 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-02-07 00:00:00\",\n    \"2095-03-21 00:00:00\",\n    \"2095-04-21 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-05-01 00:00:00\",\n    \"2095-09-16 00:00:00\",\n    \"2095-11-02 00:00:00\",\n    \"2095-11-21 00:00:00\",\n    \"2095-12-12 00:00:00\",\n    \"2095-12-25 00:00:00\",\n    \"2096-01-01 00:00:00\",\n    \"2096-02-06 00:00:00\",\n    \"2096-03-19 00:00:00\",\n    \"2096-04-12 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-05-01 00:00:00\",\n    \"2096-09-16 00:00:00\",\n    \"2096-11-02 00:00:00\",\n    \"2096-11-19 00:00:00\",\n    \"2096-12-12 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-02-04 00:00:00\",\n    \"2097-03-18 00:00:00\",\n    \"2097-03-28 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-05-01 00:00:00\",\n    \"2097-09-16 00:00:00\",\n    \"2097-11-02 00:00:00\",\n    \"2097-11-18 00:00:00\",\n    \"2097-12-12 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-02-03 00:00:00\",\n    \"2098-03-17 00:00:00\",\n    \"2098-04-17 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-05-01 00:00:00\",\n    \"2098-09-16 00:00:00\",\n    \"2098-11-02 00:00:00\",\n    \"2098-11-17 00:00:00\",\n    \"2098-12-12 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-02-02 00:00:00\",\n    \"2099-03-16 00:00:00\",\n    \"2099-04-09 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-05-01 00:00:00\",\n    \"2099-09-16 00:00:00\",\n    \"2099-11-02 00:00:00\",\n    \"2099-11-16 00:00:00\",\n    \"2099-12-12 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-02-01 00:00:00\",\n    \"2100-03-15 00:00:00\",\n    \"2100-03-25 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-05-01 00:00:00\",\n    \"2100-09-16 00:00:00\",\n    \"2100-11-02 00:00:00\",\n    \"2100-11-15 00:00:00\",\n    \"2100-12-12 00:00:00\",\n    \"2100-12-25 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-02-07 00:00:00\",\n    \"2101-03-21 00:00:00\",\n    \"2101-04-14 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-05-01 00:00:00\",\n    \"2101-09-16 00:00:00\",\n    \"2101-11-02 00:00:00\",\n    \"2101-11-21 00:00:00\",\n    \"2101-12-12 00:00:00\",\n    \"2101-12-25 00:00:00\",\n    \"2102-01-01 00:00:00\",\n    \"2102-02-06 00:00:00\",\n    \"2102-03-20 00:00:00\",\n    \"2102-04-06 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-05-01 00:00:00\",\n    \"2102-09-16 00:00:00\",\n    \"2102-11-02 00:00:00\",\n    \"2102-11-20 00:00:00\",\n    \"2102-12-12 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-02-05 00:00:00\",\n    \"2103-03-19 00:00:00\",\n    \"2103-03-22 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-05-01 00:00:00\",\n    \"2103-09-16 00:00:00\",\n    \"2103-11-02 00:00:00\",\n    \"2103-11-19 00:00:00\",\n    \"2103-12-12 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-02-04 00:00:00\",\n    \"2104-03-17 00:00:00\",\n    \"2104-04-10 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-05-01 00:00:00\",\n    \"2104-09-16 00:00:00\",\n    \"2104-11-02 00:00:00\",\n    \"2104-11-17 00:00:00\",\n    \"2104-12-12 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-02-02 00:00:00\",\n    \"2105-03-16 00:00:00\",\n    \"2105-04-02 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-05-01 00:00:00\",\n    \"2105-09-16 00:00:00\",\n    \"2105-11-02 00:00:00\",\n    \"2105-11-16 00:00:00\",\n    \"2105-12-12 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-02-01 00:00:00\",\n    \"2106-03-15 00:00:00\",\n    \"2106-04-15 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-05-01 00:00:00\",\n    \"2106-09-16 00:00:00\",\n    \"2106-11-02 00:00:00\",\n    \"2106-11-15 00:00:00\",\n    \"2106-12-12 00:00:00\",\n    \"2106-12-25 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-02-07 00:00:00\",\n    \"2107-03-21 00:00:00\",\n    \"2107-04-07 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-05-01 00:00:00\",\n    \"2107-09-16 00:00:00\",\n    \"2107-11-02 00:00:00\",\n    \"2107-11-21 00:00:00\",\n    \"2107-12-12 00:00:00\",\n    \"2107-12-25 00:00:00\",\n    \"2108-01-01 00:00:00\",\n    \"2108-02-06 00:00:00\",\n    \"2108-03-19 00:00:00\",\n    \"2108-03-29 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-05-01 00:00:00\",\n    \"2108-09-16 00:00:00\",\n    \"2108-11-02 00:00:00\",\n    \"2108-11-19 00:00:00\",\n    \"2108-12-12 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-02-04 00:00:00\",\n    \"2109-03-18 00:00:00\",\n    \"2109-04-18 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-05-01 00:00:00\",\n    \"2109-09-16 00:00:00\",\n    \"2109-11-02 00:00:00\",\n    \"2109-11-18 00:00:00\",\n    \"2109-12-12 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-02-03 00:00:00\",\n    \"2110-03-17 00:00:00\",\n    \"2110-04-03 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-05-01 00:00:00\",\n    \"2110-09-16 00:00:00\",\n    \"2110-11-02 00:00:00\",\n    \"2110-11-17 00:00:00\",\n    \"2110-12-12 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-02-02 00:00:00\",\n    \"2111-03-16 00:00:00\",\n    \"2111-03-26 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-05-01 00:00:00\",\n    \"2111-09-16 00:00:00\",\n    \"2111-11-02 00:00:00\",\n    \"2111-11-16 00:00:00\",\n    \"2111-12-12 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-02-01 00:00:00\",\n    \"2112-03-21 00:00:00\",\n    \"2112-04-14 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-05-01 00:00:00\",\n    \"2112-09-16 00:00:00\",\n    \"2112-11-02 00:00:00\",\n    \"2112-11-21 00:00:00\",\n    \"2112-12-12 00:00:00\",\n    \"2112-12-25 00:00:00\",\n    \"2113-01-01 00:00:00\",\n    \"2113-02-06 00:00:00\",\n    \"2113-03-20 00:00:00\",\n    \"2113-03-30 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-05-01 00:00:00\",\n    \"2113-09-16 00:00:00\",\n    \"2113-11-02 00:00:00\",\n    \"2113-11-20 00:00:00\",\n    \"2113-12-12 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-02-05 00:00:00\",\n    \"2114-03-19 00:00:00\",\n    \"2114-04-19 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-05-01 00:00:00\",\n    \"2114-09-16 00:00:00\",\n    \"2114-11-02 00:00:00\",\n    \"2114-11-19 00:00:00\",\n    \"2114-12-12 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-02-04 00:00:00\",\n    \"2115-03-18 00:00:00\",\n    \"2115-04-11 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-05-01 00:00:00\",\n    \"2115-09-16 00:00:00\",\n    \"2115-11-02 00:00:00\",\n    \"2115-11-18 00:00:00\",\n    \"2115-12-12 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-02-03 00:00:00\",\n    \"2116-03-16 00:00:00\",\n    \"2116-03-26 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-05-01 00:00:00\",\n    \"2116-09-16 00:00:00\",\n    \"2116-11-02 00:00:00\",\n    \"2116-11-16 00:00:00\",\n    \"2116-12-12 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-02-01 00:00:00\",\n    \"2117-03-15 00:00:00\",\n    \"2117-04-15 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-05-01 00:00:00\",\n    \"2117-09-16 00:00:00\",\n    \"2117-11-02 00:00:00\",\n    \"2117-11-15 00:00:00\",\n    \"2117-12-12 00:00:00\",\n    \"2117-12-25 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-02-07 00:00:00\",\n    \"2118-03-21 00:00:00\",\n    \"2118-04-07 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-05-01 00:00:00\",\n    \"2118-09-16 00:00:00\",\n    \"2118-11-02 00:00:00\",\n    \"2118-11-21 00:00:00\",\n    \"2118-12-12 00:00:00\",\n    \"2118-12-25 00:00:00\",\n    \"2119-01-01 00:00:00\",\n    \"2119-02-06 00:00:00\",\n    \"2119-03-20 00:00:00\",\n    \"2119-03-23 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-05-01 00:00:00\",\n    \"2119-09-16 00:00:00\",\n    \"2119-11-02 00:00:00\",\n    \"2119-11-20 00:00:00\",\n    \"2119-12-12 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-02-05 00:00:00\",\n    \"2120-03-18 00:00:00\",\n    \"2120-04-11 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-05-01 00:00:00\",\n    \"2120-09-16 00:00:00\",\n    \"2120-11-02 00:00:00\",\n    \"2120-11-18 00:00:00\",\n    \"2120-12-12 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-02-03 00:00:00\",\n    \"2121-03-17 00:00:00\",\n    \"2121-04-03 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-05-01 00:00:00\",\n    \"2121-09-16 00:00:00\",\n    \"2121-11-02 00:00:00\",\n    \"2121-11-17 00:00:00\",\n    \"2121-12-12 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-02-02 00:00:00\",\n    \"2122-03-16 00:00:00\",\n    \"2122-03-26 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-05-01 00:00:00\",\n    \"2122-09-16 00:00:00\",\n    \"2122-11-02 00:00:00\",\n    \"2122-11-16 00:00:00\",\n    \"2122-12-12 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-02-01 00:00:00\",\n    \"2123-03-15 00:00:00\",\n    \"2123-04-08 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-05-01 00:00:00\",\n    \"2123-09-16 00:00:00\",\n    \"2123-11-02 00:00:00\",\n    \"2123-11-15 00:00:00\",\n    \"2123-12-12 00:00:00\",\n    \"2123-12-25 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-02-07 00:00:00\",\n    \"2124-03-20 00:00:00\",\n    \"2124-03-30 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-05-01 00:00:00\",\n    \"2124-09-16 00:00:00\",\n    \"2124-11-02 00:00:00\",\n    \"2124-11-20 00:00:00\",\n    \"2124-12-12 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-02-05 00:00:00\",\n    \"2125-03-19 00:00:00\",\n    \"2125-04-19 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-05-01 00:00:00\",\n    \"2125-09-16 00:00:00\",\n    \"2125-11-02 00:00:00\",\n    \"2125-11-19 00:00:00\",\n    \"2125-12-12 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-02-04 00:00:00\",\n    \"2126-03-18 00:00:00\",\n    \"2126-04-11 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-05-01 00:00:00\",\n    \"2126-09-16 00:00:00\",\n    \"2126-11-02 00:00:00\",\n    \"2126-11-18 00:00:00\",\n    \"2126-12-12 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-02-03 00:00:00\",\n    \"2127-03-17 00:00:00\",\n    \"2127-03-27 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-05-01 00:00:00\",\n    \"2127-09-16 00:00:00\",\n    \"2127-11-02 00:00:00\",\n    \"2127-11-17 00:00:00\",\n    \"2127-12-12 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-02-02 00:00:00\",\n    \"2128-03-15 00:00:00\",\n    \"2128-04-15 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-05-01 00:00:00\",\n    \"2128-09-16 00:00:00\",\n    \"2128-11-02 00:00:00\",\n    \"2128-11-15 00:00:00\",\n    \"2128-12-12 00:00:00\",\n    \"2128-12-25 00:00:00\",\n    \"2129-01-01 00:00:00\",\n    \"2129-02-07 00:00:00\",\n    \"2129-03-21 00:00:00\",\n    \"2129-04-07 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-05-01 00:00:00\",\n    \"2129-09-16 00:00:00\",\n    \"2129-11-02 00:00:00\",\n    \"2129-11-21 00:00:00\",\n    \"2129-12-12 00:00:00\",\n    \"2129-12-25 00:00:00\",\n    \"2130-01-01 00:00:00\",\n    \"2130-02-06 00:00:00\",\n    \"2130-03-20 00:00:00\",\n    \"2130-03-23 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-05-01 00:00:00\",\n    \"2130-09-16 00:00:00\",\n    \"2130-11-02 00:00:00\",\n    \"2130-11-20 00:00:00\",\n    \"2130-12-12 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-02-05 00:00:00\",\n    \"2131-03-19 00:00:00\",\n    \"2131-04-12 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-05-01 00:00:00\",\n    \"2131-09-16 00:00:00\",\n    \"2131-11-02 00:00:00\",\n    \"2131-11-19 00:00:00\",\n    \"2131-12-12 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-02-04 00:00:00\",\n    \"2132-03-17 00:00:00\",\n    \"2132-04-03 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-05-01 00:00:00\",\n    \"2132-09-16 00:00:00\",\n    \"2132-11-02 00:00:00\",\n    \"2132-11-17 00:00:00\",\n    \"2132-12-12 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-02-02 00:00:00\",\n    \"2133-03-16 00:00:00\",\n    \"2133-04-16 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-05-01 00:00:00\",\n    \"2133-09-16 00:00:00\",\n    \"2133-11-02 00:00:00\",\n    \"2133-11-16 00:00:00\",\n    \"2133-12-12 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-02-01 00:00:00\",\n    \"2134-03-15 00:00:00\",\n    \"2134-04-08 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-05-01 00:00:00\",\n    \"2134-09-16 00:00:00\",\n    \"2134-11-02 00:00:00\",\n    \"2134-11-15 00:00:00\",\n    \"2134-12-12 00:00:00\",\n    \"2134-12-25 00:00:00\",\n    \"2135-01-01 00:00:00\",\n    \"2135-02-07 00:00:00\",\n    \"2135-03-21 00:00:00\",\n    \"2135-03-31 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-05-01 00:00:00\",\n    \"2135-09-16 00:00:00\",\n    \"2135-11-02 00:00:00\",\n    \"2135-11-21 00:00:00\",\n    \"2135-12-12 00:00:00\",\n    \"2135-12-25 00:00:00\",\n    \"2136-01-01 00:00:00\",\n    \"2136-02-06 00:00:00\",\n    \"2136-03-19 00:00:00\",\n    \"2136-04-19 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-05-01 00:00:00\",\n    \"2136-09-16 00:00:00\",\n    \"2136-11-02 00:00:00\",\n    \"2136-11-19 00:00:00\",\n    \"2136-12-12 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-02-04 00:00:00\",\n    \"2137-03-18 00:00:00\",\n    \"2137-04-04 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-05-01 00:00:00\",\n    \"2137-09-16 00:00:00\",\n    \"2137-11-02 00:00:00\",\n    \"2137-11-18 00:00:00\",\n    \"2137-12-12 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-02-03 00:00:00\",\n    \"2138-03-17 00:00:00\",\n    \"2138-03-27 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-05-01 00:00:00\",\n    \"2138-09-16 00:00:00\",\n    \"2138-11-02 00:00:00\",\n    \"2138-11-17 00:00:00\",\n    \"2138-12-12 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-02-02 00:00:00\",\n    \"2139-03-16 00:00:00\",\n    \"2139-04-16 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-05-01 00:00:00\",\n    \"2139-09-16 00:00:00\",\n    \"2139-11-02 00:00:00\",\n    \"2139-11-16 00:00:00\",\n    \"2139-12-12 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-02-01 00:00:00\",\n    \"2140-03-21 00:00:00\",\n    \"2140-03-31 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-05-01 00:00:00\",\n    \"2140-09-16 00:00:00\",\n    \"2140-11-02 00:00:00\",\n    \"2140-11-21 00:00:00\",\n    \"2140-12-12 00:00:00\",\n    \"2140-12-25 00:00:00\",\n    \"2141-01-01 00:00:00\",\n    \"2141-02-06 00:00:00\",\n    \"2141-03-20 00:00:00\",\n    \"2141-03-23 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-05-01 00:00:00\",\n    \"2141-09-16 00:00:00\",\n    \"2141-11-02 00:00:00\",\n    \"2141-11-20 00:00:00\",\n    \"2141-12-12 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-02-05 00:00:00\",\n    \"2142-03-19 00:00:00\",\n    \"2142-04-12 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-05-01 00:00:00\",\n    \"2142-09-16 00:00:00\",\n    \"2142-11-02 00:00:00\",\n    \"2142-11-19 00:00:00\",\n    \"2142-12-12 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-02-04 00:00:00\",\n    \"2143-03-18 00:00:00\",\n    \"2143-03-28 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-05-01 00:00:00\",\n    \"2143-09-16 00:00:00\",\n    \"2143-11-02 00:00:00\",\n    \"2143-11-18 00:00:00\",\n    \"2143-12-12 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-02-03 00:00:00\",\n    \"2144-03-16 00:00:00\",\n    \"2144-04-16 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-05-01 00:00:00\",\n    \"2144-09-16 00:00:00\",\n    \"2144-11-02 00:00:00\",\n    \"2144-11-16 00:00:00\",\n    \"2144-12-12 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-02-01 00:00:00\",\n    \"2145-03-15 00:00:00\",\n    \"2145-04-08 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-05-01 00:00:00\",\n    \"2145-09-16 00:00:00\",\n    \"2145-11-02 00:00:00\",\n    \"2145-11-15 00:00:00\",\n    \"2145-12-12 00:00:00\",\n    \"2145-12-25 00:00:00\",\n    \"2146-01-01 00:00:00\",\n    \"2146-02-07 00:00:00\",\n    \"2146-03-21 00:00:00\",\n    \"2146-03-31 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-05-01 00:00:00\",\n    \"2146-09-16 00:00:00\",\n    \"2146-11-02 00:00:00\",\n    \"2146-11-21 00:00:00\",\n    \"2146-12-12 00:00:00\",\n    \"2146-12-25 00:00:00\",\n    \"2147-01-01 00:00:00\",\n    \"2147-02-06 00:00:00\",\n    \"2147-03-20 00:00:00\",\n    \"2147-04-13 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-05-01 00:00:00\",\n    \"2147-09-16 00:00:00\",\n    \"2147-11-02 00:00:00\",\n    \"2147-11-20 00:00:00\",\n    \"2147-12-12 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-02-05 00:00:00\",\n    \"2148-03-18 00:00:00\",\n    \"2148-04-04 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-05-01 00:00:00\",\n    \"2148-09-16 00:00:00\",\n    \"2148-11-02 00:00:00\",\n    \"2148-11-18 00:00:00\",\n    \"2148-12-12 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-02-03 00:00:00\",\n    \"2149-03-17 00:00:00\",\n    \"2149-03-27 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-05-01 00:00:00\",\n    \"2149-09-16 00:00:00\",\n    \"2149-11-02 00:00:00\",\n    \"2149-11-17 00:00:00\",\n    \"2149-12-12 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-02-02 00:00:00\",\n    \"2150-03-16 00:00:00\",\n    \"2150-04-09 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-05-01 00:00:00\",\n    \"2150-09-16 00:00:00\",\n    \"2150-11-02 00:00:00\",\n    \"2150-11-16 00:00:00\",\n    \"2150-12-12 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-02-01 00:00:00\",\n    \"2151-03-15 00:00:00\",\n    \"2151-04-01 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-05-01 00:00:00\",\n    \"2151-09-16 00:00:00\",\n    \"2151-11-02 00:00:00\",\n    \"2151-11-15 00:00:00\",\n    \"2151-12-12 00:00:00\",\n    \"2151-12-25 00:00:00\",\n    \"2152-01-01 00:00:00\",\n    \"2152-02-07 00:00:00\",\n    \"2152-03-20 00:00:00\",\n    \"2152-04-20 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-05-01 00:00:00\",\n    \"2152-09-16 00:00:00\",\n    \"2152-11-02 00:00:00\",\n    \"2152-11-20 00:00:00\",\n    \"2152-12-12 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-02-05 00:00:00\",\n    \"2153-03-19 00:00:00\",\n    \"2153-04-12 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-05-01 00:00:00\",\n    \"2153-09-16 00:00:00\",\n    \"2153-11-02 00:00:00\",\n    \"2153-11-19 00:00:00\",\n    \"2153-12-12 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-02-04 00:00:00\",\n    \"2154-03-18 00:00:00\",\n    \"2154-03-28 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-05-01 00:00:00\",\n    \"2154-09-16 00:00:00\",\n    \"2154-11-02 00:00:00\",\n    \"2154-11-18 00:00:00\",\n    \"2154-12-12 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-02-03 00:00:00\",\n    \"2155-03-17 00:00:00\",\n    \"2155-04-17 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-05-01 00:00:00\",\n    \"2155-09-16 00:00:00\",\n    \"2155-11-02 00:00:00\",\n    \"2155-11-17 00:00:00\",\n    \"2155-12-12 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-02-02 00:00:00\",\n    \"2156-03-15 00:00:00\",\n    \"2156-04-08 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-05-01 00:00:00\",\n    \"2156-09-16 00:00:00\",\n    \"2156-11-02 00:00:00\",\n    \"2156-11-15 00:00:00\",\n    \"2156-12-12 00:00:00\",\n    \"2156-12-25 00:00:00\",\n    \"2157-01-01 00:00:00\",\n    \"2157-02-07 00:00:00\",\n    \"2157-03-21 00:00:00\",\n    \"2157-03-24 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-05-01 00:00:00\",\n    \"2157-09-16 00:00:00\",\n    \"2157-11-02 00:00:00\",\n    \"2157-11-21 00:00:00\",\n    \"2157-12-12 00:00:00\",\n    \"2157-12-25 00:00:00\",\n    \"2158-01-01 00:00:00\",\n    \"2158-02-06 00:00:00\",\n    \"2158-03-20 00:00:00\",\n    \"2158-04-13 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-05-01 00:00:00\",\n    \"2158-09-16 00:00:00\",\n    \"2158-11-02 00:00:00\",\n    \"2158-11-20 00:00:00\",\n    \"2158-12-12 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-02-05 00:00:00\",\n    \"2159-03-19 00:00:00\",\n    \"2159-04-05 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-05-01 00:00:00\",\n    \"2159-09-16 00:00:00\",\n    \"2159-11-02 00:00:00\",\n    \"2159-11-19 00:00:00\",\n    \"2159-12-12 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-02-04 00:00:00\",\n    \"2160-03-17 00:00:00\",\n    \"2160-03-20 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-09-16 00:00:00\",\n    \"2160-11-02 00:00:00\",\n    \"2160-11-17 00:00:00\",\n    \"2160-12-12 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-02-02 00:00:00\",\n    \"2161-03-16 00:00:00\",\n    \"2161-04-09 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-05-01 00:00:00\",\n    \"2161-09-16 00:00:00\",\n    \"2161-11-02 00:00:00\",\n    \"2161-11-16 00:00:00\",\n    \"2161-12-12 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-02-01 00:00:00\",\n    \"2162-03-15 00:00:00\",\n    \"2162-04-01 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-05-01 00:00:00\",\n    \"2162-09-16 00:00:00\",\n    \"2162-11-02 00:00:00\",\n    \"2162-11-15 00:00:00\",\n    \"2162-12-12 00:00:00\",\n    \"2162-12-25 00:00:00\",\n    \"2163-01-01 00:00:00\",\n    \"2163-02-07 00:00:00\",\n    \"2163-03-21 00:00:00\",\n    \"2163-04-21 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-05-01 00:00:00\",\n    \"2163-09-16 00:00:00\",\n    \"2163-11-02 00:00:00\",\n    \"2163-11-21 00:00:00\",\n    \"2163-12-12 00:00:00\",\n    \"2163-12-25 00:00:00\",\n    \"2164-01-01 00:00:00\",\n    \"2164-02-06 00:00:00\",\n    \"2164-03-19 00:00:00\",\n    \"2164-04-05 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-05-01 00:00:00\",\n    \"2164-09-16 00:00:00\",\n    \"2164-11-02 00:00:00\",\n    \"2164-11-19 00:00:00\",\n    \"2164-12-12 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-02-04 00:00:00\",\n    \"2165-03-18 00:00:00\",\n    \"2165-03-28 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-05-01 00:00:00\",\n    \"2165-09-16 00:00:00\",\n    \"2165-11-02 00:00:00\",\n    \"2165-11-18 00:00:00\",\n    \"2165-12-12 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-02-03 00:00:00\",\n    \"2166-03-17 00:00:00\",\n    \"2166-04-17 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-05-01 00:00:00\",\n    \"2166-09-16 00:00:00\",\n    \"2166-11-02 00:00:00\",\n    \"2166-11-17 00:00:00\",\n    \"2166-12-12 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-02-02 00:00:00\",\n    \"2167-03-16 00:00:00\",\n    \"2167-04-02 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-05-01 00:00:00\",\n    \"2167-09-16 00:00:00\",\n    \"2167-11-02 00:00:00\",\n    \"2167-11-16 00:00:00\",\n    \"2167-12-12 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-02-01 00:00:00\",\n    \"2168-03-21 00:00:00\",\n    \"2168-03-24 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-05-01 00:00:00\",\n    \"2168-09-16 00:00:00\",\n    \"2168-11-02 00:00:00\",\n    \"2168-11-21 00:00:00\",\n    \"2168-12-12 00:00:00\",\n    \"2168-12-25 00:00:00\",\n    \"2169-01-01 00:00:00\",\n    \"2169-02-06 00:00:00\",\n    \"2169-03-20 00:00:00\",\n    \"2169-04-13 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-05-01 00:00:00\",\n    \"2169-09-16 00:00:00\",\n    \"2169-11-02 00:00:00\",\n    \"2169-11-20 00:00:00\",\n    \"2169-12-12 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-02-05 00:00:00\",\n    \"2170-03-19 00:00:00\",\n    \"2170-03-29 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-05-01 00:00:00\",\n    \"2170-09-16 00:00:00\",\n    \"2170-11-02 00:00:00\",\n    \"2170-11-19 00:00:00\",\n    \"2170-12-12 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-02-04 00:00:00\",\n    \"2171-03-18 00:00:00\",\n    \"2171-04-18 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-05-01 00:00:00\",\n    \"2171-09-16 00:00:00\",\n    \"2171-11-02 00:00:00\",\n    \"2171-11-18 00:00:00\",\n    \"2171-12-12 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-02-03 00:00:00\",\n    \"2172-03-16 00:00:00\",\n    \"2172-04-09 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-05-01 00:00:00\",\n    \"2172-09-16 00:00:00\",\n    \"2172-11-02 00:00:00\",\n    \"2172-11-16 00:00:00\",\n    \"2172-12-12 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-02-01 00:00:00\",\n    \"2173-03-15 00:00:00\",\n    \"2173-04-01 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-05-01 00:00:00\",\n    \"2173-09-16 00:00:00\",\n    \"2173-11-02 00:00:00\",\n    \"2173-11-15 00:00:00\",\n    \"2173-12-12 00:00:00\",\n    \"2173-12-25 00:00:00\",\n    \"2174-01-01 00:00:00\",\n    \"2174-02-07 00:00:00\",\n    \"2174-03-21 00:00:00\",\n    \"2174-04-14 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-05-01 00:00:00\",\n    \"2174-09-16 00:00:00\",\n    \"2174-11-02 00:00:00\",\n    \"2174-11-21 00:00:00\",\n    \"2174-12-12 00:00:00\",\n    \"2174-12-25 00:00:00\",\n    \"2175-01-01 00:00:00\",\n    \"2175-02-06 00:00:00\",\n    \"2175-03-20 00:00:00\",\n    \"2175-04-06 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-05-01 00:00:00\",\n    \"2175-09-16 00:00:00\",\n    \"2175-11-02 00:00:00\",\n    \"2175-11-20 00:00:00\",\n    \"2175-12-12 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-02-05 00:00:00\",\n    \"2176-03-18 00:00:00\",\n    \"2176-03-28 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-05-01 00:00:00\",\n    \"2176-09-16 00:00:00\",\n    \"2176-11-02 00:00:00\",\n    \"2176-11-18 00:00:00\",\n    \"2176-12-12 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-02-03 00:00:00\",\n    \"2177-03-17 00:00:00\",\n    \"2177-04-17 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-05-01 00:00:00\",\n    \"2177-09-16 00:00:00\",\n    \"2177-11-02 00:00:00\",\n    \"2177-11-17 00:00:00\",\n    \"2177-12-12 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-02-02 00:00:00\",\n    \"2178-03-16 00:00:00\",\n    \"2178-04-02 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-05-01 00:00:00\",\n    \"2178-09-16 00:00:00\",\n    \"2178-11-02 00:00:00\",\n    \"2178-11-16 00:00:00\",\n    \"2178-12-12 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-02-01 00:00:00\",\n    \"2179-03-15 00:00:00\",\n    \"2179-03-25 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-05-01 00:00:00\",\n    \"2179-09-16 00:00:00\",\n    \"2179-11-02 00:00:00\",\n    \"2179-11-15 00:00:00\",\n    \"2179-12-12 00:00:00\",\n    \"2179-12-25 00:00:00\",\n    \"2180-01-01 00:00:00\",\n    \"2180-02-07 00:00:00\",\n    \"2180-03-20 00:00:00\",\n    \"2180-04-13 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-05-01 00:00:00\",\n    \"2180-09-16 00:00:00\",\n    \"2180-11-02 00:00:00\",\n    \"2180-11-20 00:00:00\",\n    \"2180-12-12 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-02-05 00:00:00\",\n    \"2181-03-19 00:00:00\",\n    \"2181-03-29 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-05-01 00:00:00\",\n    \"2181-09-16 00:00:00\",\n    \"2181-11-02 00:00:00\",\n    \"2181-11-19 00:00:00\",\n    \"2181-12-12 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-02-04 00:00:00\",\n    \"2182-03-18 00:00:00\",\n    \"2182-04-18 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-05-01 00:00:00\",\n    \"2182-09-16 00:00:00\",\n    \"2182-11-02 00:00:00\",\n    \"2182-11-18 00:00:00\",\n    \"2182-12-12 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-02-03 00:00:00\",\n    \"2183-03-17 00:00:00\",\n    \"2183-04-10 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-05-01 00:00:00\",\n    \"2183-09-16 00:00:00\",\n    \"2183-11-02 00:00:00\",\n    \"2183-11-17 00:00:00\",\n    \"2183-12-12 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-02-02 00:00:00\",\n    \"2184-03-15 00:00:00\",\n    \"2184-03-25 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-05-01 00:00:00\",\n    \"2184-09-16 00:00:00\",\n    \"2184-11-02 00:00:00\",\n    \"2184-11-15 00:00:00\",\n    \"2184-12-12 00:00:00\",\n    \"2184-12-25 00:00:00\",\n    \"2185-01-01 00:00:00\",\n    \"2185-02-07 00:00:00\",\n    \"2185-03-21 00:00:00\",\n    \"2185-04-14 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-05-01 00:00:00\",\n    \"2185-09-16 00:00:00\",\n    \"2185-11-02 00:00:00\",\n    \"2185-11-21 00:00:00\",\n    \"2185-12-12 00:00:00\",\n    \"2185-12-25 00:00:00\",\n    \"2186-01-01 00:00:00\",\n    \"2186-02-06 00:00:00\",\n    \"2186-03-20 00:00:00\",\n    \"2186-04-06 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-05-01 00:00:00\",\n    \"2186-09-16 00:00:00\",\n    \"2186-11-02 00:00:00\",\n    \"2186-11-20 00:00:00\",\n    \"2186-12-12 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-02-05 00:00:00\",\n    \"2187-03-19 00:00:00\",\n    \"2187-03-22 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-05-01 00:00:00\",\n    \"2187-09-16 00:00:00\",\n    \"2187-11-02 00:00:00\",\n    \"2187-11-19 00:00:00\",\n    \"2187-12-12 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-02-04 00:00:00\",\n    \"2188-03-17 00:00:00\",\n    \"2188-04-10 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-05-01 00:00:00\",\n    \"2188-09-16 00:00:00\",\n    \"2188-11-02 00:00:00\",\n    \"2188-11-17 00:00:00\",\n    \"2188-12-12 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-02-02 00:00:00\",\n    \"2189-03-16 00:00:00\",\n    \"2189-04-02 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-05-01 00:00:00\",\n    \"2189-09-16 00:00:00\",\n    \"2189-11-02 00:00:00\",\n    \"2189-11-16 00:00:00\",\n    \"2189-12-12 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-02-01 00:00:00\",\n    \"2190-03-15 00:00:00\",\n    \"2190-04-22 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-05-01 00:00:00\",\n    \"2190-09-16 00:00:00\",\n    \"2190-11-02 00:00:00\",\n    \"2190-11-15 00:00:00\",\n    \"2190-12-12 00:00:00\",\n    \"2190-12-25 00:00:00\",\n    \"2191-01-01 00:00:00\",\n    \"2191-02-07 00:00:00\",\n    \"2191-03-21 00:00:00\",\n    \"2191-04-07 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-05-01 00:00:00\",\n    \"2191-09-16 00:00:00\",\n    \"2191-11-02 00:00:00\",\n    \"2191-11-21 00:00:00\",\n    \"2191-12-12 00:00:00\",\n    \"2191-12-25 00:00:00\",\n    \"2192-01-01 00:00:00\",\n    \"2192-02-06 00:00:00\",\n    \"2192-03-19 00:00:00\",\n    \"2192-03-29 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-05-01 00:00:00\",\n    \"2192-09-16 00:00:00\",\n    \"2192-11-02 00:00:00\",\n    \"2192-11-19 00:00:00\",\n    \"2192-12-12 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-02-04 00:00:00\",\n    \"2193-03-18 00:00:00\",\n    \"2193-04-18 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-05-01 00:00:00\",\n    \"2193-09-16 00:00:00\",\n    \"2193-11-02 00:00:00\",\n    \"2193-11-18 00:00:00\",\n    \"2193-12-12 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-02-03 00:00:00\",\n    \"2194-03-17 00:00:00\",\n    \"2194-04-03 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-05-01 00:00:00\",\n    \"2194-09-16 00:00:00\",\n    \"2194-11-02 00:00:00\",\n    \"2194-11-17 00:00:00\",\n    \"2194-12-12 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-02-02 00:00:00\",\n    \"2195-03-16 00:00:00\",\n    \"2195-03-26 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-05-01 00:00:00\",\n    \"2195-09-16 00:00:00\",\n    \"2195-11-02 00:00:00\",\n    \"2195-11-16 00:00:00\",\n    \"2195-12-12 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-02-01 00:00:00\",\n    \"2196-03-21 00:00:00\",\n    \"2196-04-14 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-05-01 00:00:00\",\n    \"2196-09-16 00:00:00\",\n    \"2196-11-02 00:00:00\",\n    \"2196-11-21 00:00:00\",\n    \"2196-12-12 00:00:00\",\n    \"2196-12-25 00:00:00\",\n    \"2197-01-01 00:00:00\",\n    \"2197-02-06 00:00:00\",\n    \"2197-03-20 00:00:00\",\n    \"2197-04-06 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-05-01 00:00:00\",\n    \"2197-09-16 00:00:00\",\n    \"2197-11-02 00:00:00\",\n    \"2197-11-20 00:00:00\",\n    \"2197-12-12 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-02-05 00:00:00\",\n    \"2198-03-19 00:00:00\",\n    \"2198-03-22 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-05-01 00:00:00\",\n    \"2198-09-16 00:00:00\",\n    \"2198-11-02 00:00:00\",\n    \"2198-11-19 00:00:00\",\n    \"2198-12-12 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-02-04 00:00:00\",\n    \"2199-03-18 00:00:00\",\n    \"2199-04-11 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-05-01 00:00:00\",\n    \"2199-09-16 00:00:00\",\n    \"2199-11-02 00:00:00\",\n    \"2199-11-18 00:00:00\",\n    \"2199-12-12 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-02-03 00:00:00\",\n    \"2200-03-17 00:00:00\",\n    \"2200-04-03 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-05-01 00:00:00\",\n    \"2200-09-16 00:00:00\",\n    \"2200-11-02 00:00:00\",\n    \"2200-11-17 00:00:00\",\n    \"2200-12-12 00:00:00\",\n    \"2200-12-25 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/mex_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pandas as pd\nfrom dateutil.relativedelta import MO\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, DateOffset, Day, Easter\n\nRULES = [\n    Holiday(\"New Year's Day Holiday\", month=1, day=1),\n    Holiday(\"Constitution Day\", month=2, day=1, offset=DateOffset(weekday=MO(1))),\n    Holiday(\"Birth of Benito Juarez\", month=3, day=1, offset=DateOffset(weekday=MO(3))),\n    Holiday(\"Maundy Thursday\", month=1, day=1, offset=[Easter(), Day(-3)]),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Labor Day\", month=5, day=1),\n    Holiday(\"Independence Day\", month=9, day=16),\n    Holiday(\"All Souls' Day\", month=11, day=2),\n    Holiday(\"Revolution Day\", month=11, day=1, offset=DateOffset(weekday=MO(3))),\n    Holiday(\"Day of the Virgin of Guadalupe\", month=12, day=12),\n    Holiday(\"Christmas Day\", month=12, day=25),\n    *[Holiday(f\"Election {y}\", year=y, month=10, day=1) for y in range(2024, 2075, 6)],\n]\n\nCALENDAR = CustomBusinessDay(  # type: ignore[call-arg]\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Static data for pre-existing named holiday calendars.\n//!\n\npub mod all;\npub mod bjs;\npub mod bus;\npub mod fed;\npub mod ldn;\npub mod mex;\npub mod mum;\npub mod nsw;\npub mod nyc;\npub mod osl;\npub mod stk;\npub mod syd;\npub mod tgt;\npub mod tro;\npub mod tyo;\npub mod wlg;\npub mod zur;\n\nuse chrono::NaiveDateTime;\nuse std::collections::HashMap;\nuse std::sync::LazyLock;\n\npub(crate) static WEEKMASKS: LazyLock<HashMap<&str, &[u8]>> = LazyLock::new(|| {\n    HashMap::from([\n        (\"all\", all::WEEKMASK),\n        (\"bus\", bus::WEEKMASK),\n        (\"bjs\", bjs::WEEKMASK),\n        (\"nyc\", nyc::WEEKMASK),\n        (\"fed\", fed::WEEKMASK),\n        (\"tgt\", tgt::WEEKMASK),\n        (\"ldn\", ldn::WEEKMASK),\n        (\"stk\", stk::WEEKMASK),\n        (\"osl\", osl::WEEKMASK),\n        (\"zur\", zur::WEEKMASK),\n        (\"tro\", tro::WEEKMASK),\n        (\"tyo\", tyo::WEEKMASK),\n        (\"syd\", syd::WEEKMASK),\n        (\"nsw\", nsw::WEEKMASK),\n        (\"wlg\", wlg::WEEKMASK),\n        (\"mum\", mum::WEEKMASK),\n        (\"mex\", mex::WEEKMASK),\n    ])\n});\n\npub(crate) static HOLIDAYS: LazyLock<HashMap<&str, Vec<NaiveDateTime>>> = LazyLock::new(|| {\n    let temp = HashMap::<&str, &[&str]>::from([\n        (\"all\", all::HOLIDAYS),\n        (\"bus\", bus::HOLIDAYS),\n        (\"bjs\", bjs::HOLIDAYS),\n        (\"nyc\", nyc::HOLIDAYS),\n        (\"fed\", fed::HOLIDAYS),\n        (\"tgt\", tgt::HOLIDAYS),\n        (\"ldn\", ldn::HOLIDAYS),\n        (\"stk\", stk::HOLIDAYS),\n        (\"osl\", osl::HOLIDAYS),\n        (\"zur\", zur::HOLIDAYS),\n        (\"tro\", tro::HOLIDAYS),\n        (\"tyo\", tyo::HOLIDAYS),\n        (\"syd\", syd::HOLIDAYS),\n        (\"nsw\", nsw::HOLIDAYS),\n        (\"wlg\", wlg::HOLIDAYS),\n        (\"mum\", mum::HOLIDAYS),\n        (\"mex\", mex::HOLIDAYS),\n    ]);\n    let mut m: HashMap<&str, Vec<NaiveDateTime>> = HashMap::new();\n    for (k, v) in temp.into_iter() {\n        m.insert(\n            k,\n            v.iter()\n                .map(|x| NaiveDateTime::parse_from_str(x, \"%Y-%m-%d %H:%M:%S\").unwrap())\n                .collect(),\n        );\n    }\n    m\n});\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {}\n"
  },
  {
    "path": "rust/scheduling/calendars/named/mum.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a New York business day calendar, aligned with SOFR publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 26 (Republic Day)\",\n//     \"Fri before Easter (Easter Friday)\",\n//     \"Apr 14 (Ambedkar Jayanti)\",\n//     \"May 1 (May Day)\",\n//     \"Aug 15 (Independence Day)\",\n//     \"Oct 2 (Gandhi Jayanti)\",\n//     \"Dec 25 (Christmas)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-26 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-04-14 00:00:00\",\n    \"1970-05-01 00:00:00\",\n    \"1970-08-15 00:00:00\",\n    \"1970-10-02 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1971-01-26 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-14 00:00:00\",\n    \"1971-05-01 00:00:00\",\n    \"1971-08-15 00:00:00\",\n    \"1971-10-02 00:00:00\",\n    \"1971-12-25 00:00:00\",\n    \"1972-01-26 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-14 00:00:00\",\n    \"1972-05-01 00:00:00\",\n    \"1972-08-15 00:00:00\",\n    \"1972-10-02 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1973-01-26 00:00:00\",\n    \"1973-04-14 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-05-01 00:00:00\",\n    \"1973-08-15 00:00:00\",\n    \"1973-10-02 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1974-01-26 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-14 00:00:00\",\n    \"1974-05-01 00:00:00\",\n    \"1974-08-15 00:00:00\",\n    \"1974-10-02 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1975-01-26 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-04-14 00:00:00\",\n    \"1975-05-01 00:00:00\",\n    \"1975-08-15 00:00:00\",\n    \"1975-10-02 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1976-01-26 00:00:00\",\n    \"1976-04-14 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-05-01 00:00:00\",\n    \"1976-08-15 00:00:00\",\n    \"1976-10-02 00:00:00\",\n    \"1976-12-25 00:00:00\",\n    \"1977-01-26 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-14 00:00:00\",\n    \"1977-05-01 00:00:00\",\n    \"1977-08-15 00:00:00\",\n    \"1977-10-02 00:00:00\",\n    \"1977-12-25 00:00:00\",\n    \"1978-01-26 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-04-14 00:00:00\",\n    \"1978-05-01 00:00:00\",\n    \"1978-08-15 00:00:00\",\n    \"1978-10-02 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1979-01-26 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-14 00:00:00\",\n    \"1979-05-01 00:00:00\",\n    \"1979-08-15 00:00:00\",\n    \"1979-10-02 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1980-01-26 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-14 00:00:00\",\n    \"1980-05-01 00:00:00\",\n    \"1980-08-15 00:00:00\",\n    \"1980-10-02 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1981-01-26 00:00:00\",\n    \"1981-04-14 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-05-01 00:00:00\",\n    \"1981-08-15 00:00:00\",\n    \"1981-10-02 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1982-01-26 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-14 00:00:00\",\n    \"1982-05-01 00:00:00\",\n    \"1982-08-15 00:00:00\",\n    \"1982-10-02 00:00:00\",\n    \"1982-12-25 00:00:00\",\n    \"1983-01-26 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-14 00:00:00\",\n    \"1983-05-01 00:00:00\",\n    \"1983-08-15 00:00:00\",\n    \"1983-10-02 00:00:00\",\n    \"1983-12-25 00:00:00\",\n    \"1984-01-26 00:00:00\",\n    \"1984-04-14 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-05-01 00:00:00\",\n    \"1984-08-15 00:00:00\",\n    \"1984-10-02 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1985-01-26 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-14 00:00:00\",\n    \"1985-05-01 00:00:00\",\n    \"1985-08-15 00:00:00\",\n    \"1985-10-02 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1986-01-26 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-04-14 00:00:00\",\n    \"1986-05-01 00:00:00\",\n    \"1986-08-15 00:00:00\",\n    \"1986-10-02 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1987-01-26 00:00:00\",\n    \"1987-04-14 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-05-01 00:00:00\",\n    \"1987-08-15 00:00:00\",\n    \"1987-10-02 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1988-01-26 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-14 00:00:00\",\n    \"1988-05-01 00:00:00\",\n    \"1988-08-15 00:00:00\",\n    \"1988-10-02 00:00:00\",\n    \"1988-12-25 00:00:00\",\n    \"1989-01-26 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-04-14 00:00:00\",\n    \"1989-05-01 00:00:00\",\n    \"1989-08-15 00:00:00\",\n    \"1989-10-02 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1990-01-26 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-14 00:00:00\",\n    \"1990-05-01 00:00:00\",\n    \"1990-08-15 00:00:00\",\n    \"1990-10-02 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1991-01-26 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-14 00:00:00\",\n    \"1991-05-01 00:00:00\",\n    \"1991-08-15 00:00:00\",\n    \"1991-10-02 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1992-01-26 00:00:00\",\n    \"1992-04-14 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-05-01 00:00:00\",\n    \"1992-08-15 00:00:00\",\n    \"1992-10-02 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1993-01-26 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-14 00:00:00\",\n    \"1993-05-01 00:00:00\",\n    \"1993-08-15 00:00:00\",\n    \"1993-10-02 00:00:00\",\n    \"1993-12-25 00:00:00\",\n    \"1994-01-26 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-14 00:00:00\",\n    \"1994-05-01 00:00:00\",\n    \"1994-08-15 00:00:00\",\n    \"1994-10-02 00:00:00\",\n    \"1994-12-25 00:00:00\",\n    \"1995-01-26 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-05-01 00:00:00\",\n    \"1995-08-15 00:00:00\",\n    \"1995-10-02 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1996-01-26 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-14 00:00:00\",\n    \"1996-05-01 00:00:00\",\n    \"1996-08-15 00:00:00\",\n    \"1996-10-02 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1997-01-26 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-04-14 00:00:00\",\n    \"1997-05-01 00:00:00\",\n    \"1997-08-15 00:00:00\",\n    \"1997-10-02 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1998-01-26 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-14 00:00:00\",\n    \"1998-05-01 00:00:00\",\n    \"1998-08-15 00:00:00\",\n    \"1998-10-02 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1999-01-26 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-14 00:00:00\",\n    \"1999-05-01 00:00:00\",\n    \"1999-08-15 00:00:00\",\n    \"1999-10-02 00:00:00\",\n    \"1999-12-25 00:00:00\",\n    \"2000-01-26 00:00:00\",\n    \"2000-04-14 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-05-01 00:00:00\",\n    \"2000-08-15 00:00:00\",\n    \"2000-10-02 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2001-01-26 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-14 00:00:00\",\n    \"2001-05-01 00:00:00\",\n    \"2001-08-15 00:00:00\",\n    \"2001-10-02 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2002-01-26 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-14 00:00:00\",\n    \"2002-05-01 00:00:00\",\n    \"2002-08-15 00:00:00\",\n    \"2002-10-02 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2003-01-26 00:00:00\",\n    \"2003-04-14 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-05-01 00:00:00\",\n    \"2003-08-15 00:00:00\",\n    \"2003-10-02 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2004-01-26 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-14 00:00:00\",\n    \"2004-05-01 00:00:00\",\n    \"2004-08-15 00:00:00\",\n    \"2004-10-02 00:00:00\",\n    \"2004-12-25 00:00:00\",\n    \"2005-01-26 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-04-14 00:00:00\",\n    \"2005-05-01 00:00:00\",\n    \"2005-08-15 00:00:00\",\n    \"2005-10-02 00:00:00\",\n    \"2005-12-25 00:00:00\",\n    \"2006-01-26 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-05-01 00:00:00\",\n    \"2006-08-15 00:00:00\",\n    \"2006-10-02 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2007-01-26 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-14 00:00:00\",\n    \"2007-05-01 00:00:00\",\n    \"2007-08-15 00:00:00\",\n    \"2007-10-02 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2008-01-26 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-04-14 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-08-15 00:00:00\",\n    \"2008-10-02 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2009-01-26 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-14 00:00:00\",\n    \"2009-05-01 00:00:00\",\n    \"2009-08-15 00:00:00\",\n    \"2009-10-02 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2010-01-26 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-14 00:00:00\",\n    \"2010-05-01 00:00:00\",\n    \"2010-08-15 00:00:00\",\n    \"2010-10-02 00:00:00\",\n    \"2010-12-25 00:00:00\",\n    \"2011-01-26 00:00:00\",\n    \"2011-04-14 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-05-01 00:00:00\",\n    \"2011-08-15 00:00:00\",\n    \"2011-10-02 00:00:00\",\n    \"2011-12-25 00:00:00\",\n    \"2012-01-26 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-14 00:00:00\",\n    \"2012-05-01 00:00:00\",\n    \"2012-08-15 00:00:00\",\n    \"2012-10-02 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2013-01-26 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-14 00:00:00\",\n    \"2013-05-01 00:00:00\",\n    \"2013-08-15 00:00:00\",\n    \"2013-10-02 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2014-01-26 00:00:00\",\n    \"2014-04-14 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-05-01 00:00:00\",\n    \"2014-08-15 00:00:00\",\n    \"2014-10-02 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2015-01-26 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-14 00:00:00\",\n    \"2015-05-01 00:00:00\",\n    \"2015-08-15 00:00:00\",\n    \"2015-10-02 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2016-01-26 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-04-14 00:00:00\",\n    \"2016-05-01 00:00:00\",\n    \"2016-08-15 00:00:00\",\n    \"2016-10-02 00:00:00\",\n    \"2016-12-25 00:00:00\",\n    \"2017-01-26 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-05-01 00:00:00\",\n    \"2017-08-15 00:00:00\",\n    \"2017-10-02 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2018-01-26 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-14 00:00:00\",\n    \"2018-05-01 00:00:00\",\n    \"2018-08-15 00:00:00\",\n    \"2018-10-02 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2019-01-26 00:00:00\",\n    \"2019-02-19 00:00:00\",\n    \"2019-03-04 00:00:00\",\n    \"2019-03-21 00:00:00\",\n    \"2019-04-01 00:00:00\",\n    \"2019-04-14 00:00:00\",\n    \"2019-04-17 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-29 00:00:00\",\n    \"2019-05-01 00:00:00\",\n    \"2019-06-05 00:00:00\",\n    \"2019-08-12 00:00:00\",\n    \"2019-08-15 00:00:00\",\n    \"2019-09-02 00:00:00\",\n    \"2019-09-10 00:00:00\",\n    \"2019-10-02 00:00:00\",\n    \"2019-10-08 00:00:00\",\n    \"2019-10-21 00:00:00\",\n    \"2019-10-28 00:00:00\",\n    \"2019-11-12 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2020-01-26 00:00:00\",\n    \"2020-02-19 00:00:00\",\n    \"2020-02-21 00:00:00\",\n    \"2020-03-10 00:00:00\",\n    \"2020-03-25 00:00:00\",\n    \"2020-04-01 00:00:00\",\n    \"2020-04-02 00:00:00\",\n    \"2020-04-06 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-14 00:00:00\",\n    \"2020-05-01 00:00:00\",\n    \"2020-05-07 00:00:00\",\n    \"2020-05-25 00:00:00\",\n    \"2020-08-15 00:00:00\",\n    \"2020-10-02 00:00:00\",\n    \"2020-10-30 00:00:00\",\n    \"2020-11-16 00:00:00\",\n    \"2020-11-30 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2021-01-26 00:00:00\",\n    \"2021-02-19 00:00:00\",\n    \"2021-03-11 00:00:00\",\n    \"2021-03-29 00:00:00\",\n    \"2021-04-01 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-13 00:00:00\",\n    \"2021-04-14 00:00:00\",\n    \"2021-04-21 00:00:00\",\n    \"2021-05-01 00:00:00\",\n    \"2021-05-13 00:00:00\",\n    \"2021-05-26 00:00:00\",\n    \"2021-07-21 00:00:00\",\n    \"2021-08-15 00:00:00\",\n    \"2021-08-16 00:00:00\",\n    \"2021-08-19 00:00:00\",\n    \"2021-09-10 00:00:00\",\n    \"2021-10-02 00:00:00\",\n    \"2021-10-15 00:00:00\",\n    \"2021-10-19 00:00:00\",\n    \"2021-11-04 00:00:00\",\n    \"2021-11-05 00:00:00\",\n    \"2021-11-19 00:00:00\",\n    \"2021-12-25 00:00:00\",\n    \"2022-01-26 00:00:00\",\n    \"2022-02-07 00:00:00\",\n    \"2022-03-01 00:00:00\",\n    \"2022-03-18 00:00:00\",\n    \"2022-04-01 00:00:00\",\n    \"2022-04-14 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-05-01 00:00:00\",\n    \"2022-05-03 00:00:00\",\n    \"2022-05-16 00:00:00\",\n    \"2022-08-09 00:00:00\",\n    \"2022-08-15 00:00:00\",\n    \"2022-08-16 00:00:00\",\n    \"2022-08-31 00:00:00\",\n    \"2022-10-02 00:00:00\",\n    \"2022-10-05 00:00:00\",\n    \"2022-10-24 00:00:00\",\n    \"2022-10-26 00:00:00\",\n    \"2022-11-08 00:00:00\",\n    \"2022-12-25 00:00:00\",\n    \"2023-01-26 00:00:00\",\n    \"2023-03-07 00:00:00\",\n    \"2023-03-22 00:00:00\",\n    \"2023-03-30 00:00:00\",\n    \"2023-04-04 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-14 00:00:00\",\n    \"2023-05-01 00:00:00\",\n    \"2023-05-05 00:00:00\",\n    \"2023-06-29 00:00:00\",\n    \"2023-08-15 00:00:00\",\n    \"2023-08-16 00:00:00\",\n    \"2023-09-19 00:00:00\",\n    \"2023-10-02 00:00:00\",\n    \"2023-10-24 00:00:00\",\n    \"2023-11-14 00:00:00\",\n    \"2023-11-27 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2024-01-22 00:00:00\",\n    \"2024-01-26 00:00:00\",\n    \"2024-02-19 00:00:00\",\n    \"2024-03-08 00:00:00\",\n    \"2024-03-25 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-04-09 00:00:00\",\n    \"2024-04-11 00:00:00\",\n    \"2024-04-14 00:00:00\",\n    \"2024-04-17 00:00:00\",\n    \"2024-05-01 00:00:00\",\n    \"2024-05-20 00:00:00\",\n    \"2024-05-23 00:00:00\",\n    \"2024-06-17 00:00:00\",\n    \"2024-07-17 00:00:00\",\n    \"2024-08-15 00:00:00\",\n    \"2024-09-18 00:00:00\",\n    \"2024-10-02 00:00:00\",\n    \"2024-11-01 00:00:00\",\n    \"2024-11-15 00:00:00\",\n    \"2024-11-20 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2025-01-26 00:00:00\",\n    \"2025-04-14 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-05-01 00:00:00\",\n    \"2025-08-15 00:00:00\",\n    \"2025-10-02 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2026-01-26 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-14 00:00:00\",\n    \"2026-05-01 00:00:00\",\n    \"2026-08-15 00:00:00\",\n    \"2026-10-02 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2027-01-26 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-04-14 00:00:00\",\n    \"2027-05-01 00:00:00\",\n    \"2027-08-15 00:00:00\",\n    \"2027-10-02 00:00:00\",\n    \"2027-12-25 00:00:00\",\n    \"2028-01-26 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-05-01 00:00:00\",\n    \"2028-08-15 00:00:00\",\n    \"2028-10-02 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2029-01-26 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-14 00:00:00\",\n    \"2029-05-01 00:00:00\",\n    \"2029-08-15 00:00:00\",\n    \"2029-10-02 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2030-01-26 00:00:00\",\n    \"2030-04-14 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-05-01 00:00:00\",\n    \"2030-08-15 00:00:00\",\n    \"2030-10-02 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2031-01-26 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-05-01 00:00:00\",\n    \"2031-08-15 00:00:00\",\n    \"2031-10-02 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2032-01-26 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-04-14 00:00:00\",\n    \"2032-05-01 00:00:00\",\n    \"2032-08-15 00:00:00\",\n    \"2032-10-02 00:00:00\",\n    \"2032-12-25 00:00:00\",\n    \"2033-01-26 00:00:00\",\n    \"2033-04-14 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-05-01 00:00:00\",\n    \"2033-08-15 00:00:00\",\n    \"2033-10-02 00:00:00\",\n    \"2033-12-25 00:00:00\",\n    \"2034-01-26 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-14 00:00:00\",\n    \"2034-05-01 00:00:00\",\n    \"2034-08-15 00:00:00\",\n    \"2034-10-02 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2035-01-26 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-04-14 00:00:00\",\n    \"2035-05-01 00:00:00\",\n    \"2035-08-15 00:00:00\",\n    \"2035-10-02 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2036-01-26 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-05-01 00:00:00\",\n    \"2036-08-15 00:00:00\",\n    \"2036-10-02 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2037-01-26 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-14 00:00:00\",\n    \"2037-05-01 00:00:00\",\n    \"2037-08-15 00:00:00\",\n    \"2037-10-02 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2038-01-26 00:00:00\",\n    \"2038-04-14 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-05-01 00:00:00\",\n    \"2038-08-15 00:00:00\",\n    \"2038-10-02 00:00:00\",\n    \"2038-12-25 00:00:00\",\n    \"2039-01-26 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-14 00:00:00\",\n    \"2039-05-01 00:00:00\",\n    \"2039-08-15 00:00:00\",\n    \"2039-10-02 00:00:00\",\n    \"2039-12-25 00:00:00\",\n    \"2040-01-26 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-14 00:00:00\",\n    \"2040-05-01 00:00:00\",\n    \"2040-08-15 00:00:00\",\n    \"2040-10-02 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2041-01-26 00:00:00\",\n    \"2041-04-14 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-05-01 00:00:00\",\n    \"2041-08-15 00:00:00\",\n    \"2041-10-02 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2042-01-26 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-14 00:00:00\",\n    \"2042-05-01 00:00:00\",\n    \"2042-08-15 00:00:00\",\n    \"2042-10-02 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2043-01-26 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-04-14 00:00:00\",\n    \"2043-05-01 00:00:00\",\n    \"2043-08-15 00:00:00\",\n    \"2043-10-02 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2044-01-26 00:00:00\",\n    \"2044-04-14 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-05-01 00:00:00\",\n    \"2044-08-15 00:00:00\",\n    \"2044-10-02 00:00:00\",\n    \"2044-12-25 00:00:00\",\n    \"2045-01-26 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-14 00:00:00\",\n    \"2045-05-01 00:00:00\",\n    \"2045-08-15 00:00:00\",\n    \"2045-10-02 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2046-01-26 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-04-14 00:00:00\",\n    \"2046-05-01 00:00:00\",\n    \"2046-08-15 00:00:00\",\n    \"2046-10-02 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2047-01-26 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-14 00:00:00\",\n    \"2047-05-01 00:00:00\",\n    \"2047-08-15 00:00:00\",\n    \"2047-10-02 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2048-01-26 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-14 00:00:00\",\n    \"2048-05-01 00:00:00\",\n    \"2048-08-15 00:00:00\",\n    \"2048-10-02 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2049-01-26 00:00:00\",\n    \"2049-04-14 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-05-01 00:00:00\",\n    \"2049-08-15 00:00:00\",\n    \"2049-10-02 00:00:00\",\n    \"2049-12-25 00:00:00\",\n    \"2050-01-26 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-14 00:00:00\",\n    \"2050-05-01 00:00:00\",\n    \"2050-08-15 00:00:00\",\n    \"2050-10-02 00:00:00\",\n    \"2050-12-25 00:00:00\",\n    \"2051-01-26 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-14 00:00:00\",\n    \"2051-05-01 00:00:00\",\n    \"2051-08-15 00:00:00\",\n    \"2051-10-02 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2052-01-26 00:00:00\",\n    \"2052-04-14 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-05-01 00:00:00\",\n    \"2052-08-15 00:00:00\",\n    \"2052-10-02 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2053-01-26 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-14 00:00:00\",\n    \"2053-05-01 00:00:00\",\n    \"2053-08-15 00:00:00\",\n    \"2053-10-02 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2054-01-26 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-04-14 00:00:00\",\n    \"2054-05-01 00:00:00\",\n    \"2054-08-15 00:00:00\",\n    \"2054-10-02 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2055-01-26 00:00:00\",\n    \"2055-04-14 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-05-01 00:00:00\",\n    \"2055-08-15 00:00:00\",\n    \"2055-10-02 00:00:00\",\n    \"2055-12-25 00:00:00\",\n    \"2056-01-26 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-14 00:00:00\",\n    \"2056-05-01 00:00:00\",\n    \"2056-08-15 00:00:00\",\n    \"2056-10-02 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2057-01-26 00:00:00\",\n    \"2057-04-14 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-05-01 00:00:00\",\n    \"2057-08-15 00:00:00\",\n    \"2057-10-02 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2058-01-26 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-14 00:00:00\",\n    \"2058-05-01 00:00:00\",\n    \"2058-08-15 00:00:00\",\n    \"2058-10-02 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2059-01-26 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-04-14 00:00:00\",\n    \"2059-05-01 00:00:00\",\n    \"2059-08-15 00:00:00\",\n    \"2059-10-02 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2060-01-26 00:00:00\",\n    \"2060-04-14 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-05-01 00:00:00\",\n    \"2060-08-15 00:00:00\",\n    \"2060-10-02 00:00:00\",\n    \"2060-12-25 00:00:00\",\n    \"2061-01-26 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-14 00:00:00\",\n    \"2061-05-01 00:00:00\",\n    \"2061-08-15 00:00:00\",\n    \"2061-10-02 00:00:00\",\n    \"2061-12-25 00:00:00\",\n    \"2062-01-26 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-04-14 00:00:00\",\n    \"2062-05-01 00:00:00\",\n    \"2062-08-15 00:00:00\",\n    \"2062-10-02 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2063-01-26 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-14 00:00:00\",\n    \"2063-05-01 00:00:00\",\n    \"2063-08-15 00:00:00\",\n    \"2063-10-02 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2064-01-26 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-14 00:00:00\",\n    \"2064-05-01 00:00:00\",\n    \"2064-08-15 00:00:00\",\n    \"2064-10-02 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2065-01-26 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-04-14 00:00:00\",\n    \"2065-05-01 00:00:00\",\n    \"2065-08-15 00:00:00\",\n    \"2065-10-02 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2066-01-26 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-14 00:00:00\",\n    \"2066-05-01 00:00:00\",\n    \"2066-08-15 00:00:00\",\n    \"2066-10-02 00:00:00\",\n    \"2066-12-25 00:00:00\",\n    \"2067-01-26 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-14 00:00:00\",\n    \"2067-05-01 00:00:00\",\n    \"2067-08-15 00:00:00\",\n    \"2067-10-02 00:00:00\",\n    \"2067-12-25 00:00:00\",\n    \"2068-01-26 00:00:00\",\n    \"2068-04-14 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-05-01 00:00:00\",\n    \"2068-08-15 00:00:00\",\n    \"2068-10-02 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2069-01-26 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-14 00:00:00\",\n    \"2069-05-01 00:00:00\",\n    \"2069-08-15 00:00:00\",\n    \"2069-10-02 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2070-01-26 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-04-14 00:00:00\",\n    \"2070-05-01 00:00:00\",\n    \"2070-08-15 00:00:00\",\n    \"2070-10-02 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2071-01-26 00:00:00\",\n    \"2071-04-14 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-05-01 00:00:00\",\n    \"2071-08-15 00:00:00\",\n    \"2071-10-02 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2072-01-26 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-14 00:00:00\",\n    \"2072-05-01 00:00:00\",\n    \"2072-08-15 00:00:00\",\n    \"2072-10-02 00:00:00\",\n    \"2072-12-25 00:00:00\",\n    \"2073-01-26 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-04-14 00:00:00\",\n    \"2073-05-01 00:00:00\",\n    \"2073-08-15 00:00:00\",\n    \"2073-10-02 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2074-01-26 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-14 00:00:00\",\n    \"2074-05-01 00:00:00\",\n    \"2074-08-15 00:00:00\",\n    \"2074-10-02 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2075-01-26 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-14 00:00:00\",\n    \"2075-05-01 00:00:00\",\n    \"2075-08-15 00:00:00\",\n    \"2075-10-02 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2076-01-26 00:00:00\",\n    \"2076-04-14 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-05-01 00:00:00\",\n    \"2076-08-15 00:00:00\",\n    \"2076-10-02 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2077-01-26 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-14 00:00:00\",\n    \"2077-05-01 00:00:00\",\n    \"2077-08-15 00:00:00\",\n    \"2077-10-02 00:00:00\",\n    \"2077-12-25 00:00:00\",\n    \"2078-01-26 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-14 00:00:00\",\n    \"2078-05-01 00:00:00\",\n    \"2078-08-15 00:00:00\",\n    \"2078-10-02 00:00:00\",\n    \"2078-12-25 00:00:00\",\n    \"2079-01-26 00:00:00\",\n    \"2079-04-14 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-05-01 00:00:00\",\n    \"2079-08-15 00:00:00\",\n    \"2079-10-02 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2080-01-26 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-14 00:00:00\",\n    \"2080-05-01 00:00:00\",\n    \"2080-08-15 00:00:00\",\n    \"2080-10-02 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2081-01-26 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-04-14 00:00:00\",\n    \"2081-05-01 00:00:00\",\n    \"2081-08-15 00:00:00\",\n    \"2081-10-02 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2082-01-26 00:00:00\",\n    \"2082-04-14 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-05-01 00:00:00\",\n    \"2082-08-15 00:00:00\",\n    \"2082-10-02 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2083-01-26 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-14 00:00:00\",\n    \"2083-05-01 00:00:00\",\n    \"2083-08-15 00:00:00\",\n    \"2083-10-02 00:00:00\",\n    \"2083-12-25 00:00:00\",\n    \"2084-01-26 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-04-14 00:00:00\",\n    \"2084-05-01 00:00:00\",\n    \"2084-08-15 00:00:00\",\n    \"2084-10-02 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2085-01-26 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-14 00:00:00\",\n    \"2085-05-01 00:00:00\",\n    \"2085-08-15 00:00:00\",\n    \"2085-10-02 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2086-01-26 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-14 00:00:00\",\n    \"2086-05-01 00:00:00\",\n    \"2086-08-15 00:00:00\",\n    \"2086-10-02 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2087-01-26 00:00:00\",\n    \"2087-04-14 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-05-01 00:00:00\",\n    \"2087-08-15 00:00:00\",\n    \"2087-10-02 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2088-01-26 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-14 00:00:00\",\n    \"2088-05-01 00:00:00\",\n    \"2088-08-15 00:00:00\",\n    \"2088-10-02 00:00:00\",\n    \"2088-12-25 00:00:00\",\n    \"2089-01-26 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-14 00:00:00\",\n    \"2089-05-01 00:00:00\",\n    \"2089-08-15 00:00:00\",\n    \"2089-10-02 00:00:00\",\n    \"2089-12-25 00:00:00\",\n    \"2090-01-26 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-05-01 00:00:00\",\n    \"2090-08-15 00:00:00\",\n    \"2090-10-02 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2091-01-26 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-14 00:00:00\",\n    \"2091-05-01 00:00:00\",\n    \"2091-08-15 00:00:00\",\n    \"2091-10-02 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2092-01-26 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-04-14 00:00:00\",\n    \"2092-05-01 00:00:00\",\n    \"2092-08-15 00:00:00\",\n    \"2092-10-02 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2093-01-26 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-14 00:00:00\",\n    \"2093-05-01 00:00:00\",\n    \"2093-08-15 00:00:00\",\n    \"2093-10-02 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2094-01-26 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-14 00:00:00\",\n    \"2094-05-01 00:00:00\",\n    \"2094-08-15 00:00:00\",\n    \"2094-10-02 00:00:00\",\n    \"2094-12-25 00:00:00\",\n    \"2095-01-26 00:00:00\",\n    \"2095-04-14 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-05-01 00:00:00\",\n    \"2095-08-15 00:00:00\",\n    \"2095-10-02 00:00:00\",\n    \"2095-12-25 00:00:00\",\n    \"2096-01-26 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-14 00:00:00\",\n    \"2096-05-01 00:00:00\",\n    \"2096-08-15 00:00:00\",\n    \"2096-10-02 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2097-01-26 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-14 00:00:00\",\n    \"2097-05-01 00:00:00\",\n    \"2097-08-15 00:00:00\",\n    \"2097-10-02 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2098-01-26 00:00:00\",\n    \"2098-04-14 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-05-01 00:00:00\",\n    \"2098-08-15 00:00:00\",\n    \"2098-10-02 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2099-01-26 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-14 00:00:00\",\n    \"2099-05-01 00:00:00\",\n    \"2099-08-15 00:00:00\",\n    \"2099-10-02 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2100-01-26 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-04-14 00:00:00\",\n    \"2100-05-01 00:00:00\",\n    \"2100-08-15 00:00:00\",\n    \"2100-10-02 00:00:00\",\n    \"2100-12-25 00:00:00\",\n    \"2101-01-26 00:00:00\",\n    \"2101-04-14 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-05-01 00:00:00\",\n    \"2101-08-15 00:00:00\",\n    \"2101-10-02 00:00:00\",\n    \"2101-12-25 00:00:00\",\n    \"2102-01-26 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-14 00:00:00\",\n    \"2102-05-01 00:00:00\",\n    \"2102-08-15 00:00:00\",\n    \"2102-10-02 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2103-01-26 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-04-14 00:00:00\",\n    \"2103-05-01 00:00:00\",\n    \"2103-08-15 00:00:00\",\n    \"2103-10-02 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2104-01-26 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-05-01 00:00:00\",\n    \"2104-08-15 00:00:00\",\n    \"2104-10-02 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2105-01-26 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-14 00:00:00\",\n    \"2105-05-01 00:00:00\",\n    \"2105-08-15 00:00:00\",\n    \"2105-10-02 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2106-01-26 00:00:00\",\n    \"2106-04-14 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-05-01 00:00:00\",\n    \"2106-08-15 00:00:00\",\n    \"2106-10-02 00:00:00\",\n    \"2106-12-25 00:00:00\",\n    \"2107-01-26 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-14 00:00:00\",\n    \"2107-05-01 00:00:00\",\n    \"2107-08-15 00:00:00\",\n    \"2107-10-02 00:00:00\",\n    \"2107-12-25 00:00:00\",\n    \"2108-01-26 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-14 00:00:00\",\n    \"2108-05-01 00:00:00\",\n    \"2108-08-15 00:00:00\",\n    \"2108-10-02 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2109-01-26 00:00:00\",\n    \"2109-04-14 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-05-01 00:00:00\",\n    \"2109-08-15 00:00:00\",\n    \"2109-10-02 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2110-01-26 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-14 00:00:00\",\n    \"2110-05-01 00:00:00\",\n    \"2110-08-15 00:00:00\",\n    \"2110-10-02 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2111-01-26 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-04-14 00:00:00\",\n    \"2111-05-01 00:00:00\",\n    \"2111-08-15 00:00:00\",\n    \"2111-10-02 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2112-01-26 00:00:00\",\n    \"2112-04-14 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-05-01 00:00:00\",\n    \"2112-08-15 00:00:00\",\n    \"2112-10-02 00:00:00\",\n    \"2112-12-25 00:00:00\",\n    \"2113-01-26 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-14 00:00:00\",\n    \"2113-05-01 00:00:00\",\n    \"2113-08-15 00:00:00\",\n    \"2113-10-02 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2114-01-26 00:00:00\",\n    \"2114-04-14 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-05-01 00:00:00\",\n    \"2114-08-15 00:00:00\",\n    \"2114-10-02 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2115-01-26 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-14 00:00:00\",\n    \"2115-05-01 00:00:00\",\n    \"2115-08-15 00:00:00\",\n    \"2115-10-02 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2116-01-26 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-04-14 00:00:00\",\n    \"2116-05-01 00:00:00\",\n    \"2116-08-15 00:00:00\",\n    \"2116-10-02 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2117-01-26 00:00:00\",\n    \"2117-04-14 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-05-01 00:00:00\",\n    \"2117-08-15 00:00:00\",\n    \"2117-10-02 00:00:00\",\n    \"2117-12-25 00:00:00\",\n    \"2118-01-26 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-14 00:00:00\",\n    \"2118-05-01 00:00:00\",\n    \"2118-08-15 00:00:00\",\n    \"2118-10-02 00:00:00\",\n    \"2118-12-25 00:00:00\",\n    \"2119-01-26 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-04-14 00:00:00\",\n    \"2119-05-01 00:00:00\",\n    \"2119-08-15 00:00:00\",\n    \"2119-10-02 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2120-01-26 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-14 00:00:00\",\n    \"2120-05-01 00:00:00\",\n    \"2120-08-15 00:00:00\",\n    \"2120-10-02 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2121-01-26 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-14 00:00:00\",\n    \"2121-05-01 00:00:00\",\n    \"2121-08-15 00:00:00\",\n    \"2121-10-02 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2122-01-26 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-04-14 00:00:00\",\n    \"2122-05-01 00:00:00\",\n    \"2122-08-15 00:00:00\",\n    \"2122-10-02 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2123-01-26 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-14 00:00:00\",\n    \"2123-05-01 00:00:00\",\n    \"2123-08-15 00:00:00\",\n    \"2123-10-02 00:00:00\",\n    \"2123-12-25 00:00:00\",\n    \"2124-01-26 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-14 00:00:00\",\n    \"2124-05-01 00:00:00\",\n    \"2124-08-15 00:00:00\",\n    \"2124-10-02 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2125-01-26 00:00:00\",\n    \"2125-04-14 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-05-01 00:00:00\",\n    \"2125-08-15 00:00:00\",\n    \"2125-10-02 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2126-01-26 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-14 00:00:00\",\n    \"2126-05-01 00:00:00\",\n    \"2126-08-15 00:00:00\",\n    \"2126-10-02 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2127-01-26 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-04-14 00:00:00\",\n    \"2127-05-01 00:00:00\",\n    \"2127-08-15 00:00:00\",\n    \"2127-10-02 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2128-01-26 00:00:00\",\n    \"2128-04-14 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-05-01 00:00:00\",\n    \"2128-08-15 00:00:00\",\n    \"2128-10-02 00:00:00\",\n    \"2128-12-25 00:00:00\",\n    \"2129-01-26 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-14 00:00:00\",\n    \"2129-05-01 00:00:00\",\n    \"2129-08-15 00:00:00\",\n    \"2129-10-02 00:00:00\",\n    \"2129-12-25 00:00:00\",\n    \"2130-01-26 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-04-14 00:00:00\",\n    \"2130-05-01 00:00:00\",\n    \"2130-08-15 00:00:00\",\n    \"2130-10-02 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2131-01-26 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-14 00:00:00\",\n    \"2131-05-01 00:00:00\",\n    \"2131-08-15 00:00:00\",\n    \"2131-10-02 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2132-01-26 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-14 00:00:00\",\n    \"2132-05-01 00:00:00\",\n    \"2132-08-15 00:00:00\",\n    \"2132-10-02 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2133-01-26 00:00:00\",\n    \"2133-04-14 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-05-01 00:00:00\",\n    \"2133-08-15 00:00:00\",\n    \"2133-10-02 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2134-01-26 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-14 00:00:00\",\n    \"2134-05-01 00:00:00\",\n    \"2134-08-15 00:00:00\",\n    \"2134-10-02 00:00:00\",\n    \"2134-12-25 00:00:00\",\n    \"2135-01-26 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-14 00:00:00\",\n    \"2135-05-01 00:00:00\",\n    \"2135-08-15 00:00:00\",\n    \"2135-10-02 00:00:00\",\n    \"2135-12-25 00:00:00\",\n    \"2136-01-26 00:00:00\",\n    \"2136-04-14 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-05-01 00:00:00\",\n    \"2136-08-15 00:00:00\",\n    \"2136-10-02 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2137-01-26 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-14 00:00:00\",\n    \"2137-05-01 00:00:00\",\n    \"2137-08-15 00:00:00\",\n    \"2137-10-02 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2138-01-26 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-04-14 00:00:00\",\n    \"2138-05-01 00:00:00\",\n    \"2138-08-15 00:00:00\",\n    \"2138-10-02 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2139-01-26 00:00:00\",\n    \"2139-04-14 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-05-01 00:00:00\",\n    \"2139-08-15 00:00:00\",\n    \"2139-10-02 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2140-01-26 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-14 00:00:00\",\n    \"2140-05-01 00:00:00\",\n    \"2140-08-15 00:00:00\",\n    \"2140-10-02 00:00:00\",\n    \"2140-12-25 00:00:00\",\n    \"2141-01-26 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-04-14 00:00:00\",\n    \"2141-05-01 00:00:00\",\n    \"2141-08-15 00:00:00\",\n    \"2141-10-02 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2142-01-26 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-14 00:00:00\",\n    \"2142-05-01 00:00:00\",\n    \"2142-08-15 00:00:00\",\n    \"2142-10-02 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2143-01-26 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-14 00:00:00\",\n    \"2143-05-01 00:00:00\",\n    \"2143-08-15 00:00:00\",\n    \"2143-10-02 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2144-01-26 00:00:00\",\n    \"2144-04-14 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-05-01 00:00:00\",\n    \"2144-08-15 00:00:00\",\n    \"2144-10-02 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2145-01-26 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-14 00:00:00\",\n    \"2145-05-01 00:00:00\",\n    \"2145-08-15 00:00:00\",\n    \"2145-10-02 00:00:00\",\n    \"2145-12-25 00:00:00\",\n    \"2146-01-26 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-14 00:00:00\",\n    \"2146-05-01 00:00:00\",\n    \"2146-08-15 00:00:00\",\n    \"2146-10-02 00:00:00\",\n    \"2146-12-25 00:00:00\",\n    \"2147-01-26 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-05-01 00:00:00\",\n    \"2147-08-15 00:00:00\",\n    \"2147-10-02 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2148-01-26 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-14 00:00:00\",\n    \"2148-05-01 00:00:00\",\n    \"2148-08-15 00:00:00\",\n    \"2148-10-02 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2149-01-26 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-04-14 00:00:00\",\n    \"2149-05-01 00:00:00\",\n    \"2149-08-15 00:00:00\",\n    \"2149-10-02 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2150-01-26 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-14 00:00:00\",\n    \"2150-05-01 00:00:00\",\n    \"2150-08-15 00:00:00\",\n    \"2150-10-02 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2151-01-26 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-14 00:00:00\",\n    \"2151-05-01 00:00:00\",\n    \"2151-08-15 00:00:00\",\n    \"2151-10-02 00:00:00\",\n    \"2151-12-25 00:00:00\",\n    \"2152-01-26 00:00:00\",\n    \"2152-04-14 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-05-01 00:00:00\",\n    \"2152-08-15 00:00:00\",\n    \"2152-10-02 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2153-01-26 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-14 00:00:00\",\n    \"2153-05-01 00:00:00\",\n    \"2153-08-15 00:00:00\",\n    \"2153-10-02 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2154-01-26 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-14 00:00:00\",\n    \"2154-05-01 00:00:00\",\n    \"2154-08-15 00:00:00\",\n    \"2154-10-02 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2155-01-26 00:00:00\",\n    \"2155-04-14 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-05-01 00:00:00\",\n    \"2155-08-15 00:00:00\",\n    \"2155-10-02 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2156-01-26 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-14 00:00:00\",\n    \"2156-05-01 00:00:00\",\n    \"2156-08-15 00:00:00\",\n    \"2156-10-02 00:00:00\",\n    \"2156-12-25 00:00:00\",\n    \"2157-01-26 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-04-14 00:00:00\",\n    \"2157-05-01 00:00:00\",\n    \"2157-08-15 00:00:00\",\n    \"2157-10-02 00:00:00\",\n    \"2157-12-25 00:00:00\",\n    \"2158-01-26 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-05-01 00:00:00\",\n    \"2158-08-15 00:00:00\",\n    \"2158-10-02 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2159-01-26 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-14 00:00:00\",\n    \"2159-05-01 00:00:00\",\n    \"2159-08-15 00:00:00\",\n    \"2159-10-02 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2160-01-26 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-04-14 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-08-15 00:00:00\",\n    \"2160-10-02 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2161-01-26 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-14 00:00:00\",\n    \"2161-05-01 00:00:00\",\n    \"2161-08-15 00:00:00\",\n    \"2161-10-02 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2162-01-26 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-14 00:00:00\",\n    \"2162-05-01 00:00:00\",\n    \"2162-08-15 00:00:00\",\n    \"2162-10-02 00:00:00\",\n    \"2162-12-25 00:00:00\",\n    \"2163-01-26 00:00:00\",\n    \"2163-04-14 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-05-01 00:00:00\",\n    \"2163-08-15 00:00:00\",\n    \"2163-10-02 00:00:00\",\n    \"2163-12-25 00:00:00\",\n    \"2164-01-26 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-14 00:00:00\",\n    \"2164-05-01 00:00:00\",\n    \"2164-08-15 00:00:00\",\n    \"2164-10-02 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2165-01-26 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-14 00:00:00\",\n    \"2165-05-01 00:00:00\",\n    \"2165-08-15 00:00:00\",\n    \"2165-10-02 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2166-01-26 00:00:00\",\n    \"2166-04-14 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-05-01 00:00:00\",\n    \"2166-08-15 00:00:00\",\n    \"2166-10-02 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2167-01-26 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-14 00:00:00\",\n    \"2167-05-01 00:00:00\",\n    \"2167-08-15 00:00:00\",\n    \"2167-10-02 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2168-01-26 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-04-14 00:00:00\",\n    \"2168-05-01 00:00:00\",\n    \"2168-08-15 00:00:00\",\n    \"2168-10-02 00:00:00\",\n    \"2168-12-25 00:00:00\",\n    \"2169-01-26 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-05-01 00:00:00\",\n    \"2169-08-15 00:00:00\",\n    \"2169-10-02 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2170-01-26 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-14 00:00:00\",\n    \"2170-05-01 00:00:00\",\n    \"2170-08-15 00:00:00\",\n    \"2170-10-02 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2171-01-26 00:00:00\",\n    \"2171-04-14 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-05-01 00:00:00\",\n    \"2171-08-15 00:00:00\",\n    \"2171-10-02 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2172-01-26 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-14 00:00:00\",\n    \"2172-05-01 00:00:00\",\n    \"2172-08-15 00:00:00\",\n    \"2172-10-02 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2173-01-26 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-14 00:00:00\",\n    \"2173-05-01 00:00:00\",\n    \"2173-08-15 00:00:00\",\n    \"2173-10-02 00:00:00\",\n    \"2173-12-25 00:00:00\",\n    \"2174-01-26 00:00:00\",\n    \"2174-04-14 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-05-01 00:00:00\",\n    \"2174-08-15 00:00:00\",\n    \"2174-10-02 00:00:00\",\n    \"2174-12-25 00:00:00\",\n    \"2175-01-26 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-14 00:00:00\",\n    \"2175-05-01 00:00:00\",\n    \"2175-08-15 00:00:00\",\n    \"2175-10-02 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2176-01-26 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-14 00:00:00\",\n    \"2176-05-01 00:00:00\",\n    \"2176-08-15 00:00:00\",\n    \"2176-10-02 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2177-01-26 00:00:00\",\n    \"2177-04-14 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-05-01 00:00:00\",\n    \"2177-08-15 00:00:00\",\n    \"2177-10-02 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2178-01-26 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-14 00:00:00\",\n    \"2178-05-01 00:00:00\",\n    \"2178-08-15 00:00:00\",\n    \"2178-10-02 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2179-01-26 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-04-14 00:00:00\",\n    \"2179-05-01 00:00:00\",\n    \"2179-08-15 00:00:00\",\n    \"2179-10-02 00:00:00\",\n    \"2179-12-25 00:00:00\",\n    \"2180-01-26 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-05-01 00:00:00\",\n    \"2180-08-15 00:00:00\",\n    \"2180-10-02 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2181-01-26 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-14 00:00:00\",\n    \"2181-05-01 00:00:00\",\n    \"2181-08-15 00:00:00\",\n    \"2181-10-02 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2182-01-26 00:00:00\",\n    \"2182-04-14 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-05-01 00:00:00\",\n    \"2182-08-15 00:00:00\",\n    \"2182-10-02 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2183-01-26 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-05-01 00:00:00\",\n    \"2183-08-15 00:00:00\",\n    \"2183-10-02 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2184-01-26 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-04-14 00:00:00\",\n    \"2184-05-01 00:00:00\",\n    \"2184-08-15 00:00:00\",\n    \"2184-10-02 00:00:00\",\n    \"2184-12-25 00:00:00\",\n    \"2185-01-26 00:00:00\",\n    \"2185-04-14 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-05-01 00:00:00\",\n    \"2185-08-15 00:00:00\",\n    \"2185-10-02 00:00:00\",\n    \"2185-12-25 00:00:00\",\n    \"2186-01-26 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-14 00:00:00\",\n    \"2186-05-01 00:00:00\",\n    \"2186-08-15 00:00:00\",\n    \"2186-10-02 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2187-01-26 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-04-14 00:00:00\",\n    \"2187-05-01 00:00:00\",\n    \"2187-08-15 00:00:00\",\n    \"2187-10-02 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2188-01-26 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-05-01 00:00:00\",\n    \"2188-08-15 00:00:00\",\n    \"2188-10-02 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2189-01-26 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-14 00:00:00\",\n    \"2189-05-01 00:00:00\",\n    \"2189-08-15 00:00:00\",\n    \"2189-10-02 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2190-01-26 00:00:00\",\n    \"2190-04-14 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-05-01 00:00:00\",\n    \"2190-08-15 00:00:00\",\n    \"2190-10-02 00:00:00\",\n    \"2190-12-25 00:00:00\",\n    \"2191-01-26 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-14 00:00:00\",\n    \"2191-05-01 00:00:00\",\n    \"2191-08-15 00:00:00\",\n    \"2191-10-02 00:00:00\",\n    \"2191-12-25 00:00:00\",\n    \"2192-01-26 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-14 00:00:00\",\n    \"2192-05-01 00:00:00\",\n    \"2192-08-15 00:00:00\",\n    \"2192-10-02 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2193-01-26 00:00:00\",\n    \"2193-04-14 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-05-01 00:00:00\",\n    \"2193-08-15 00:00:00\",\n    \"2193-10-02 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2194-01-26 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-14 00:00:00\",\n    \"2194-05-01 00:00:00\",\n    \"2194-08-15 00:00:00\",\n    \"2194-10-02 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2195-01-26 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-04-14 00:00:00\",\n    \"2195-05-01 00:00:00\",\n    \"2195-08-15 00:00:00\",\n    \"2195-10-02 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2196-01-26 00:00:00\",\n    \"2196-04-14 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-05-01 00:00:00\",\n    \"2196-08-15 00:00:00\",\n    \"2196-10-02 00:00:00\",\n    \"2196-12-25 00:00:00\",\n    \"2197-01-26 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-14 00:00:00\",\n    \"2197-05-01 00:00:00\",\n    \"2197-08-15 00:00:00\",\n    \"2197-10-02 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2198-01-26 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-04-14 00:00:00\",\n    \"2198-05-01 00:00:00\",\n    \"2198-08-15 00:00:00\",\n    \"2198-10-02 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2199-01-26 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-14 00:00:00\",\n    \"2199-05-01 00:00:00\",\n    \"2199-08-15 00:00:00\",\n    \"2199-10-02 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2200-01-26 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-14 00:00:00\",\n    \"2200-05-01 00:00:00\",\n    \"2200-08-15 00:00:00\",\n    \"2200-10-02 00:00:00\",\n    \"2200-12-25 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/mum_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pandas as pd\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Day,\n    Easter,\n    Holiday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay\n\nRULES = [\n    # Days defined by the national stock exchange\n    Holiday(\"Republic Day\", month=1, day=26),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Ambedkar Jayanti\", month=4, day=14),\n    Holiday(\"May Day\", month=5, day=1),\n    Holiday(\"Independence Day\", month=8, day=15),\n    Holiday(\"Gandhi Jayanti\", month=10, day=2),\n    Holiday(\"Christmas Day\", month=12, day=25),\n    # Additional Adhoc holidays identified from fixings publications\n    Holiday(\"adhoc0\", year=2019, month=2, day=19),\n    Holiday(\"adhoc1\", year=2019, month=3, day=4),\n    Holiday(\"adhoc2\", year=2019, month=3, day=21),\n    Holiday(\"adhoc3\", year=2019, month=4, day=1),\n    Holiday(\"adhoc4\", year=2019, month=4, day=17),\n    Holiday(\"adhoc5\", year=2019, month=4, day=29),\n    Holiday(\"adhoc6\", year=2019, month=6, day=5),\n    Holiday(\"adhoc7\", year=2019, month=8, day=12),\n    Holiday(\"adhoc8\", year=2019, month=9, day=2),\n    Holiday(\"adhoc9\", year=2019, month=9, day=10),\n    Holiday(\"adhoc10\", year=2019, month=10, day=8),\n    Holiday(\"adhoc11\", year=2019, month=10, day=21),\n    Holiday(\"adhoc12\", year=2019, month=10, day=28),\n    Holiday(\"adhoc13\", year=2019, month=11, day=12),\n    Holiday(\"adhoc14\", year=2020, month=2, day=19),\n    Holiday(\"adhoc15\", year=2020, month=2, day=21),\n    Holiday(\"adhoc16\", year=2020, month=3, day=10),\n    Holiday(\"adhoc17\", year=2020, month=3, day=25),\n    Holiday(\"adhoc18\", year=2020, month=4, day=1),\n    Holiday(\"adhoc19\", year=2020, month=4, day=2),\n    Holiday(\"adhoc20\", year=2020, month=4, day=6),\n    Holiday(\"adhoc21\", year=2020, month=5, day=7),\n    Holiday(\"adhoc22\", year=2020, month=5, day=25),\n    Holiday(\"adhoc23\", year=2020, month=10, day=30),\n    Holiday(\"adhoc24\", year=2020, month=11, day=16),\n    Holiday(\"adhoc25\", year=2020, month=11, day=30),\n    Holiday(\"adhoc26\", year=2021, month=2, day=19),\n    Holiday(\"adhoc27\", year=2021, month=3, day=11),\n    Holiday(\"adhoc28\", year=2021, month=3, day=29),\n    Holiday(\"adhoc29\", year=2021, month=4, day=1),\n    Holiday(\"adhoc30\", year=2021, month=4, day=13),\n    Holiday(\"adhoc31\", year=2021, month=4, day=21),\n    Holiday(\"adhoc32\", year=2021, month=5, day=13),\n    Holiday(\"adhoc33\", year=2021, month=5, day=26),\n    Holiday(\"adhoc34\", year=2021, month=7, day=21),\n    Holiday(\"adhoc35\", year=2021, month=8, day=16),\n    Holiday(\"adhoc36\", year=2021, month=8, day=19),\n    Holiday(\"adhoc37\", year=2021, month=9, day=10),\n    Holiday(\"adhoc38\", year=2021, month=10, day=15),\n    Holiday(\"adhoc39\", year=2021, month=10, day=19),\n    Holiday(\"adhoc40\", year=2021, month=11, day=4),\n    Holiday(\"adhoc41\", year=2021, month=11, day=5),\n    Holiday(\"adhoc42\", year=2021, month=11, day=19),\n    Holiday(\"adhoc43\", year=2022, month=2, day=7),\n    Holiday(\"adhoc44\", year=2022, month=3, day=1),\n    Holiday(\"adhoc45\", year=2022, month=3, day=18),\n    Holiday(\"adhoc46\", year=2022, month=4, day=1),\n    Holiday(\"adhoc47\", year=2022, month=5, day=3),\n    Holiday(\"adhoc48\", year=2022, month=5, day=16),\n    Holiday(\"adhoc49\", year=2022, month=8, day=9),\n    Holiday(\"adhoc50\", year=2022, month=8, day=16),\n    Holiday(\"adhoc51\", year=2022, month=8, day=31),\n    Holiday(\"adhoc52\", year=2022, month=10, day=5),\n    Holiday(\"adhoc53\", year=2022, month=10, day=24),\n    Holiday(\"adhoc54\", year=2022, month=10, day=26),\n    Holiday(\"adhoc55\", year=2022, month=11, day=8),\n    Holiday(\"adhoc56\", year=2023, month=3, day=7),\n    Holiday(\"adhoc57\", year=2023, month=3, day=22),\n    Holiday(\"adhoc58\", year=2023, month=3, day=30),\n    Holiday(\"adhoc59\", year=2023, month=4, day=4),\n    Holiday(\"adhoc60\", year=2023, month=5, day=5),\n    Holiday(\"adhoc61\", year=2023, month=6, day=29),\n    Holiday(\"adhoc62\", year=2023, month=8, day=16),\n    Holiday(\"adhoc63\", year=2023, month=9, day=19),\n    Holiday(\"adhoc64\", year=2023, month=10, day=24),\n    Holiday(\"adhoc65\", year=2023, month=11, day=14),\n    Holiday(\"adhoc66\", year=2023, month=11, day=27),\n    Holiday(\"adhoc67\", year=2024, month=1, day=22),\n    Holiday(\"adhoc68\", year=2024, month=2, day=19),\n    Holiday(\"adhoc69\", year=2024, month=3, day=8),\n    Holiday(\"adhoc70\", year=2024, month=3, day=25),\n    Holiday(\"adhoc71\", year=2024, month=4, day=1),\n    Holiday(\"adhoc72\", year=2024, month=4, day=9),\n    Holiday(\"adhoc73\", year=2024, month=4, day=11),\n    Holiday(\"adhoc74\", year=2024, month=4, day=17),\n    Holiday(\"adhoc75\", year=2024, month=5, day=20),\n    Holiday(\"adhoc76\", year=2024, month=5, day=23),\n    Holiday(\"adhoc77\", year=2024, month=6, day=17),\n    Holiday(\"adhoc78\", year=2024, month=7, day=17),\n    Holiday(\"adhoc79\", year=2024, month=9, day=18),\n    Holiday(\"adhoc80\", year=2024, month=11, day=1),\n    Holiday(\"adhoc81\", year=2024, month=11, day=15),\n    Holiday(\"adhoc82\", year=2024, month=11, day=20),\n]\n\nCALENDAR = CustomBusinessDay(\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/nsw.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Sydney business day calendar, aligned with AONIA publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Jan 26 (Australia)\",\n//     \"Fri before Easter (Good Friday)\",\n//     \"Mon after Easter (Easter Monday)\",\n//     \"Apr 25 (Anzac)\",\n//     \"Jun 2nd Mon (Kings Bday)\",\n//     \"Dec 25 (Christmas)\",\n//     \"Dec 26 (Boxing Day)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-01-26 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-03-30 00:00:00\",\n    \"1970-04-25 00:00:00\",\n    \"1970-06-08 00:00:00\",\n    \"1970-08-03 00:00:00\",\n    \"1970-10-05 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-28 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-01-26 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-12 00:00:00\",\n    \"1971-04-25 00:00:00\",\n    \"1971-06-14 00:00:00\",\n    \"1971-08-02 00:00:00\",\n    \"1971-10-04 00:00:00\",\n    \"1971-12-27 00:00:00\",\n    \"1971-12-28 00:00:00\",\n    \"1972-01-03 00:00:00\",\n    \"1972-01-26 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-03 00:00:00\",\n    \"1972-04-25 00:00:00\",\n    \"1972-06-12 00:00:00\",\n    \"1972-08-07 00:00:00\",\n    \"1972-10-02 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-01-26 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-04-23 00:00:00\",\n    \"1973-04-25 00:00:00\",\n    \"1973-06-11 00:00:00\",\n    \"1973-08-06 00:00:00\",\n    \"1973-10-01 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-01-28 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-15 00:00:00\",\n    \"1974-04-25 00:00:00\",\n    \"1974-06-10 00:00:00\",\n    \"1974-08-05 00:00:00\",\n    \"1974-10-07 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-01-27 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-03-31 00:00:00\",\n    \"1975-04-25 00:00:00\",\n    \"1975-06-09 00:00:00\",\n    \"1975-08-04 00:00:00\",\n    \"1975-10-06 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-01-26 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-04-19 00:00:00\",\n    \"1976-04-25 00:00:00\",\n    \"1976-06-14 00:00:00\",\n    \"1976-08-02 00:00:00\",\n    \"1976-10-04 00:00:00\",\n    \"1976-12-27 00:00:00\",\n    \"1976-12-28 00:00:00\",\n    \"1977-01-03 00:00:00\",\n    \"1977-01-26 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-11 00:00:00\",\n    \"1977-04-25 00:00:00\",\n    \"1977-06-13 00:00:00\",\n    \"1977-08-01 00:00:00\",\n    \"1977-10-03 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1977-12-27 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-01-26 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-03-27 00:00:00\",\n    \"1978-04-25 00:00:00\",\n    \"1978-06-12 00:00:00\",\n    \"1978-08-07 00:00:00\",\n    \"1978-10-02 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-01-26 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-16 00:00:00\",\n    \"1979-04-25 00:00:00\",\n    \"1979-06-11 00:00:00\",\n    \"1979-08-06 00:00:00\",\n    \"1979-10-01 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-01-28 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-07 00:00:00\",\n    \"1980-04-25 00:00:00\",\n    \"1980-06-09 00:00:00\",\n    \"1980-08-04 00:00:00\",\n    \"1980-10-06 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-01-26 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-04-20 00:00:00\",\n    \"1981-04-25 00:00:00\",\n    \"1981-06-08 00:00:00\",\n    \"1981-08-03 00:00:00\",\n    \"1981-10-05 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-28 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-01-26 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-12 00:00:00\",\n    \"1982-04-25 00:00:00\",\n    \"1982-06-14 00:00:00\",\n    \"1982-08-02 00:00:00\",\n    \"1982-10-04 00:00:00\",\n    \"1982-12-27 00:00:00\",\n    \"1982-12-28 00:00:00\",\n    \"1983-01-03 00:00:00\",\n    \"1983-01-26 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-04 00:00:00\",\n    \"1983-04-25 00:00:00\",\n    \"1983-06-13 00:00:00\",\n    \"1983-08-01 00:00:00\",\n    \"1983-10-03 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1983-12-27 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-01-26 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-04-23 00:00:00\",\n    \"1984-04-25 00:00:00\",\n    \"1984-06-11 00:00:00\",\n    \"1984-08-06 00:00:00\",\n    \"1984-10-01 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-01-28 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-08 00:00:00\",\n    \"1985-04-25 00:00:00\",\n    \"1985-06-10 00:00:00\",\n    \"1985-08-05 00:00:00\",\n    \"1985-10-07 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-01-27 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-03-31 00:00:00\",\n    \"1986-04-25 00:00:00\",\n    \"1986-06-09 00:00:00\",\n    \"1986-08-04 00:00:00\",\n    \"1986-10-06 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-01-26 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-04-20 00:00:00\",\n    \"1987-04-25 00:00:00\",\n    \"1987-06-08 00:00:00\",\n    \"1987-08-03 00:00:00\",\n    \"1987-10-05 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-28 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-01-26 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-04 00:00:00\",\n    \"1988-04-25 00:00:00\",\n    \"1988-06-13 00:00:00\",\n    \"1988-08-01 00:00:00\",\n    \"1988-10-03 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1988-12-27 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-01-26 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-03-27 00:00:00\",\n    \"1989-04-25 00:00:00\",\n    \"1989-06-12 00:00:00\",\n    \"1989-08-07 00:00:00\",\n    \"1989-10-02 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-01-26 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-16 00:00:00\",\n    \"1990-04-25 00:00:00\",\n    \"1990-06-11 00:00:00\",\n    \"1990-08-06 00:00:00\",\n    \"1990-10-01 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-01-28 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-01 00:00:00\",\n    \"1991-04-25 00:00:00\",\n    \"1991-06-10 00:00:00\",\n    \"1991-08-05 00:00:00\",\n    \"1991-10-07 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-01-27 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-04-20 00:00:00\",\n    \"1992-04-25 00:00:00\",\n    \"1992-06-08 00:00:00\",\n    \"1992-08-03 00:00:00\",\n    \"1992-10-05 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-28 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-01-26 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-12 00:00:00\",\n    \"1993-04-25 00:00:00\",\n    \"1993-06-14 00:00:00\",\n    \"1993-08-02 00:00:00\",\n    \"1993-10-04 00:00:00\",\n    \"1993-12-27 00:00:00\",\n    \"1993-12-28 00:00:00\",\n    \"1994-01-03 00:00:00\",\n    \"1994-01-26 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-04 00:00:00\",\n    \"1994-04-25 00:00:00\",\n    \"1994-06-13 00:00:00\",\n    \"1994-08-01 00:00:00\",\n    \"1994-10-03 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1994-12-27 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-01-26 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-17 00:00:00\",\n    \"1995-04-25 00:00:00\",\n    \"1995-06-12 00:00:00\",\n    \"1995-08-07 00:00:00\",\n    \"1995-10-02 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-01-26 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-08 00:00:00\",\n    \"1996-04-25 00:00:00\",\n    \"1996-06-10 00:00:00\",\n    \"1996-08-05 00:00:00\",\n    \"1996-10-07 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-01-27 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-03-31 00:00:00\",\n    \"1997-04-25 00:00:00\",\n    \"1997-06-09 00:00:00\",\n    \"1997-08-04 00:00:00\",\n    \"1997-10-06 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-01-26 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-13 00:00:00\",\n    \"1998-04-25 00:00:00\",\n    \"1998-06-08 00:00:00\",\n    \"1998-08-03 00:00:00\",\n    \"1998-10-05 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-28 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-01-26 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-05 00:00:00\",\n    \"1999-04-25 00:00:00\",\n    \"1999-06-14 00:00:00\",\n    \"1999-08-02 00:00:00\",\n    \"1999-10-04 00:00:00\",\n    \"1999-12-27 00:00:00\",\n    \"1999-12-28 00:00:00\",\n    \"2000-01-03 00:00:00\",\n    \"2000-01-26 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-04-24 00:00:00\",\n    \"2000-04-25 00:00:00\",\n    \"2000-06-12 00:00:00\",\n    \"2000-08-07 00:00:00\",\n    \"2000-10-02 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-01-26 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-16 00:00:00\",\n    \"2001-04-25 00:00:00\",\n    \"2001-06-11 00:00:00\",\n    \"2001-08-06 00:00:00\",\n    \"2001-10-01 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-01-28 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-01 00:00:00\",\n    \"2002-04-25 00:00:00\",\n    \"2002-06-10 00:00:00\",\n    \"2002-08-05 00:00:00\",\n    \"2002-10-07 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-01-27 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-04-21 00:00:00\",\n    \"2003-04-25 00:00:00\",\n    \"2003-06-09 00:00:00\",\n    \"2003-08-04 00:00:00\",\n    \"2003-10-06 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-01-26 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-12 00:00:00\",\n    \"2004-04-25 00:00:00\",\n    \"2004-06-14 00:00:00\",\n    \"2004-08-02 00:00:00\",\n    \"2004-10-04 00:00:00\",\n    \"2004-12-27 00:00:00\",\n    \"2004-12-28 00:00:00\",\n    \"2005-01-03 00:00:00\",\n    \"2005-01-26 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-03-28 00:00:00\",\n    \"2005-04-25 00:00:00\",\n    \"2005-06-13 00:00:00\",\n    \"2005-08-01 00:00:00\",\n    \"2005-10-03 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2005-12-27 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-01-26 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-17 00:00:00\",\n    \"2006-04-25 00:00:00\",\n    \"2006-06-12 00:00:00\",\n    \"2006-08-07 00:00:00\",\n    \"2006-10-02 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-01-26 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-09 00:00:00\",\n    \"2007-04-25 00:00:00\",\n    \"2007-06-11 00:00:00\",\n    \"2007-08-06 00:00:00\",\n    \"2007-10-01 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-01-28 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-03-24 00:00:00\",\n    \"2008-04-25 00:00:00\",\n    \"2008-06-09 00:00:00\",\n    \"2008-08-04 00:00:00\",\n    \"2008-10-06 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-01-26 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-13 00:00:00\",\n    \"2009-04-25 00:00:00\",\n    \"2009-06-08 00:00:00\",\n    \"2009-08-03 00:00:00\",\n    \"2009-10-05 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-28 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-01-26 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-05 00:00:00\",\n    \"2010-04-25 00:00:00\",\n    \"2010-06-14 00:00:00\",\n    \"2010-08-02 00:00:00\",\n    \"2010-10-04 00:00:00\",\n    \"2010-12-27 00:00:00\",\n    \"2010-12-28 00:00:00\",\n    \"2011-01-03 00:00:00\",\n    \"2011-01-26 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-04-26 00:00:00\",\n    \"2011-06-13 00:00:00\",\n    \"2011-08-01 00:00:00\",\n    \"2011-10-03 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2011-12-27 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-01-26 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-09 00:00:00\",\n    \"2012-04-25 00:00:00\",\n    \"2012-06-11 00:00:00\",\n    \"2012-08-06 00:00:00\",\n    \"2012-10-01 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-01-28 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-01 00:00:00\",\n    \"2013-04-25 00:00:00\",\n    \"2013-06-10 00:00:00\",\n    \"2013-08-05 00:00:00\",\n    \"2013-10-07 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-01-27 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-04-21 00:00:00\",\n    \"2014-04-25 00:00:00\",\n    \"2014-06-09 00:00:00\",\n    \"2014-08-04 00:00:00\",\n    \"2014-10-06 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-01-26 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-06 00:00:00\",\n    \"2015-04-25 00:00:00\",\n    \"2015-06-08 00:00:00\",\n    \"2015-08-03 00:00:00\",\n    \"2015-10-05 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-28 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-01-26 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-03-28 00:00:00\",\n    \"2016-04-25 00:00:00\",\n    \"2016-06-13 00:00:00\",\n    \"2016-08-01 00:00:00\",\n    \"2016-10-03 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2016-12-27 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-01-26 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-17 00:00:00\",\n    \"2017-04-25 00:00:00\",\n    \"2017-06-12 00:00:00\",\n    \"2017-08-07 00:00:00\",\n    \"2017-10-02 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-01-26 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-02 00:00:00\",\n    \"2018-04-25 00:00:00\",\n    \"2018-06-11 00:00:00\",\n    \"2018-08-06 00:00:00\",\n    \"2018-10-01 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-01-28 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-22 00:00:00\",\n    \"2019-04-25 00:00:00\",\n    \"2019-06-10 00:00:00\",\n    \"2019-08-05 00:00:00\",\n    \"2019-10-07 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-01-27 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-13 00:00:00\",\n    \"2020-04-25 00:00:00\",\n    \"2020-06-08 00:00:00\",\n    \"2020-08-03 00:00:00\",\n    \"2020-10-05 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-28 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-01-26 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-05 00:00:00\",\n    \"2021-04-25 00:00:00\",\n    \"2021-06-14 00:00:00\",\n    \"2021-08-02 00:00:00\",\n    \"2021-10-04 00:00:00\",\n    \"2021-12-27 00:00:00\",\n    \"2021-12-28 00:00:00\",\n    \"2022-01-03 00:00:00\",\n    \"2022-01-26 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-04-18 00:00:00\",\n    \"2022-04-25 00:00:00\",\n    \"2022-06-13 00:00:00\",\n    \"2022-08-01 00:00:00\",\n    \"2022-09-22 00:00:00\",\n    \"2022-10-03 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2022-12-27 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-01-26 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-10 00:00:00\",\n    \"2023-04-25 00:00:00\",\n    \"2023-06-12 00:00:00\",\n    \"2023-08-07 00:00:00\",\n    \"2023-10-02 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-01-26 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-04-25 00:00:00\",\n    \"2024-06-10 00:00:00\",\n    \"2024-08-05 00:00:00\",\n    \"2024-10-07 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-27 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-04-21 00:00:00\",\n    \"2025-04-25 00:00:00\",\n    \"2025-06-09 00:00:00\",\n    \"2025-08-04 00:00:00\",\n    \"2025-10-06 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-26 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-04-25 00:00:00\",\n    \"2026-06-08 00:00:00\",\n    \"2026-08-03 00:00:00\",\n    \"2026-10-05 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-28 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-26 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-03-29 00:00:00\",\n    \"2027-04-25 00:00:00\",\n    \"2027-06-14 00:00:00\",\n    \"2027-08-02 00:00:00\",\n    \"2027-10-04 00:00:00\",\n    \"2027-12-27 00:00:00\",\n    \"2027-12-28 00:00:00\",\n    \"2028-01-03 00:00:00\",\n    \"2028-01-26 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-17 00:00:00\",\n    \"2028-04-25 00:00:00\",\n    \"2028-06-12 00:00:00\",\n    \"2028-08-07 00:00:00\",\n    \"2028-10-02 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-26 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-02 00:00:00\",\n    \"2029-04-25 00:00:00\",\n    \"2029-06-11 00:00:00\",\n    \"2029-08-06 00:00:00\",\n    \"2029-10-01 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-28 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-04-22 00:00:00\",\n    \"2030-04-25 00:00:00\",\n    \"2030-06-10 00:00:00\",\n    \"2030-08-05 00:00:00\",\n    \"2030-10-07 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-27 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-04-25 00:00:00\",\n    \"2031-06-09 00:00:00\",\n    \"2031-08-04 00:00:00\",\n    \"2031-10-06 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-26 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-03-29 00:00:00\",\n    \"2032-04-25 00:00:00\",\n    \"2032-06-14 00:00:00\",\n    \"2032-08-02 00:00:00\",\n    \"2032-10-04 00:00:00\",\n    \"2032-12-27 00:00:00\",\n    \"2032-12-28 00:00:00\",\n    \"2033-01-03 00:00:00\",\n    \"2033-01-26 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-04-18 00:00:00\",\n    \"2033-04-25 00:00:00\",\n    \"2033-06-13 00:00:00\",\n    \"2033-08-01 00:00:00\",\n    \"2033-10-03 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2033-12-27 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-01-26 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-10 00:00:00\",\n    \"2034-04-25 00:00:00\",\n    \"2034-06-12 00:00:00\",\n    \"2034-08-07 00:00:00\",\n    \"2034-10-02 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-26 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-03-26 00:00:00\",\n    \"2035-04-25 00:00:00\",\n    \"2035-06-11 00:00:00\",\n    \"2035-08-06 00:00:00\",\n    \"2035-10-01 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-28 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-04-25 00:00:00\",\n    \"2036-06-09 00:00:00\",\n    \"2036-08-04 00:00:00\",\n    \"2036-10-06 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-26 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-04-25 00:00:00\",\n    \"2037-06-08 00:00:00\",\n    \"2037-08-03 00:00:00\",\n    \"2037-10-05 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-28 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-26 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-04-25 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-06-14 00:00:00\",\n    \"2038-08-02 00:00:00\",\n    \"2038-10-04 00:00:00\",\n    \"2038-12-27 00:00:00\",\n    \"2038-12-28 00:00:00\",\n    \"2039-01-03 00:00:00\",\n    \"2039-01-26 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-11 00:00:00\",\n    \"2039-04-25 00:00:00\",\n    \"2039-06-13 00:00:00\",\n    \"2039-08-01 00:00:00\",\n    \"2039-10-03 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2039-12-27 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-01-26 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-02 00:00:00\",\n    \"2040-04-25 00:00:00\",\n    \"2040-06-11 00:00:00\",\n    \"2040-08-06 00:00:00\",\n    \"2040-10-01 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-28 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-04-22 00:00:00\",\n    \"2041-04-25 00:00:00\",\n    \"2041-06-10 00:00:00\",\n    \"2041-08-05 00:00:00\",\n    \"2041-10-07 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-27 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-07 00:00:00\",\n    \"2042-04-25 00:00:00\",\n    \"2042-06-09 00:00:00\",\n    \"2042-08-04 00:00:00\",\n    \"2042-10-06 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-26 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-03-30 00:00:00\",\n    \"2043-04-25 00:00:00\",\n    \"2043-06-08 00:00:00\",\n    \"2043-08-03 00:00:00\",\n    \"2043-10-05 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-28 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-26 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-04-18 00:00:00\",\n    \"2044-04-25 00:00:00\",\n    \"2044-06-13 00:00:00\",\n    \"2044-08-01 00:00:00\",\n    \"2044-10-03 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2044-12-27 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-01-26 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-10 00:00:00\",\n    \"2045-04-25 00:00:00\",\n    \"2045-06-12 00:00:00\",\n    \"2045-08-07 00:00:00\",\n    \"2045-10-02 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-26 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-03-26 00:00:00\",\n    \"2046-04-25 00:00:00\",\n    \"2046-06-11 00:00:00\",\n    \"2046-08-06 00:00:00\",\n    \"2046-10-01 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-28 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-15 00:00:00\",\n    \"2047-04-25 00:00:00\",\n    \"2047-06-10 00:00:00\",\n    \"2047-08-05 00:00:00\",\n    \"2047-10-07 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-27 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-06 00:00:00\",\n    \"2048-04-25 00:00:00\",\n    \"2048-06-08 00:00:00\",\n    \"2048-08-03 00:00:00\",\n    \"2048-10-05 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-28 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-26 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-04-19 00:00:00\",\n    \"2049-04-25 00:00:00\",\n    \"2049-06-14 00:00:00\",\n    \"2049-08-02 00:00:00\",\n    \"2049-10-04 00:00:00\",\n    \"2049-12-27 00:00:00\",\n    \"2049-12-28 00:00:00\",\n    \"2050-01-03 00:00:00\",\n    \"2050-01-26 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-11 00:00:00\",\n    \"2050-04-25 00:00:00\",\n    \"2050-06-13 00:00:00\",\n    \"2050-08-01 00:00:00\",\n    \"2050-10-03 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2050-12-27 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-01-26 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-04-25 00:00:00\",\n    \"2051-06-12 00:00:00\",\n    \"2051-08-07 00:00:00\",\n    \"2051-10-02 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-26 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-04-22 00:00:00\",\n    \"2052-04-25 00:00:00\",\n    \"2052-06-10 00:00:00\",\n    \"2052-08-05 00:00:00\",\n    \"2052-10-07 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-27 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-07 00:00:00\",\n    \"2053-04-25 00:00:00\",\n    \"2053-06-09 00:00:00\",\n    \"2053-08-04 00:00:00\",\n    \"2053-10-06 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-26 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-03-30 00:00:00\",\n    \"2054-04-25 00:00:00\",\n    \"2054-06-08 00:00:00\",\n    \"2054-08-03 00:00:00\",\n    \"2054-10-05 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-28 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-26 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-04-19 00:00:00\",\n    \"2055-04-25 00:00:00\",\n    \"2055-06-14 00:00:00\",\n    \"2055-08-02 00:00:00\",\n    \"2055-10-04 00:00:00\",\n    \"2055-12-27 00:00:00\",\n    \"2055-12-28 00:00:00\",\n    \"2056-01-03 00:00:00\",\n    \"2056-01-26 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-04-25 00:00:00\",\n    \"2056-06-12 00:00:00\",\n    \"2056-08-07 00:00:00\",\n    \"2056-10-02 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-26 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-04-23 00:00:00\",\n    \"2057-04-25 00:00:00\",\n    \"2057-06-11 00:00:00\",\n    \"2057-08-06 00:00:00\",\n    \"2057-10-01 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-28 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-15 00:00:00\",\n    \"2058-04-25 00:00:00\",\n    \"2058-06-10 00:00:00\",\n    \"2058-08-05 00:00:00\",\n    \"2058-10-07 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-27 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-03-31 00:00:00\",\n    \"2059-04-25 00:00:00\",\n    \"2059-06-09 00:00:00\",\n    \"2059-08-04 00:00:00\",\n    \"2059-10-06 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-26 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-04-19 00:00:00\",\n    \"2060-04-25 00:00:00\",\n    \"2060-06-14 00:00:00\",\n    \"2060-08-02 00:00:00\",\n    \"2060-10-04 00:00:00\",\n    \"2060-12-27 00:00:00\",\n    \"2060-12-28 00:00:00\",\n    \"2061-01-03 00:00:00\",\n    \"2061-01-26 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-11 00:00:00\",\n    \"2061-04-25 00:00:00\",\n    \"2061-06-13 00:00:00\",\n    \"2061-08-01 00:00:00\",\n    \"2061-10-03 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2061-12-27 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-01-26 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-03-27 00:00:00\",\n    \"2062-04-25 00:00:00\",\n    \"2062-06-12 00:00:00\",\n    \"2062-08-07 00:00:00\",\n    \"2062-10-02 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-26 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-16 00:00:00\",\n    \"2063-04-25 00:00:00\",\n    \"2063-06-11 00:00:00\",\n    \"2063-08-06 00:00:00\",\n    \"2063-10-01 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-28 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-07 00:00:00\",\n    \"2064-04-25 00:00:00\",\n    \"2064-06-09 00:00:00\",\n    \"2064-08-04 00:00:00\",\n    \"2064-10-06 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-26 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-03-30 00:00:00\",\n    \"2065-04-25 00:00:00\",\n    \"2065-06-08 00:00:00\",\n    \"2065-08-03 00:00:00\",\n    \"2065-10-05 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-28 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-26 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-12 00:00:00\",\n    \"2066-04-25 00:00:00\",\n    \"2066-06-14 00:00:00\",\n    \"2066-08-02 00:00:00\",\n    \"2066-10-04 00:00:00\",\n    \"2066-12-27 00:00:00\",\n    \"2066-12-28 00:00:00\",\n    \"2067-01-03 00:00:00\",\n    \"2067-01-26 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-04-25 00:00:00\",\n    \"2067-06-13 00:00:00\",\n    \"2067-08-01 00:00:00\",\n    \"2067-10-03 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2067-12-27 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-01-26 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-04-23 00:00:00\",\n    \"2068-04-25 00:00:00\",\n    \"2068-06-11 00:00:00\",\n    \"2068-08-06 00:00:00\",\n    \"2068-10-01 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-28 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-15 00:00:00\",\n    \"2069-04-25 00:00:00\",\n    \"2069-06-10 00:00:00\",\n    \"2069-08-05 00:00:00\",\n    \"2069-10-07 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-27 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-03-31 00:00:00\",\n    \"2070-04-25 00:00:00\",\n    \"2070-06-09 00:00:00\",\n    \"2070-08-04 00:00:00\",\n    \"2070-10-06 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-26 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-04-20 00:00:00\",\n    \"2071-04-25 00:00:00\",\n    \"2071-06-08 00:00:00\",\n    \"2071-08-03 00:00:00\",\n    \"2071-10-05 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-28 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-26 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-11 00:00:00\",\n    \"2072-04-25 00:00:00\",\n    \"2072-06-13 00:00:00\",\n    \"2072-08-01 00:00:00\",\n    \"2072-10-03 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2072-12-27 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-01-26 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-03-27 00:00:00\",\n    \"2073-04-25 00:00:00\",\n    \"2073-06-12 00:00:00\",\n    \"2073-08-07 00:00:00\",\n    \"2073-10-02 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-26 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-16 00:00:00\",\n    \"2074-04-25 00:00:00\",\n    \"2074-06-11 00:00:00\",\n    \"2074-08-06 00:00:00\",\n    \"2074-10-01 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-28 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-08 00:00:00\",\n    \"2075-04-25 00:00:00\",\n    \"2075-06-10 00:00:00\",\n    \"2075-08-05 00:00:00\",\n    \"2075-10-07 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-27 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-04-20 00:00:00\",\n    \"2076-04-25 00:00:00\",\n    \"2076-06-08 00:00:00\",\n    \"2076-08-03 00:00:00\",\n    \"2076-10-05 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-28 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-26 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-12 00:00:00\",\n    \"2077-04-25 00:00:00\",\n    \"2077-06-14 00:00:00\",\n    \"2077-08-02 00:00:00\",\n    \"2077-10-04 00:00:00\",\n    \"2077-12-27 00:00:00\",\n    \"2077-12-28 00:00:00\",\n    \"2078-01-03 00:00:00\",\n    \"2078-01-26 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-04-25 00:00:00\",\n    \"2078-06-13 00:00:00\",\n    \"2078-08-01 00:00:00\",\n    \"2078-10-03 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2078-12-27 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-01-26 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-04-24 00:00:00\",\n    \"2079-04-25 00:00:00\",\n    \"2079-06-12 00:00:00\",\n    \"2079-08-07 00:00:00\",\n    \"2079-10-02 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-26 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-08 00:00:00\",\n    \"2080-04-25 00:00:00\",\n    \"2080-06-10 00:00:00\",\n    \"2080-08-05 00:00:00\",\n    \"2080-10-07 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-27 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-03-31 00:00:00\",\n    \"2081-04-25 00:00:00\",\n    \"2081-06-09 00:00:00\",\n    \"2081-08-04 00:00:00\",\n    \"2081-10-06 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-26 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-04-20 00:00:00\",\n    \"2082-04-25 00:00:00\",\n    \"2082-06-08 00:00:00\",\n    \"2082-08-03 00:00:00\",\n    \"2082-10-05 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-28 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-26 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-04-25 00:00:00\",\n    \"2083-06-14 00:00:00\",\n    \"2083-08-02 00:00:00\",\n    \"2083-10-04 00:00:00\",\n    \"2083-12-27 00:00:00\",\n    \"2083-12-28 00:00:00\",\n    \"2084-01-03 00:00:00\",\n    \"2084-01-26 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-03-27 00:00:00\",\n    \"2084-04-25 00:00:00\",\n    \"2084-06-12 00:00:00\",\n    \"2084-08-07 00:00:00\",\n    \"2084-10-02 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-26 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-16 00:00:00\",\n    \"2085-04-25 00:00:00\",\n    \"2085-06-11 00:00:00\",\n    \"2085-08-06 00:00:00\",\n    \"2085-10-01 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-28 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-01 00:00:00\",\n    \"2086-04-25 00:00:00\",\n    \"2086-06-10 00:00:00\",\n    \"2086-08-05 00:00:00\",\n    \"2086-10-07 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-27 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-04-21 00:00:00\",\n    \"2087-04-25 00:00:00\",\n    \"2087-06-09 00:00:00\",\n    \"2087-08-04 00:00:00\",\n    \"2087-10-06 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-26 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-12 00:00:00\",\n    \"2088-04-25 00:00:00\",\n    \"2088-06-14 00:00:00\",\n    \"2088-08-02 00:00:00\",\n    \"2088-10-04 00:00:00\",\n    \"2088-12-27 00:00:00\",\n    \"2088-12-28 00:00:00\",\n    \"2089-01-03 00:00:00\",\n    \"2089-01-26 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-04-25 00:00:00\",\n    \"2089-06-13 00:00:00\",\n    \"2089-08-01 00:00:00\",\n    \"2089-10-03 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2089-12-27 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-01-26 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-17 00:00:00\",\n    \"2090-04-25 00:00:00\",\n    \"2090-06-12 00:00:00\",\n    \"2090-08-07 00:00:00\",\n    \"2090-10-02 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-26 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-09 00:00:00\",\n    \"2091-04-25 00:00:00\",\n    \"2091-06-11 00:00:00\",\n    \"2091-08-06 00:00:00\",\n    \"2091-10-01 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-28 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-03-31 00:00:00\",\n    \"2092-04-25 00:00:00\",\n    \"2092-06-09 00:00:00\",\n    \"2092-08-04 00:00:00\",\n    \"2092-10-06 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-26 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-13 00:00:00\",\n    \"2093-04-25 00:00:00\",\n    \"2093-06-08 00:00:00\",\n    \"2093-08-03 00:00:00\",\n    \"2093-10-05 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-28 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-26 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-04-25 00:00:00\",\n    \"2094-06-14 00:00:00\",\n    \"2094-08-02 00:00:00\",\n    \"2094-10-04 00:00:00\",\n    \"2094-12-27 00:00:00\",\n    \"2094-12-28 00:00:00\",\n    \"2095-01-03 00:00:00\",\n    \"2095-01-26 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-06-13 00:00:00\",\n    \"2095-08-01 00:00:00\",\n    \"2095-10-03 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2095-12-27 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-01-26 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-16 00:00:00\",\n    \"2096-04-25 00:00:00\",\n    \"2096-06-11 00:00:00\",\n    \"2096-08-06 00:00:00\",\n    \"2096-10-01 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-28 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-01 00:00:00\",\n    \"2097-04-25 00:00:00\",\n    \"2097-06-10 00:00:00\",\n    \"2097-08-05 00:00:00\",\n    \"2097-10-07 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-27 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-04-21 00:00:00\",\n    \"2098-04-25 00:00:00\",\n    \"2098-06-09 00:00:00\",\n    \"2098-08-04 00:00:00\",\n    \"2098-10-06 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-26 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-13 00:00:00\",\n    \"2099-04-25 00:00:00\",\n    \"2099-06-08 00:00:00\",\n    \"2099-08-03 00:00:00\",\n    \"2099-10-05 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-28 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-26 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-03-29 00:00:00\",\n    \"2100-04-25 00:00:00\",\n    \"2100-06-14 00:00:00\",\n    \"2100-08-02 00:00:00\",\n    \"2100-10-04 00:00:00\",\n    \"2100-12-27 00:00:00\",\n    \"2100-12-28 00:00:00\",\n    \"2101-01-03 00:00:00\",\n    \"2101-01-26 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-04-18 00:00:00\",\n    \"2101-04-25 00:00:00\",\n    \"2101-06-13 00:00:00\",\n    \"2101-08-01 00:00:00\",\n    \"2101-10-03 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2101-12-27 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-01-26 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-10 00:00:00\",\n    \"2102-04-25 00:00:00\",\n    \"2102-06-12 00:00:00\",\n    \"2102-08-07 00:00:00\",\n    \"2102-10-02 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-01-26 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-03-26 00:00:00\",\n    \"2103-04-25 00:00:00\",\n    \"2103-06-11 00:00:00\",\n    \"2103-08-06 00:00:00\",\n    \"2103-10-01 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-01-28 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-04-25 00:00:00\",\n    \"2104-06-09 00:00:00\",\n    \"2104-08-04 00:00:00\",\n    \"2104-10-06 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-01-26 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-06 00:00:00\",\n    \"2105-04-25 00:00:00\",\n    \"2105-06-08 00:00:00\",\n    \"2105-08-03 00:00:00\",\n    \"2105-10-05 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-28 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-01-26 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-04-19 00:00:00\",\n    \"2106-04-25 00:00:00\",\n    \"2106-06-14 00:00:00\",\n    \"2106-08-02 00:00:00\",\n    \"2106-10-04 00:00:00\",\n    \"2106-12-27 00:00:00\",\n    \"2106-12-28 00:00:00\",\n    \"2107-01-03 00:00:00\",\n    \"2107-01-26 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-11 00:00:00\",\n    \"2107-04-25 00:00:00\",\n    \"2107-06-13 00:00:00\",\n    \"2107-08-01 00:00:00\",\n    \"2107-10-03 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2107-12-27 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-01-26 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-02 00:00:00\",\n    \"2108-04-25 00:00:00\",\n    \"2108-06-11 00:00:00\",\n    \"2108-08-06 00:00:00\",\n    \"2108-10-01 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-01-28 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-04-22 00:00:00\",\n    \"2109-04-25 00:00:00\",\n    \"2109-06-10 00:00:00\",\n    \"2109-08-05 00:00:00\",\n    \"2109-10-07 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-01-27 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-07 00:00:00\",\n    \"2110-04-25 00:00:00\",\n    \"2110-06-09 00:00:00\",\n    \"2110-08-04 00:00:00\",\n    \"2110-10-06 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-01-26 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-03-30 00:00:00\",\n    \"2111-04-25 00:00:00\",\n    \"2111-06-08 00:00:00\",\n    \"2111-08-03 00:00:00\",\n    \"2111-10-05 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-28 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-01-26 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-04-18 00:00:00\",\n    \"2112-04-25 00:00:00\",\n    \"2112-06-13 00:00:00\",\n    \"2112-08-01 00:00:00\",\n    \"2112-10-03 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2112-12-27 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-01-26 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-03 00:00:00\",\n    \"2113-04-25 00:00:00\",\n    \"2113-06-12 00:00:00\",\n    \"2113-08-07 00:00:00\",\n    \"2113-10-02 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-01-26 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-04-23 00:00:00\",\n    \"2114-04-25 00:00:00\",\n    \"2114-06-11 00:00:00\",\n    \"2114-08-06 00:00:00\",\n    \"2114-10-01 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-01-28 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-15 00:00:00\",\n    \"2115-04-25 00:00:00\",\n    \"2115-06-10 00:00:00\",\n    \"2115-08-05 00:00:00\",\n    \"2115-10-07 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-01-27 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-03-30 00:00:00\",\n    \"2116-04-25 00:00:00\",\n    \"2116-06-08 00:00:00\",\n    \"2116-08-03 00:00:00\",\n    \"2116-10-05 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-28 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-01-26 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-04-19 00:00:00\",\n    \"2117-04-25 00:00:00\",\n    \"2117-06-14 00:00:00\",\n    \"2117-08-02 00:00:00\",\n    \"2117-10-04 00:00:00\",\n    \"2117-12-27 00:00:00\",\n    \"2117-12-28 00:00:00\",\n    \"2118-01-03 00:00:00\",\n    \"2118-01-26 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-11 00:00:00\",\n    \"2118-04-25 00:00:00\",\n    \"2118-06-13 00:00:00\",\n    \"2118-08-01 00:00:00\",\n    \"2118-10-03 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2118-12-27 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-01-26 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-03-27 00:00:00\",\n    \"2119-04-25 00:00:00\",\n    \"2119-06-12 00:00:00\",\n    \"2119-08-07 00:00:00\",\n    \"2119-10-02 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-01-26 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-15 00:00:00\",\n    \"2120-04-25 00:00:00\",\n    \"2120-06-10 00:00:00\",\n    \"2120-08-05 00:00:00\",\n    \"2120-10-07 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-01-27 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-07 00:00:00\",\n    \"2121-04-25 00:00:00\",\n    \"2121-06-09 00:00:00\",\n    \"2121-08-04 00:00:00\",\n    \"2121-10-06 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-01-26 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-03-30 00:00:00\",\n    \"2122-04-25 00:00:00\",\n    \"2122-06-08 00:00:00\",\n    \"2122-08-03 00:00:00\",\n    \"2122-10-05 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-28 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-01-26 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-12 00:00:00\",\n    \"2123-04-25 00:00:00\",\n    \"2123-06-14 00:00:00\",\n    \"2123-08-02 00:00:00\",\n    \"2123-10-04 00:00:00\",\n    \"2123-12-27 00:00:00\",\n    \"2123-12-28 00:00:00\",\n    \"2124-01-03 00:00:00\",\n    \"2124-01-26 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-03 00:00:00\",\n    \"2124-04-25 00:00:00\",\n    \"2124-06-12 00:00:00\",\n    \"2124-08-07 00:00:00\",\n    \"2124-10-02 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-01-26 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-04-23 00:00:00\",\n    \"2125-04-25 00:00:00\",\n    \"2125-06-11 00:00:00\",\n    \"2125-08-06 00:00:00\",\n    \"2125-10-01 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-01-28 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-15 00:00:00\",\n    \"2126-04-25 00:00:00\",\n    \"2126-06-10 00:00:00\",\n    \"2126-08-05 00:00:00\",\n    \"2126-10-07 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-01-27 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-03-31 00:00:00\",\n    \"2127-04-25 00:00:00\",\n    \"2127-06-09 00:00:00\",\n    \"2127-08-04 00:00:00\",\n    \"2127-10-06 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-01-26 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-04-19 00:00:00\",\n    \"2128-04-25 00:00:00\",\n    \"2128-06-14 00:00:00\",\n    \"2128-08-02 00:00:00\",\n    \"2128-10-04 00:00:00\",\n    \"2128-12-27 00:00:00\",\n    \"2128-12-28 00:00:00\",\n    \"2129-01-03 00:00:00\",\n    \"2129-01-26 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-11 00:00:00\",\n    \"2129-04-25 00:00:00\",\n    \"2129-06-13 00:00:00\",\n    \"2129-08-01 00:00:00\",\n    \"2129-10-03 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2129-12-27 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-01-26 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-03-27 00:00:00\",\n    \"2130-04-25 00:00:00\",\n    \"2130-06-12 00:00:00\",\n    \"2130-08-07 00:00:00\",\n    \"2130-10-02 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-01-26 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-16 00:00:00\",\n    \"2131-04-25 00:00:00\",\n    \"2131-06-11 00:00:00\",\n    \"2131-08-06 00:00:00\",\n    \"2131-10-01 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-01-28 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-07 00:00:00\",\n    \"2132-04-25 00:00:00\",\n    \"2132-06-09 00:00:00\",\n    \"2132-08-04 00:00:00\",\n    \"2132-10-06 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-01-26 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-04-20 00:00:00\",\n    \"2133-04-25 00:00:00\",\n    \"2133-06-08 00:00:00\",\n    \"2133-08-03 00:00:00\",\n    \"2133-10-05 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-28 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-01-26 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-12 00:00:00\",\n    \"2134-04-25 00:00:00\",\n    \"2134-06-14 00:00:00\",\n    \"2134-08-02 00:00:00\",\n    \"2134-10-04 00:00:00\",\n    \"2134-12-27 00:00:00\",\n    \"2134-12-28 00:00:00\",\n    \"2135-01-03 00:00:00\",\n    \"2135-01-26 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-04 00:00:00\",\n    \"2135-04-25 00:00:00\",\n    \"2135-06-13 00:00:00\",\n    \"2135-08-01 00:00:00\",\n    \"2135-10-03 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2135-12-27 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-01-26 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-04-23 00:00:00\",\n    \"2136-04-25 00:00:00\",\n    \"2136-06-11 00:00:00\",\n    \"2136-08-06 00:00:00\",\n    \"2136-10-01 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-01-28 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-08 00:00:00\",\n    \"2137-04-25 00:00:00\",\n    \"2137-06-10 00:00:00\",\n    \"2137-08-05 00:00:00\",\n    \"2137-10-07 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-01-27 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-03-31 00:00:00\",\n    \"2138-04-25 00:00:00\",\n    \"2138-06-09 00:00:00\",\n    \"2138-08-04 00:00:00\",\n    \"2138-10-06 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-01-26 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-04-20 00:00:00\",\n    \"2139-04-25 00:00:00\",\n    \"2139-06-08 00:00:00\",\n    \"2139-08-03 00:00:00\",\n    \"2139-10-05 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-28 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-01-26 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-04 00:00:00\",\n    \"2140-04-25 00:00:00\",\n    \"2140-06-13 00:00:00\",\n    \"2140-08-01 00:00:00\",\n    \"2140-10-03 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2140-12-27 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-01-26 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-03-27 00:00:00\",\n    \"2141-04-25 00:00:00\",\n    \"2141-06-12 00:00:00\",\n    \"2141-08-07 00:00:00\",\n    \"2141-10-02 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-01-26 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-16 00:00:00\",\n    \"2142-04-25 00:00:00\",\n    \"2142-06-11 00:00:00\",\n    \"2142-08-06 00:00:00\",\n    \"2142-10-01 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-01-28 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-01 00:00:00\",\n    \"2143-04-25 00:00:00\",\n    \"2143-06-10 00:00:00\",\n    \"2143-08-05 00:00:00\",\n    \"2143-10-07 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-01-27 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-04-20 00:00:00\",\n    \"2144-04-25 00:00:00\",\n    \"2144-06-08 00:00:00\",\n    \"2144-08-03 00:00:00\",\n    \"2144-10-05 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-28 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-01-26 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-12 00:00:00\",\n    \"2145-04-25 00:00:00\",\n    \"2145-06-14 00:00:00\",\n    \"2145-08-02 00:00:00\",\n    \"2145-10-04 00:00:00\",\n    \"2145-12-27 00:00:00\",\n    \"2145-12-28 00:00:00\",\n    \"2146-01-03 00:00:00\",\n    \"2146-01-26 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-04 00:00:00\",\n    \"2146-04-25 00:00:00\",\n    \"2146-06-13 00:00:00\",\n    \"2146-08-01 00:00:00\",\n    \"2146-10-03 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2146-12-27 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-01-26 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-17 00:00:00\",\n    \"2147-04-25 00:00:00\",\n    \"2147-06-12 00:00:00\",\n    \"2147-08-07 00:00:00\",\n    \"2147-10-02 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-01-26 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-08 00:00:00\",\n    \"2148-04-25 00:00:00\",\n    \"2148-06-10 00:00:00\",\n    \"2148-08-05 00:00:00\",\n    \"2148-10-07 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-01-27 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-03-31 00:00:00\",\n    \"2149-04-25 00:00:00\",\n    \"2149-06-09 00:00:00\",\n    \"2149-08-04 00:00:00\",\n    \"2149-10-06 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-01-26 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-13 00:00:00\",\n    \"2150-04-25 00:00:00\",\n    \"2150-06-08 00:00:00\",\n    \"2150-08-03 00:00:00\",\n    \"2150-10-05 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-28 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-01-26 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-05 00:00:00\",\n    \"2151-04-25 00:00:00\",\n    \"2151-06-14 00:00:00\",\n    \"2151-08-02 00:00:00\",\n    \"2151-10-04 00:00:00\",\n    \"2151-12-27 00:00:00\",\n    \"2151-12-28 00:00:00\",\n    \"2152-01-03 00:00:00\",\n    \"2152-01-26 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-04-24 00:00:00\",\n    \"2152-04-25 00:00:00\",\n    \"2152-06-12 00:00:00\",\n    \"2152-08-07 00:00:00\",\n    \"2152-10-02 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-01-26 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-16 00:00:00\",\n    \"2153-04-25 00:00:00\",\n    \"2153-06-11 00:00:00\",\n    \"2153-08-06 00:00:00\",\n    \"2153-10-01 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-01-28 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-01 00:00:00\",\n    \"2154-04-25 00:00:00\",\n    \"2154-06-10 00:00:00\",\n    \"2154-08-05 00:00:00\",\n    \"2154-10-07 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-01-27 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-04-21 00:00:00\",\n    \"2155-04-25 00:00:00\",\n    \"2155-06-09 00:00:00\",\n    \"2155-08-04 00:00:00\",\n    \"2155-10-06 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-01-26 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-12 00:00:00\",\n    \"2156-04-25 00:00:00\",\n    \"2156-06-14 00:00:00\",\n    \"2156-08-02 00:00:00\",\n    \"2156-10-04 00:00:00\",\n    \"2156-12-27 00:00:00\",\n    \"2156-12-28 00:00:00\",\n    \"2157-01-03 00:00:00\",\n    \"2157-01-26 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-03-28 00:00:00\",\n    \"2157-04-25 00:00:00\",\n    \"2157-06-13 00:00:00\",\n    \"2157-08-01 00:00:00\",\n    \"2157-10-03 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2157-12-27 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-01-26 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-17 00:00:00\",\n    \"2158-04-25 00:00:00\",\n    \"2158-06-12 00:00:00\",\n    \"2158-08-07 00:00:00\",\n    \"2158-10-02 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-01-26 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-09 00:00:00\",\n    \"2159-04-25 00:00:00\",\n    \"2159-06-11 00:00:00\",\n    \"2159-08-06 00:00:00\",\n    \"2159-10-01 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-01-28 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-03-24 00:00:00\",\n    \"2160-04-25 00:00:00\",\n    \"2160-06-09 00:00:00\",\n    \"2160-08-04 00:00:00\",\n    \"2160-10-06 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-01-26 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-13 00:00:00\",\n    \"2161-04-25 00:00:00\",\n    \"2161-06-08 00:00:00\",\n    \"2161-08-03 00:00:00\",\n    \"2161-10-05 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-28 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-01-26 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-05 00:00:00\",\n    \"2162-04-25 00:00:00\",\n    \"2162-06-14 00:00:00\",\n    \"2162-08-02 00:00:00\",\n    \"2162-10-04 00:00:00\",\n    \"2162-12-27 00:00:00\",\n    \"2162-12-28 00:00:00\",\n    \"2163-01-03 00:00:00\",\n    \"2163-01-26 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-06-13 00:00:00\",\n    \"2163-08-01 00:00:00\",\n    \"2163-10-03 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2163-12-27 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-01-26 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-09 00:00:00\",\n    \"2164-04-25 00:00:00\",\n    \"2164-06-11 00:00:00\",\n    \"2164-08-06 00:00:00\",\n    \"2164-10-01 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-01-28 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-01 00:00:00\",\n    \"2165-04-25 00:00:00\",\n    \"2165-06-10 00:00:00\",\n    \"2165-08-05 00:00:00\",\n    \"2165-10-07 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-01-27 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-04-21 00:00:00\",\n    \"2166-04-25 00:00:00\",\n    \"2166-06-09 00:00:00\",\n    \"2166-08-04 00:00:00\",\n    \"2166-10-06 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-01-26 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-06 00:00:00\",\n    \"2167-04-25 00:00:00\",\n    \"2167-06-08 00:00:00\",\n    \"2167-08-03 00:00:00\",\n    \"2167-10-05 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-28 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-01-26 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-03-28 00:00:00\",\n    \"2168-04-25 00:00:00\",\n    \"2168-06-13 00:00:00\",\n    \"2168-08-01 00:00:00\",\n    \"2168-10-03 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2168-12-27 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-01-26 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-17 00:00:00\",\n    \"2169-04-25 00:00:00\",\n    \"2169-06-12 00:00:00\",\n    \"2169-08-07 00:00:00\",\n    \"2169-10-02 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-01-26 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-02 00:00:00\",\n    \"2170-04-25 00:00:00\",\n    \"2170-06-11 00:00:00\",\n    \"2170-08-06 00:00:00\",\n    \"2170-10-01 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-01-28 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-04-22 00:00:00\",\n    \"2171-04-25 00:00:00\",\n    \"2171-06-10 00:00:00\",\n    \"2171-08-05 00:00:00\",\n    \"2171-10-07 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-01-27 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-13 00:00:00\",\n    \"2172-04-25 00:00:00\",\n    \"2172-06-08 00:00:00\",\n    \"2172-08-03 00:00:00\",\n    \"2172-10-05 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-28 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-01-26 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-05 00:00:00\",\n    \"2173-04-25 00:00:00\",\n    \"2173-06-14 00:00:00\",\n    \"2173-08-02 00:00:00\",\n    \"2173-10-04 00:00:00\",\n    \"2173-12-27 00:00:00\",\n    \"2173-12-28 00:00:00\",\n    \"2174-01-03 00:00:00\",\n    \"2174-01-26 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-04-18 00:00:00\",\n    \"2174-04-25 00:00:00\",\n    \"2174-06-13 00:00:00\",\n    \"2174-08-01 00:00:00\",\n    \"2174-10-03 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2174-12-27 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-01-26 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-10 00:00:00\",\n    \"2175-04-25 00:00:00\",\n    \"2175-06-12 00:00:00\",\n    \"2175-08-07 00:00:00\",\n    \"2175-10-02 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-01-26 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-01 00:00:00\",\n    \"2176-04-25 00:00:00\",\n    \"2176-06-10 00:00:00\",\n    \"2176-08-05 00:00:00\",\n    \"2176-10-07 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-01-27 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-04-21 00:00:00\",\n    \"2177-04-25 00:00:00\",\n    \"2177-06-09 00:00:00\",\n    \"2177-08-04 00:00:00\",\n    \"2177-10-06 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-01-26 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-06 00:00:00\",\n    \"2178-04-25 00:00:00\",\n    \"2178-06-08 00:00:00\",\n    \"2178-08-03 00:00:00\",\n    \"2178-10-05 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-28 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-01-26 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-03-29 00:00:00\",\n    \"2179-04-25 00:00:00\",\n    \"2179-06-14 00:00:00\",\n    \"2179-08-02 00:00:00\",\n    \"2179-10-04 00:00:00\",\n    \"2179-12-27 00:00:00\",\n    \"2179-12-28 00:00:00\",\n    \"2180-01-03 00:00:00\",\n    \"2180-01-26 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-17 00:00:00\",\n    \"2180-04-25 00:00:00\",\n    \"2180-06-12 00:00:00\",\n    \"2180-08-07 00:00:00\",\n    \"2180-10-02 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-01-26 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-02 00:00:00\",\n    \"2181-04-25 00:00:00\",\n    \"2181-06-11 00:00:00\",\n    \"2181-08-06 00:00:00\",\n    \"2181-10-01 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-01-28 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-04-22 00:00:00\",\n    \"2182-04-25 00:00:00\",\n    \"2182-06-10 00:00:00\",\n    \"2182-08-05 00:00:00\",\n    \"2182-10-07 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-01-27 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-04-25 00:00:00\",\n    \"2183-06-09 00:00:00\",\n    \"2183-08-04 00:00:00\",\n    \"2183-10-06 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-01-26 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-03-29 00:00:00\",\n    \"2184-04-25 00:00:00\",\n    \"2184-06-14 00:00:00\",\n    \"2184-08-02 00:00:00\",\n    \"2184-10-04 00:00:00\",\n    \"2184-12-27 00:00:00\",\n    \"2184-12-28 00:00:00\",\n    \"2185-01-03 00:00:00\",\n    \"2185-01-26 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-04-18 00:00:00\",\n    \"2185-04-25 00:00:00\",\n    \"2185-06-13 00:00:00\",\n    \"2185-08-01 00:00:00\",\n    \"2185-10-03 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2185-12-27 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-01-26 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-10 00:00:00\",\n    \"2186-04-25 00:00:00\",\n    \"2186-06-12 00:00:00\",\n    \"2186-08-07 00:00:00\",\n    \"2186-10-02 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-01-26 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-03-26 00:00:00\",\n    \"2187-04-25 00:00:00\",\n    \"2187-06-11 00:00:00\",\n    \"2187-08-06 00:00:00\",\n    \"2187-10-01 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-01-28 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-04-25 00:00:00\",\n    \"2188-06-09 00:00:00\",\n    \"2188-08-04 00:00:00\",\n    \"2188-10-06 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-01-26 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-06 00:00:00\",\n    \"2189-04-25 00:00:00\",\n    \"2189-06-08 00:00:00\",\n    \"2189-08-03 00:00:00\",\n    \"2189-10-05 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-28 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-01-26 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-04-25 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-06-14 00:00:00\",\n    \"2190-08-02 00:00:00\",\n    \"2190-10-04 00:00:00\",\n    \"2190-12-27 00:00:00\",\n    \"2190-12-28 00:00:00\",\n    \"2191-01-03 00:00:00\",\n    \"2191-01-26 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-11 00:00:00\",\n    \"2191-04-25 00:00:00\",\n    \"2191-06-13 00:00:00\",\n    \"2191-08-01 00:00:00\",\n    \"2191-10-03 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2191-12-27 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-01-26 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-02 00:00:00\",\n    \"2192-04-25 00:00:00\",\n    \"2192-06-11 00:00:00\",\n    \"2192-08-06 00:00:00\",\n    \"2192-10-01 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-01-28 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-04-22 00:00:00\",\n    \"2193-04-25 00:00:00\",\n    \"2193-06-10 00:00:00\",\n    \"2193-08-05 00:00:00\",\n    \"2193-10-07 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-01-27 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-07 00:00:00\",\n    \"2194-04-25 00:00:00\",\n    \"2194-06-09 00:00:00\",\n    \"2194-08-04 00:00:00\",\n    \"2194-10-06 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-01-26 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-03-30 00:00:00\",\n    \"2195-04-25 00:00:00\",\n    \"2195-06-08 00:00:00\",\n    \"2195-08-03 00:00:00\",\n    \"2195-10-05 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-28 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-01-26 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-04-18 00:00:00\",\n    \"2196-04-25 00:00:00\",\n    \"2196-06-13 00:00:00\",\n    \"2196-08-01 00:00:00\",\n    \"2196-10-03 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2196-12-27 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-01-26 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-10 00:00:00\",\n    \"2197-04-25 00:00:00\",\n    \"2197-06-12 00:00:00\",\n    \"2197-08-07 00:00:00\",\n    \"2197-10-02 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-01-26 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-03-26 00:00:00\",\n    \"2198-04-25 00:00:00\",\n    \"2198-06-11 00:00:00\",\n    \"2198-08-06 00:00:00\",\n    \"2198-10-01 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-01-28 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-15 00:00:00\",\n    \"2199-04-25 00:00:00\",\n    \"2199-06-10 00:00:00\",\n    \"2199-08-05 00:00:00\",\n    \"2199-10-07 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-01-27 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-07 00:00:00\",\n    \"2200-04-25 00:00:00\",\n    \"2200-06-09 00:00:00\",\n    \"2200-08-04 00:00:00\",\n    \"2200-10-06 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/nsw_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pandas as pd\nfrom dateutil.relativedelta import MO\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Day,\n    Easter,\n    Holiday,\n    next_monday,\n    next_monday_or_tuesday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, DateOffset\n\nRULES = [\n    Holiday(\"New Year's Day\", month=1, day=1, observance=next_monday),\n    Holiday(\"Australia Day\", month=1, day=26, observance=next_monday),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Easter Monday\", month=1, day=1, offset=[Easter(), Day(1)]),\n    Holiday(\"Anzac Day\", month=4, day=25),\n    Holiday(\"King's Birthday\", month=6, day=1, offset=DateOffset(weekday=MO(2))),\n    Holiday(\"NSW Bank Holiday\", month=8, day=1, offset=DateOffset(weekday=MO(1))),\n    Holiday(\"NSW Labour Day\", month=10, day=1, offset=DateOffset(weekday=MO(1))),\n    Holiday(\"Christmas Day Holiday\", month=12, day=25, observance=next_monday),\n    Holiday(\"Boxing Day Holiday\", month=12, day=26, observance=next_monday_or_tuesday),\n    # One Off\n    Holiday(\"Memorial\", year=2022, month=9, day=22),\n    Holiday(\"Adhoc1-Anzac fix\", year=2011, month=4, day=26),\n]\n\nCALENDAR = CustomBusinessDay(  # type: ignore[call-arg]\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/nyc.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a New York business day calendar, aligned with SOFR publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1: Sun->Mon (New Year)\",\n//     \"Jan 3rd Mon (Martin Luther King Jr.)\",\n//     \"Feb 3rd Mon (President's)\",\n//     \"Fri before Easter (Easter Friday)\",\n//     \"May last Mon (Memorial)\",\n//     \"Jun 19: Sun->Mon (Juneteenth)\",\n//     \"Jul 4: Sat->Fri, Sun->Mon (Independence)\",\n//     \"Sep 1st Mon (Labour)\",\n//     \"Oct 2nd Mon (Columbus)\",\n//     \"Nov 11: Sun->Mon (Veteran's)\",\n//     \"Nov 4th Thu (Thanksgiving)\",\n//     \"Dec 25: Sat->Fri,Sun->Mon (Christmas)\",\n//     \"Note: Special additional dates.\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-02-16 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-05-25 00:00:00\",\n    \"1970-07-03 00:00:00\",\n    \"1970-09-07 00:00:00\",\n    \"1970-10-12 00:00:00\",\n    \"1970-11-11 00:00:00\",\n    \"1970-11-26 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-02-15 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-05-31 00:00:00\",\n    \"1971-07-05 00:00:00\",\n    \"1971-09-06 00:00:00\",\n    \"1971-10-11 00:00:00\",\n    \"1971-11-11 00:00:00\",\n    \"1971-11-25 00:00:00\",\n    \"1971-12-24 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-02-21 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-05-29 00:00:00\",\n    \"1972-07-04 00:00:00\",\n    \"1972-09-04 00:00:00\",\n    \"1972-10-09 00:00:00\",\n    \"1972-11-11 00:00:00\",\n    \"1972-11-23 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-02-19 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-05-28 00:00:00\",\n    \"1973-07-04 00:00:00\",\n    \"1973-09-03 00:00:00\",\n    \"1973-10-08 00:00:00\",\n    \"1973-11-12 00:00:00\",\n    \"1973-11-22 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-02-18 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-05-27 00:00:00\",\n    \"1974-07-04 00:00:00\",\n    \"1974-09-02 00:00:00\",\n    \"1974-10-14 00:00:00\",\n    \"1974-11-11 00:00:00\",\n    \"1974-11-28 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-02-17 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-05-26 00:00:00\",\n    \"1975-07-04 00:00:00\",\n    \"1975-09-01 00:00:00\",\n    \"1975-10-13 00:00:00\",\n    \"1975-11-11 00:00:00\",\n    \"1975-11-27 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-02-16 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-05-31 00:00:00\",\n    \"1976-07-05 00:00:00\",\n    \"1976-09-06 00:00:00\",\n    \"1976-10-11 00:00:00\",\n    \"1976-11-11 00:00:00\",\n    \"1976-11-25 00:00:00\",\n    \"1976-12-24 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-02-21 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-05-30 00:00:00\",\n    \"1977-07-04 00:00:00\",\n    \"1977-09-05 00:00:00\",\n    \"1977-10-10 00:00:00\",\n    \"1977-11-11 00:00:00\",\n    \"1977-11-24 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-02-20 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-05-29 00:00:00\",\n    \"1978-07-04 00:00:00\",\n    \"1978-09-04 00:00:00\",\n    \"1978-10-09 00:00:00\",\n    \"1978-11-11 00:00:00\",\n    \"1978-11-23 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-02-19 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-05-28 00:00:00\",\n    \"1979-07-04 00:00:00\",\n    \"1979-09-03 00:00:00\",\n    \"1979-10-08 00:00:00\",\n    \"1979-11-12 00:00:00\",\n    \"1979-11-22 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-02-18 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-05-26 00:00:00\",\n    \"1980-07-04 00:00:00\",\n    \"1980-09-01 00:00:00\",\n    \"1980-10-13 00:00:00\",\n    \"1980-11-11 00:00:00\",\n    \"1980-11-27 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-02-16 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-05-25 00:00:00\",\n    \"1981-07-03 00:00:00\",\n    \"1981-09-07 00:00:00\",\n    \"1981-10-12 00:00:00\",\n    \"1981-11-11 00:00:00\",\n    \"1981-11-26 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-02-15 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-05-31 00:00:00\",\n    \"1982-07-05 00:00:00\",\n    \"1982-09-06 00:00:00\",\n    \"1982-10-11 00:00:00\",\n    \"1982-11-11 00:00:00\",\n    \"1982-11-25 00:00:00\",\n    \"1982-12-24 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-02-21 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-05-30 00:00:00\",\n    \"1983-07-04 00:00:00\",\n    \"1983-09-05 00:00:00\",\n    \"1983-10-10 00:00:00\",\n    \"1983-11-11 00:00:00\",\n    \"1983-11-24 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-02-20 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-05-28 00:00:00\",\n    \"1984-07-04 00:00:00\",\n    \"1984-09-03 00:00:00\",\n    \"1984-10-08 00:00:00\",\n    \"1984-11-12 00:00:00\",\n    \"1984-11-22 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-02-18 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-05-27 00:00:00\",\n    \"1985-07-04 00:00:00\",\n    \"1985-09-02 00:00:00\",\n    \"1985-10-14 00:00:00\",\n    \"1985-11-11 00:00:00\",\n    \"1985-11-28 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-01-20 00:00:00\",\n    \"1986-02-17 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-05-26 00:00:00\",\n    \"1986-07-04 00:00:00\",\n    \"1986-09-01 00:00:00\",\n    \"1986-10-13 00:00:00\",\n    \"1986-11-11 00:00:00\",\n    \"1986-11-27 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-01-19 00:00:00\",\n    \"1987-02-16 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-05-25 00:00:00\",\n    \"1987-07-03 00:00:00\",\n    \"1987-09-07 00:00:00\",\n    \"1987-10-12 00:00:00\",\n    \"1987-11-11 00:00:00\",\n    \"1987-11-26 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-01-18 00:00:00\",\n    \"1988-02-15 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-05-30 00:00:00\",\n    \"1988-07-04 00:00:00\",\n    \"1988-09-05 00:00:00\",\n    \"1988-10-10 00:00:00\",\n    \"1988-11-11 00:00:00\",\n    \"1988-11-24 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-01-16 00:00:00\",\n    \"1989-02-20 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-05-29 00:00:00\",\n    \"1989-07-04 00:00:00\",\n    \"1989-09-04 00:00:00\",\n    \"1989-10-09 00:00:00\",\n    \"1989-11-11 00:00:00\",\n    \"1989-11-23 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-01-15 00:00:00\",\n    \"1990-02-19 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-05-28 00:00:00\",\n    \"1990-07-04 00:00:00\",\n    \"1990-09-03 00:00:00\",\n    \"1990-10-08 00:00:00\",\n    \"1990-11-12 00:00:00\",\n    \"1990-11-22 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-01-21 00:00:00\",\n    \"1991-02-18 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-05-27 00:00:00\",\n    \"1991-07-04 00:00:00\",\n    \"1991-09-02 00:00:00\",\n    \"1991-10-14 00:00:00\",\n    \"1991-11-11 00:00:00\",\n    \"1991-11-28 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-01-20 00:00:00\",\n    \"1992-02-17 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-05-25 00:00:00\",\n    \"1992-07-03 00:00:00\",\n    \"1992-09-07 00:00:00\",\n    \"1992-10-12 00:00:00\",\n    \"1992-11-11 00:00:00\",\n    \"1992-11-26 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-01-18 00:00:00\",\n    \"1993-02-15 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-05-31 00:00:00\",\n    \"1993-07-05 00:00:00\",\n    \"1993-09-06 00:00:00\",\n    \"1993-10-11 00:00:00\",\n    \"1993-11-11 00:00:00\",\n    \"1993-11-25 00:00:00\",\n    \"1993-12-24 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-01-17 00:00:00\",\n    \"1994-02-21 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-05-30 00:00:00\",\n    \"1994-07-04 00:00:00\",\n    \"1994-09-05 00:00:00\",\n    \"1994-10-10 00:00:00\",\n    \"1994-11-11 00:00:00\",\n    \"1994-11-24 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-01-16 00:00:00\",\n    \"1995-02-20 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-05-29 00:00:00\",\n    \"1995-07-04 00:00:00\",\n    \"1995-09-04 00:00:00\",\n    \"1995-10-09 00:00:00\",\n    \"1995-11-11 00:00:00\",\n    \"1995-11-23 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-01-15 00:00:00\",\n    \"1996-02-19 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-05-27 00:00:00\",\n    \"1996-07-04 00:00:00\",\n    \"1996-09-02 00:00:00\",\n    \"1996-10-14 00:00:00\",\n    \"1996-11-11 00:00:00\",\n    \"1996-11-28 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-01-20 00:00:00\",\n    \"1997-02-17 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-05-26 00:00:00\",\n    \"1997-07-04 00:00:00\",\n    \"1997-09-01 00:00:00\",\n    \"1997-10-13 00:00:00\",\n    \"1997-11-11 00:00:00\",\n    \"1997-11-27 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-01-19 00:00:00\",\n    \"1998-02-16 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-05-25 00:00:00\",\n    \"1998-07-03 00:00:00\",\n    \"1998-09-07 00:00:00\",\n    \"1998-10-12 00:00:00\",\n    \"1998-11-11 00:00:00\",\n    \"1998-11-26 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-01-18 00:00:00\",\n    \"1999-02-15 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-05-31 00:00:00\",\n    \"1999-07-05 00:00:00\",\n    \"1999-09-06 00:00:00\",\n    \"1999-10-11 00:00:00\",\n    \"1999-11-11 00:00:00\",\n    \"1999-11-25 00:00:00\",\n    \"1999-12-24 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-01-17 00:00:00\",\n    \"2000-02-21 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-05-29 00:00:00\",\n    \"2000-07-04 00:00:00\",\n    \"2000-09-04 00:00:00\",\n    \"2000-10-09 00:00:00\",\n    \"2000-11-11 00:00:00\",\n    \"2000-11-23 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-01-15 00:00:00\",\n    \"2001-02-19 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-05-28 00:00:00\",\n    \"2001-07-04 00:00:00\",\n    \"2001-09-03 00:00:00\",\n    \"2001-10-08 00:00:00\",\n    \"2001-11-12 00:00:00\",\n    \"2001-11-22 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-01-21 00:00:00\",\n    \"2002-02-18 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-05-27 00:00:00\",\n    \"2002-07-04 00:00:00\",\n    \"2002-09-02 00:00:00\",\n    \"2002-10-14 00:00:00\",\n    \"2002-11-11 00:00:00\",\n    \"2002-11-28 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-01-20 00:00:00\",\n    \"2003-02-17 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-05-26 00:00:00\",\n    \"2003-07-04 00:00:00\",\n    \"2003-09-01 00:00:00\",\n    \"2003-10-13 00:00:00\",\n    \"2003-11-11 00:00:00\",\n    \"2003-11-27 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-01-19 00:00:00\",\n    \"2004-02-16 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-05-31 00:00:00\",\n    \"2004-07-05 00:00:00\",\n    \"2004-09-06 00:00:00\",\n    \"2004-10-11 00:00:00\",\n    \"2004-11-11 00:00:00\",\n    \"2004-11-25 00:00:00\",\n    \"2004-12-24 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-01-17 00:00:00\",\n    \"2005-02-21 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-05-30 00:00:00\",\n    \"2005-07-04 00:00:00\",\n    \"2005-09-05 00:00:00\",\n    \"2005-10-10 00:00:00\",\n    \"2005-11-11 00:00:00\",\n    \"2005-11-24 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-01-16 00:00:00\",\n    \"2006-02-20 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-05-29 00:00:00\",\n    \"2006-07-04 00:00:00\",\n    \"2006-09-04 00:00:00\",\n    \"2006-10-09 00:00:00\",\n    \"2006-11-11 00:00:00\",\n    \"2006-11-23 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-01-15 00:00:00\",\n    \"2007-02-19 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-05-28 00:00:00\",\n    \"2007-07-04 00:00:00\",\n    \"2007-09-03 00:00:00\",\n    \"2007-10-08 00:00:00\",\n    \"2007-11-12 00:00:00\",\n    \"2007-11-22 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-01-21 00:00:00\",\n    \"2008-02-18 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-05-26 00:00:00\",\n    \"2008-07-04 00:00:00\",\n    \"2008-09-01 00:00:00\",\n    \"2008-10-13 00:00:00\",\n    \"2008-11-11 00:00:00\",\n    \"2008-11-27 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-01-19 00:00:00\",\n    \"2009-02-16 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-05-25 00:00:00\",\n    \"2009-07-03 00:00:00\",\n    \"2009-09-07 00:00:00\",\n    \"2009-10-12 00:00:00\",\n    \"2009-11-11 00:00:00\",\n    \"2009-11-26 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-01-18 00:00:00\",\n    \"2010-02-15 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-05-31 00:00:00\",\n    \"2010-07-05 00:00:00\",\n    \"2010-09-06 00:00:00\",\n    \"2010-10-11 00:00:00\",\n    \"2010-11-11 00:00:00\",\n    \"2010-11-25 00:00:00\",\n    \"2010-12-24 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-01-17 00:00:00\",\n    \"2011-02-21 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-05-30 00:00:00\",\n    \"2011-07-04 00:00:00\",\n    \"2011-09-05 00:00:00\",\n    \"2011-10-10 00:00:00\",\n    \"2011-11-11 00:00:00\",\n    \"2011-11-24 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-01-16 00:00:00\",\n    \"2012-02-20 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-05-28 00:00:00\",\n    \"2012-07-04 00:00:00\",\n    \"2012-09-03 00:00:00\",\n    \"2012-10-08 00:00:00\",\n    \"2012-11-12 00:00:00\",\n    \"2012-11-22 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-01-21 00:00:00\",\n    \"2013-02-18 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-05-27 00:00:00\",\n    \"2013-07-04 00:00:00\",\n    \"2013-09-02 00:00:00\",\n    \"2013-10-14 00:00:00\",\n    \"2013-11-11 00:00:00\",\n    \"2013-11-28 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-01-20 00:00:00\",\n    \"2014-02-17 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-05-26 00:00:00\",\n    \"2014-07-04 00:00:00\",\n    \"2014-09-01 00:00:00\",\n    \"2014-10-13 00:00:00\",\n    \"2014-11-11 00:00:00\",\n    \"2014-11-27 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-01-19 00:00:00\",\n    \"2015-02-16 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-05-25 00:00:00\",\n    \"2015-07-03 00:00:00\",\n    \"2015-09-07 00:00:00\",\n    \"2015-10-12 00:00:00\",\n    \"2015-11-11 00:00:00\",\n    \"2015-11-26 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-01-18 00:00:00\",\n    \"2016-02-15 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-05-30 00:00:00\",\n    \"2016-07-04 00:00:00\",\n    \"2016-09-05 00:00:00\",\n    \"2016-10-10 00:00:00\",\n    \"2016-11-11 00:00:00\",\n    \"2016-11-24 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-01-16 00:00:00\",\n    \"2017-02-20 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-05-29 00:00:00\",\n    \"2017-07-04 00:00:00\",\n    \"2017-09-04 00:00:00\",\n    \"2017-10-09 00:00:00\",\n    \"2017-11-11 00:00:00\",\n    \"2017-11-23 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-01-15 00:00:00\",\n    \"2018-02-19 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-05-28 00:00:00\",\n    \"2018-07-04 00:00:00\",\n    \"2018-09-03 00:00:00\",\n    \"2018-10-08 00:00:00\",\n    \"2018-11-12 00:00:00\",\n    \"2018-11-22 00:00:00\",\n    \"2018-12-05 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-01-21 00:00:00\",\n    \"2019-02-18 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-05-27 00:00:00\",\n    \"2019-07-04 00:00:00\",\n    \"2019-09-02 00:00:00\",\n    \"2019-10-14 00:00:00\",\n    \"2019-11-11 00:00:00\",\n    \"2019-11-28 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-01-20 00:00:00\",\n    \"2020-02-17 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-05-25 00:00:00\",\n    \"2020-07-03 00:00:00\",\n    \"2020-09-07 00:00:00\",\n    \"2020-10-12 00:00:00\",\n    \"2020-11-11 00:00:00\",\n    \"2020-11-26 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-01-18 00:00:00\",\n    \"2021-02-15 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-05-31 00:00:00\",\n    \"2021-07-05 00:00:00\",\n    \"2021-09-06 00:00:00\",\n    \"2021-10-11 00:00:00\",\n    \"2021-11-11 00:00:00\",\n    \"2021-11-25 00:00:00\",\n    \"2021-12-24 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-01-17 00:00:00\",\n    \"2022-02-21 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-05-30 00:00:00\",\n    \"2022-06-20 00:00:00\",\n    \"2022-07-04 00:00:00\",\n    \"2022-09-05 00:00:00\",\n    \"2022-10-10 00:00:00\",\n    \"2022-11-11 00:00:00\",\n    \"2022-11-24 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-01-16 00:00:00\",\n    \"2023-02-20 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-05-29 00:00:00\",\n    \"2023-06-19 00:00:00\",\n    \"2023-07-04 00:00:00\",\n    \"2023-09-04 00:00:00\",\n    \"2023-10-09 00:00:00\",\n    \"2023-11-11 00:00:00\",\n    \"2023-11-23 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-01-15 00:00:00\",\n    \"2024-02-19 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-05-27 00:00:00\",\n    \"2024-06-19 00:00:00\",\n    \"2024-07-04 00:00:00\",\n    \"2024-09-02 00:00:00\",\n    \"2024-10-14 00:00:00\",\n    \"2024-11-11 00:00:00\",\n    \"2024-11-28 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-20 00:00:00\",\n    \"2025-02-17 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-05-26 00:00:00\",\n    \"2025-06-19 00:00:00\",\n    \"2025-07-04 00:00:00\",\n    \"2025-09-01 00:00:00\",\n    \"2025-10-13 00:00:00\",\n    \"2025-11-11 00:00:00\",\n    \"2025-11-27 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-19 00:00:00\",\n    \"2026-02-16 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-05-25 00:00:00\",\n    \"2026-06-19 00:00:00\",\n    \"2026-07-03 00:00:00\",\n    \"2026-09-07 00:00:00\",\n    \"2026-10-12 00:00:00\",\n    \"2026-11-11 00:00:00\",\n    \"2026-11-26 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-18 00:00:00\",\n    \"2027-02-15 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-05-31 00:00:00\",\n    \"2027-06-19 00:00:00\",\n    \"2027-07-05 00:00:00\",\n    \"2027-09-06 00:00:00\",\n    \"2027-10-11 00:00:00\",\n    \"2027-11-11 00:00:00\",\n    \"2027-11-25 00:00:00\",\n    \"2027-12-24 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-01-17 00:00:00\",\n    \"2028-02-21 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-05-29 00:00:00\",\n    \"2028-06-19 00:00:00\",\n    \"2028-07-04 00:00:00\",\n    \"2028-09-04 00:00:00\",\n    \"2028-10-09 00:00:00\",\n    \"2028-11-11 00:00:00\",\n    \"2028-11-23 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-15 00:00:00\",\n    \"2029-02-19 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-05-28 00:00:00\",\n    \"2029-06-19 00:00:00\",\n    \"2029-07-04 00:00:00\",\n    \"2029-09-03 00:00:00\",\n    \"2029-10-08 00:00:00\",\n    \"2029-11-12 00:00:00\",\n    \"2029-11-22 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-21 00:00:00\",\n    \"2030-02-18 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-05-27 00:00:00\",\n    \"2030-06-19 00:00:00\",\n    \"2030-07-04 00:00:00\",\n    \"2030-09-02 00:00:00\",\n    \"2030-10-14 00:00:00\",\n    \"2030-11-11 00:00:00\",\n    \"2030-11-28 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-20 00:00:00\",\n    \"2031-02-17 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-05-26 00:00:00\",\n    \"2031-06-19 00:00:00\",\n    \"2031-07-04 00:00:00\",\n    \"2031-09-01 00:00:00\",\n    \"2031-10-13 00:00:00\",\n    \"2031-11-11 00:00:00\",\n    \"2031-11-27 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-19 00:00:00\",\n    \"2032-02-16 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-05-31 00:00:00\",\n    \"2032-06-19 00:00:00\",\n    \"2032-07-05 00:00:00\",\n    \"2032-09-06 00:00:00\",\n    \"2032-10-11 00:00:00\",\n    \"2032-11-11 00:00:00\",\n    \"2032-11-25 00:00:00\",\n    \"2032-12-24 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-01-17 00:00:00\",\n    \"2033-02-21 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-05-30 00:00:00\",\n    \"2033-06-20 00:00:00\",\n    \"2033-07-04 00:00:00\",\n    \"2033-09-05 00:00:00\",\n    \"2033-10-10 00:00:00\",\n    \"2033-11-11 00:00:00\",\n    \"2033-11-24 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-01-16 00:00:00\",\n    \"2034-02-20 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-05-29 00:00:00\",\n    \"2034-06-19 00:00:00\",\n    \"2034-07-04 00:00:00\",\n    \"2034-09-04 00:00:00\",\n    \"2034-10-09 00:00:00\",\n    \"2034-11-11 00:00:00\",\n    \"2034-11-23 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-15 00:00:00\",\n    \"2035-02-19 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-05-28 00:00:00\",\n    \"2035-06-19 00:00:00\",\n    \"2035-07-04 00:00:00\",\n    \"2035-09-03 00:00:00\",\n    \"2035-10-08 00:00:00\",\n    \"2035-11-12 00:00:00\",\n    \"2035-11-22 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-21 00:00:00\",\n    \"2036-02-18 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-05-26 00:00:00\",\n    \"2036-06-19 00:00:00\",\n    \"2036-07-04 00:00:00\",\n    \"2036-09-01 00:00:00\",\n    \"2036-10-13 00:00:00\",\n    \"2036-11-11 00:00:00\",\n    \"2036-11-27 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-19 00:00:00\",\n    \"2037-02-16 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-05-25 00:00:00\",\n    \"2037-06-19 00:00:00\",\n    \"2037-07-03 00:00:00\",\n    \"2037-09-07 00:00:00\",\n    \"2037-10-12 00:00:00\",\n    \"2037-11-11 00:00:00\",\n    \"2037-11-26 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-18 00:00:00\",\n    \"2038-02-15 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-05-31 00:00:00\",\n    \"2038-06-19 00:00:00\",\n    \"2038-07-05 00:00:00\",\n    \"2038-09-06 00:00:00\",\n    \"2038-10-11 00:00:00\",\n    \"2038-11-11 00:00:00\",\n    \"2038-11-25 00:00:00\",\n    \"2038-12-24 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-01-17 00:00:00\",\n    \"2039-02-21 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-05-30 00:00:00\",\n    \"2039-06-20 00:00:00\",\n    \"2039-07-04 00:00:00\",\n    \"2039-09-05 00:00:00\",\n    \"2039-10-10 00:00:00\",\n    \"2039-11-11 00:00:00\",\n    \"2039-11-24 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-01-16 00:00:00\",\n    \"2040-02-20 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-05-28 00:00:00\",\n    \"2040-06-19 00:00:00\",\n    \"2040-07-04 00:00:00\",\n    \"2040-09-03 00:00:00\",\n    \"2040-10-08 00:00:00\",\n    \"2040-11-12 00:00:00\",\n    \"2040-11-22 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-21 00:00:00\",\n    \"2041-02-18 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-05-27 00:00:00\",\n    \"2041-06-19 00:00:00\",\n    \"2041-07-04 00:00:00\",\n    \"2041-09-02 00:00:00\",\n    \"2041-10-14 00:00:00\",\n    \"2041-11-11 00:00:00\",\n    \"2041-11-28 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-20 00:00:00\",\n    \"2042-02-17 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-05-26 00:00:00\",\n    \"2042-06-19 00:00:00\",\n    \"2042-07-04 00:00:00\",\n    \"2042-09-01 00:00:00\",\n    \"2042-10-13 00:00:00\",\n    \"2042-11-11 00:00:00\",\n    \"2042-11-27 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-19 00:00:00\",\n    \"2043-02-16 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-05-25 00:00:00\",\n    \"2043-06-19 00:00:00\",\n    \"2043-07-03 00:00:00\",\n    \"2043-09-07 00:00:00\",\n    \"2043-10-12 00:00:00\",\n    \"2043-11-11 00:00:00\",\n    \"2043-11-26 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-18 00:00:00\",\n    \"2044-02-15 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-05-30 00:00:00\",\n    \"2044-06-20 00:00:00\",\n    \"2044-07-04 00:00:00\",\n    \"2044-09-05 00:00:00\",\n    \"2044-10-10 00:00:00\",\n    \"2044-11-11 00:00:00\",\n    \"2044-11-24 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-01-16 00:00:00\",\n    \"2045-02-20 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-05-29 00:00:00\",\n    \"2045-06-19 00:00:00\",\n    \"2045-07-04 00:00:00\",\n    \"2045-09-04 00:00:00\",\n    \"2045-10-09 00:00:00\",\n    \"2045-11-11 00:00:00\",\n    \"2045-11-23 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-15 00:00:00\",\n    \"2046-02-19 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-05-28 00:00:00\",\n    \"2046-06-19 00:00:00\",\n    \"2046-07-04 00:00:00\",\n    \"2046-09-03 00:00:00\",\n    \"2046-10-08 00:00:00\",\n    \"2046-11-12 00:00:00\",\n    \"2046-11-22 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-21 00:00:00\",\n    \"2047-02-18 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-05-27 00:00:00\",\n    \"2047-06-19 00:00:00\",\n    \"2047-07-04 00:00:00\",\n    \"2047-09-02 00:00:00\",\n    \"2047-10-14 00:00:00\",\n    \"2047-11-11 00:00:00\",\n    \"2047-11-28 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-20 00:00:00\",\n    \"2048-02-17 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-05-25 00:00:00\",\n    \"2048-06-19 00:00:00\",\n    \"2048-07-03 00:00:00\",\n    \"2048-09-07 00:00:00\",\n    \"2048-10-12 00:00:00\",\n    \"2048-11-11 00:00:00\",\n    \"2048-11-26 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-18 00:00:00\",\n    \"2049-02-15 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-05-31 00:00:00\",\n    \"2049-06-19 00:00:00\",\n    \"2049-07-05 00:00:00\",\n    \"2049-09-06 00:00:00\",\n    \"2049-10-11 00:00:00\",\n    \"2049-11-11 00:00:00\",\n    \"2049-11-25 00:00:00\",\n    \"2049-12-24 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-01-17 00:00:00\",\n    \"2050-02-21 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-05-30 00:00:00\",\n    \"2050-06-20 00:00:00\",\n    \"2050-07-04 00:00:00\",\n    \"2050-09-05 00:00:00\",\n    \"2050-10-10 00:00:00\",\n    \"2050-11-11 00:00:00\",\n    \"2050-11-24 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-01-16 00:00:00\",\n    \"2051-02-20 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-05-29 00:00:00\",\n    \"2051-06-19 00:00:00\",\n    \"2051-07-04 00:00:00\",\n    \"2051-09-04 00:00:00\",\n    \"2051-10-09 00:00:00\",\n    \"2051-11-11 00:00:00\",\n    \"2051-11-23 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-15 00:00:00\",\n    \"2052-02-19 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-05-27 00:00:00\",\n    \"2052-06-19 00:00:00\",\n    \"2052-07-04 00:00:00\",\n    \"2052-09-02 00:00:00\",\n    \"2052-10-14 00:00:00\",\n    \"2052-11-11 00:00:00\",\n    \"2052-11-28 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-20 00:00:00\",\n    \"2053-02-17 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-05-26 00:00:00\",\n    \"2053-06-19 00:00:00\",\n    \"2053-07-04 00:00:00\",\n    \"2053-09-01 00:00:00\",\n    \"2053-10-13 00:00:00\",\n    \"2053-11-11 00:00:00\",\n    \"2053-11-27 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-19 00:00:00\",\n    \"2054-02-16 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-05-25 00:00:00\",\n    \"2054-06-19 00:00:00\",\n    \"2054-07-03 00:00:00\",\n    \"2054-09-07 00:00:00\",\n    \"2054-10-12 00:00:00\",\n    \"2054-11-11 00:00:00\",\n    \"2054-11-26 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-18 00:00:00\",\n    \"2055-02-15 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-05-31 00:00:00\",\n    \"2055-06-19 00:00:00\",\n    \"2055-07-05 00:00:00\",\n    \"2055-09-06 00:00:00\",\n    \"2055-10-11 00:00:00\",\n    \"2055-11-11 00:00:00\",\n    \"2055-11-25 00:00:00\",\n    \"2055-12-24 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-01-17 00:00:00\",\n    \"2056-02-21 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-05-29 00:00:00\",\n    \"2056-06-19 00:00:00\",\n    \"2056-07-04 00:00:00\",\n    \"2056-09-04 00:00:00\",\n    \"2056-10-09 00:00:00\",\n    \"2056-11-11 00:00:00\",\n    \"2056-11-23 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-15 00:00:00\",\n    \"2057-02-19 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-05-28 00:00:00\",\n    \"2057-06-19 00:00:00\",\n    \"2057-07-04 00:00:00\",\n    \"2057-09-03 00:00:00\",\n    \"2057-10-08 00:00:00\",\n    \"2057-11-12 00:00:00\",\n    \"2057-11-22 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-21 00:00:00\",\n    \"2058-02-18 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-05-27 00:00:00\",\n    \"2058-06-19 00:00:00\",\n    \"2058-07-04 00:00:00\",\n    \"2058-09-02 00:00:00\",\n    \"2058-10-14 00:00:00\",\n    \"2058-11-11 00:00:00\",\n    \"2058-11-28 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-20 00:00:00\",\n    \"2059-02-17 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-05-26 00:00:00\",\n    \"2059-06-19 00:00:00\",\n    \"2059-07-04 00:00:00\",\n    \"2059-09-01 00:00:00\",\n    \"2059-10-13 00:00:00\",\n    \"2059-11-11 00:00:00\",\n    \"2059-11-27 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-19 00:00:00\",\n    \"2060-02-16 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-05-31 00:00:00\",\n    \"2060-06-19 00:00:00\",\n    \"2060-07-05 00:00:00\",\n    \"2060-09-06 00:00:00\",\n    \"2060-10-11 00:00:00\",\n    \"2060-11-11 00:00:00\",\n    \"2060-11-25 00:00:00\",\n    \"2060-12-24 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-01-17 00:00:00\",\n    \"2061-02-21 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-05-30 00:00:00\",\n    \"2061-06-20 00:00:00\",\n    \"2061-07-04 00:00:00\",\n    \"2061-09-05 00:00:00\",\n    \"2061-10-10 00:00:00\",\n    \"2061-11-11 00:00:00\",\n    \"2061-11-24 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-01-16 00:00:00\",\n    \"2062-02-20 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-05-29 00:00:00\",\n    \"2062-06-19 00:00:00\",\n    \"2062-07-04 00:00:00\",\n    \"2062-09-04 00:00:00\",\n    \"2062-10-09 00:00:00\",\n    \"2062-11-11 00:00:00\",\n    \"2062-11-23 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-15 00:00:00\",\n    \"2063-02-19 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-05-28 00:00:00\",\n    \"2063-06-19 00:00:00\",\n    \"2063-07-04 00:00:00\",\n    \"2063-09-03 00:00:00\",\n    \"2063-10-08 00:00:00\",\n    \"2063-11-12 00:00:00\",\n    \"2063-11-22 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-21 00:00:00\",\n    \"2064-02-18 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-05-26 00:00:00\",\n    \"2064-06-19 00:00:00\",\n    \"2064-07-04 00:00:00\",\n    \"2064-09-01 00:00:00\",\n    \"2064-10-13 00:00:00\",\n    \"2064-11-11 00:00:00\",\n    \"2064-11-27 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-19 00:00:00\",\n    \"2065-02-16 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-05-25 00:00:00\",\n    \"2065-06-19 00:00:00\",\n    \"2065-07-03 00:00:00\",\n    \"2065-09-07 00:00:00\",\n    \"2065-10-12 00:00:00\",\n    \"2065-11-11 00:00:00\",\n    \"2065-11-26 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-18 00:00:00\",\n    \"2066-02-15 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-05-31 00:00:00\",\n    \"2066-06-19 00:00:00\",\n    \"2066-07-05 00:00:00\",\n    \"2066-09-06 00:00:00\",\n    \"2066-10-11 00:00:00\",\n    \"2066-11-11 00:00:00\",\n    \"2066-11-25 00:00:00\",\n    \"2066-12-24 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-01-17 00:00:00\",\n    \"2067-02-21 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-05-30 00:00:00\",\n    \"2067-06-20 00:00:00\",\n    \"2067-07-04 00:00:00\",\n    \"2067-09-05 00:00:00\",\n    \"2067-10-10 00:00:00\",\n    \"2067-11-11 00:00:00\",\n    \"2067-11-24 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-01-16 00:00:00\",\n    \"2068-02-20 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-05-28 00:00:00\",\n    \"2068-06-19 00:00:00\",\n    \"2068-07-04 00:00:00\",\n    \"2068-09-03 00:00:00\",\n    \"2068-10-08 00:00:00\",\n    \"2068-11-12 00:00:00\",\n    \"2068-11-22 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-21 00:00:00\",\n    \"2069-02-18 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-05-27 00:00:00\",\n    \"2069-06-19 00:00:00\",\n    \"2069-07-04 00:00:00\",\n    \"2069-09-02 00:00:00\",\n    \"2069-10-14 00:00:00\",\n    \"2069-11-11 00:00:00\",\n    \"2069-11-28 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-20 00:00:00\",\n    \"2070-02-17 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-05-26 00:00:00\",\n    \"2070-06-19 00:00:00\",\n    \"2070-07-04 00:00:00\",\n    \"2070-09-01 00:00:00\",\n    \"2070-10-13 00:00:00\",\n    \"2070-11-11 00:00:00\",\n    \"2070-11-27 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-19 00:00:00\",\n    \"2071-02-16 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-05-25 00:00:00\",\n    \"2071-06-19 00:00:00\",\n    \"2071-07-03 00:00:00\",\n    \"2071-09-07 00:00:00\",\n    \"2071-10-12 00:00:00\",\n    \"2071-11-11 00:00:00\",\n    \"2071-11-26 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-18 00:00:00\",\n    \"2072-02-15 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-05-30 00:00:00\",\n    \"2072-06-20 00:00:00\",\n    \"2072-07-04 00:00:00\",\n    \"2072-09-05 00:00:00\",\n    \"2072-10-10 00:00:00\",\n    \"2072-11-11 00:00:00\",\n    \"2072-11-24 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-01-16 00:00:00\",\n    \"2073-02-20 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-05-29 00:00:00\",\n    \"2073-06-19 00:00:00\",\n    \"2073-07-04 00:00:00\",\n    \"2073-09-04 00:00:00\",\n    \"2073-10-09 00:00:00\",\n    \"2073-11-11 00:00:00\",\n    \"2073-11-23 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-15 00:00:00\",\n    \"2074-02-19 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-05-28 00:00:00\",\n    \"2074-06-19 00:00:00\",\n    \"2074-07-04 00:00:00\",\n    \"2074-09-03 00:00:00\",\n    \"2074-10-08 00:00:00\",\n    \"2074-11-12 00:00:00\",\n    \"2074-11-22 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-21 00:00:00\",\n    \"2075-02-18 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-05-27 00:00:00\",\n    \"2075-06-19 00:00:00\",\n    \"2075-07-04 00:00:00\",\n    \"2075-09-02 00:00:00\",\n    \"2075-10-14 00:00:00\",\n    \"2075-11-11 00:00:00\",\n    \"2075-11-28 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-20 00:00:00\",\n    \"2076-02-17 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-05-25 00:00:00\",\n    \"2076-06-19 00:00:00\",\n    \"2076-07-03 00:00:00\",\n    \"2076-09-07 00:00:00\",\n    \"2076-10-12 00:00:00\",\n    \"2076-11-11 00:00:00\",\n    \"2076-11-26 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-18 00:00:00\",\n    \"2077-02-15 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-05-31 00:00:00\",\n    \"2077-06-19 00:00:00\",\n    \"2077-07-05 00:00:00\",\n    \"2077-09-06 00:00:00\",\n    \"2077-10-11 00:00:00\",\n    \"2077-11-11 00:00:00\",\n    \"2077-11-25 00:00:00\",\n    \"2077-12-24 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-01-17 00:00:00\",\n    \"2078-02-21 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-05-30 00:00:00\",\n    \"2078-06-20 00:00:00\",\n    \"2078-07-04 00:00:00\",\n    \"2078-09-05 00:00:00\",\n    \"2078-10-10 00:00:00\",\n    \"2078-11-11 00:00:00\",\n    \"2078-11-24 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-01-16 00:00:00\",\n    \"2079-02-20 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-05-29 00:00:00\",\n    \"2079-06-19 00:00:00\",\n    \"2079-07-04 00:00:00\",\n    \"2079-09-04 00:00:00\",\n    \"2079-10-09 00:00:00\",\n    \"2079-11-11 00:00:00\",\n    \"2079-11-23 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-15 00:00:00\",\n    \"2080-02-19 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-05-27 00:00:00\",\n    \"2080-06-19 00:00:00\",\n    \"2080-07-04 00:00:00\",\n    \"2080-09-02 00:00:00\",\n    \"2080-10-14 00:00:00\",\n    \"2080-11-11 00:00:00\",\n    \"2080-11-28 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-20 00:00:00\",\n    \"2081-02-17 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-05-26 00:00:00\",\n    \"2081-06-19 00:00:00\",\n    \"2081-07-04 00:00:00\",\n    \"2081-09-01 00:00:00\",\n    \"2081-10-13 00:00:00\",\n    \"2081-11-11 00:00:00\",\n    \"2081-11-27 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-19 00:00:00\",\n    \"2082-02-16 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-05-25 00:00:00\",\n    \"2082-06-19 00:00:00\",\n    \"2082-07-03 00:00:00\",\n    \"2082-09-07 00:00:00\",\n    \"2082-10-12 00:00:00\",\n    \"2082-11-11 00:00:00\",\n    \"2082-11-26 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-18 00:00:00\",\n    \"2083-02-15 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-05-31 00:00:00\",\n    \"2083-06-19 00:00:00\",\n    \"2083-07-05 00:00:00\",\n    \"2083-09-06 00:00:00\",\n    \"2083-10-11 00:00:00\",\n    \"2083-11-11 00:00:00\",\n    \"2083-11-25 00:00:00\",\n    \"2083-12-24 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-01-17 00:00:00\",\n    \"2084-02-21 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-05-29 00:00:00\",\n    \"2084-06-19 00:00:00\",\n    \"2084-07-04 00:00:00\",\n    \"2084-09-04 00:00:00\",\n    \"2084-10-09 00:00:00\",\n    \"2084-11-11 00:00:00\",\n    \"2084-11-23 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-15 00:00:00\",\n    \"2085-02-19 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-05-28 00:00:00\",\n    \"2085-06-19 00:00:00\",\n    \"2085-07-04 00:00:00\",\n    \"2085-09-03 00:00:00\",\n    \"2085-10-08 00:00:00\",\n    \"2085-11-12 00:00:00\",\n    \"2085-11-22 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-21 00:00:00\",\n    \"2086-02-18 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-05-27 00:00:00\",\n    \"2086-06-19 00:00:00\",\n    \"2086-07-04 00:00:00\",\n    \"2086-09-02 00:00:00\",\n    \"2086-10-14 00:00:00\",\n    \"2086-11-11 00:00:00\",\n    \"2086-11-28 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-20 00:00:00\",\n    \"2087-02-17 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-05-26 00:00:00\",\n    \"2087-06-19 00:00:00\",\n    \"2087-07-04 00:00:00\",\n    \"2087-09-01 00:00:00\",\n    \"2087-10-13 00:00:00\",\n    \"2087-11-11 00:00:00\",\n    \"2087-11-27 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-19 00:00:00\",\n    \"2088-02-16 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-05-31 00:00:00\",\n    \"2088-06-19 00:00:00\",\n    \"2088-07-05 00:00:00\",\n    \"2088-09-06 00:00:00\",\n    \"2088-10-11 00:00:00\",\n    \"2088-11-11 00:00:00\",\n    \"2088-11-25 00:00:00\",\n    \"2088-12-24 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-01-17 00:00:00\",\n    \"2089-02-21 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-05-30 00:00:00\",\n    \"2089-06-20 00:00:00\",\n    \"2089-07-04 00:00:00\",\n    \"2089-09-05 00:00:00\",\n    \"2089-10-10 00:00:00\",\n    \"2089-11-11 00:00:00\",\n    \"2089-11-24 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-01-16 00:00:00\",\n    \"2090-02-20 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-05-29 00:00:00\",\n    \"2090-06-19 00:00:00\",\n    \"2090-07-04 00:00:00\",\n    \"2090-09-04 00:00:00\",\n    \"2090-10-09 00:00:00\",\n    \"2090-11-11 00:00:00\",\n    \"2090-11-23 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-15 00:00:00\",\n    \"2091-02-19 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-05-28 00:00:00\",\n    \"2091-06-19 00:00:00\",\n    \"2091-07-04 00:00:00\",\n    \"2091-09-03 00:00:00\",\n    \"2091-10-08 00:00:00\",\n    \"2091-11-12 00:00:00\",\n    \"2091-11-22 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-21 00:00:00\",\n    \"2092-02-18 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-05-26 00:00:00\",\n    \"2092-06-19 00:00:00\",\n    \"2092-07-04 00:00:00\",\n    \"2092-09-01 00:00:00\",\n    \"2092-10-13 00:00:00\",\n    \"2092-11-11 00:00:00\",\n    \"2092-11-27 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-19 00:00:00\",\n    \"2093-02-16 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-05-25 00:00:00\",\n    \"2093-06-19 00:00:00\",\n    \"2093-07-03 00:00:00\",\n    \"2093-09-07 00:00:00\",\n    \"2093-10-12 00:00:00\",\n    \"2093-11-11 00:00:00\",\n    \"2093-11-26 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-18 00:00:00\",\n    \"2094-02-15 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-05-31 00:00:00\",\n    \"2094-06-19 00:00:00\",\n    \"2094-07-05 00:00:00\",\n    \"2094-09-06 00:00:00\",\n    \"2094-10-11 00:00:00\",\n    \"2094-11-11 00:00:00\",\n    \"2094-11-25 00:00:00\",\n    \"2094-12-24 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-01-17 00:00:00\",\n    \"2095-02-21 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-05-30 00:00:00\",\n    \"2095-06-20 00:00:00\",\n    \"2095-07-04 00:00:00\",\n    \"2095-09-05 00:00:00\",\n    \"2095-10-10 00:00:00\",\n    \"2095-11-11 00:00:00\",\n    \"2095-11-24 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-01-16 00:00:00\",\n    \"2096-02-20 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-05-28 00:00:00\",\n    \"2096-06-19 00:00:00\",\n    \"2096-07-04 00:00:00\",\n    \"2096-09-03 00:00:00\",\n    \"2096-10-08 00:00:00\",\n    \"2096-11-12 00:00:00\",\n    \"2096-11-22 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-21 00:00:00\",\n    \"2097-02-18 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-05-27 00:00:00\",\n    \"2097-06-19 00:00:00\",\n    \"2097-07-04 00:00:00\",\n    \"2097-09-02 00:00:00\",\n    \"2097-10-14 00:00:00\",\n    \"2097-11-11 00:00:00\",\n    \"2097-11-28 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-20 00:00:00\",\n    \"2098-02-17 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-05-26 00:00:00\",\n    \"2098-06-19 00:00:00\",\n    \"2098-07-04 00:00:00\",\n    \"2098-09-01 00:00:00\",\n    \"2098-10-13 00:00:00\",\n    \"2098-11-11 00:00:00\",\n    \"2098-11-27 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-19 00:00:00\",\n    \"2099-02-16 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-05-25 00:00:00\",\n    \"2099-06-19 00:00:00\",\n    \"2099-07-03 00:00:00\",\n    \"2099-09-07 00:00:00\",\n    \"2099-10-12 00:00:00\",\n    \"2099-11-11 00:00:00\",\n    \"2099-11-26 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-18 00:00:00\",\n    \"2100-02-15 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-05-31 00:00:00\",\n    \"2100-06-19 00:00:00\",\n    \"2100-07-05 00:00:00\",\n    \"2100-09-06 00:00:00\",\n    \"2100-10-11 00:00:00\",\n    \"2100-11-11 00:00:00\",\n    \"2100-11-25 00:00:00\",\n    \"2100-12-24 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-01-17 00:00:00\",\n    \"2101-02-21 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-05-30 00:00:00\",\n    \"2101-06-20 00:00:00\",\n    \"2101-07-04 00:00:00\",\n    \"2101-09-05 00:00:00\",\n    \"2101-10-10 00:00:00\",\n    \"2101-11-11 00:00:00\",\n    \"2101-11-24 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-01-16 00:00:00\",\n    \"2102-02-20 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-05-29 00:00:00\",\n    \"2102-06-19 00:00:00\",\n    \"2102-07-04 00:00:00\",\n    \"2102-09-04 00:00:00\",\n    \"2102-10-09 00:00:00\",\n    \"2102-11-11 00:00:00\",\n    \"2102-11-23 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-01-15 00:00:00\",\n    \"2103-02-19 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-05-28 00:00:00\",\n    \"2103-06-19 00:00:00\",\n    \"2103-07-04 00:00:00\",\n    \"2103-09-03 00:00:00\",\n    \"2103-10-08 00:00:00\",\n    \"2103-11-12 00:00:00\",\n    \"2103-11-22 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-01-21 00:00:00\",\n    \"2104-02-18 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-05-26 00:00:00\",\n    \"2104-06-19 00:00:00\",\n    \"2104-07-04 00:00:00\",\n    \"2104-09-01 00:00:00\",\n    \"2104-10-13 00:00:00\",\n    \"2104-11-11 00:00:00\",\n    \"2104-11-27 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-01-19 00:00:00\",\n    \"2105-02-16 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-05-25 00:00:00\",\n    \"2105-06-19 00:00:00\",\n    \"2105-07-03 00:00:00\",\n    \"2105-09-07 00:00:00\",\n    \"2105-10-12 00:00:00\",\n    \"2105-11-11 00:00:00\",\n    \"2105-11-26 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-01-18 00:00:00\",\n    \"2106-02-15 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-05-31 00:00:00\",\n    \"2106-06-19 00:00:00\",\n    \"2106-07-05 00:00:00\",\n    \"2106-09-06 00:00:00\",\n    \"2106-10-11 00:00:00\",\n    \"2106-11-11 00:00:00\",\n    \"2106-11-25 00:00:00\",\n    \"2106-12-24 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-01-17 00:00:00\",\n    \"2107-02-21 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-05-30 00:00:00\",\n    \"2107-06-20 00:00:00\",\n    \"2107-07-04 00:00:00\",\n    \"2107-09-05 00:00:00\",\n    \"2107-10-10 00:00:00\",\n    \"2107-11-11 00:00:00\",\n    \"2107-11-24 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-01-16 00:00:00\",\n    \"2108-02-20 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-05-28 00:00:00\",\n    \"2108-06-19 00:00:00\",\n    \"2108-07-04 00:00:00\",\n    \"2108-09-03 00:00:00\",\n    \"2108-10-08 00:00:00\",\n    \"2108-11-12 00:00:00\",\n    \"2108-11-22 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-01-21 00:00:00\",\n    \"2109-02-18 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-05-27 00:00:00\",\n    \"2109-06-19 00:00:00\",\n    \"2109-07-04 00:00:00\",\n    \"2109-09-02 00:00:00\",\n    \"2109-10-14 00:00:00\",\n    \"2109-11-11 00:00:00\",\n    \"2109-11-28 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-01-20 00:00:00\",\n    \"2110-02-17 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-05-26 00:00:00\",\n    \"2110-06-19 00:00:00\",\n    \"2110-07-04 00:00:00\",\n    \"2110-09-01 00:00:00\",\n    \"2110-10-13 00:00:00\",\n    \"2110-11-11 00:00:00\",\n    \"2110-11-27 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-01-19 00:00:00\",\n    \"2111-02-16 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-05-25 00:00:00\",\n    \"2111-06-19 00:00:00\",\n    \"2111-07-03 00:00:00\",\n    \"2111-09-07 00:00:00\",\n    \"2111-10-12 00:00:00\",\n    \"2111-11-11 00:00:00\",\n    \"2111-11-26 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-01-18 00:00:00\",\n    \"2112-02-15 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-05-30 00:00:00\",\n    \"2112-06-20 00:00:00\",\n    \"2112-07-04 00:00:00\",\n    \"2112-09-05 00:00:00\",\n    \"2112-10-10 00:00:00\",\n    \"2112-11-11 00:00:00\",\n    \"2112-11-24 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-01-16 00:00:00\",\n    \"2113-02-20 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-05-29 00:00:00\",\n    \"2113-06-19 00:00:00\",\n    \"2113-07-04 00:00:00\",\n    \"2113-09-04 00:00:00\",\n    \"2113-10-09 00:00:00\",\n    \"2113-11-11 00:00:00\",\n    \"2113-11-23 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-01-15 00:00:00\",\n    \"2114-02-19 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-05-28 00:00:00\",\n    \"2114-06-19 00:00:00\",\n    \"2114-07-04 00:00:00\",\n    \"2114-09-03 00:00:00\",\n    \"2114-10-08 00:00:00\",\n    \"2114-11-12 00:00:00\",\n    \"2114-11-22 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-01-21 00:00:00\",\n    \"2115-02-18 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-05-27 00:00:00\",\n    \"2115-06-19 00:00:00\",\n    \"2115-07-04 00:00:00\",\n    \"2115-09-02 00:00:00\",\n    \"2115-10-14 00:00:00\",\n    \"2115-11-11 00:00:00\",\n    \"2115-11-28 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-01-20 00:00:00\",\n    \"2116-02-17 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-05-25 00:00:00\",\n    \"2116-06-19 00:00:00\",\n    \"2116-07-03 00:00:00\",\n    \"2116-09-07 00:00:00\",\n    \"2116-10-12 00:00:00\",\n    \"2116-11-11 00:00:00\",\n    \"2116-11-26 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-01-18 00:00:00\",\n    \"2117-02-15 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-05-31 00:00:00\",\n    \"2117-06-19 00:00:00\",\n    \"2117-07-05 00:00:00\",\n    \"2117-09-06 00:00:00\",\n    \"2117-10-11 00:00:00\",\n    \"2117-11-11 00:00:00\",\n    \"2117-11-25 00:00:00\",\n    \"2117-12-24 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-01-17 00:00:00\",\n    \"2118-02-21 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-05-30 00:00:00\",\n    \"2118-06-20 00:00:00\",\n    \"2118-07-04 00:00:00\",\n    \"2118-09-05 00:00:00\",\n    \"2118-10-10 00:00:00\",\n    \"2118-11-11 00:00:00\",\n    \"2118-11-24 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-01-16 00:00:00\",\n    \"2119-02-20 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-05-29 00:00:00\",\n    \"2119-06-19 00:00:00\",\n    \"2119-07-04 00:00:00\",\n    \"2119-09-04 00:00:00\",\n    \"2119-10-09 00:00:00\",\n    \"2119-11-11 00:00:00\",\n    \"2119-11-23 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-01-15 00:00:00\",\n    \"2120-02-19 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-05-27 00:00:00\",\n    \"2120-06-19 00:00:00\",\n    \"2120-07-04 00:00:00\",\n    \"2120-09-02 00:00:00\",\n    \"2120-10-14 00:00:00\",\n    \"2120-11-11 00:00:00\",\n    \"2120-11-28 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-01-20 00:00:00\",\n    \"2121-02-17 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-05-26 00:00:00\",\n    \"2121-06-19 00:00:00\",\n    \"2121-07-04 00:00:00\",\n    \"2121-09-01 00:00:00\",\n    \"2121-10-13 00:00:00\",\n    \"2121-11-11 00:00:00\",\n    \"2121-11-27 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-01-19 00:00:00\",\n    \"2122-02-16 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-05-25 00:00:00\",\n    \"2122-06-19 00:00:00\",\n    \"2122-07-03 00:00:00\",\n    \"2122-09-07 00:00:00\",\n    \"2122-10-12 00:00:00\",\n    \"2122-11-11 00:00:00\",\n    \"2122-11-26 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-01-18 00:00:00\",\n    \"2123-02-15 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-05-31 00:00:00\",\n    \"2123-06-19 00:00:00\",\n    \"2123-07-05 00:00:00\",\n    \"2123-09-06 00:00:00\",\n    \"2123-10-11 00:00:00\",\n    \"2123-11-11 00:00:00\",\n    \"2123-11-25 00:00:00\",\n    \"2123-12-24 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-01-17 00:00:00\",\n    \"2124-02-21 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-05-29 00:00:00\",\n    \"2124-06-19 00:00:00\",\n    \"2124-07-04 00:00:00\",\n    \"2124-09-04 00:00:00\",\n    \"2124-10-09 00:00:00\",\n    \"2124-11-11 00:00:00\",\n    \"2124-11-23 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-01-15 00:00:00\",\n    \"2125-02-19 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-05-28 00:00:00\",\n    \"2125-06-19 00:00:00\",\n    \"2125-07-04 00:00:00\",\n    \"2125-09-03 00:00:00\",\n    \"2125-10-08 00:00:00\",\n    \"2125-11-12 00:00:00\",\n    \"2125-11-22 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-01-21 00:00:00\",\n    \"2126-02-18 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-05-27 00:00:00\",\n    \"2126-06-19 00:00:00\",\n    \"2126-07-04 00:00:00\",\n    \"2126-09-02 00:00:00\",\n    \"2126-10-14 00:00:00\",\n    \"2126-11-11 00:00:00\",\n    \"2126-11-28 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-01-20 00:00:00\",\n    \"2127-02-17 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-05-26 00:00:00\",\n    \"2127-06-19 00:00:00\",\n    \"2127-07-04 00:00:00\",\n    \"2127-09-01 00:00:00\",\n    \"2127-10-13 00:00:00\",\n    \"2127-11-11 00:00:00\",\n    \"2127-11-27 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-01-19 00:00:00\",\n    \"2128-02-16 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-05-31 00:00:00\",\n    \"2128-06-19 00:00:00\",\n    \"2128-07-05 00:00:00\",\n    \"2128-09-06 00:00:00\",\n    \"2128-10-11 00:00:00\",\n    \"2128-11-11 00:00:00\",\n    \"2128-11-25 00:00:00\",\n    \"2128-12-24 00:00:00\",\n    \"2129-01-01 00:00:00\",\n    \"2129-01-17 00:00:00\",\n    \"2129-02-21 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-05-30 00:00:00\",\n    \"2129-06-20 00:00:00\",\n    \"2129-07-04 00:00:00\",\n    \"2129-09-05 00:00:00\",\n    \"2129-10-10 00:00:00\",\n    \"2129-11-11 00:00:00\",\n    \"2129-11-24 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-01-16 00:00:00\",\n    \"2130-02-20 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-05-29 00:00:00\",\n    \"2130-06-19 00:00:00\",\n    \"2130-07-04 00:00:00\",\n    \"2130-09-04 00:00:00\",\n    \"2130-10-09 00:00:00\",\n    \"2130-11-11 00:00:00\",\n    \"2130-11-23 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-01-15 00:00:00\",\n    \"2131-02-19 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-05-28 00:00:00\",\n    \"2131-06-19 00:00:00\",\n    \"2131-07-04 00:00:00\",\n    \"2131-09-03 00:00:00\",\n    \"2131-10-08 00:00:00\",\n    \"2131-11-12 00:00:00\",\n    \"2131-11-22 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-01-21 00:00:00\",\n    \"2132-02-18 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-05-26 00:00:00\",\n    \"2132-06-19 00:00:00\",\n    \"2132-07-04 00:00:00\",\n    \"2132-09-01 00:00:00\",\n    \"2132-10-13 00:00:00\",\n    \"2132-11-11 00:00:00\",\n    \"2132-11-27 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-01-19 00:00:00\",\n    \"2133-02-16 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-05-25 00:00:00\",\n    \"2133-06-19 00:00:00\",\n    \"2133-07-03 00:00:00\",\n    \"2133-09-07 00:00:00\",\n    \"2133-10-12 00:00:00\",\n    \"2133-11-11 00:00:00\",\n    \"2133-11-26 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-01-18 00:00:00\",\n    \"2134-02-15 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-05-31 00:00:00\",\n    \"2134-06-19 00:00:00\",\n    \"2134-07-05 00:00:00\",\n    \"2134-09-06 00:00:00\",\n    \"2134-10-11 00:00:00\",\n    \"2134-11-11 00:00:00\",\n    \"2134-11-25 00:00:00\",\n    \"2134-12-24 00:00:00\",\n    \"2135-01-01 00:00:00\",\n    \"2135-01-17 00:00:00\",\n    \"2135-02-21 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-05-30 00:00:00\",\n    \"2135-06-20 00:00:00\",\n    \"2135-07-04 00:00:00\",\n    \"2135-09-05 00:00:00\",\n    \"2135-10-10 00:00:00\",\n    \"2135-11-11 00:00:00\",\n    \"2135-11-24 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-01-16 00:00:00\",\n    \"2136-02-20 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-05-28 00:00:00\",\n    \"2136-06-19 00:00:00\",\n    \"2136-07-04 00:00:00\",\n    \"2136-09-03 00:00:00\",\n    \"2136-10-08 00:00:00\",\n    \"2136-11-12 00:00:00\",\n    \"2136-11-22 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-01-21 00:00:00\",\n    \"2137-02-18 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-05-27 00:00:00\",\n    \"2137-06-19 00:00:00\",\n    \"2137-07-04 00:00:00\",\n    \"2137-09-02 00:00:00\",\n    \"2137-10-14 00:00:00\",\n    \"2137-11-11 00:00:00\",\n    \"2137-11-28 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-01-20 00:00:00\",\n    \"2138-02-17 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-05-26 00:00:00\",\n    \"2138-06-19 00:00:00\",\n    \"2138-07-04 00:00:00\",\n    \"2138-09-01 00:00:00\",\n    \"2138-10-13 00:00:00\",\n    \"2138-11-11 00:00:00\",\n    \"2138-11-27 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-01-19 00:00:00\",\n    \"2139-02-16 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-05-25 00:00:00\",\n    \"2139-06-19 00:00:00\",\n    \"2139-07-03 00:00:00\",\n    \"2139-09-07 00:00:00\",\n    \"2139-10-12 00:00:00\",\n    \"2139-11-11 00:00:00\",\n    \"2139-11-26 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-01-18 00:00:00\",\n    \"2140-02-15 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-05-30 00:00:00\",\n    \"2140-06-20 00:00:00\",\n    \"2140-07-04 00:00:00\",\n    \"2140-09-05 00:00:00\",\n    \"2140-10-10 00:00:00\",\n    \"2140-11-11 00:00:00\",\n    \"2140-11-24 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-01-16 00:00:00\",\n    \"2141-02-20 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-05-29 00:00:00\",\n    \"2141-06-19 00:00:00\",\n    \"2141-07-04 00:00:00\",\n    \"2141-09-04 00:00:00\",\n    \"2141-10-09 00:00:00\",\n    \"2141-11-11 00:00:00\",\n    \"2141-11-23 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-01-15 00:00:00\",\n    \"2142-02-19 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-05-28 00:00:00\",\n    \"2142-06-19 00:00:00\",\n    \"2142-07-04 00:00:00\",\n    \"2142-09-03 00:00:00\",\n    \"2142-10-08 00:00:00\",\n    \"2142-11-12 00:00:00\",\n    \"2142-11-22 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-01-21 00:00:00\",\n    \"2143-02-18 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-05-27 00:00:00\",\n    \"2143-06-19 00:00:00\",\n    \"2143-07-04 00:00:00\",\n    \"2143-09-02 00:00:00\",\n    \"2143-10-14 00:00:00\",\n    \"2143-11-11 00:00:00\",\n    \"2143-11-28 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-01-20 00:00:00\",\n    \"2144-02-17 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-05-25 00:00:00\",\n    \"2144-06-19 00:00:00\",\n    \"2144-07-03 00:00:00\",\n    \"2144-09-07 00:00:00\",\n    \"2144-10-12 00:00:00\",\n    \"2144-11-11 00:00:00\",\n    \"2144-11-26 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-01-18 00:00:00\",\n    \"2145-02-15 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-05-31 00:00:00\",\n    \"2145-06-19 00:00:00\",\n    \"2145-07-05 00:00:00\",\n    \"2145-09-06 00:00:00\",\n    \"2145-10-11 00:00:00\",\n    \"2145-11-11 00:00:00\",\n    \"2145-11-25 00:00:00\",\n    \"2145-12-24 00:00:00\",\n    \"2146-01-01 00:00:00\",\n    \"2146-01-17 00:00:00\",\n    \"2146-02-21 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-05-30 00:00:00\",\n    \"2146-06-20 00:00:00\",\n    \"2146-07-04 00:00:00\",\n    \"2146-09-05 00:00:00\",\n    \"2146-10-10 00:00:00\",\n    \"2146-11-11 00:00:00\",\n    \"2146-11-24 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-01-16 00:00:00\",\n    \"2147-02-20 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-05-29 00:00:00\",\n    \"2147-06-19 00:00:00\",\n    \"2147-07-04 00:00:00\",\n    \"2147-09-04 00:00:00\",\n    \"2147-10-09 00:00:00\",\n    \"2147-11-11 00:00:00\",\n    \"2147-11-23 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-01-15 00:00:00\",\n    \"2148-02-19 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-05-27 00:00:00\",\n    \"2148-06-19 00:00:00\",\n    \"2148-07-04 00:00:00\",\n    \"2148-09-02 00:00:00\",\n    \"2148-10-14 00:00:00\",\n    \"2148-11-11 00:00:00\",\n    \"2148-11-28 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-01-20 00:00:00\",\n    \"2149-02-17 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-05-26 00:00:00\",\n    \"2149-06-19 00:00:00\",\n    \"2149-07-04 00:00:00\",\n    \"2149-09-01 00:00:00\",\n    \"2149-10-13 00:00:00\",\n    \"2149-11-11 00:00:00\",\n    \"2149-11-27 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-01-19 00:00:00\",\n    \"2150-02-16 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-05-25 00:00:00\",\n    \"2150-06-19 00:00:00\",\n    \"2150-07-03 00:00:00\",\n    \"2150-09-07 00:00:00\",\n    \"2150-10-12 00:00:00\",\n    \"2150-11-11 00:00:00\",\n    \"2150-11-26 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-01-18 00:00:00\",\n    \"2151-02-15 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-05-31 00:00:00\",\n    \"2151-06-19 00:00:00\",\n    \"2151-07-05 00:00:00\",\n    \"2151-09-06 00:00:00\",\n    \"2151-10-11 00:00:00\",\n    \"2151-11-11 00:00:00\",\n    \"2151-11-25 00:00:00\",\n    \"2151-12-24 00:00:00\",\n    \"2152-01-01 00:00:00\",\n    \"2152-01-17 00:00:00\",\n    \"2152-02-21 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-05-29 00:00:00\",\n    \"2152-06-19 00:00:00\",\n    \"2152-07-04 00:00:00\",\n    \"2152-09-04 00:00:00\",\n    \"2152-10-09 00:00:00\",\n    \"2152-11-11 00:00:00\",\n    \"2152-11-23 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-01-15 00:00:00\",\n    \"2153-02-19 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-05-28 00:00:00\",\n    \"2153-06-19 00:00:00\",\n    \"2153-07-04 00:00:00\",\n    \"2153-09-03 00:00:00\",\n    \"2153-10-08 00:00:00\",\n    \"2153-11-12 00:00:00\",\n    \"2153-11-22 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-01-21 00:00:00\",\n    \"2154-02-18 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-05-27 00:00:00\",\n    \"2154-06-19 00:00:00\",\n    \"2154-07-04 00:00:00\",\n    \"2154-09-02 00:00:00\",\n    \"2154-10-14 00:00:00\",\n    \"2154-11-11 00:00:00\",\n    \"2154-11-28 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-01-20 00:00:00\",\n    \"2155-02-17 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-05-26 00:00:00\",\n    \"2155-06-19 00:00:00\",\n    \"2155-07-04 00:00:00\",\n    \"2155-09-01 00:00:00\",\n    \"2155-10-13 00:00:00\",\n    \"2155-11-11 00:00:00\",\n    \"2155-11-27 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-01-19 00:00:00\",\n    \"2156-02-16 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-05-31 00:00:00\",\n    \"2156-06-19 00:00:00\",\n    \"2156-07-05 00:00:00\",\n    \"2156-09-06 00:00:00\",\n    \"2156-10-11 00:00:00\",\n    \"2156-11-11 00:00:00\",\n    \"2156-11-25 00:00:00\",\n    \"2156-12-24 00:00:00\",\n    \"2157-01-01 00:00:00\",\n    \"2157-01-17 00:00:00\",\n    \"2157-02-21 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-05-30 00:00:00\",\n    \"2157-06-20 00:00:00\",\n    \"2157-07-04 00:00:00\",\n    \"2157-09-05 00:00:00\",\n    \"2157-10-10 00:00:00\",\n    \"2157-11-11 00:00:00\",\n    \"2157-11-24 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-01-16 00:00:00\",\n    \"2158-02-20 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-05-29 00:00:00\",\n    \"2158-06-19 00:00:00\",\n    \"2158-07-04 00:00:00\",\n    \"2158-09-04 00:00:00\",\n    \"2158-10-09 00:00:00\",\n    \"2158-11-11 00:00:00\",\n    \"2158-11-23 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-01-15 00:00:00\",\n    \"2159-02-19 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-05-28 00:00:00\",\n    \"2159-06-19 00:00:00\",\n    \"2159-07-04 00:00:00\",\n    \"2159-09-03 00:00:00\",\n    \"2159-10-08 00:00:00\",\n    \"2159-11-12 00:00:00\",\n    \"2159-11-22 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-01-21 00:00:00\",\n    \"2160-02-18 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-05-26 00:00:00\",\n    \"2160-06-19 00:00:00\",\n    \"2160-07-04 00:00:00\",\n    \"2160-09-01 00:00:00\",\n    \"2160-10-13 00:00:00\",\n    \"2160-11-11 00:00:00\",\n    \"2160-11-27 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-01-19 00:00:00\",\n    \"2161-02-16 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-05-25 00:00:00\",\n    \"2161-06-19 00:00:00\",\n    \"2161-07-03 00:00:00\",\n    \"2161-09-07 00:00:00\",\n    \"2161-10-12 00:00:00\",\n    \"2161-11-11 00:00:00\",\n    \"2161-11-26 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-01-18 00:00:00\",\n    \"2162-02-15 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-05-31 00:00:00\",\n    \"2162-06-19 00:00:00\",\n    \"2162-07-05 00:00:00\",\n    \"2162-09-06 00:00:00\",\n    \"2162-10-11 00:00:00\",\n    \"2162-11-11 00:00:00\",\n    \"2162-11-25 00:00:00\",\n    \"2162-12-24 00:00:00\",\n    \"2163-01-01 00:00:00\",\n    \"2163-01-17 00:00:00\",\n    \"2163-02-21 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-05-30 00:00:00\",\n    \"2163-06-20 00:00:00\",\n    \"2163-07-04 00:00:00\",\n    \"2163-09-05 00:00:00\",\n    \"2163-10-10 00:00:00\",\n    \"2163-11-11 00:00:00\",\n    \"2163-11-24 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-01-16 00:00:00\",\n    \"2164-02-20 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-05-28 00:00:00\",\n    \"2164-06-19 00:00:00\",\n    \"2164-07-04 00:00:00\",\n    \"2164-09-03 00:00:00\",\n    \"2164-10-08 00:00:00\",\n    \"2164-11-12 00:00:00\",\n    \"2164-11-22 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-01-21 00:00:00\",\n    \"2165-02-18 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-05-27 00:00:00\",\n    \"2165-06-19 00:00:00\",\n    \"2165-07-04 00:00:00\",\n    \"2165-09-02 00:00:00\",\n    \"2165-10-14 00:00:00\",\n    \"2165-11-11 00:00:00\",\n    \"2165-11-28 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-01-20 00:00:00\",\n    \"2166-02-17 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-05-26 00:00:00\",\n    \"2166-06-19 00:00:00\",\n    \"2166-07-04 00:00:00\",\n    \"2166-09-01 00:00:00\",\n    \"2166-10-13 00:00:00\",\n    \"2166-11-11 00:00:00\",\n    \"2166-11-27 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-01-19 00:00:00\",\n    \"2167-02-16 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-05-25 00:00:00\",\n    \"2167-06-19 00:00:00\",\n    \"2167-07-03 00:00:00\",\n    \"2167-09-07 00:00:00\",\n    \"2167-10-12 00:00:00\",\n    \"2167-11-11 00:00:00\",\n    \"2167-11-26 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-01-18 00:00:00\",\n    \"2168-02-15 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-05-30 00:00:00\",\n    \"2168-06-20 00:00:00\",\n    \"2168-07-04 00:00:00\",\n    \"2168-09-05 00:00:00\",\n    \"2168-10-10 00:00:00\",\n    \"2168-11-11 00:00:00\",\n    \"2168-11-24 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-01-16 00:00:00\",\n    \"2169-02-20 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-05-29 00:00:00\",\n    \"2169-06-19 00:00:00\",\n    \"2169-07-04 00:00:00\",\n    \"2169-09-04 00:00:00\",\n    \"2169-10-09 00:00:00\",\n    \"2169-11-11 00:00:00\",\n    \"2169-11-23 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-01-15 00:00:00\",\n    \"2170-02-19 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-05-28 00:00:00\",\n    \"2170-06-19 00:00:00\",\n    \"2170-07-04 00:00:00\",\n    \"2170-09-03 00:00:00\",\n    \"2170-10-08 00:00:00\",\n    \"2170-11-12 00:00:00\",\n    \"2170-11-22 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-01-21 00:00:00\",\n    \"2171-02-18 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-05-27 00:00:00\",\n    \"2171-06-19 00:00:00\",\n    \"2171-07-04 00:00:00\",\n    \"2171-09-02 00:00:00\",\n    \"2171-10-14 00:00:00\",\n    \"2171-11-11 00:00:00\",\n    \"2171-11-28 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-01-20 00:00:00\",\n    \"2172-02-17 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-05-25 00:00:00\",\n    \"2172-06-19 00:00:00\",\n    \"2172-07-03 00:00:00\",\n    \"2172-09-07 00:00:00\",\n    \"2172-10-12 00:00:00\",\n    \"2172-11-11 00:00:00\",\n    \"2172-11-26 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-01-18 00:00:00\",\n    \"2173-02-15 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-05-31 00:00:00\",\n    \"2173-06-19 00:00:00\",\n    \"2173-07-05 00:00:00\",\n    \"2173-09-06 00:00:00\",\n    \"2173-10-11 00:00:00\",\n    \"2173-11-11 00:00:00\",\n    \"2173-11-25 00:00:00\",\n    \"2173-12-24 00:00:00\",\n    \"2174-01-01 00:00:00\",\n    \"2174-01-17 00:00:00\",\n    \"2174-02-21 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-05-30 00:00:00\",\n    \"2174-06-20 00:00:00\",\n    \"2174-07-04 00:00:00\",\n    \"2174-09-05 00:00:00\",\n    \"2174-10-10 00:00:00\",\n    \"2174-11-11 00:00:00\",\n    \"2174-11-24 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-01-16 00:00:00\",\n    \"2175-02-20 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-05-29 00:00:00\",\n    \"2175-06-19 00:00:00\",\n    \"2175-07-04 00:00:00\",\n    \"2175-09-04 00:00:00\",\n    \"2175-10-09 00:00:00\",\n    \"2175-11-11 00:00:00\",\n    \"2175-11-23 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-01-15 00:00:00\",\n    \"2176-02-19 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-05-27 00:00:00\",\n    \"2176-06-19 00:00:00\",\n    \"2176-07-04 00:00:00\",\n    \"2176-09-02 00:00:00\",\n    \"2176-10-14 00:00:00\",\n    \"2176-11-11 00:00:00\",\n    \"2176-11-28 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-01-20 00:00:00\",\n    \"2177-02-17 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-05-26 00:00:00\",\n    \"2177-06-19 00:00:00\",\n    \"2177-07-04 00:00:00\",\n    \"2177-09-01 00:00:00\",\n    \"2177-10-13 00:00:00\",\n    \"2177-11-11 00:00:00\",\n    \"2177-11-27 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-01-19 00:00:00\",\n    \"2178-02-16 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-05-25 00:00:00\",\n    \"2178-06-19 00:00:00\",\n    \"2178-07-03 00:00:00\",\n    \"2178-09-07 00:00:00\",\n    \"2178-10-12 00:00:00\",\n    \"2178-11-11 00:00:00\",\n    \"2178-11-26 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-01-18 00:00:00\",\n    \"2179-02-15 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-05-31 00:00:00\",\n    \"2179-06-19 00:00:00\",\n    \"2179-07-05 00:00:00\",\n    \"2179-09-06 00:00:00\",\n    \"2179-10-11 00:00:00\",\n    \"2179-11-11 00:00:00\",\n    \"2179-11-25 00:00:00\",\n    \"2179-12-24 00:00:00\",\n    \"2180-01-01 00:00:00\",\n    \"2180-01-17 00:00:00\",\n    \"2180-02-21 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-05-29 00:00:00\",\n    \"2180-06-19 00:00:00\",\n    \"2180-07-04 00:00:00\",\n    \"2180-09-04 00:00:00\",\n    \"2180-10-09 00:00:00\",\n    \"2180-11-11 00:00:00\",\n    \"2180-11-23 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-01-15 00:00:00\",\n    \"2181-02-19 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-05-28 00:00:00\",\n    \"2181-06-19 00:00:00\",\n    \"2181-07-04 00:00:00\",\n    \"2181-09-03 00:00:00\",\n    \"2181-10-08 00:00:00\",\n    \"2181-11-12 00:00:00\",\n    \"2181-11-22 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-01-21 00:00:00\",\n    \"2182-02-18 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-05-27 00:00:00\",\n    \"2182-06-19 00:00:00\",\n    \"2182-07-04 00:00:00\",\n    \"2182-09-02 00:00:00\",\n    \"2182-10-14 00:00:00\",\n    \"2182-11-11 00:00:00\",\n    \"2182-11-28 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-01-20 00:00:00\",\n    \"2183-02-17 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-05-26 00:00:00\",\n    \"2183-06-19 00:00:00\",\n    \"2183-07-04 00:00:00\",\n    \"2183-09-01 00:00:00\",\n    \"2183-10-13 00:00:00\",\n    \"2183-11-11 00:00:00\",\n    \"2183-11-27 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-01-19 00:00:00\",\n    \"2184-02-16 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-05-31 00:00:00\",\n    \"2184-06-19 00:00:00\",\n    \"2184-07-05 00:00:00\",\n    \"2184-09-06 00:00:00\",\n    \"2184-10-11 00:00:00\",\n    \"2184-11-11 00:00:00\",\n    \"2184-11-25 00:00:00\",\n    \"2184-12-24 00:00:00\",\n    \"2185-01-01 00:00:00\",\n    \"2185-01-17 00:00:00\",\n    \"2185-02-21 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-05-30 00:00:00\",\n    \"2185-06-20 00:00:00\",\n    \"2185-07-04 00:00:00\",\n    \"2185-09-05 00:00:00\",\n    \"2185-10-10 00:00:00\",\n    \"2185-11-11 00:00:00\",\n    \"2185-11-24 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-01-16 00:00:00\",\n    \"2186-02-20 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-05-29 00:00:00\",\n    \"2186-06-19 00:00:00\",\n    \"2186-07-04 00:00:00\",\n    \"2186-09-04 00:00:00\",\n    \"2186-10-09 00:00:00\",\n    \"2186-11-11 00:00:00\",\n    \"2186-11-23 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-01-15 00:00:00\",\n    \"2187-02-19 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-05-28 00:00:00\",\n    \"2187-06-19 00:00:00\",\n    \"2187-07-04 00:00:00\",\n    \"2187-09-03 00:00:00\",\n    \"2187-10-08 00:00:00\",\n    \"2187-11-12 00:00:00\",\n    \"2187-11-22 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-01-21 00:00:00\",\n    \"2188-02-18 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-05-26 00:00:00\",\n    \"2188-06-19 00:00:00\",\n    \"2188-07-04 00:00:00\",\n    \"2188-09-01 00:00:00\",\n    \"2188-10-13 00:00:00\",\n    \"2188-11-11 00:00:00\",\n    \"2188-11-27 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-01-19 00:00:00\",\n    \"2189-02-16 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-05-25 00:00:00\",\n    \"2189-06-19 00:00:00\",\n    \"2189-07-03 00:00:00\",\n    \"2189-09-07 00:00:00\",\n    \"2189-10-12 00:00:00\",\n    \"2189-11-11 00:00:00\",\n    \"2189-11-26 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-01-18 00:00:00\",\n    \"2190-02-15 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-05-31 00:00:00\",\n    \"2190-06-19 00:00:00\",\n    \"2190-07-05 00:00:00\",\n    \"2190-09-06 00:00:00\",\n    \"2190-10-11 00:00:00\",\n    \"2190-11-11 00:00:00\",\n    \"2190-11-25 00:00:00\",\n    \"2190-12-24 00:00:00\",\n    \"2191-01-01 00:00:00\",\n    \"2191-01-17 00:00:00\",\n    \"2191-02-21 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-05-30 00:00:00\",\n    \"2191-06-20 00:00:00\",\n    \"2191-07-04 00:00:00\",\n    \"2191-09-05 00:00:00\",\n    \"2191-10-10 00:00:00\",\n    \"2191-11-11 00:00:00\",\n    \"2191-11-24 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-01-16 00:00:00\",\n    \"2192-02-20 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-05-28 00:00:00\",\n    \"2192-06-19 00:00:00\",\n    \"2192-07-04 00:00:00\",\n    \"2192-09-03 00:00:00\",\n    \"2192-10-08 00:00:00\",\n    \"2192-11-12 00:00:00\",\n    \"2192-11-22 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-01-21 00:00:00\",\n    \"2193-02-18 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-05-27 00:00:00\",\n    \"2193-06-19 00:00:00\",\n    \"2193-07-04 00:00:00\",\n    \"2193-09-02 00:00:00\",\n    \"2193-10-14 00:00:00\",\n    \"2193-11-11 00:00:00\",\n    \"2193-11-28 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-01-20 00:00:00\",\n    \"2194-02-17 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-05-26 00:00:00\",\n    \"2194-06-19 00:00:00\",\n    \"2194-07-04 00:00:00\",\n    \"2194-09-01 00:00:00\",\n    \"2194-10-13 00:00:00\",\n    \"2194-11-11 00:00:00\",\n    \"2194-11-27 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-01-19 00:00:00\",\n    \"2195-02-16 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-05-25 00:00:00\",\n    \"2195-06-19 00:00:00\",\n    \"2195-07-03 00:00:00\",\n    \"2195-09-07 00:00:00\",\n    \"2195-10-12 00:00:00\",\n    \"2195-11-11 00:00:00\",\n    \"2195-11-26 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-01-18 00:00:00\",\n    \"2196-02-15 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-05-30 00:00:00\",\n    \"2196-06-20 00:00:00\",\n    \"2196-07-04 00:00:00\",\n    \"2196-09-05 00:00:00\",\n    \"2196-10-10 00:00:00\",\n    \"2196-11-11 00:00:00\",\n    \"2196-11-24 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-01-16 00:00:00\",\n    \"2197-02-20 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-05-29 00:00:00\",\n    \"2197-06-19 00:00:00\",\n    \"2197-07-04 00:00:00\",\n    \"2197-09-04 00:00:00\",\n    \"2197-10-09 00:00:00\",\n    \"2197-11-11 00:00:00\",\n    \"2197-11-23 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-01-15 00:00:00\",\n    \"2198-02-19 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-05-28 00:00:00\",\n    \"2198-06-19 00:00:00\",\n    \"2198-07-04 00:00:00\",\n    \"2198-09-03 00:00:00\",\n    \"2198-10-08 00:00:00\",\n    \"2198-11-12 00:00:00\",\n    \"2198-11-22 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-01-21 00:00:00\",\n    \"2199-02-18 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-05-27 00:00:00\",\n    \"2199-06-19 00:00:00\",\n    \"2199-07-04 00:00:00\",\n    \"2199-09-02 00:00:00\",\n    \"2199-10-14 00:00:00\",\n    \"2199-11-11 00:00:00\",\n    \"2199-11-28 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-01-20 00:00:00\",\n    \"2200-02-17 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-05-26 00:00:00\",\n    \"2200-06-19 00:00:00\",\n    \"2200-07-04 00:00:00\",\n    \"2200-09-01 00:00:00\",\n    \"2200-10-13 00:00:00\",\n    \"2200-11-11 00:00:00\",\n    \"2200-11-27 00:00:00\",\n    \"2200-12-25 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/nyc_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime\n\nimport pandas as pd\nfrom dateutil.relativedelta import MO, TH\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n    nearest_workday,\n    sunday_to_monday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, DateOffset, Day, Easter\n\nRULES = [\n    Holiday(\"New Year's Day Holiday\", month=1, day=1, observance=sunday_to_monday),\n    Holiday(\n        \"Dr. Martin Luther King Jr.\",\n        start_date=datetime(1986, 1, 1),\n        month=1,\n        day=1,\n        offset=DateOffset(weekday=MO(3)),\n    ),\n    Holiday(\"US Presidents Day\", month=2, day=1, offset=DateOffset(weekday=MO(3))),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"US Memorial Day\", month=5, day=31, offset=DateOffset(weekday=MO(-1))),\n    Holiday(\n        \"Juneteenth Independence Day\",\n        start_date=datetime(2022, 1, 1),\n        month=6,\n        day=19,\n        observance=sunday_to_monday,\n    ),\n    Holiday(\"US Independence Day\", month=7, day=4, observance=nearest_workday),\n    Holiday(\"US Labour Day\", month=9, day=1, offset=DateOffset(weekday=MO(1))),\n    Holiday(\"US Columbus Day\", month=10, day=1, offset=DateOffset(weekday=MO(2))),\n    Holiday(\"Veterans Day\", month=11, day=11, observance=sunday_to_monday),\n    Holiday(\"US Thanksgiving\", month=11, day=1, offset=DateOffset(weekday=TH(4))),\n    Holiday(\"Christmas Day Sunday Holiday\", month=12, day=25, observance=nearest_workday),\n    Holiday(\"GHW Bush Funeral\", year=2018, month=12, day=5),\n]\n\nCALENDAR = CustomBusinessDay(\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/osl.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define an Oslo business day calendar, aligned with NOWA publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Thu before Easter (Maundy Thursday)\",\n//     \"Fri before Easter (Good Friday)\",\n//     \"Mon after Easter (Easter Monday)\",\n//     \"May 1 (EU Labour)\",\n//     \"May 17 (Constitution)\",\n//     \"39 Days after Easter (Ascention)\",\n//     \"50 Days after Easter (Whit Monday)\",\n//     \"Dec 24 (Christmas Eve)\",\n//     \"Dec 25 (Christmas)\",\n//     \"Dec 26 (Boxing Day)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-03-26 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-03-30 00:00:00\",\n    \"1970-05-01 00:00:00\",\n    \"1970-05-07 00:00:00\",\n    \"1970-05-17 00:00:00\",\n    \"1970-05-18 00:00:00\",\n    \"1970-12-24 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-26 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-04-08 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-12 00:00:00\",\n    \"1971-05-01 00:00:00\",\n    \"1971-05-17 00:00:00\",\n    \"1971-05-20 00:00:00\",\n    \"1971-05-31 00:00:00\",\n    \"1971-12-24 00:00:00\",\n    \"1971-12-25 00:00:00\",\n    \"1971-12-26 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-03-30 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-03 00:00:00\",\n    \"1972-05-01 00:00:00\",\n    \"1972-05-11 00:00:00\",\n    \"1972-05-17 00:00:00\",\n    \"1972-05-22 00:00:00\",\n    \"1972-12-24 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-04-19 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-04-23 00:00:00\",\n    \"1973-05-01 00:00:00\",\n    \"1973-05-17 00:00:00\",\n    \"1973-05-31 00:00:00\",\n    \"1973-06-11 00:00:00\",\n    \"1973-12-24 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-04-11 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-15 00:00:00\",\n    \"1974-05-01 00:00:00\",\n    \"1974-05-17 00:00:00\",\n    \"1974-05-23 00:00:00\",\n    \"1974-06-03 00:00:00\",\n    \"1974-12-24 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-03-27 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-03-31 00:00:00\",\n    \"1975-05-01 00:00:00\",\n    \"1975-05-08 00:00:00\",\n    \"1975-05-17 00:00:00\",\n    \"1975-05-19 00:00:00\",\n    \"1975-12-24 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-04-15 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-04-19 00:00:00\",\n    \"1976-05-01 00:00:00\",\n    \"1976-05-17 00:00:00\",\n    \"1976-05-27 00:00:00\",\n    \"1976-06-07 00:00:00\",\n    \"1976-12-24 00:00:00\",\n    \"1976-12-25 00:00:00\",\n    \"1976-12-26 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-04-07 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-11 00:00:00\",\n    \"1977-05-01 00:00:00\",\n    \"1977-05-17 00:00:00\",\n    \"1977-05-19 00:00:00\",\n    \"1977-05-30 00:00:00\",\n    \"1977-12-24 00:00:00\",\n    \"1977-12-25 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1978-01-01 00:00:00\",\n    \"1978-03-23 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-03-27 00:00:00\",\n    \"1978-05-01 00:00:00\",\n    \"1978-05-04 00:00:00\",\n    \"1978-05-15 00:00:00\",\n    \"1978-05-17 00:00:00\",\n    \"1978-12-24 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-04-12 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-16 00:00:00\",\n    \"1979-05-01 00:00:00\",\n    \"1979-05-17 00:00:00\",\n    \"1979-05-24 00:00:00\",\n    \"1979-06-04 00:00:00\",\n    \"1979-12-24 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-04-03 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-07 00:00:00\",\n    \"1980-05-01 00:00:00\",\n    \"1980-05-15 00:00:00\",\n    \"1980-05-17 00:00:00\",\n    \"1980-05-26 00:00:00\",\n    \"1980-12-24 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-04-16 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-04-20 00:00:00\",\n    \"1981-05-01 00:00:00\",\n    \"1981-05-17 00:00:00\",\n    \"1981-05-28 00:00:00\",\n    \"1981-06-08 00:00:00\",\n    \"1981-12-24 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-26 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-04-08 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-12 00:00:00\",\n    \"1982-05-01 00:00:00\",\n    \"1982-05-17 00:00:00\",\n    \"1982-05-20 00:00:00\",\n    \"1982-05-31 00:00:00\",\n    \"1982-12-24 00:00:00\",\n    \"1982-12-25 00:00:00\",\n    \"1982-12-26 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-03-31 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-04 00:00:00\",\n    \"1983-05-01 00:00:00\",\n    \"1983-05-12 00:00:00\",\n    \"1983-05-17 00:00:00\",\n    \"1983-05-23 00:00:00\",\n    \"1983-12-24 00:00:00\",\n    \"1983-12-25 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1984-01-01 00:00:00\",\n    \"1984-04-19 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-04-23 00:00:00\",\n    \"1984-05-01 00:00:00\",\n    \"1984-05-17 00:00:00\",\n    \"1984-05-31 00:00:00\",\n    \"1984-06-11 00:00:00\",\n    \"1984-12-24 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-04-04 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-08 00:00:00\",\n    \"1985-05-01 00:00:00\",\n    \"1985-05-16 00:00:00\",\n    \"1985-05-17 00:00:00\",\n    \"1985-05-27 00:00:00\",\n    \"1985-12-24 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-03-27 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-03-31 00:00:00\",\n    \"1986-05-01 00:00:00\",\n    \"1986-05-08 00:00:00\",\n    \"1986-05-17 00:00:00\",\n    \"1986-05-19 00:00:00\",\n    \"1986-12-24 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-04-16 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-04-20 00:00:00\",\n    \"1987-05-01 00:00:00\",\n    \"1987-05-17 00:00:00\",\n    \"1987-05-28 00:00:00\",\n    \"1987-06-08 00:00:00\",\n    \"1987-12-24 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-26 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-03-31 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-04 00:00:00\",\n    \"1988-05-01 00:00:00\",\n    \"1988-05-12 00:00:00\",\n    \"1988-05-17 00:00:00\",\n    \"1988-05-23 00:00:00\",\n    \"1988-12-24 00:00:00\",\n    \"1988-12-25 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1989-01-01 00:00:00\",\n    \"1989-03-23 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-03-27 00:00:00\",\n    \"1989-05-01 00:00:00\",\n    \"1989-05-04 00:00:00\",\n    \"1989-05-15 00:00:00\",\n    \"1989-05-17 00:00:00\",\n    \"1989-12-24 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-04-12 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-16 00:00:00\",\n    \"1990-05-01 00:00:00\",\n    \"1990-05-17 00:00:00\",\n    \"1990-05-24 00:00:00\",\n    \"1990-06-04 00:00:00\",\n    \"1990-12-24 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-03-28 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-01 00:00:00\",\n    \"1991-05-01 00:00:00\",\n    \"1991-05-09 00:00:00\",\n    \"1991-05-17 00:00:00\",\n    \"1991-05-20 00:00:00\",\n    \"1991-12-24 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-04-16 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-04-20 00:00:00\",\n    \"1992-05-01 00:00:00\",\n    \"1992-05-17 00:00:00\",\n    \"1992-05-28 00:00:00\",\n    \"1992-06-08 00:00:00\",\n    \"1992-12-24 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-26 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-04-08 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-12 00:00:00\",\n    \"1993-05-01 00:00:00\",\n    \"1993-05-17 00:00:00\",\n    \"1993-05-20 00:00:00\",\n    \"1993-05-31 00:00:00\",\n    \"1993-12-24 00:00:00\",\n    \"1993-12-25 00:00:00\",\n    \"1993-12-26 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-03-31 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-04 00:00:00\",\n    \"1994-05-01 00:00:00\",\n    \"1994-05-12 00:00:00\",\n    \"1994-05-17 00:00:00\",\n    \"1994-05-23 00:00:00\",\n    \"1994-12-24 00:00:00\",\n    \"1994-12-25 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1995-01-01 00:00:00\",\n    \"1995-04-13 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-17 00:00:00\",\n    \"1995-05-01 00:00:00\",\n    \"1995-05-17 00:00:00\",\n    \"1995-05-25 00:00:00\",\n    \"1995-06-05 00:00:00\",\n    \"1995-12-24 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-04-04 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-08 00:00:00\",\n    \"1996-05-01 00:00:00\",\n    \"1996-05-16 00:00:00\",\n    \"1996-05-17 00:00:00\",\n    \"1996-05-27 00:00:00\",\n    \"1996-12-24 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-03-27 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-03-31 00:00:00\",\n    \"1997-05-01 00:00:00\",\n    \"1997-05-08 00:00:00\",\n    \"1997-05-17 00:00:00\",\n    \"1997-05-19 00:00:00\",\n    \"1997-12-24 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-04-09 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-13 00:00:00\",\n    \"1998-05-01 00:00:00\",\n    \"1998-05-17 00:00:00\",\n    \"1998-05-21 00:00:00\",\n    \"1998-06-01 00:00:00\",\n    \"1998-12-24 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-26 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-04-01 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-05 00:00:00\",\n    \"1999-05-01 00:00:00\",\n    \"1999-05-13 00:00:00\",\n    \"1999-05-17 00:00:00\",\n    \"1999-05-24 00:00:00\",\n    \"1999-12-24 00:00:00\",\n    \"1999-12-25 00:00:00\",\n    \"1999-12-26 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-04-20 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-04-24 00:00:00\",\n    \"2000-05-01 00:00:00\",\n    \"2000-05-17 00:00:00\",\n    \"2000-06-01 00:00:00\",\n    \"2000-06-12 00:00:00\",\n    \"2000-12-24 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-04-12 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-16 00:00:00\",\n    \"2001-05-01 00:00:00\",\n    \"2001-05-17 00:00:00\",\n    \"2001-05-24 00:00:00\",\n    \"2001-06-04 00:00:00\",\n    \"2001-12-24 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-03-28 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-01 00:00:00\",\n    \"2002-05-01 00:00:00\",\n    \"2002-05-09 00:00:00\",\n    \"2002-05-17 00:00:00\",\n    \"2002-05-20 00:00:00\",\n    \"2002-12-24 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-04-17 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-04-21 00:00:00\",\n    \"2003-05-01 00:00:00\",\n    \"2003-05-17 00:00:00\",\n    \"2003-05-29 00:00:00\",\n    \"2003-06-09 00:00:00\",\n    \"2003-12-24 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-04-08 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-12 00:00:00\",\n    \"2004-05-01 00:00:00\",\n    \"2004-05-17 00:00:00\",\n    \"2004-05-20 00:00:00\",\n    \"2004-05-31 00:00:00\",\n    \"2004-12-24 00:00:00\",\n    \"2004-12-25 00:00:00\",\n    \"2004-12-26 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-03-24 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-03-28 00:00:00\",\n    \"2005-05-01 00:00:00\",\n    \"2005-05-05 00:00:00\",\n    \"2005-05-16 00:00:00\",\n    \"2005-05-17 00:00:00\",\n    \"2005-12-24 00:00:00\",\n    \"2005-12-25 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2006-01-01 00:00:00\",\n    \"2006-04-13 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-17 00:00:00\",\n    \"2006-05-01 00:00:00\",\n    \"2006-05-17 00:00:00\",\n    \"2006-05-25 00:00:00\",\n    \"2006-06-05 00:00:00\",\n    \"2006-12-24 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-04-05 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-09 00:00:00\",\n    \"2007-05-01 00:00:00\",\n    \"2007-05-17 00:00:00\",\n    \"2007-05-17 00:00:00\",\n    \"2007-05-28 00:00:00\",\n    \"2007-12-24 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-03-20 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-03-24 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-05-12 00:00:00\",\n    \"2008-05-17 00:00:00\",\n    \"2008-12-24 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-04-09 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-13 00:00:00\",\n    \"2009-05-01 00:00:00\",\n    \"2009-05-17 00:00:00\",\n    \"2009-05-21 00:00:00\",\n    \"2009-06-01 00:00:00\",\n    \"2009-12-24 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-26 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-04-01 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-05 00:00:00\",\n    \"2010-05-01 00:00:00\",\n    \"2010-05-13 00:00:00\",\n    \"2010-05-17 00:00:00\",\n    \"2010-05-24 00:00:00\",\n    \"2010-12-24 00:00:00\",\n    \"2010-12-25 00:00:00\",\n    \"2010-12-26 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-04-21 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-05-01 00:00:00\",\n    \"2011-05-17 00:00:00\",\n    \"2011-06-02 00:00:00\",\n    \"2011-06-13 00:00:00\",\n    \"2011-12-24 00:00:00\",\n    \"2011-12-25 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2012-01-01 00:00:00\",\n    \"2012-04-05 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-09 00:00:00\",\n    \"2012-05-01 00:00:00\",\n    \"2012-05-17 00:00:00\",\n    \"2012-05-17 00:00:00\",\n    \"2012-05-28 00:00:00\",\n    \"2012-12-24 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-03-28 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-01 00:00:00\",\n    \"2013-05-01 00:00:00\",\n    \"2013-05-09 00:00:00\",\n    \"2013-05-17 00:00:00\",\n    \"2013-05-20 00:00:00\",\n    \"2013-12-24 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-04-17 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-04-21 00:00:00\",\n    \"2014-05-01 00:00:00\",\n    \"2014-05-17 00:00:00\",\n    \"2014-05-29 00:00:00\",\n    \"2014-06-09 00:00:00\",\n    \"2014-12-24 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-04-02 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-06 00:00:00\",\n    \"2015-05-01 00:00:00\",\n    \"2015-05-14 00:00:00\",\n    \"2015-05-17 00:00:00\",\n    \"2015-05-25 00:00:00\",\n    \"2015-12-24 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-26 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-03-24 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-03-28 00:00:00\",\n    \"2016-05-01 00:00:00\",\n    \"2016-05-05 00:00:00\",\n    \"2016-05-16 00:00:00\",\n    \"2016-05-17 00:00:00\",\n    \"2016-12-24 00:00:00\",\n    \"2016-12-25 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2017-01-01 00:00:00\",\n    \"2017-04-13 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-17 00:00:00\",\n    \"2017-05-01 00:00:00\",\n    \"2017-05-17 00:00:00\",\n    \"2017-05-25 00:00:00\",\n    \"2017-06-05 00:00:00\",\n    \"2017-12-24 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-03-29 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-02 00:00:00\",\n    \"2018-05-01 00:00:00\",\n    \"2018-05-10 00:00:00\",\n    \"2018-05-17 00:00:00\",\n    \"2018-05-21 00:00:00\",\n    \"2018-12-24 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-04-18 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-22 00:00:00\",\n    \"2019-05-01 00:00:00\",\n    \"2019-05-17 00:00:00\",\n    \"2019-05-30 00:00:00\",\n    \"2019-06-10 00:00:00\",\n    \"2019-12-24 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-04-09 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-13 00:00:00\",\n    \"2020-05-01 00:00:00\",\n    \"2020-05-17 00:00:00\",\n    \"2020-05-21 00:00:00\",\n    \"2020-06-01 00:00:00\",\n    \"2020-12-24 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-26 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-04-01 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-05 00:00:00\",\n    \"2021-05-01 00:00:00\",\n    \"2021-05-13 00:00:00\",\n    \"2021-05-17 00:00:00\",\n    \"2021-05-24 00:00:00\",\n    \"2021-12-24 00:00:00\",\n    \"2021-12-25 00:00:00\",\n    \"2021-12-26 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-04-14 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-04-18 00:00:00\",\n    \"2022-05-01 00:00:00\",\n    \"2022-05-17 00:00:00\",\n    \"2022-05-26 00:00:00\",\n    \"2022-06-06 00:00:00\",\n    \"2022-12-24 00:00:00\",\n    \"2022-12-25 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2023-01-01 00:00:00\",\n    \"2023-04-06 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-10 00:00:00\",\n    \"2023-05-01 00:00:00\",\n    \"2023-05-17 00:00:00\",\n    \"2023-05-18 00:00:00\",\n    \"2023-05-29 00:00:00\",\n    \"2023-12-24 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-03-28 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-05-01 00:00:00\",\n    \"2024-05-09 00:00:00\",\n    \"2024-05-17 00:00:00\",\n    \"2024-05-20 00:00:00\",\n    \"2024-12-24 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-04-17 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-04-21 00:00:00\",\n    \"2025-05-01 00:00:00\",\n    \"2025-05-17 00:00:00\",\n    \"2025-05-29 00:00:00\",\n    \"2025-06-09 00:00:00\",\n    \"2025-12-24 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-04-02 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-05-01 00:00:00\",\n    \"2026-05-14 00:00:00\",\n    \"2026-05-17 00:00:00\",\n    \"2026-05-25 00:00:00\",\n    \"2026-12-24 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-26 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-03-25 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-03-29 00:00:00\",\n    \"2027-05-01 00:00:00\",\n    \"2027-05-06 00:00:00\",\n    \"2027-05-17 00:00:00\",\n    \"2027-05-17 00:00:00\",\n    \"2027-12-24 00:00:00\",\n    \"2027-12-25 00:00:00\",\n    \"2027-12-26 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-04-13 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-17 00:00:00\",\n    \"2028-05-01 00:00:00\",\n    \"2028-05-17 00:00:00\",\n    \"2028-05-25 00:00:00\",\n    \"2028-06-05 00:00:00\",\n    \"2028-12-24 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-03-29 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-02 00:00:00\",\n    \"2029-05-01 00:00:00\",\n    \"2029-05-10 00:00:00\",\n    \"2029-05-17 00:00:00\",\n    \"2029-05-21 00:00:00\",\n    \"2029-12-24 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-04-18 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-04-22 00:00:00\",\n    \"2030-05-01 00:00:00\",\n    \"2030-05-17 00:00:00\",\n    \"2030-05-30 00:00:00\",\n    \"2030-06-10 00:00:00\",\n    \"2030-12-24 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-04-10 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-05-01 00:00:00\",\n    \"2031-05-17 00:00:00\",\n    \"2031-05-22 00:00:00\",\n    \"2031-06-02 00:00:00\",\n    \"2031-12-24 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-03-25 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-03-29 00:00:00\",\n    \"2032-05-01 00:00:00\",\n    \"2032-05-06 00:00:00\",\n    \"2032-05-17 00:00:00\",\n    \"2032-05-17 00:00:00\",\n    \"2032-12-24 00:00:00\",\n    \"2032-12-25 00:00:00\",\n    \"2032-12-26 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-04-14 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-04-18 00:00:00\",\n    \"2033-05-01 00:00:00\",\n    \"2033-05-17 00:00:00\",\n    \"2033-05-26 00:00:00\",\n    \"2033-06-06 00:00:00\",\n    \"2033-12-24 00:00:00\",\n    \"2033-12-25 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2034-01-01 00:00:00\",\n    \"2034-04-06 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-10 00:00:00\",\n    \"2034-05-01 00:00:00\",\n    \"2034-05-17 00:00:00\",\n    \"2034-05-18 00:00:00\",\n    \"2034-05-29 00:00:00\",\n    \"2034-12-24 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-03-22 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-03-26 00:00:00\",\n    \"2035-05-01 00:00:00\",\n    \"2035-05-03 00:00:00\",\n    \"2035-05-14 00:00:00\",\n    \"2035-05-17 00:00:00\",\n    \"2035-12-24 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-04-10 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-05-01 00:00:00\",\n    \"2036-05-17 00:00:00\",\n    \"2036-05-22 00:00:00\",\n    \"2036-06-02 00:00:00\",\n    \"2036-12-24 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-04-02 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-05-01 00:00:00\",\n    \"2037-05-14 00:00:00\",\n    \"2037-05-17 00:00:00\",\n    \"2037-05-25 00:00:00\",\n    \"2037-12-24 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-26 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-04-22 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-05-01 00:00:00\",\n    \"2038-05-17 00:00:00\",\n    \"2038-06-03 00:00:00\",\n    \"2038-06-14 00:00:00\",\n    \"2038-12-24 00:00:00\",\n    \"2038-12-25 00:00:00\",\n    \"2038-12-26 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-04-07 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-11 00:00:00\",\n    \"2039-05-01 00:00:00\",\n    \"2039-05-17 00:00:00\",\n    \"2039-05-19 00:00:00\",\n    \"2039-05-30 00:00:00\",\n    \"2039-12-24 00:00:00\",\n    \"2039-12-25 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2040-01-01 00:00:00\",\n    \"2040-03-29 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-02 00:00:00\",\n    \"2040-05-01 00:00:00\",\n    \"2040-05-10 00:00:00\",\n    \"2040-05-17 00:00:00\",\n    \"2040-05-21 00:00:00\",\n    \"2040-12-24 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-04-18 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-04-22 00:00:00\",\n    \"2041-05-01 00:00:00\",\n    \"2041-05-17 00:00:00\",\n    \"2041-05-30 00:00:00\",\n    \"2041-06-10 00:00:00\",\n    \"2041-12-24 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-04-03 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-07 00:00:00\",\n    \"2042-05-01 00:00:00\",\n    \"2042-05-15 00:00:00\",\n    \"2042-05-17 00:00:00\",\n    \"2042-05-26 00:00:00\",\n    \"2042-12-24 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-03-26 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-03-30 00:00:00\",\n    \"2043-05-01 00:00:00\",\n    \"2043-05-07 00:00:00\",\n    \"2043-05-17 00:00:00\",\n    \"2043-05-18 00:00:00\",\n    \"2043-12-24 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-26 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-04-14 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-04-18 00:00:00\",\n    \"2044-05-01 00:00:00\",\n    \"2044-05-17 00:00:00\",\n    \"2044-05-26 00:00:00\",\n    \"2044-06-06 00:00:00\",\n    \"2044-12-24 00:00:00\",\n    \"2044-12-25 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2045-01-01 00:00:00\",\n    \"2045-04-06 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-10 00:00:00\",\n    \"2045-05-01 00:00:00\",\n    \"2045-05-17 00:00:00\",\n    \"2045-05-18 00:00:00\",\n    \"2045-05-29 00:00:00\",\n    \"2045-12-24 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-03-22 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-03-26 00:00:00\",\n    \"2046-05-01 00:00:00\",\n    \"2046-05-03 00:00:00\",\n    \"2046-05-14 00:00:00\",\n    \"2046-05-17 00:00:00\",\n    \"2046-12-24 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-04-11 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-15 00:00:00\",\n    \"2047-05-01 00:00:00\",\n    \"2047-05-17 00:00:00\",\n    \"2047-05-23 00:00:00\",\n    \"2047-06-03 00:00:00\",\n    \"2047-12-24 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-04-02 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-06 00:00:00\",\n    \"2048-05-01 00:00:00\",\n    \"2048-05-14 00:00:00\",\n    \"2048-05-17 00:00:00\",\n    \"2048-05-25 00:00:00\",\n    \"2048-12-24 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-26 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-04-15 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-04-19 00:00:00\",\n    \"2049-05-01 00:00:00\",\n    \"2049-05-17 00:00:00\",\n    \"2049-05-27 00:00:00\",\n    \"2049-06-07 00:00:00\",\n    \"2049-12-24 00:00:00\",\n    \"2049-12-25 00:00:00\",\n    \"2049-12-26 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-04-07 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-11 00:00:00\",\n    \"2050-05-01 00:00:00\",\n    \"2050-05-17 00:00:00\",\n    \"2050-05-19 00:00:00\",\n    \"2050-05-30 00:00:00\",\n    \"2050-12-24 00:00:00\",\n    \"2050-12-25 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2051-01-01 00:00:00\",\n    \"2051-03-30 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-05-01 00:00:00\",\n    \"2051-05-11 00:00:00\",\n    \"2051-05-17 00:00:00\",\n    \"2051-05-22 00:00:00\",\n    \"2051-12-24 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-04-18 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-04-22 00:00:00\",\n    \"2052-05-01 00:00:00\",\n    \"2052-05-17 00:00:00\",\n    \"2052-05-30 00:00:00\",\n    \"2052-06-10 00:00:00\",\n    \"2052-12-24 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-04-03 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-07 00:00:00\",\n    \"2053-05-01 00:00:00\",\n    \"2053-05-15 00:00:00\",\n    \"2053-05-17 00:00:00\",\n    \"2053-05-26 00:00:00\",\n    \"2053-12-24 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-03-26 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-03-30 00:00:00\",\n    \"2054-05-01 00:00:00\",\n    \"2054-05-07 00:00:00\",\n    \"2054-05-17 00:00:00\",\n    \"2054-05-18 00:00:00\",\n    \"2054-12-24 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-26 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-04-15 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-04-19 00:00:00\",\n    \"2055-05-01 00:00:00\",\n    \"2055-05-17 00:00:00\",\n    \"2055-05-27 00:00:00\",\n    \"2055-06-07 00:00:00\",\n    \"2055-12-24 00:00:00\",\n    \"2055-12-25 00:00:00\",\n    \"2055-12-26 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-03-30 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-05-01 00:00:00\",\n    \"2056-05-11 00:00:00\",\n    \"2056-05-17 00:00:00\",\n    \"2056-05-22 00:00:00\",\n    \"2056-12-24 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-04-19 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-04-23 00:00:00\",\n    \"2057-05-01 00:00:00\",\n    \"2057-05-17 00:00:00\",\n    \"2057-05-31 00:00:00\",\n    \"2057-06-11 00:00:00\",\n    \"2057-12-24 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-04-11 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-15 00:00:00\",\n    \"2058-05-01 00:00:00\",\n    \"2058-05-17 00:00:00\",\n    \"2058-05-23 00:00:00\",\n    \"2058-06-03 00:00:00\",\n    \"2058-12-24 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-03-27 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-03-31 00:00:00\",\n    \"2059-05-01 00:00:00\",\n    \"2059-05-08 00:00:00\",\n    \"2059-05-17 00:00:00\",\n    \"2059-05-19 00:00:00\",\n    \"2059-12-24 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-04-15 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-04-19 00:00:00\",\n    \"2060-05-01 00:00:00\",\n    \"2060-05-17 00:00:00\",\n    \"2060-05-27 00:00:00\",\n    \"2060-06-07 00:00:00\",\n    \"2060-12-24 00:00:00\",\n    \"2060-12-25 00:00:00\",\n    \"2060-12-26 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-04-07 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-11 00:00:00\",\n    \"2061-05-01 00:00:00\",\n    \"2061-05-17 00:00:00\",\n    \"2061-05-19 00:00:00\",\n    \"2061-05-30 00:00:00\",\n    \"2061-12-24 00:00:00\",\n    \"2061-12-25 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2062-01-01 00:00:00\",\n    \"2062-03-23 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-03-27 00:00:00\",\n    \"2062-05-01 00:00:00\",\n    \"2062-05-04 00:00:00\",\n    \"2062-05-15 00:00:00\",\n    \"2062-05-17 00:00:00\",\n    \"2062-12-24 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-04-12 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-16 00:00:00\",\n    \"2063-05-01 00:00:00\",\n    \"2063-05-17 00:00:00\",\n    \"2063-05-24 00:00:00\",\n    \"2063-06-04 00:00:00\",\n    \"2063-12-24 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-04-03 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-07 00:00:00\",\n    \"2064-05-01 00:00:00\",\n    \"2064-05-15 00:00:00\",\n    \"2064-05-17 00:00:00\",\n    \"2064-05-26 00:00:00\",\n    \"2064-12-24 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-03-26 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-03-30 00:00:00\",\n    \"2065-05-01 00:00:00\",\n    \"2065-05-07 00:00:00\",\n    \"2065-05-17 00:00:00\",\n    \"2065-05-18 00:00:00\",\n    \"2065-12-24 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-26 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-04-08 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-12 00:00:00\",\n    \"2066-05-01 00:00:00\",\n    \"2066-05-17 00:00:00\",\n    \"2066-05-20 00:00:00\",\n    \"2066-05-31 00:00:00\",\n    \"2066-12-24 00:00:00\",\n    \"2066-12-25 00:00:00\",\n    \"2066-12-26 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-03-31 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-05-01 00:00:00\",\n    \"2067-05-12 00:00:00\",\n    \"2067-05-17 00:00:00\",\n    \"2067-05-23 00:00:00\",\n    \"2067-12-24 00:00:00\",\n    \"2067-12-25 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2068-01-01 00:00:00\",\n    \"2068-04-19 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-04-23 00:00:00\",\n    \"2068-05-01 00:00:00\",\n    \"2068-05-17 00:00:00\",\n    \"2068-05-31 00:00:00\",\n    \"2068-06-11 00:00:00\",\n    \"2068-12-24 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-04-11 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-15 00:00:00\",\n    \"2069-05-01 00:00:00\",\n    \"2069-05-17 00:00:00\",\n    \"2069-05-23 00:00:00\",\n    \"2069-06-03 00:00:00\",\n    \"2069-12-24 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-03-27 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-03-31 00:00:00\",\n    \"2070-05-01 00:00:00\",\n    \"2070-05-08 00:00:00\",\n    \"2070-05-17 00:00:00\",\n    \"2070-05-19 00:00:00\",\n    \"2070-12-24 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-04-16 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-04-20 00:00:00\",\n    \"2071-05-01 00:00:00\",\n    \"2071-05-17 00:00:00\",\n    \"2071-05-28 00:00:00\",\n    \"2071-06-08 00:00:00\",\n    \"2071-12-24 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-26 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-04-07 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-11 00:00:00\",\n    \"2072-05-01 00:00:00\",\n    \"2072-05-17 00:00:00\",\n    \"2072-05-19 00:00:00\",\n    \"2072-05-30 00:00:00\",\n    \"2072-12-24 00:00:00\",\n    \"2072-12-25 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2073-01-01 00:00:00\",\n    \"2073-03-23 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-03-27 00:00:00\",\n    \"2073-05-01 00:00:00\",\n    \"2073-05-04 00:00:00\",\n    \"2073-05-15 00:00:00\",\n    \"2073-05-17 00:00:00\",\n    \"2073-12-24 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-04-12 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-16 00:00:00\",\n    \"2074-05-01 00:00:00\",\n    \"2074-05-17 00:00:00\",\n    \"2074-05-24 00:00:00\",\n    \"2074-06-04 00:00:00\",\n    \"2074-12-24 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-04-04 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-08 00:00:00\",\n    \"2075-05-01 00:00:00\",\n    \"2075-05-16 00:00:00\",\n    \"2075-05-17 00:00:00\",\n    \"2075-05-27 00:00:00\",\n    \"2075-12-24 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-04-16 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-04-20 00:00:00\",\n    \"2076-05-01 00:00:00\",\n    \"2076-05-17 00:00:00\",\n    \"2076-05-28 00:00:00\",\n    \"2076-06-08 00:00:00\",\n    \"2076-12-24 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-26 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-04-08 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-12 00:00:00\",\n    \"2077-05-01 00:00:00\",\n    \"2077-05-17 00:00:00\",\n    \"2077-05-20 00:00:00\",\n    \"2077-05-31 00:00:00\",\n    \"2077-12-24 00:00:00\",\n    \"2077-12-25 00:00:00\",\n    \"2077-12-26 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-03-31 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-05-01 00:00:00\",\n    \"2078-05-12 00:00:00\",\n    \"2078-05-17 00:00:00\",\n    \"2078-05-23 00:00:00\",\n    \"2078-12-24 00:00:00\",\n    \"2078-12-25 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2079-01-01 00:00:00\",\n    \"2079-04-20 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-04-24 00:00:00\",\n    \"2079-05-01 00:00:00\",\n    \"2079-05-17 00:00:00\",\n    \"2079-06-01 00:00:00\",\n    \"2079-06-12 00:00:00\",\n    \"2079-12-24 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-04-04 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-08 00:00:00\",\n    \"2080-05-01 00:00:00\",\n    \"2080-05-16 00:00:00\",\n    \"2080-05-17 00:00:00\",\n    \"2080-05-27 00:00:00\",\n    \"2080-12-24 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-03-27 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-03-31 00:00:00\",\n    \"2081-05-01 00:00:00\",\n    \"2081-05-08 00:00:00\",\n    \"2081-05-17 00:00:00\",\n    \"2081-05-19 00:00:00\",\n    \"2081-12-24 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-04-16 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-04-20 00:00:00\",\n    \"2082-05-01 00:00:00\",\n    \"2082-05-17 00:00:00\",\n    \"2082-05-28 00:00:00\",\n    \"2082-06-08 00:00:00\",\n    \"2082-12-24 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-26 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-04-01 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-05-01 00:00:00\",\n    \"2083-05-13 00:00:00\",\n    \"2083-05-17 00:00:00\",\n    \"2083-05-24 00:00:00\",\n    \"2083-12-24 00:00:00\",\n    \"2083-12-25 00:00:00\",\n    \"2083-12-26 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-03-23 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-03-27 00:00:00\",\n    \"2084-05-01 00:00:00\",\n    \"2084-05-04 00:00:00\",\n    \"2084-05-15 00:00:00\",\n    \"2084-05-17 00:00:00\",\n    \"2084-12-24 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-04-12 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-16 00:00:00\",\n    \"2085-05-01 00:00:00\",\n    \"2085-05-17 00:00:00\",\n    \"2085-05-24 00:00:00\",\n    \"2085-06-04 00:00:00\",\n    \"2085-12-24 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-03-28 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-01 00:00:00\",\n    \"2086-05-01 00:00:00\",\n    \"2086-05-09 00:00:00\",\n    \"2086-05-17 00:00:00\",\n    \"2086-05-20 00:00:00\",\n    \"2086-12-24 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-04-17 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-04-21 00:00:00\",\n    \"2087-05-01 00:00:00\",\n    \"2087-05-17 00:00:00\",\n    \"2087-05-29 00:00:00\",\n    \"2087-06-09 00:00:00\",\n    \"2087-12-24 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-04-08 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-12 00:00:00\",\n    \"2088-05-01 00:00:00\",\n    \"2088-05-17 00:00:00\",\n    \"2088-05-20 00:00:00\",\n    \"2088-05-31 00:00:00\",\n    \"2088-12-24 00:00:00\",\n    \"2088-12-25 00:00:00\",\n    \"2088-12-26 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-03-31 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-05-01 00:00:00\",\n    \"2089-05-12 00:00:00\",\n    \"2089-05-17 00:00:00\",\n    \"2089-05-23 00:00:00\",\n    \"2089-12-24 00:00:00\",\n    \"2089-12-25 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2090-01-01 00:00:00\",\n    \"2090-04-13 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-17 00:00:00\",\n    \"2090-05-01 00:00:00\",\n    \"2090-05-17 00:00:00\",\n    \"2090-05-25 00:00:00\",\n    \"2090-06-05 00:00:00\",\n    \"2090-12-24 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-04-05 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-09 00:00:00\",\n    \"2091-05-01 00:00:00\",\n    \"2091-05-17 00:00:00\",\n    \"2091-05-17 00:00:00\",\n    \"2091-05-28 00:00:00\",\n    \"2091-12-24 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-03-27 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-03-31 00:00:00\",\n    \"2092-05-01 00:00:00\",\n    \"2092-05-08 00:00:00\",\n    \"2092-05-17 00:00:00\",\n    \"2092-05-19 00:00:00\",\n    \"2092-12-24 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-04-09 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-13 00:00:00\",\n    \"2093-05-01 00:00:00\",\n    \"2093-05-17 00:00:00\",\n    \"2093-05-21 00:00:00\",\n    \"2093-06-01 00:00:00\",\n    \"2093-12-24 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-26 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-04-01 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-05-01 00:00:00\",\n    \"2094-05-13 00:00:00\",\n    \"2094-05-17 00:00:00\",\n    \"2094-05-24 00:00:00\",\n    \"2094-12-24 00:00:00\",\n    \"2094-12-25 00:00:00\",\n    \"2094-12-26 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-04-21 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-05-01 00:00:00\",\n    \"2095-05-17 00:00:00\",\n    \"2095-06-02 00:00:00\",\n    \"2095-06-13 00:00:00\",\n    \"2095-12-24 00:00:00\",\n    \"2095-12-25 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2096-01-01 00:00:00\",\n    \"2096-04-12 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-16 00:00:00\",\n    \"2096-05-01 00:00:00\",\n    \"2096-05-17 00:00:00\",\n    \"2096-05-24 00:00:00\",\n    \"2096-06-04 00:00:00\",\n    \"2096-12-24 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-03-28 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-01 00:00:00\",\n    \"2097-05-01 00:00:00\",\n    \"2097-05-09 00:00:00\",\n    \"2097-05-17 00:00:00\",\n    \"2097-05-20 00:00:00\",\n    \"2097-12-24 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-04-17 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-04-21 00:00:00\",\n    \"2098-05-01 00:00:00\",\n    \"2098-05-17 00:00:00\",\n    \"2098-05-29 00:00:00\",\n    \"2098-06-09 00:00:00\",\n    \"2098-12-24 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-04-09 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-13 00:00:00\",\n    \"2099-05-01 00:00:00\",\n    \"2099-05-17 00:00:00\",\n    \"2099-05-21 00:00:00\",\n    \"2099-06-01 00:00:00\",\n    \"2099-12-24 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-26 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-03-25 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-03-29 00:00:00\",\n    \"2100-05-01 00:00:00\",\n    \"2100-05-06 00:00:00\",\n    \"2100-05-17 00:00:00\",\n    \"2100-05-17 00:00:00\",\n    \"2100-12-24 00:00:00\",\n    \"2100-12-25 00:00:00\",\n    \"2100-12-26 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-04-14 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-04-18 00:00:00\",\n    \"2101-05-01 00:00:00\",\n    \"2101-05-17 00:00:00\",\n    \"2101-05-26 00:00:00\",\n    \"2101-06-06 00:00:00\",\n    \"2101-12-24 00:00:00\",\n    \"2101-12-25 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2102-01-01 00:00:00\",\n    \"2102-04-06 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-10 00:00:00\",\n    \"2102-05-01 00:00:00\",\n    \"2102-05-17 00:00:00\",\n    \"2102-05-18 00:00:00\",\n    \"2102-05-29 00:00:00\",\n    \"2102-12-24 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-03-22 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-03-26 00:00:00\",\n    \"2103-05-01 00:00:00\",\n    \"2103-05-03 00:00:00\",\n    \"2103-05-14 00:00:00\",\n    \"2103-05-17 00:00:00\",\n    \"2103-12-24 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-04-10 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-05-01 00:00:00\",\n    \"2104-05-17 00:00:00\",\n    \"2104-05-22 00:00:00\",\n    \"2104-06-02 00:00:00\",\n    \"2104-12-24 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-04-02 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-06 00:00:00\",\n    \"2105-05-01 00:00:00\",\n    \"2105-05-14 00:00:00\",\n    \"2105-05-17 00:00:00\",\n    \"2105-05-25 00:00:00\",\n    \"2105-12-24 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-26 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-04-15 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-04-19 00:00:00\",\n    \"2106-05-01 00:00:00\",\n    \"2106-05-17 00:00:00\",\n    \"2106-05-27 00:00:00\",\n    \"2106-06-07 00:00:00\",\n    \"2106-12-24 00:00:00\",\n    \"2106-12-25 00:00:00\",\n    \"2106-12-26 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-04-07 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-11 00:00:00\",\n    \"2107-05-01 00:00:00\",\n    \"2107-05-17 00:00:00\",\n    \"2107-05-19 00:00:00\",\n    \"2107-05-30 00:00:00\",\n    \"2107-12-24 00:00:00\",\n    \"2107-12-25 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2108-01-01 00:00:00\",\n    \"2108-03-29 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-02 00:00:00\",\n    \"2108-05-01 00:00:00\",\n    \"2108-05-10 00:00:00\",\n    \"2108-05-17 00:00:00\",\n    \"2108-05-21 00:00:00\",\n    \"2108-12-24 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-04-18 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-04-22 00:00:00\",\n    \"2109-05-01 00:00:00\",\n    \"2109-05-17 00:00:00\",\n    \"2109-05-30 00:00:00\",\n    \"2109-06-10 00:00:00\",\n    \"2109-12-24 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-04-03 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-07 00:00:00\",\n    \"2110-05-01 00:00:00\",\n    \"2110-05-15 00:00:00\",\n    \"2110-05-17 00:00:00\",\n    \"2110-05-26 00:00:00\",\n    \"2110-12-24 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-03-26 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-03-30 00:00:00\",\n    \"2111-05-01 00:00:00\",\n    \"2111-05-07 00:00:00\",\n    \"2111-05-17 00:00:00\",\n    \"2111-05-18 00:00:00\",\n    \"2111-12-24 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-26 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-04-14 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-04-18 00:00:00\",\n    \"2112-05-01 00:00:00\",\n    \"2112-05-17 00:00:00\",\n    \"2112-05-26 00:00:00\",\n    \"2112-06-06 00:00:00\",\n    \"2112-12-24 00:00:00\",\n    \"2112-12-25 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2113-01-01 00:00:00\",\n    \"2113-03-30 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-03 00:00:00\",\n    \"2113-05-01 00:00:00\",\n    \"2113-05-11 00:00:00\",\n    \"2113-05-17 00:00:00\",\n    \"2113-05-22 00:00:00\",\n    \"2113-12-24 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-04-19 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-04-23 00:00:00\",\n    \"2114-05-01 00:00:00\",\n    \"2114-05-17 00:00:00\",\n    \"2114-05-31 00:00:00\",\n    \"2114-06-11 00:00:00\",\n    \"2114-12-24 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-04-11 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-15 00:00:00\",\n    \"2115-05-01 00:00:00\",\n    \"2115-05-17 00:00:00\",\n    \"2115-05-23 00:00:00\",\n    \"2115-06-03 00:00:00\",\n    \"2115-12-24 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-03-26 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-03-30 00:00:00\",\n    \"2116-05-01 00:00:00\",\n    \"2116-05-07 00:00:00\",\n    \"2116-05-17 00:00:00\",\n    \"2116-05-18 00:00:00\",\n    \"2116-12-24 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-26 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-04-15 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-04-19 00:00:00\",\n    \"2117-05-01 00:00:00\",\n    \"2117-05-17 00:00:00\",\n    \"2117-05-27 00:00:00\",\n    \"2117-06-07 00:00:00\",\n    \"2117-12-24 00:00:00\",\n    \"2117-12-25 00:00:00\",\n    \"2117-12-26 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-04-07 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-11 00:00:00\",\n    \"2118-05-01 00:00:00\",\n    \"2118-05-17 00:00:00\",\n    \"2118-05-19 00:00:00\",\n    \"2118-05-30 00:00:00\",\n    \"2118-12-24 00:00:00\",\n    \"2118-12-25 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2119-01-01 00:00:00\",\n    \"2119-03-23 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-03-27 00:00:00\",\n    \"2119-05-01 00:00:00\",\n    \"2119-05-04 00:00:00\",\n    \"2119-05-15 00:00:00\",\n    \"2119-05-17 00:00:00\",\n    \"2119-12-24 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-04-11 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-15 00:00:00\",\n    \"2120-05-01 00:00:00\",\n    \"2120-05-17 00:00:00\",\n    \"2120-05-23 00:00:00\",\n    \"2120-06-03 00:00:00\",\n    \"2120-12-24 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-04-03 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-07 00:00:00\",\n    \"2121-05-01 00:00:00\",\n    \"2121-05-15 00:00:00\",\n    \"2121-05-17 00:00:00\",\n    \"2121-05-26 00:00:00\",\n    \"2121-12-24 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-03-26 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-03-30 00:00:00\",\n    \"2122-05-01 00:00:00\",\n    \"2122-05-07 00:00:00\",\n    \"2122-05-17 00:00:00\",\n    \"2122-05-18 00:00:00\",\n    \"2122-12-24 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-26 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-04-08 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-12 00:00:00\",\n    \"2123-05-01 00:00:00\",\n    \"2123-05-17 00:00:00\",\n    \"2123-05-20 00:00:00\",\n    \"2123-05-31 00:00:00\",\n    \"2123-12-24 00:00:00\",\n    \"2123-12-25 00:00:00\",\n    \"2123-12-26 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-03-30 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-03 00:00:00\",\n    \"2124-05-01 00:00:00\",\n    \"2124-05-11 00:00:00\",\n    \"2124-05-17 00:00:00\",\n    \"2124-05-22 00:00:00\",\n    \"2124-12-24 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-04-19 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-04-23 00:00:00\",\n    \"2125-05-01 00:00:00\",\n    \"2125-05-17 00:00:00\",\n    \"2125-05-31 00:00:00\",\n    \"2125-06-11 00:00:00\",\n    \"2125-12-24 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-04-11 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-15 00:00:00\",\n    \"2126-05-01 00:00:00\",\n    \"2126-05-17 00:00:00\",\n    \"2126-05-23 00:00:00\",\n    \"2126-06-03 00:00:00\",\n    \"2126-12-24 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-03-27 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-03-31 00:00:00\",\n    \"2127-05-01 00:00:00\",\n    \"2127-05-08 00:00:00\",\n    \"2127-05-17 00:00:00\",\n    \"2127-05-19 00:00:00\",\n    \"2127-12-24 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-04-15 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-04-19 00:00:00\",\n    \"2128-05-01 00:00:00\",\n    \"2128-05-17 00:00:00\",\n    \"2128-05-27 00:00:00\",\n    \"2128-06-07 00:00:00\",\n    \"2128-12-24 00:00:00\",\n    \"2128-12-25 00:00:00\",\n    \"2128-12-26 00:00:00\",\n    \"2129-01-01 00:00:00\",\n    \"2129-04-07 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-11 00:00:00\",\n    \"2129-05-01 00:00:00\",\n    \"2129-05-17 00:00:00\",\n    \"2129-05-19 00:00:00\",\n    \"2129-05-30 00:00:00\",\n    \"2129-12-24 00:00:00\",\n    \"2129-12-25 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2130-01-01 00:00:00\",\n    \"2130-03-23 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-03-27 00:00:00\",\n    \"2130-05-01 00:00:00\",\n    \"2130-05-04 00:00:00\",\n    \"2130-05-15 00:00:00\",\n    \"2130-05-17 00:00:00\",\n    \"2130-12-24 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-04-12 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-16 00:00:00\",\n    \"2131-05-01 00:00:00\",\n    \"2131-05-17 00:00:00\",\n    \"2131-05-24 00:00:00\",\n    \"2131-06-04 00:00:00\",\n    \"2131-12-24 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-04-03 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-07 00:00:00\",\n    \"2132-05-01 00:00:00\",\n    \"2132-05-15 00:00:00\",\n    \"2132-05-17 00:00:00\",\n    \"2132-05-26 00:00:00\",\n    \"2132-12-24 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-04-16 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-04-20 00:00:00\",\n    \"2133-05-01 00:00:00\",\n    \"2133-05-17 00:00:00\",\n    \"2133-05-28 00:00:00\",\n    \"2133-06-08 00:00:00\",\n    \"2133-12-24 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-26 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-04-08 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-12 00:00:00\",\n    \"2134-05-01 00:00:00\",\n    \"2134-05-17 00:00:00\",\n    \"2134-05-20 00:00:00\",\n    \"2134-05-31 00:00:00\",\n    \"2134-12-24 00:00:00\",\n    \"2134-12-25 00:00:00\",\n    \"2134-12-26 00:00:00\",\n    \"2135-01-01 00:00:00\",\n    \"2135-03-31 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-04 00:00:00\",\n    \"2135-05-01 00:00:00\",\n    \"2135-05-12 00:00:00\",\n    \"2135-05-17 00:00:00\",\n    \"2135-05-23 00:00:00\",\n    \"2135-12-24 00:00:00\",\n    \"2135-12-25 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2136-01-01 00:00:00\",\n    \"2136-04-19 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-04-23 00:00:00\",\n    \"2136-05-01 00:00:00\",\n    \"2136-05-17 00:00:00\",\n    \"2136-05-31 00:00:00\",\n    \"2136-06-11 00:00:00\",\n    \"2136-12-24 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-04-04 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-08 00:00:00\",\n    \"2137-05-01 00:00:00\",\n    \"2137-05-16 00:00:00\",\n    \"2137-05-17 00:00:00\",\n    \"2137-05-27 00:00:00\",\n    \"2137-12-24 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-03-27 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-03-31 00:00:00\",\n    \"2138-05-01 00:00:00\",\n    \"2138-05-08 00:00:00\",\n    \"2138-05-17 00:00:00\",\n    \"2138-05-19 00:00:00\",\n    \"2138-12-24 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-04-16 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-04-20 00:00:00\",\n    \"2139-05-01 00:00:00\",\n    \"2139-05-17 00:00:00\",\n    \"2139-05-28 00:00:00\",\n    \"2139-06-08 00:00:00\",\n    \"2139-12-24 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-26 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-03-31 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-04 00:00:00\",\n    \"2140-05-01 00:00:00\",\n    \"2140-05-12 00:00:00\",\n    \"2140-05-17 00:00:00\",\n    \"2140-05-23 00:00:00\",\n    \"2140-12-24 00:00:00\",\n    \"2140-12-25 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2141-01-01 00:00:00\",\n    \"2141-03-23 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-03-27 00:00:00\",\n    \"2141-05-01 00:00:00\",\n    \"2141-05-04 00:00:00\",\n    \"2141-05-15 00:00:00\",\n    \"2141-05-17 00:00:00\",\n    \"2141-12-24 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-04-12 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-16 00:00:00\",\n    \"2142-05-01 00:00:00\",\n    \"2142-05-17 00:00:00\",\n    \"2142-05-24 00:00:00\",\n    \"2142-06-04 00:00:00\",\n    \"2142-12-24 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-03-28 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-01 00:00:00\",\n    \"2143-05-01 00:00:00\",\n    \"2143-05-09 00:00:00\",\n    \"2143-05-17 00:00:00\",\n    \"2143-05-20 00:00:00\",\n    \"2143-12-24 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-04-16 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-04-20 00:00:00\",\n    \"2144-05-01 00:00:00\",\n    \"2144-05-17 00:00:00\",\n    \"2144-05-28 00:00:00\",\n    \"2144-06-08 00:00:00\",\n    \"2144-12-24 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-26 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-04-08 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-12 00:00:00\",\n    \"2145-05-01 00:00:00\",\n    \"2145-05-17 00:00:00\",\n    \"2145-05-20 00:00:00\",\n    \"2145-05-31 00:00:00\",\n    \"2145-12-24 00:00:00\",\n    \"2145-12-25 00:00:00\",\n    \"2145-12-26 00:00:00\",\n    \"2146-01-01 00:00:00\",\n    \"2146-03-31 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-04 00:00:00\",\n    \"2146-05-01 00:00:00\",\n    \"2146-05-12 00:00:00\",\n    \"2146-05-17 00:00:00\",\n    \"2146-05-23 00:00:00\",\n    \"2146-12-24 00:00:00\",\n    \"2146-12-25 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2147-01-01 00:00:00\",\n    \"2147-04-13 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-17 00:00:00\",\n    \"2147-05-01 00:00:00\",\n    \"2147-05-17 00:00:00\",\n    \"2147-05-25 00:00:00\",\n    \"2147-06-05 00:00:00\",\n    \"2147-12-24 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-04-04 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-08 00:00:00\",\n    \"2148-05-01 00:00:00\",\n    \"2148-05-16 00:00:00\",\n    \"2148-05-17 00:00:00\",\n    \"2148-05-27 00:00:00\",\n    \"2148-12-24 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-03-27 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-03-31 00:00:00\",\n    \"2149-05-01 00:00:00\",\n    \"2149-05-08 00:00:00\",\n    \"2149-05-17 00:00:00\",\n    \"2149-05-19 00:00:00\",\n    \"2149-12-24 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-04-09 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-13 00:00:00\",\n    \"2150-05-01 00:00:00\",\n    \"2150-05-17 00:00:00\",\n    \"2150-05-21 00:00:00\",\n    \"2150-06-01 00:00:00\",\n    \"2150-12-24 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-26 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-04-01 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-05 00:00:00\",\n    \"2151-05-01 00:00:00\",\n    \"2151-05-13 00:00:00\",\n    \"2151-05-17 00:00:00\",\n    \"2151-05-24 00:00:00\",\n    \"2151-12-24 00:00:00\",\n    \"2151-12-25 00:00:00\",\n    \"2151-12-26 00:00:00\",\n    \"2152-01-01 00:00:00\",\n    \"2152-04-20 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-04-24 00:00:00\",\n    \"2152-05-01 00:00:00\",\n    \"2152-05-17 00:00:00\",\n    \"2152-06-01 00:00:00\",\n    \"2152-06-12 00:00:00\",\n    \"2152-12-24 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-04-12 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-16 00:00:00\",\n    \"2153-05-01 00:00:00\",\n    \"2153-05-17 00:00:00\",\n    \"2153-05-24 00:00:00\",\n    \"2153-06-04 00:00:00\",\n    \"2153-12-24 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-03-28 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-01 00:00:00\",\n    \"2154-05-01 00:00:00\",\n    \"2154-05-09 00:00:00\",\n    \"2154-05-17 00:00:00\",\n    \"2154-05-20 00:00:00\",\n    \"2154-12-24 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-04-17 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-04-21 00:00:00\",\n    \"2155-05-01 00:00:00\",\n    \"2155-05-17 00:00:00\",\n    \"2155-05-29 00:00:00\",\n    \"2155-06-09 00:00:00\",\n    \"2155-12-24 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-04-08 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-12 00:00:00\",\n    \"2156-05-01 00:00:00\",\n    \"2156-05-17 00:00:00\",\n    \"2156-05-20 00:00:00\",\n    \"2156-05-31 00:00:00\",\n    \"2156-12-24 00:00:00\",\n    \"2156-12-25 00:00:00\",\n    \"2156-12-26 00:00:00\",\n    \"2157-01-01 00:00:00\",\n    \"2157-03-24 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-03-28 00:00:00\",\n    \"2157-05-01 00:00:00\",\n    \"2157-05-05 00:00:00\",\n    \"2157-05-16 00:00:00\",\n    \"2157-05-17 00:00:00\",\n    \"2157-12-24 00:00:00\",\n    \"2157-12-25 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2158-01-01 00:00:00\",\n    \"2158-04-13 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-17 00:00:00\",\n    \"2158-05-01 00:00:00\",\n    \"2158-05-17 00:00:00\",\n    \"2158-05-25 00:00:00\",\n    \"2158-06-05 00:00:00\",\n    \"2158-12-24 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-04-05 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-09 00:00:00\",\n    \"2159-05-01 00:00:00\",\n    \"2159-05-17 00:00:00\",\n    \"2159-05-17 00:00:00\",\n    \"2159-05-28 00:00:00\",\n    \"2159-12-24 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-03-20 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-03-24 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-05-12 00:00:00\",\n    \"2160-05-17 00:00:00\",\n    \"2160-12-24 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-04-09 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-13 00:00:00\",\n    \"2161-05-01 00:00:00\",\n    \"2161-05-17 00:00:00\",\n    \"2161-05-21 00:00:00\",\n    \"2161-06-01 00:00:00\",\n    \"2161-12-24 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-26 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-04-01 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-05 00:00:00\",\n    \"2162-05-01 00:00:00\",\n    \"2162-05-13 00:00:00\",\n    \"2162-05-17 00:00:00\",\n    \"2162-05-24 00:00:00\",\n    \"2162-12-24 00:00:00\",\n    \"2162-12-25 00:00:00\",\n    \"2162-12-26 00:00:00\",\n    \"2163-01-01 00:00:00\",\n    \"2163-04-21 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-05-01 00:00:00\",\n    \"2163-05-17 00:00:00\",\n    \"2163-06-02 00:00:00\",\n    \"2163-06-13 00:00:00\",\n    \"2163-12-24 00:00:00\",\n    \"2163-12-25 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2164-01-01 00:00:00\",\n    \"2164-04-05 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-09 00:00:00\",\n    \"2164-05-01 00:00:00\",\n    \"2164-05-17 00:00:00\",\n    \"2164-05-17 00:00:00\",\n    \"2164-05-28 00:00:00\",\n    \"2164-12-24 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-03-28 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-01 00:00:00\",\n    \"2165-05-01 00:00:00\",\n    \"2165-05-09 00:00:00\",\n    \"2165-05-17 00:00:00\",\n    \"2165-05-20 00:00:00\",\n    \"2165-12-24 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-04-17 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-04-21 00:00:00\",\n    \"2166-05-01 00:00:00\",\n    \"2166-05-17 00:00:00\",\n    \"2166-05-29 00:00:00\",\n    \"2166-06-09 00:00:00\",\n    \"2166-12-24 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-04-02 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-06 00:00:00\",\n    \"2167-05-01 00:00:00\",\n    \"2167-05-14 00:00:00\",\n    \"2167-05-17 00:00:00\",\n    \"2167-05-25 00:00:00\",\n    \"2167-12-24 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-26 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-03-24 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-03-28 00:00:00\",\n    \"2168-05-01 00:00:00\",\n    \"2168-05-05 00:00:00\",\n    \"2168-05-16 00:00:00\",\n    \"2168-05-17 00:00:00\",\n    \"2168-12-24 00:00:00\",\n    \"2168-12-25 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2169-01-01 00:00:00\",\n    \"2169-04-13 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-17 00:00:00\",\n    \"2169-05-01 00:00:00\",\n    \"2169-05-17 00:00:00\",\n    \"2169-05-25 00:00:00\",\n    \"2169-06-05 00:00:00\",\n    \"2169-12-24 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-03-29 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-02 00:00:00\",\n    \"2170-05-01 00:00:00\",\n    \"2170-05-10 00:00:00\",\n    \"2170-05-17 00:00:00\",\n    \"2170-05-21 00:00:00\",\n    \"2170-12-24 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-04-18 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-04-22 00:00:00\",\n    \"2171-05-01 00:00:00\",\n    \"2171-05-17 00:00:00\",\n    \"2171-05-30 00:00:00\",\n    \"2171-06-10 00:00:00\",\n    \"2171-12-24 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-04-09 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-13 00:00:00\",\n    \"2172-05-01 00:00:00\",\n    \"2172-05-17 00:00:00\",\n    \"2172-05-21 00:00:00\",\n    \"2172-06-01 00:00:00\",\n    \"2172-12-24 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-26 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-04-01 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-05 00:00:00\",\n    \"2173-05-01 00:00:00\",\n    \"2173-05-13 00:00:00\",\n    \"2173-05-17 00:00:00\",\n    \"2173-05-24 00:00:00\",\n    \"2173-12-24 00:00:00\",\n    \"2173-12-25 00:00:00\",\n    \"2173-12-26 00:00:00\",\n    \"2174-01-01 00:00:00\",\n    \"2174-04-14 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-04-18 00:00:00\",\n    \"2174-05-01 00:00:00\",\n    \"2174-05-17 00:00:00\",\n    \"2174-05-26 00:00:00\",\n    \"2174-06-06 00:00:00\",\n    \"2174-12-24 00:00:00\",\n    \"2174-12-25 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2175-01-01 00:00:00\",\n    \"2175-04-06 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-10 00:00:00\",\n    \"2175-05-01 00:00:00\",\n    \"2175-05-17 00:00:00\",\n    \"2175-05-18 00:00:00\",\n    \"2175-05-29 00:00:00\",\n    \"2175-12-24 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-03-28 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-01 00:00:00\",\n    \"2176-05-01 00:00:00\",\n    \"2176-05-09 00:00:00\",\n    \"2176-05-17 00:00:00\",\n    \"2176-05-20 00:00:00\",\n    \"2176-12-24 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-04-17 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-04-21 00:00:00\",\n    \"2177-05-01 00:00:00\",\n    \"2177-05-17 00:00:00\",\n    \"2177-05-29 00:00:00\",\n    \"2177-06-09 00:00:00\",\n    \"2177-12-24 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-04-02 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-06 00:00:00\",\n    \"2178-05-01 00:00:00\",\n    \"2178-05-14 00:00:00\",\n    \"2178-05-17 00:00:00\",\n    \"2178-05-25 00:00:00\",\n    \"2178-12-24 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-26 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-03-25 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-03-29 00:00:00\",\n    \"2179-05-01 00:00:00\",\n    \"2179-05-06 00:00:00\",\n    \"2179-05-17 00:00:00\",\n    \"2179-05-17 00:00:00\",\n    \"2179-12-24 00:00:00\",\n    \"2179-12-25 00:00:00\",\n    \"2179-12-26 00:00:00\",\n    \"2180-01-01 00:00:00\",\n    \"2180-04-13 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-17 00:00:00\",\n    \"2180-05-01 00:00:00\",\n    \"2180-05-17 00:00:00\",\n    \"2180-05-25 00:00:00\",\n    \"2180-06-05 00:00:00\",\n    \"2180-12-24 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-03-29 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-02 00:00:00\",\n    \"2181-05-01 00:00:00\",\n    \"2181-05-10 00:00:00\",\n    \"2181-05-17 00:00:00\",\n    \"2181-05-21 00:00:00\",\n    \"2181-12-24 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-04-18 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-04-22 00:00:00\",\n    \"2182-05-01 00:00:00\",\n    \"2182-05-17 00:00:00\",\n    \"2182-05-30 00:00:00\",\n    \"2182-06-10 00:00:00\",\n    \"2182-12-24 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-04-10 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-05-01 00:00:00\",\n    \"2183-05-17 00:00:00\",\n    \"2183-05-22 00:00:00\",\n    \"2183-06-02 00:00:00\",\n    \"2183-12-24 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-03-25 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-03-29 00:00:00\",\n    \"2184-05-01 00:00:00\",\n    \"2184-05-06 00:00:00\",\n    \"2184-05-17 00:00:00\",\n    \"2184-05-17 00:00:00\",\n    \"2184-12-24 00:00:00\",\n    \"2184-12-25 00:00:00\",\n    \"2184-12-26 00:00:00\",\n    \"2185-01-01 00:00:00\",\n    \"2185-04-14 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-04-18 00:00:00\",\n    \"2185-05-01 00:00:00\",\n    \"2185-05-17 00:00:00\",\n    \"2185-05-26 00:00:00\",\n    \"2185-06-06 00:00:00\",\n    \"2185-12-24 00:00:00\",\n    \"2185-12-25 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2186-01-01 00:00:00\",\n    \"2186-04-06 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-10 00:00:00\",\n    \"2186-05-01 00:00:00\",\n    \"2186-05-17 00:00:00\",\n    \"2186-05-18 00:00:00\",\n    \"2186-05-29 00:00:00\",\n    \"2186-12-24 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-03-22 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-03-26 00:00:00\",\n    \"2187-05-01 00:00:00\",\n    \"2187-05-03 00:00:00\",\n    \"2187-05-14 00:00:00\",\n    \"2187-05-17 00:00:00\",\n    \"2187-12-24 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-04-10 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-05-01 00:00:00\",\n    \"2188-05-17 00:00:00\",\n    \"2188-05-22 00:00:00\",\n    \"2188-06-02 00:00:00\",\n    \"2188-12-24 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-04-02 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-06 00:00:00\",\n    \"2189-05-01 00:00:00\",\n    \"2189-05-14 00:00:00\",\n    \"2189-05-17 00:00:00\",\n    \"2189-05-25 00:00:00\",\n    \"2189-12-24 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-26 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-04-22 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-05-01 00:00:00\",\n    \"2190-05-17 00:00:00\",\n    \"2190-06-03 00:00:00\",\n    \"2190-06-14 00:00:00\",\n    \"2190-12-24 00:00:00\",\n    \"2190-12-25 00:00:00\",\n    \"2190-12-26 00:00:00\",\n    \"2191-01-01 00:00:00\",\n    \"2191-04-07 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-11 00:00:00\",\n    \"2191-05-01 00:00:00\",\n    \"2191-05-17 00:00:00\",\n    \"2191-05-19 00:00:00\",\n    \"2191-05-30 00:00:00\",\n    \"2191-12-24 00:00:00\",\n    \"2191-12-25 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2192-01-01 00:00:00\",\n    \"2192-03-29 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-02 00:00:00\",\n    \"2192-05-01 00:00:00\",\n    \"2192-05-10 00:00:00\",\n    \"2192-05-17 00:00:00\",\n    \"2192-05-21 00:00:00\",\n    \"2192-12-24 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-04-18 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-04-22 00:00:00\",\n    \"2193-05-01 00:00:00\",\n    \"2193-05-17 00:00:00\",\n    \"2193-05-30 00:00:00\",\n    \"2193-06-10 00:00:00\",\n    \"2193-12-24 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-04-03 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-07 00:00:00\",\n    \"2194-05-01 00:00:00\",\n    \"2194-05-15 00:00:00\",\n    \"2194-05-17 00:00:00\",\n    \"2194-05-26 00:00:00\",\n    \"2194-12-24 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-03-26 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-03-30 00:00:00\",\n    \"2195-05-01 00:00:00\",\n    \"2195-05-07 00:00:00\",\n    \"2195-05-17 00:00:00\",\n    \"2195-05-18 00:00:00\",\n    \"2195-12-24 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-26 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-04-14 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-04-18 00:00:00\",\n    \"2196-05-01 00:00:00\",\n    \"2196-05-17 00:00:00\",\n    \"2196-05-26 00:00:00\",\n    \"2196-06-06 00:00:00\",\n    \"2196-12-24 00:00:00\",\n    \"2196-12-25 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2197-01-01 00:00:00\",\n    \"2197-04-06 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-10 00:00:00\",\n    \"2197-05-01 00:00:00\",\n    \"2197-05-17 00:00:00\",\n    \"2197-05-18 00:00:00\",\n    \"2197-05-29 00:00:00\",\n    \"2197-12-24 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-03-22 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-03-26 00:00:00\",\n    \"2198-05-01 00:00:00\",\n    \"2198-05-03 00:00:00\",\n    \"2198-05-14 00:00:00\",\n    \"2198-05-17 00:00:00\",\n    \"2198-12-24 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-04-11 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-15 00:00:00\",\n    \"2199-05-01 00:00:00\",\n    \"2199-05-17 00:00:00\",\n    \"2199-05-23 00:00:00\",\n    \"2199-06-03 00:00:00\",\n    \"2199-12-24 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-04-03 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-07 00:00:00\",\n    \"2200-05-01 00:00:00\",\n    \"2200-05-15 00:00:00\",\n    \"2200-05-17 00:00:00\",\n    \"2200-05-26 00:00:00\",\n    \"2200-12-24 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/osl_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pandas as pd\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, Day, Easter\n\nRULES = [\n    Holiday(\"New Year's Day\", month=1, day=1),\n    Holiday(\"Maundy Thursday\", month=1, day=1, offset=[Easter(), Day(-3)]),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Easter Monday\", month=1, day=1, offset=[Easter(), Day(1)]),\n    Holiday(\"EU Labour Day\", month=5, day=1),\n    Holiday(\"Norway Constitution Day\", month=5, day=17),\n    Holiday(\"Ascention Day\", month=1, day=1, offset=[Easter(), Day(39)]),\n    Holiday(\"Whit Monday\", month=1, day=1, offset=[Easter(), Day(50)]),\n    Holiday(\"Christmas Eve\", month=12, day=24),\n    Holiday(\"Christmas Day\", month=12, day=25),\n    Holiday(\"Boxing Day\", month=12, day=26),\n]\n\nCALENDAR = CustomBusinessDay(\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/stk.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Stockholm business day calendar, aligned with SWESTR publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Jan 6 (Epiphany)\",\n//     \"Fri before Easter (Good Friday)\",\n//     \"Mon after Easter (Easter Monday)\",\n//     \"May 1 (EU Labour)\",\n//     \"39 Days after Easter (Ascention)\",\n//     \"Jun 6 (National)\",\n//     \"Fri before 25 Jun (Midsommar)\",\n//     \"Dec 24 (Christmas Eve)\",\n//     \"Dec 25 (Christmas)\",\n//     \"Dec 26 (Boxing Day)\",\n//     \"Dec 31 (New Year's Eve)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-01-06 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-03-30 00:00:00\",\n    \"1970-05-01 00:00:00\",\n    \"1970-05-07 00:00:00\",\n    \"1970-06-06 00:00:00\",\n    \"1970-06-19 00:00:00\",\n    \"1970-12-24 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-26 00:00:00\",\n    \"1970-12-31 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-01-06 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-12 00:00:00\",\n    \"1971-05-01 00:00:00\",\n    \"1971-05-20 00:00:00\",\n    \"1971-06-06 00:00:00\",\n    \"1971-06-25 00:00:00\",\n    \"1971-12-24 00:00:00\",\n    \"1971-12-25 00:00:00\",\n    \"1971-12-26 00:00:00\",\n    \"1971-12-31 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-01-06 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-03 00:00:00\",\n    \"1972-05-01 00:00:00\",\n    \"1972-05-11 00:00:00\",\n    \"1972-06-06 00:00:00\",\n    \"1972-06-23 00:00:00\",\n    \"1972-12-24 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1972-12-31 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-01-06 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-04-23 00:00:00\",\n    \"1973-05-01 00:00:00\",\n    \"1973-05-31 00:00:00\",\n    \"1973-06-06 00:00:00\",\n    \"1973-06-22 00:00:00\",\n    \"1973-12-24 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1973-12-31 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-01-06 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-15 00:00:00\",\n    \"1974-05-01 00:00:00\",\n    \"1974-05-23 00:00:00\",\n    \"1974-06-06 00:00:00\",\n    \"1974-06-21 00:00:00\",\n    \"1974-12-24 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1974-12-31 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-01-06 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-03-31 00:00:00\",\n    \"1975-05-01 00:00:00\",\n    \"1975-05-08 00:00:00\",\n    \"1975-06-06 00:00:00\",\n    \"1975-06-20 00:00:00\",\n    \"1975-12-24 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1975-12-31 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-01-06 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-04-19 00:00:00\",\n    \"1976-05-01 00:00:00\",\n    \"1976-05-27 00:00:00\",\n    \"1976-06-06 00:00:00\",\n    \"1976-06-25 00:00:00\",\n    \"1976-12-24 00:00:00\",\n    \"1976-12-25 00:00:00\",\n    \"1976-12-26 00:00:00\",\n    \"1976-12-31 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-01-06 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-11 00:00:00\",\n    \"1977-05-01 00:00:00\",\n    \"1977-05-19 00:00:00\",\n    \"1977-06-06 00:00:00\",\n    \"1977-06-24 00:00:00\",\n    \"1977-12-24 00:00:00\",\n    \"1977-12-25 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1977-12-31 00:00:00\",\n    \"1978-01-01 00:00:00\",\n    \"1978-01-06 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-03-27 00:00:00\",\n    \"1978-05-01 00:00:00\",\n    \"1978-05-04 00:00:00\",\n    \"1978-06-06 00:00:00\",\n    \"1978-06-23 00:00:00\",\n    \"1978-12-24 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1978-12-31 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-01-06 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-16 00:00:00\",\n    \"1979-05-01 00:00:00\",\n    \"1979-05-24 00:00:00\",\n    \"1979-06-06 00:00:00\",\n    \"1979-06-22 00:00:00\",\n    \"1979-12-24 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1979-12-31 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-01-06 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-07 00:00:00\",\n    \"1980-05-01 00:00:00\",\n    \"1980-05-15 00:00:00\",\n    \"1980-06-06 00:00:00\",\n    \"1980-06-20 00:00:00\",\n    \"1980-12-24 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1980-12-31 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-01-06 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-04-20 00:00:00\",\n    \"1981-05-01 00:00:00\",\n    \"1981-05-28 00:00:00\",\n    \"1981-06-06 00:00:00\",\n    \"1981-06-19 00:00:00\",\n    \"1981-12-24 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-26 00:00:00\",\n    \"1981-12-31 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-01-06 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-12 00:00:00\",\n    \"1982-05-01 00:00:00\",\n    \"1982-05-20 00:00:00\",\n    \"1982-06-06 00:00:00\",\n    \"1982-06-25 00:00:00\",\n    \"1982-12-24 00:00:00\",\n    \"1982-12-25 00:00:00\",\n    \"1982-12-26 00:00:00\",\n    \"1982-12-31 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-01-06 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-04 00:00:00\",\n    \"1983-05-01 00:00:00\",\n    \"1983-05-12 00:00:00\",\n    \"1983-06-06 00:00:00\",\n    \"1983-06-24 00:00:00\",\n    \"1983-12-24 00:00:00\",\n    \"1983-12-25 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1983-12-31 00:00:00\",\n    \"1984-01-01 00:00:00\",\n    \"1984-01-06 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-04-23 00:00:00\",\n    \"1984-05-01 00:00:00\",\n    \"1984-05-31 00:00:00\",\n    \"1984-06-06 00:00:00\",\n    \"1984-06-22 00:00:00\",\n    \"1984-12-24 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1984-12-31 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-01-06 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-08 00:00:00\",\n    \"1985-05-01 00:00:00\",\n    \"1985-05-16 00:00:00\",\n    \"1985-06-06 00:00:00\",\n    \"1985-06-21 00:00:00\",\n    \"1985-12-24 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1985-12-31 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-01-06 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-03-31 00:00:00\",\n    \"1986-05-01 00:00:00\",\n    \"1986-05-08 00:00:00\",\n    \"1986-06-06 00:00:00\",\n    \"1986-06-20 00:00:00\",\n    \"1986-12-24 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1986-12-31 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-01-06 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-04-20 00:00:00\",\n    \"1987-05-01 00:00:00\",\n    \"1987-05-28 00:00:00\",\n    \"1987-06-06 00:00:00\",\n    \"1987-06-19 00:00:00\",\n    \"1987-12-24 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-26 00:00:00\",\n    \"1987-12-31 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-01-06 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-04 00:00:00\",\n    \"1988-05-01 00:00:00\",\n    \"1988-05-12 00:00:00\",\n    \"1988-06-06 00:00:00\",\n    \"1988-06-24 00:00:00\",\n    \"1988-12-24 00:00:00\",\n    \"1988-12-25 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1988-12-31 00:00:00\",\n    \"1989-01-01 00:00:00\",\n    \"1989-01-06 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-03-27 00:00:00\",\n    \"1989-05-01 00:00:00\",\n    \"1989-05-04 00:00:00\",\n    \"1989-06-06 00:00:00\",\n    \"1989-06-23 00:00:00\",\n    \"1989-12-24 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1989-12-31 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-01-06 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-16 00:00:00\",\n    \"1990-05-01 00:00:00\",\n    \"1990-05-24 00:00:00\",\n    \"1990-06-06 00:00:00\",\n    \"1990-06-22 00:00:00\",\n    \"1990-12-24 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1990-12-31 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-01-06 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-01 00:00:00\",\n    \"1991-05-01 00:00:00\",\n    \"1991-05-09 00:00:00\",\n    \"1991-06-06 00:00:00\",\n    \"1991-06-21 00:00:00\",\n    \"1991-12-24 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1991-12-31 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-01-06 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-04-20 00:00:00\",\n    \"1992-05-01 00:00:00\",\n    \"1992-05-28 00:00:00\",\n    \"1992-06-06 00:00:00\",\n    \"1992-06-19 00:00:00\",\n    \"1992-12-24 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-26 00:00:00\",\n    \"1992-12-31 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-01-06 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-12 00:00:00\",\n    \"1993-05-01 00:00:00\",\n    \"1993-05-20 00:00:00\",\n    \"1993-06-06 00:00:00\",\n    \"1993-06-25 00:00:00\",\n    \"1993-12-24 00:00:00\",\n    \"1993-12-25 00:00:00\",\n    \"1993-12-26 00:00:00\",\n    \"1993-12-31 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-01-06 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-04 00:00:00\",\n    \"1994-05-01 00:00:00\",\n    \"1994-05-12 00:00:00\",\n    \"1994-06-06 00:00:00\",\n    \"1994-06-24 00:00:00\",\n    \"1994-12-24 00:00:00\",\n    \"1994-12-25 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1994-12-31 00:00:00\",\n    \"1995-01-01 00:00:00\",\n    \"1995-01-06 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-17 00:00:00\",\n    \"1995-05-01 00:00:00\",\n    \"1995-05-25 00:00:00\",\n    \"1995-06-06 00:00:00\",\n    \"1995-06-23 00:00:00\",\n    \"1995-12-24 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1995-12-31 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-01-06 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-08 00:00:00\",\n    \"1996-05-01 00:00:00\",\n    \"1996-05-16 00:00:00\",\n    \"1996-06-06 00:00:00\",\n    \"1996-06-21 00:00:00\",\n    \"1996-12-24 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1996-12-31 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-01-06 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-03-31 00:00:00\",\n    \"1997-05-01 00:00:00\",\n    \"1997-05-08 00:00:00\",\n    \"1997-06-06 00:00:00\",\n    \"1997-06-20 00:00:00\",\n    \"1997-12-24 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1997-12-31 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-01-06 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-13 00:00:00\",\n    \"1998-05-01 00:00:00\",\n    \"1998-05-21 00:00:00\",\n    \"1998-06-06 00:00:00\",\n    \"1998-06-19 00:00:00\",\n    \"1998-12-24 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-26 00:00:00\",\n    \"1998-12-31 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-01-06 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-05 00:00:00\",\n    \"1999-05-01 00:00:00\",\n    \"1999-05-13 00:00:00\",\n    \"1999-06-06 00:00:00\",\n    \"1999-06-25 00:00:00\",\n    \"1999-12-24 00:00:00\",\n    \"1999-12-25 00:00:00\",\n    \"1999-12-26 00:00:00\",\n    \"1999-12-31 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-01-06 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-04-24 00:00:00\",\n    \"2000-05-01 00:00:00\",\n    \"2000-06-01 00:00:00\",\n    \"2000-06-06 00:00:00\",\n    \"2000-06-23 00:00:00\",\n    \"2000-12-24 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2000-12-31 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-01-06 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-16 00:00:00\",\n    \"2001-05-01 00:00:00\",\n    \"2001-05-24 00:00:00\",\n    \"2001-06-06 00:00:00\",\n    \"2001-06-22 00:00:00\",\n    \"2001-12-24 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2001-12-31 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-01-06 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-01 00:00:00\",\n    \"2002-05-01 00:00:00\",\n    \"2002-05-09 00:00:00\",\n    \"2002-06-06 00:00:00\",\n    \"2002-06-21 00:00:00\",\n    \"2002-12-24 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2002-12-31 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-01-06 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-04-21 00:00:00\",\n    \"2003-05-01 00:00:00\",\n    \"2003-05-29 00:00:00\",\n    \"2003-06-06 00:00:00\",\n    \"2003-06-20 00:00:00\",\n    \"2003-12-24 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2003-12-31 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-01-06 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-12 00:00:00\",\n    \"2004-05-01 00:00:00\",\n    \"2004-05-20 00:00:00\",\n    \"2004-06-06 00:00:00\",\n    \"2004-06-25 00:00:00\",\n    \"2004-12-24 00:00:00\",\n    \"2004-12-25 00:00:00\",\n    \"2004-12-26 00:00:00\",\n    \"2004-12-31 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-01-06 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-03-28 00:00:00\",\n    \"2005-05-01 00:00:00\",\n    \"2005-05-05 00:00:00\",\n    \"2005-06-06 00:00:00\",\n    \"2005-06-24 00:00:00\",\n    \"2005-12-24 00:00:00\",\n    \"2005-12-25 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2005-12-31 00:00:00\",\n    \"2006-01-01 00:00:00\",\n    \"2006-01-06 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-17 00:00:00\",\n    \"2006-05-01 00:00:00\",\n    \"2006-05-25 00:00:00\",\n    \"2006-06-06 00:00:00\",\n    \"2006-06-23 00:00:00\",\n    \"2006-12-24 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2006-12-31 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-01-06 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-09 00:00:00\",\n    \"2007-05-01 00:00:00\",\n    \"2007-05-17 00:00:00\",\n    \"2007-06-06 00:00:00\",\n    \"2007-06-22 00:00:00\",\n    \"2007-12-24 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2007-12-31 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-01-06 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-03-24 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-06-06 00:00:00\",\n    \"2008-06-20 00:00:00\",\n    \"2008-12-24 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2008-12-31 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-01-06 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-13 00:00:00\",\n    \"2009-05-01 00:00:00\",\n    \"2009-05-21 00:00:00\",\n    \"2009-06-06 00:00:00\",\n    \"2009-06-19 00:00:00\",\n    \"2009-12-24 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-26 00:00:00\",\n    \"2009-12-31 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-01-06 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-05 00:00:00\",\n    \"2010-05-01 00:00:00\",\n    \"2010-05-13 00:00:00\",\n    \"2010-06-06 00:00:00\",\n    \"2010-06-25 00:00:00\",\n    \"2010-12-24 00:00:00\",\n    \"2010-12-25 00:00:00\",\n    \"2010-12-26 00:00:00\",\n    \"2010-12-31 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-01-06 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-05-01 00:00:00\",\n    \"2011-06-02 00:00:00\",\n    \"2011-06-06 00:00:00\",\n    \"2011-06-24 00:00:00\",\n    \"2011-12-24 00:00:00\",\n    \"2011-12-25 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2011-12-31 00:00:00\",\n    \"2012-01-01 00:00:00\",\n    \"2012-01-06 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-09 00:00:00\",\n    \"2012-05-01 00:00:00\",\n    \"2012-05-17 00:00:00\",\n    \"2012-06-06 00:00:00\",\n    \"2012-06-22 00:00:00\",\n    \"2012-12-24 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2012-12-31 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-01-06 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-01 00:00:00\",\n    \"2013-05-01 00:00:00\",\n    \"2013-05-09 00:00:00\",\n    \"2013-06-06 00:00:00\",\n    \"2013-06-21 00:00:00\",\n    \"2013-12-24 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2013-12-31 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-01-06 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-04-21 00:00:00\",\n    \"2014-05-01 00:00:00\",\n    \"2014-05-29 00:00:00\",\n    \"2014-06-06 00:00:00\",\n    \"2014-06-20 00:00:00\",\n    \"2014-12-24 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2014-12-31 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-01-06 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-06 00:00:00\",\n    \"2015-05-01 00:00:00\",\n    \"2015-05-14 00:00:00\",\n    \"2015-06-06 00:00:00\",\n    \"2015-06-19 00:00:00\",\n    \"2015-12-24 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-26 00:00:00\",\n    \"2015-12-31 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-01-06 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-03-28 00:00:00\",\n    \"2016-05-01 00:00:00\",\n    \"2016-05-05 00:00:00\",\n    \"2016-06-06 00:00:00\",\n    \"2016-06-24 00:00:00\",\n    \"2016-12-24 00:00:00\",\n    \"2016-12-25 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2016-12-31 00:00:00\",\n    \"2017-01-01 00:00:00\",\n    \"2017-01-06 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-17 00:00:00\",\n    \"2017-05-01 00:00:00\",\n    \"2017-05-25 00:00:00\",\n    \"2017-06-06 00:00:00\",\n    \"2017-06-23 00:00:00\",\n    \"2017-12-24 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2017-12-31 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-01-06 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-02 00:00:00\",\n    \"2018-05-01 00:00:00\",\n    \"2018-05-10 00:00:00\",\n    \"2018-06-06 00:00:00\",\n    \"2018-06-22 00:00:00\",\n    \"2018-12-24 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2018-12-31 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-01-06 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-22 00:00:00\",\n    \"2019-05-01 00:00:00\",\n    \"2019-05-30 00:00:00\",\n    \"2019-06-06 00:00:00\",\n    \"2019-06-21 00:00:00\",\n    \"2019-12-24 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2019-12-31 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-01-06 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-13 00:00:00\",\n    \"2020-05-01 00:00:00\",\n    \"2020-05-21 00:00:00\",\n    \"2020-06-06 00:00:00\",\n    \"2020-06-19 00:00:00\",\n    \"2020-12-24 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-26 00:00:00\",\n    \"2020-12-31 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-01-06 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-05 00:00:00\",\n    \"2021-05-01 00:00:00\",\n    \"2021-05-13 00:00:00\",\n    \"2021-06-06 00:00:00\",\n    \"2021-06-25 00:00:00\",\n    \"2021-12-24 00:00:00\",\n    \"2021-12-25 00:00:00\",\n    \"2021-12-26 00:00:00\",\n    \"2021-12-31 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-01-06 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-04-18 00:00:00\",\n    \"2022-05-01 00:00:00\",\n    \"2022-05-26 00:00:00\",\n    \"2022-06-06 00:00:00\",\n    \"2022-06-24 00:00:00\",\n    \"2022-12-24 00:00:00\",\n    \"2022-12-25 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2022-12-31 00:00:00\",\n    \"2023-01-01 00:00:00\",\n    \"2023-01-06 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-10 00:00:00\",\n    \"2023-05-01 00:00:00\",\n    \"2023-05-18 00:00:00\",\n    \"2023-06-06 00:00:00\",\n    \"2023-06-23 00:00:00\",\n    \"2023-12-24 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2023-12-31 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-01-06 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-05-01 00:00:00\",\n    \"2024-05-09 00:00:00\",\n    \"2024-06-06 00:00:00\",\n    \"2024-06-21 00:00:00\",\n    \"2024-12-24 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2024-12-31 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-06 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-04-21 00:00:00\",\n    \"2025-05-01 00:00:00\",\n    \"2025-05-29 00:00:00\",\n    \"2025-06-06 00:00:00\",\n    \"2025-06-20 00:00:00\",\n    \"2025-12-24 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2025-12-31 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-06 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-05-01 00:00:00\",\n    \"2026-05-14 00:00:00\",\n    \"2026-06-06 00:00:00\",\n    \"2026-06-19 00:00:00\",\n    \"2026-12-24 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-26 00:00:00\",\n    \"2026-12-31 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-06 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-03-29 00:00:00\",\n    \"2027-05-01 00:00:00\",\n    \"2027-05-06 00:00:00\",\n    \"2027-06-06 00:00:00\",\n    \"2027-06-25 00:00:00\",\n    \"2027-12-24 00:00:00\",\n    \"2027-12-25 00:00:00\",\n    \"2027-12-26 00:00:00\",\n    \"2027-12-31 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-01-06 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-17 00:00:00\",\n    \"2028-05-01 00:00:00\",\n    \"2028-05-25 00:00:00\",\n    \"2028-06-06 00:00:00\",\n    \"2028-06-23 00:00:00\",\n    \"2028-12-24 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2028-12-31 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-06 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-02 00:00:00\",\n    \"2029-05-01 00:00:00\",\n    \"2029-05-10 00:00:00\",\n    \"2029-06-06 00:00:00\",\n    \"2029-06-22 00:00:00\",\n    \"2029-12-24 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2029-12-31 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-06 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-04-22 00:00:00\",\n    \"2030-05-01 00:00:00\",\n    \"2030-05-30 00:00:00\",\n    \"2030-06-06 00:00:00\",\n    \"2030-06-21 00:00:00\",\n    \"2030-12-24 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2030-12-31 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-06 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-05-01 00:00:00\",\n    \"2031-05-22 00:00:00\",\n    \"2031-06-06 00:00:00\",\n    \"2031-06-20 00:00:00\",\n    \"2031-12-24 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2031-12-31 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-06 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-03-29 00:00:00\",\n    \"2032-05-01 00:00:00\",\n    \"2032-05-06 00:00:00\",\n    \"2032-06-06 00:00:00\",\n    \"2032-06-25 00:00:00\",\n    \"2032-12-24 00:00:00\",\n    \"2032-12-25 00:00:00\",\n    \"2032-12-26 00:00:00\",\n    \"2032-12-31 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-01-06 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-04-18 00:00:00\",\n    \"2033-05-01 00:00:00\",\n    \"2033-05-26 00:00:00\",\n    \"2033-06-06 00:00:00\",\n    \"2033-06-24 00:00:00\",\n    \"2033-12-24 00:00:00\",\n    \"2033-12-25 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2033-12-31 00:00:00\",\n    \"2034-01-01 00:00:00\",\n    \"2034-01-06 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-10 00:00:00\",\n    \"2034-05-01 00:00:00\",\n    \"2034-05-18 00:00:00\",\n    \"2034-06-06 00:00:00\",\n    \"2034-06-23 00:00:00\",\n    \"2034-12-24 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2034-12-31 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-06 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-03-26 00:00:00\",\n    \"2035-05-01 00:00:00\",\n    \"2035-05-03 00:00:00\",\n    \"2035-06-06 00:00:00\",\n    \"2035-06-22 00:00:00\",\n    \"2035-12-24 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2035-12-31 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-06 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-05-01 00:00:00\",\n    \"2036-05-22 00:00:00\",\n    \"2036-06-06 00:00:00\",\n    \"2036-06-20 00:00:00\",\n    \"2036-12-24 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2036-12-31 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-06 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-05-01 00:00:00\",\n    \"2037-05-14 00:00:00\",\n    \"2037-06-06 00:00:00\",\n    \"2037-06-19 00:00:00\",\n    \"2037-12-24 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-26 00:00:00\",\n    \"2037-12-31 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-06 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-05-01 00:00:00\",\n    \"2038-06-03 00:00:00\",\n    \"2038-06-06 00:00:00\",\n    \"2038-06-25 00:00:00\",\n    \"2038-12-24 00:00:00\",\n    \"2038-12-25 00:00:00\",\n    \"2038-12-26 00:00:00\",\n    \"2038-12-31 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-01-06 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-11 00:00:00\",\n    \"2039-05-01 00:00:00\",\n    \"2039-05-19 00:00:00\",\n    \"2039-06-06 00:00:00\",\n    \"2039-06-24 00:00:00\",\n    \"2039-12-24 00:00:00\",\n    \"2039-12-25 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2039-12-31 00:00:00\",\n    \"2040-01-01 00:00:00\",\n    \"2040-01-06 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-02 00:00:00\",\n    \"2040-05-01 00:00:00\",\n    \"2040-05-10 00:00:00\",\n    \"2040-06-06 00:00:00\",\n    \"2040-06-22 00:00:00\",\n    \"2040-12-24 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2040-12-31 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-06 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-04-22 00:00:00\",\n    \"2041-05-01 00:00:00\",\n    \"2041-05-30 00:00:00\",\n    \"2041-06-06 00:00:00\",\n    \"2041-06-21 00:00:00\",\n    \"2041-12-24 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2041-12-31 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-06 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-07 00:00:00\",\n    \"2042-05-01 00:00:00\",\n    \"2042-05-15 00:00:00\",\n    \"2042-06-06 00:00:00\",\n    \"2042-06-20 00:00:00\",\n    \"2042-12-24 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2042-12-31 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-06 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-03-30 00:00:00\",\n    \"2043-05-01 00:00:00\",\n    \"2043-05-07 00:00:00\",\n    \"2043-06-06 00:00:00\",\n    \"2043-06-19 00:00:00\",\n    \"2043-12-24 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-26 00:00:00\",\n    \"2043-12-31 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-06 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-04-18 00:00:00\",\n    \"2044-05-01 00:00:00\",\n    \"2044-05-26 00:00:00\",\n    \"2044-06-06 00:00:00\",\n    \"2044-06-24 00:00:00\",\n    \"2044-12-24 00:00:00\",\n    \"2044-12-25 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2044-12-31 00:00:00\",\n    \"2045-01-01 00:00:00\",\n    \"2045-01-06 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-10 00:00:00\",\n    \"2045-05-01 00:00:00\",\n    \"2045-05-18 00:00:00\",\n    \"2045-06-06 00:00:00\",\n    \"2045-06-23 00:00:00\",\n    \"2045-12-24 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2045-12-31 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-06 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-03-26 00:00:00\",\n    \"2046-05-01 00:00:00\",\n    \"2046-05-03 00:00:00\",\n    \"2046-06-06 00:00:00\",\n    \"2046-06-22 00:00:00\",\n    \"2046-12-24 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2046-12-31 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-06 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-15 00:00:00\",\n    \"2047-05-01 00:00:00\",\n    \"2047-05-23 00:00:00\",\n    \"2047-06-06 00:00:00\",\n    \"2047-06-21 00:00:00\",\n    \"2047-12-24 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2047-12-31 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-06 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-06 00:00:00\",\n    \"2048-05-01 00:00:00\",\n    \"2048-05-14 00:00:00\",\n    \"2048-06-06 00:00:00\",\n    \"2048-06-19 00:00:00\",\n    \"2048-12-24 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-26 00:00:00\",\n    \"2048-12-31 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-06 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-04-19 00:00:00\",\n    \"2049-05-01 00:00:00\",\n    \"2049-05-27 00:00:00\",\n    \"2049-06-06 00:00:00\",\n    \"2049-06-25 00:00:00\",\n    \"2049-12-24 00:00:00\",\n    \"2049-12-25 00:00:00\",\n    \"2049-12-26 00:00:00\",\n    \"2049-12-31 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-01-06 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-11 00:00:00\",\n    \"2050-05-01 00:00:00\",\n    \"2050-05-19 00:00:00\",\n    \"2050-06-06 00:00:00\",\n    \"2050-06-24 00:00:00\",\n    \"2050-12-24 00:00:00\",\n    \"2050-12-25 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2050-12-31 00:00:00\",\n    \"2051-01-01 00:00:00\",\n    \"2051-01-06 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-05-01 00:00:00\",\n    \"2051-05-11 00:00:00\",\n    \"2051-06-06 00:00:00\",\n    \"2051-06-23 00:00:00\",\n    \"2051-12-24 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2051-12-31 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-06 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-04-22 00:00:00\",\n    \"2052-05-01 00:00:00\",\n    \"2052-05-30 00:00:00\",\n    \"2052-06-06 00:00:00\",\n    \"2052-06-21 00:00:00\",\n    \"2052-12-24 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2052-12-31 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-06 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-07 00:00:00\",\n    \"2053-05-01 00:00:00\",\n    \"2053-05-15 00:00:00\",\n    \"2053-06-06 00:00:00\",\n    \"2053-06-20 00:00:00\",\n    \"2053-12-24 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2053-12-31 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-06 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-03-30 00:00:00\",\n    \"2054-05-01 00:00:00\",\n    \"2054-05-07 00:00:00\",\n    \"2054-06-06 00:00:00\",\n    \"2054-06-19 00:00:00\",\n    \"2054-12-24 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-26 00:00:00\",\n    \"2054-12-31 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-06 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-04-19 00:00:00\",\n    \"2055-05-01 00:00:00\",\n    \"2055-05-27 00:00:00\",\n    \"2055-06-06 00:00:00\",\n    \"2055-06-25 00:00:00\",\n    \"2055-12-24 00:00:00\",\n    \"2055-12-25 00:00:00\",\n    \"2055-12-26 00:00:00\",\n    \"2055-12-31 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-01-06 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-05-01 00:00:00\",\n    \"2056-05-11 00:00:00\",\n    \"2056-06-06 00:00:00\",\n    \"2056-06-23 00:00:00\",\n    \"2056-12-24 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2056-12-31 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-06 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-04-23 00:00:00\",\n    \"2057-05-01 00:00:00\",\n    \"2057-05-31 00:00:00\",\n    \"2057-06-06 00:00:00\",\n    \"2057-06-22 00:00:00\",\n    \"2057-12-24 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2057-12-31 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-06 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-15 00:00:00\",\n    \"2058-05-01 00:00:00\",\n    \"2058-05-23 00:00:00\",\n    \"2058-06-06 00:00:00\",\n    \"2058-06-21 00:00:00\",\n    \"2058-12-24 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2058-12-31 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-06 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-03-31 00:00:00\",\n    \"2059-05-01 00:00:00\",\n    \"2059-05-08 00:00:00\",\n    \"2059-06-06 00:00:00\",\n    \"2059-06-20 00:00:00\",\n    \"2059-12-24 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2059-12-31 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-06 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-04-19 00:00:00\",\n    \"2060-05-01 00:00:00\",\n    \"2060-05-27 00:00:00\",\n    \"2060-06-06 00:00:00\",\n    \"2060-06-25 00:00:00\",\n    \"2060-12-24 00:00:00\",\n    \"2060-12-25 00:00:00\",\n    \"2060-12-26 00:00:00\",\n    \"2060-12-31 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-01-06 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-11 00:00:00\",\n    \"2061-05-01 00:00:00\",\n    \"2061-05-19 00:00:00\",\n    \"2061-06-06 00:00:00\",\n    \"2061-06-24 00:00:00\",\n    \"2061-12-24 00:00:00\",\n    \"2061-12-25 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2061-12-31 00:00:00\",\n    \"2062-01-01 00:00:00\",\n    \"2062-01-06 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-03-27 00:00:00\",\n    \"2062-05-01 00:00:00\",\n    \"2062-05-04 00:00:00\",\n    \"2062-06-06 00:00:00\",\n    \"2062-06-23 00:00:00\",\n    \"2062-12-24 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2062-12-31 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-06 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-16 00:00:00\",\n    \"2063-05-01 00:00:00\",\n    \"2063-05-24 00:00:00\",\n    \"2063-06-06 00:00:00\",\n    \"2063-06-22 00:00:00\",\n    \"2063-12-24 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2063-12-31 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-06 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-07 00:00:00\",\n    \"2064-05-01 00:00:00\",\n    \"2064-05-15 00:00:00\",\n    \"2064-06-06 00:00:00\",\n    \"2064-06-20 00:00:00\",\n    \"2064-12-24 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2064-12-31 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-06 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-03-30 00:00:00\",\n    \"2065-05-01 00:00:00\",\n    \"2065-05-07 00:00:00\",\n    \"2065-06-06 00:00:00\",\n    \"2065-06-19 00:00:00\",\n    \"2065-12-24 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-26 00:00:00\",\n    \"2065-12-31 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-06 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-12 00:00:00\",\n    \"2066-05-01 00:00:00\",\n    \"2066-05-20 00:00:00\",\n    \"2066-06-06 00:00:00\",\n    \"2066-06-25 00:00:00\",\n    \"2066-12-24 00:00:00\",\n    \"2066-12-25 00:00:00\",\n    \"2066-12-26 00:00:00\",\n    \"2066-12-31 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-01-06 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-05-01 00:00:00\",\n    \"2067-05-12 00:00:00\",\n    \"2067-06-06 00:00:00\",\n    \"2067-06-24 00:00:00\",\n    \"2067-12-24 00:00:00\",\n    \"2067-12-25 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2067-12-31 00:00:00\",\n    \"2068-01-01 00:00:00\",\n    \"2068-01-06 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-04-23 00:00:00\",\n    \"2068-05-01 00:00:00\",\n    \"2068-05-31 00:00:00\",\n    \"2068-06-06 00:00:00\",\n    \"2068-06-22 00:00:00\",\n    \"2068-12-24 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2068-12-31 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-06 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-15 00:00:00\",\n    \"2069-05-01 00:00:00\",\n    \"2069-05-23 00:00:00\",\n    \"2069-06-06 00:00:00\",\n    \"2069-06-21 00:00:00\",\n    \"2069-12-24 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2069-12-31 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-06 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-03-31 00:00:00\",\n    \"2070-05-01 00:00:00\",\n    \"2070-05-08 00:00:00\",\n    \"2070-06-06 00:00:00\",\n    \"2070-06-20 00:00:00\",\n    \"2070-12-24 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2070-12-31 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-06 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-04-20 00:00:00\",\n    \"2071-05-01 00:00:00\",\n    \"2071-05-28 00:00:00\",\n    \"2071-06-06 00:00:00\",\n    \"2071-06-19 00:00:00\",\n    \"2071-12-24 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-26 00:00:00\",\n    \"2071-12-31 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-06 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-11 00:00:00\",\n    \"2072-05-01 00:00:00\",\n    \"2072-05-19 00:00:00\",\n    \"2072-06-06 00:00:00\",\n    \"2072-06-24 00:00:00\",\n    \"2072-12-24 00:00:00\",\n    \"2072-12-25 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2072-12-31 00:00:00\",\n    \"2073-01-01 00:00:00\",\n    \"2073-01-06 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-03-27 00:00:00\",\n    \"2073-05-01 00:00:00\",\n    \"2073-05-04 00:00:00\",\n    \"2073-06-06 00:00:00\",\n    \"2073-06-23 00:00:00\",\n    \"2073-12-24 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2073-12-31 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-06 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-16 00:00:00\",\n    \"2074-05-01 00:00:00\",\n    \"2074-05-24 00:00:00\",\n    \"2074-06-06 00:00:00\",\n    \"2074-06-22 00:00:00\",\n    \"2074-12-24 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2074-12-31 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-06 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-08 00:00:00\",\n    \"2075-05-01 00:00:00\",\n    \"2075-05-16 00:00:00\",\n    \"2075-06-06 00:00:00\",\n    \"2075-06-21 00:00:00\",\n    \"2075-12-24 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2075-12-31 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-06 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-04-20 00:00:00\",\n    \"2076-05-01 00:00:00\",\n    \"2076-05-28 00:00:00\",\n    \"2076-06-06 00:00:00\",\n    \"2076-06-19 00:00:00\",\n    \"2076-12-24 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-26 00:00:00\",\n    \"2076-12-31 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-06 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-12 00:00:00\",\n    \"2077-05-01 00:00:00\",\n    \"2077-05-20 00:00:00\",\n    \"2077-06-06 00:00:00\",\n    \"2077-06-25 00:00:00\",\n    \"2077-12-24 00:00:00\",\n    \"2077-12-25 00:00:00\",\n    \"2077-12-26 00:00:00\",\n    \"2077-12-31 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-01-06 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-05-01 00:00:00\",\n    \"2078-05-12 00:00:00\",\n    \"2078-06-06 00:00:00\",\n    \"2078-06-24 00:00:00\",\n    \"2078-12-24 00:00:00\",\n    \"2078-12-25 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2078-12-31 00:00:00\",\n    \"2079-01-01 00:00:00\",\n    \"2079-01-06 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-04-24 00:00:00\",\n    \"2079-05-01 00:00:00\",\n    \"2079-06-01 00:00:00\",\n    \"2079-06-06 00:00:00\",\n    \"2079-06-23 00:00:00\",\n    \"2079-12-24 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2079-12-31 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-06 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-08 00:00:00\",\n    \"2080-05-01 00:00:00\",\n    \"2080-05-16 00:00:00\",\n    \"2080-06-06 00:00:00\",\n    \"2080-06-21 00:00:00\",\n    \"2080-12-24 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2080-12-31 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-06 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-03-31 00:00:00\",\n    \"2081-05-01 00:00:00\",\n    \"2081-05-08 00:00:00\",\n    \"2081-06-06 00:00:00\",\n    \"2081-06-20 00:00:00\",\n    \"2081-12-24 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2081-12-31 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-06 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-04-20 00:00:00\",\n    \"2082-05-01 00:00:00\",\n    \"2082-05-28 00:00:00\",\n    \"2082-06-06 00:00:00\",\n    \"2082-06-19 00:00:00\",\n    \"2082-12-24 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-26 00:00:00\",\n    \"2082-12-31 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-06 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-05-01 00:00:00\",\n    \"2083-05-13 00:00:00\",\n    \"2083-06-06 00:00:00\",\n    \"2083-06-25 00:00:00\",\n    \"2083-12-24 00:00:00\",\n    \"2083-12-25 00:00:00\",\n    \"2083-12-26 00:00:00\",\n    \"2083-12-31 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-01-06 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-03-27 00:00:00\",\n    \"2084-05-01 00:00:00\",\n    \"2084-05-04 00:00:00\",\n    \"2084-06-06 00:00:00\",\n    \"2084-06-23 00:00:00\",\n    \"2084-12-24 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2084-12-31 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-06 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-16 00:00:00\",\n    \"2085-05-01 00:00:00\",\n    \"2085-05-24 00:00:00\",\n    \"2085-06-06 00:00:00\",\n    \"2085-06-22 00:00:00\",\n    \"2085-12-24 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2085-12-31 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-06 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-01 00:00:00\",\n    \"2086-05-01 00:00:00\",\n    \"2086-05-09 00:00:00\",\n    \"2086-06-06 00:00:00\",\n    \"2086-06-21 00:00:00\",\n    \"2086-12-24 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2086-12-31 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-06 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-04-21 00:00:00\",\n    \"2087-05-01 00:00:00\",\n    \"2087-05-29 00:00:00\",\n    \"2087-06-06 00:00:00\",\n    \"2087-06-20 00:00:00\",\n    \"2087-12-24 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2087-12-31 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-06 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-12 00:00:00\",\n    \"2088-05-01 00:00:00\",\n    \"2088-05-20 00:00:00\",\n    \"2088-06-06 00:00:00\",\n    \"2088-06-25 00:00:00\",\n    \"2088-12-24 00:00:00\",\n    \"2088-12-25 00:00:00\",\n    \"2088-12-26 00:00:00\",\n    \"2088-12-31 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-01-06 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-05-01 00:00:00\",\n    \"2089-05-12 00:00:00\",\n    \"2089-06-06 00:00:00\",\n    \"2089-06-24 00:00:00\",\n    \"2089-12-24 00:00:00\",\n    \"2089-12-25 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2089-12-31 00:00:00\",\n    \"2090-01-01 00:00:00\",\n    \"2090-01-06 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-17 00:00:00\",\n    \"2090-05-01 00:00:00\",\n    \"2090-05-25 00:00:00\",\n    \"2090-06-06 00:00:00\",\n    \"2090-06-23 00:00:00\",\n    \"2090-12-24 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2090-12-31 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-06 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-09 00:00:00\",\n    \"2091-05-01 00:00:00\",\n    \"2091-05-17 00:00:00\",\n    \"2091-06-06 00:00:00\",\n    \"2091-06-22 00:00:00\",\n    \"2091-12-24 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2091-12-31 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-06 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-03-31 00:00:00\",\n    \"2092-05-01 00:00:00\",\n    \"2092-05-08 00:00:00\",\n    \"2092-06-06 00:00:00\",\n    \"2092-06-20 00:00:00\",\n    \"2092-12-24 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2092-12-31 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-06 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-13 00:00:00\",\n    \"2093-05-01 00:00:00\",\n    \"2093-05-21 00:00:00\",\n    \"2093-06-06 00:00:00\",\n    \"2093-06-19 00:00:00\",\n    \"2093-12-24 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-26 00:00:00\",\n    \"2093-12-31 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-06 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-05-01 00:00:00\",\n    \"2094-05-13 00:00:00\",\n    \"2094-06-06 00:00:00\",\n    \"2094-06-25 00:00:00\",\n    \"2094-12-24 00:00:00\",\n    \"2094-12-25 00:00:00\",\n    \"2094-12-26 00:00:00\",\n    \"2094-12-31 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-01-06 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-05-01 00:00:00\",\n    \"2095-06-02 00:00:00\",\n    \"2095-06-06 00:00:00\",\n    \"2095-06-24 00:00:00\",\n    \"2095-12-24 00:00:00\",\n    \"2095-12-25 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2095-12-31 00:00:00\",\n    \"2096-01-01 00:00:00\",\n    \"2096-01-06 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-16 00:00:00\",\n    \"2096-05-01 00:00:00\",\n    \"2096-05-24 00:00:00\",\n    \"2096-06-06 00:00:00\",\n    \"2096-06-22 00:00:00\",\n    \"2096-12-24 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2096-12-31 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-06 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-01 00:00:00\",\n    \"2097-05-01 00:00:00\",\n    \"2097-05-09 00:00:00\",\n    \"2097-06-06 00:00:00\",\n    \"2097-06-21 00:00:00\",\n    \"2097-12-24 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2097-12-31 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-06 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-04-21 00:00:00\",\n    \"2098-05-01 00:00:00\",\n    \"2098-05-29 00:00:00\",\n    \"2098-06-06 00:00:00\",\n    \"2098-06-20 00:00:00\",\n    \"2098-12-24 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2098-12-31 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-06 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-13 00:00:00\",\n    \"2099-05-01 00:00:00\",\n    \"2099-05-21 00:00:00\",\n    \"2099-06-06 00:00:00\",\n    \"2099-06-19 00:00:00\",\n    \"2099-12-24 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-26 00:00:00\",\n    \"2099-12-31 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-06 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-03-29 00:00:00\",\n    \"2100-05-01 00:00:00\",\n    \"2100-05-06 00:00:00\",\n    \"2100-06-06 00:00:00\",\n    \"2100-06-25 00:00:00\",\n    \"2100-12-24 00:00:00\",\n    \"2100-12-25 00:00:00\",\n    \"2100-12-26 00:00:00\",\n    \"2100-12-31 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-01-06 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-04-18 00:00:00\",\n    \"2101-05-01 00:00:00\",\n    \"2101-05-26 00:00:00\",\n    \"2101-06-06 00:00:00\",\n    \"2101-06-24 00:00:00\",\n    \"2101-12-24 00:00:00\",\n    \"2101-12-25 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2101-12-31 00:00:00\",\n    \"2102-01-01 00:00:00\",\n    \"2102-01-06 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-10 00:00:00\",\n    \"2102-05-01 00:00:00\",\n    \"2102-05-18 00:00:00\",\n    \"2102-06-06 00:00:00\",\n    \"2102-06-23 00:00:00\",\n    \"2102-12-24 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2102-12-31 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-01-06 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-03-26 00:00:00\",\n    \"2103-05-01 00:00:00\",\n    \"2103-05-03 00:00:00\",\n    \"2103-06-06 00:00:00\",\n    \"2103-06-22 00:00:00\",\n    \"2103-12-24 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2103-12-31 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-01-06 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-05-01 00:00:00\",\n    \"2104-05-22 00:00:00\",\n    \"2104-06-06 00:00:00\",\n    \"2104-06-20 00:00:00\",\n    \"2104-12-24 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2104-12-31 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-01-06 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-06 00:00:00\",\n    \"2105-05-01 00:00:00\",\n    \"2105-05-14 00:00:00\",\n    \"2105-06-06 00:00:00\",\n    \"2105-06-19 00:00:00\",\n    \"2105-12-24 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-26 00:00:00\",\n    \"2105-12-31 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-01-06 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-04-19 00:00:00\",\n    \"2106-05-01 00:00:00\",\n    \"2106-05-27 00:00:00\",\n    \"2106-06-06 00:00:00\",\n    \"2106-06-25 00:00:00\",\n    \"2106-12-24 00:00:00\",\n    \"2106-12-25 00:00:00\",\n    \"2106-12-26 00:00:00\",\n    \"2106-12-31 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-01-06 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-11 00:00:00\",\n    \"2107-05-01 00:00:00\",\n    \"2107-05-19 00:00:00\",\n    \"2107-06-06 00:00:00\",\n    \"2107-06-24 00:00:00\",\n    \"2107-12-24 00:00:00\",\n    \"2107-12-25 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2107-12-31 00:00:00\",\n    \"2108-01-01 00:00:00\",\n    \"2108-01-06 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-02 00:00:00\",\n    \"2108-05-01 00:00:00\",\n    \"2108-05-10 00:00:00\",\n    \"2108-06-06 00:00:00\",\n    \"2108-06-22 00:00:00\",\n    \"2108-12-24 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2108-12-31 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-01-06 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-04-22 00:00:00\",\n    \"2109-05-01 00:00:00\",\n    \"2109-05-30 00:00:00\",\n    \"2109-06-06 00:00:00\",\n    \"2109-06-21 00:00:00\",\n    \"2109-12-24 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2109-12-31 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-01-06 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-07 00:00:00\",\n    \"2110-05-01 00:00:00\",\n    \"2110-05-15 00:00:00\",\n    \"2110-06-06 00:00:00\",\n    \"2110-06-20 00:00:00\",\n    \"2110-12-24 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2110-12-31 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-01-06 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-03-30 00:00:00\",\n    \"2111-05-01 00:00:00\",\n    \"2111-05-07 00:00:00\",\n    \"2111-06-06 00:00:00\",\n    \"2111-06-19 00:00:00\",\n    \"2111-12-24 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-26 00:00:00\",\n    \"2111-12-31 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-01-06 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-04-18 00:00:00\",\n    \"2112-05-01 00:00:00\",\n    \"2112-05-26 00:00:00\",\n    \"2112-06-06 00:00:00\",\n    \"2112-06-24 00:00:00\",\n    \"2112-12-24 00:00:00\",\n    \"2112-12-25 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2112-12-31 00:00:00\",\n    \"2113-01-01 00:00:00\",\n    \"2113-01-06 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-03 00:00:00\",\n    \"2113-05-01 00:00:00\",\n    \"2113-05-11 00:00:00\",\n    \"2113-06-06 00:00:00\",\n    \"2113-06-23 00:00:00\",\n    \"2113-12-24 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2113-12-31 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-01-06 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-04-23 00:00:00\",\n    \"2114-05-01 00:00:00\",\n    \"2114-05-31 00:00:00\",\n    \"2114-06-06 00:00:00\",\n    \"2114-06-22 00:00:00\",\n    \"2114-12-24 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2114-12-31 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-01-06 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-15 00:00:00\",\n    \"2115-05-01 00:00:00\",\n    \"2115-05-23 00:00:00\",\n    \"2115-06-06 00:00:00\",\n    \"2115-06-21 00:00:00\",\n    \"2115-12-24 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2115-12-31 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-01-06 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-03-30 00:00:00\",\n    \"2116-05-01 00:00:00\",\n    \"2116-05-07 00:00:00\",\n    \"2116-06-06 00:00:00\",\n    \"2116-06-19 00:00:00\",\n    \"2116-12-24 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-26 00:00:00\",\n    \"2116-12-31 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-01-06 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-04-19 00:00:00\",\n    \"2117-05-01 00:00:00\",\n    \"2117-05-27 00:00:00\",\n    \"2117-06-06 00:00:00\",\n    \"2117-06-25 00:00:00\",\n    \"2117-12-24 00:00:00\",\n    \"2117-12-25 00:00:00\",\n    \"2117-12-26 00:00:00\",\n    \"2117-12-31 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-01-06 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-11 00:00:00\",\n    \"2118-05-01 00:00:00\",\n    \"2118-05-19 00:00:00\",\n    \"2118-06-06 00:00:00\",\n    \"2118-06-24 00:00:00\",\n    \"2118-12-24 00:00:00\",\n    \"2118-12-25 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2118-12-31 00:00:00\",\n    \"2119-01-01 00:00:00\",\n    \"2119-01-06 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-03-27 00:00:00\",\n    \"2119-05-01 00:00:00\",\n    \"2119-05-04 00:00:00\",\n    \"2119-06-06 00:00:00\",\n    \"2119-06-23 00:00:00\",\n    \"2119-12-24 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2119-12-31 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-01-06 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-15 00:00:00\",\n    \"2120-05-01 00:00:00\",\n    \"2120-05-23 00:00:00\",\n    \"2120-06-06 00:00:00\",\n    \"2120-06-21 00:00:00\",\n    \"2120-12-24 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2120-12-31 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-01-06 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-07 00:00:00\",\n    \"2121-05-01 00:00:00\",\n    \"2121-05-15 00:00:00\",\n    \"2121-06-06 00:00:00\",\n    \"2121-06-20 00:00:00\",\n    \"2121-12-24 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2121-12-31 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-01-06 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-03-30 00:00:00\",\n    \"2122-05-01 00:00:00\",\n    \"2122-05-07 00:00:00\",\n    \"2122-06-06 00:00:00\",\n    \"2122-06-19 00:00:00\",\n    \"2122-12-24 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-26 00:00:00\",\n    \"2122-12-31 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-01-06 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-12 00:00:00\",\n    \"2123-05-01 00:00:00\",\n    \"2123-05-20 00:00:00\",\n    \"2123-06-06 00:00:00\",\n    \"2123-06-25 00:00:00\",\n    \"2123-12-24 00:00:00\",\n    \"2123-12-25 00:00:00\",\n    \"2123-12-26 00:00:00\",\n    \"2123-12-31 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-01-06 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-03 00:00:00\",\n    \"2124-05-01 00:00:00\",\n    \"2124-05-11 00:00:00\",\n    \"2124-06-06 00:00:00\",\n    \"2124-06-23 00:00:00\",\n    \"2124-12-24 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2124-12-31 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-01-06 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-04-23 00:00:00\",\n    \"2125-05-01 00:00:00\",\n    \"2125-05-31 00:00:00\",\n    \"2125-06-06 00:00:00\",\n    \"2125-06-22 00:00:00\",\n    \"2125-12-24 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2125-12-31 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-01-06 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-15 00:00:00\",\n    \"2126-05-01 00:00:00\",\n    \"2126-05-23 00:00:00\",\n    \"2126-06-06 00:00:00\",\n    \"2126-06-21 00:00:00\",\n    \"2126-12-24 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2126-12-31 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-01-06 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-03-31 00:00:00\",\n    \"2127-05-01 00:00:00\",\n    \"2127-05-08 00:00:00\",\n    \"2127-06-06 00:00:00\",\n    \"2127-06-20 00:00:00\",\n    \"2127-12-24 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2127-12-31 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-01-06 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-04-19 00:00:00\",\n    \"2128-05-01 00:00:00\",\n    \"2128-05-27 00:00:00\",\n    \"2128-06-06 00:00:00\",\n    \"2128-06-25 00:00:00\",\n    \"2128-12-24 00:00:00\",\n    \"2128-12-25 00:00:00\",\n    \"2128-12-26 00:00:00\",\n    \"2128-12-31 00:00:00\",\n    \"2129-01-01 00:00:00\",\n    \"2129-01-06 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-11 00:00:00\",\n    \"2129-05-01 00:00:00\",\n    \"2129-05-19 00:00:00\",\n    \"2129-06-06 00:00:00\",\n    \"2129-06-24 00:00:00\",\n    \"2129-12-24 00:00:00\",\n    \"2129-12-25 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2129-12-31 00:00:00\",\n    \"2130-01-01 00:00:00\",\n    \"2130-01-06 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-03-27 00:00:00\",\n    \"2130-05-01 00:00:00\",\n    \"2130-05-04 00:00:00\",\n    \"2130-06-06 00:00:00\",\n    \"2130-06-23 00:00:00\",\n    \"2130-12-24 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2130-12-31 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-01-06 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-16 00:00:00\",\n    \"2131-05-01 00:00:00\",\n    \"2131-05-24 00:00:00\",\n    \"2131-06-06 00:00:00\",\n    \"2131-06-22 00:00:00\",\n    \"2131-12-24 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2131-12-31 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-01-06 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-07 00:00:00\",\n    \"2132-05-01 00:00:00\",\n    \"2132-05-15 00:00:00\",\n    \"2132-06-06 00:00:00\",\n    \"2132-06-20 00:00:00\",\n    \"2132-12-24 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2132-12-31 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-01-06 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-04-20 00:00:00\",\n    \"2133-05-01 00:00:00\",\n    \"2133-05-28 00:00:00\",\n    \"2133-06-06 00:00:00\",\n    \"2133-06-19 00:00:00\",\n    \"2133-12-24 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-26 00:00:00\",\n    \"2133-12-31 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-01-06 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-12 00:00:00\",\n    \"2134-05-01 00:00:00\",\n    \"2134-05-20 00:00:00\",\n    \"2134-06-06 00:00:00\",\n    \"2134-06-25 00:00:00\",\n    \"2134-12-24 00:00:00\",\n    \"2134-12-25 00:00:00\",\n    \"2134-12-26 00:00:00\",\n    \"2134-12-31 00:00:00\",\n    \"2135-01-01 00:00:00\",\n    \"2135-01-06 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-04 00:00:00\",\n    \"2135-05-01 00:00:00\",\n    \"2135-05-12 00:00:00\",\n    \"2135-06-06 00:00:00\",\n    \"2135-06-24 00:00:00\",\n    \"2135-12-24 00:00:00\",\n    \"2135-12-25 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2135-12-31 00:00:00\",\n    \"2136-01-01 00:00:00\",\n    \"2136-01-06 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-04-23 00:00:00\",\n    \"2136-05-01 00:00:00\",\n    \"2136-05-31 00:00:00\",\n    \"2136-06-06 00:00:00\",\n    \"2136-06-22 00:00:00\",\n    \"2136-12-24 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2136-12-31 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-01-06 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-08 00:00:00\",\n    \"2137-05-01 00:00:00\",\n    \"2137-05-16 00:00:00\",\n    \"2137-06-06 00:00:00\",\n    \"2137-06-21 00:00:00\",\n    \"2137-12-24 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2137-12-31 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-01-06 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-03-31 00:00:00\",\n    \"2138-05-01 00:00:00\",\n    \"2138-05-08 00:00:00\",\n    \"2138-06-06 00:00:00\",\n    \"2138-06-20 00:00:00\",\n    \"2138-12-24 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2138-12-31 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-01-06 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-04-20 00:00:00\",\n    \"2139-05-01 00:00:00\",\n    \"2139-05-28 00:00:00\",\n    \"2139-06-06 00:00:00\",\n    \"2139-06-19 00:00:00\",\n    \"2139-12-24 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-26 00:00:00\",\n    \"2139-12-31 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-01-06 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-04 00:00:00\",\n    \"2140-05-01 00:00:00\",\n    \"2140-05-12 00:00:00\",\n    \"2140-06-06 00:00:00\",\n    \"2140-06-24 00:00:00\",\n    \"2140-12-24 00:00:00\",\n    \"2140-12-25 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2140-12-31 00:00:00\",\n    \"2141-01-01 00:00:00\",\n    \"2141-01-06 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-03-27 00:00:00\",\n    \"2141-05-01 00:00:00\",\n    \"2141-05-04 00:00:00\",\n    \"2141-06-06 00:00:00\",\n    \"2141-06-23 00:00:00\",\n    \"2141-12-24 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2141-12-31 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-01-06 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-16 00:00:00\",\n    \"2142-05-01 00:00:00\",\n    \"2142-05-24 00:00:00\",\n    \"2142-06-06 00:00:00\",\n    \"2142-06-22 00:00:00\",\n    \"2142-12-24 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2142-12-31 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-01-06 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-01 00:00:00\",\n    \"2143-05-01 00:00:00\",\n    \"2143-05-09 00:00:00\",\n    \"2143-06-06 00:00:00\",\n    \"2143-06-21 00:00:00\",\n    \"2143-12-24 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2143-12-31 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-01-06 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-04-20 00:00:00\",\n    \"2144-05-01 00:00:00\",\n    \"2144-05-28 00:00:00\",\n    \"2144-06-06 00:00:00\",\n    \"2144-06-19 00:00:00\",\n    \"2144-12-24 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-26 00:00:00\",\n    \"2144-12-31 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-01-06 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-12 00:00:00\",\n    \"2145-05-01 00:00:00\",\n    \"2145-05-20 00:00:00\",\n    \"2145-06-06 00:00:00\",\n    \"2145-06-25 00:00:00\",\n    \"2145-12-24 00:00:00\",\n    \"2145-12-25 00:00:00\",\n    \"2145-12-26 00:00:00\",\n    \"2145-12-31 00:00:00\",\n    \"2146-01-01 00:00:00\",\n    \"2146-01-06 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-04 00:00:00\",\n    \"2146-05-01 00:00:00\",\n    \"2146-05-12 00:00:00\",\n    \"2146-06-06 00:00:00\",\n    \"2146-06-24 00:00:00\",\n    \"2146-12-24 00:00:00\",\n    \"2146-12-25 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2146-12-31 00:00:00\",\n    \"2147-01-01 00:00:00\",\n    \"2147-01-06 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-17 00:00:00\",\n    \"2147-05-01 00:00:00\",\n    \"2147-05-25 00:00:00\",\n    \"2147-06-06 00:00:00\",\n    \"2147-06-23 00:00:00\",\n    \"2147-12-24 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2147-12-31 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-01-06 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-08 00:00:00\",\n    \"2148-05-01 00:00:00\",\n    \"2148-05-16 00:00:00\",\n    \"2148-06-06 00:00:00\",\n    \"2148-06-21 00:00:00\",\n    \"2148-12-24 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2148-12-31 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-01-06 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-03-31 00:00:00\",\n    \"2149-05-01 00:00:00\",\n    \"2149-05-08 00:00:00\",\n    \"2149-06-06 00:00:00\",\n    \"2149-06-20 00:00:00\",\n    \"2149-12-24 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2149-12-31 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-01-06 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-13 00:00:00\",\n    \"2150-05-01 00:00:00\",\n    \"2150-05-21 00:00:00\",\n    \"2150-06-06 00:00:00\",\n    \"2150-06-19 00:00:00\",\n    \"2150-12-24 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-26 00:00:00\",\n    \"2150-12-31 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-01-06 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-05 00:00:00\",\n    \"2151-05-01 00:00:00\",\n    \"2151-05-13 00:00:00\",\n    \"2151-06-06 00:00:00\",\n    \"2151-06-25 00:00:00\",\n    \"2151-12-24 00:00:00\",\n    \"2151-12-25 00:00:00\",\n    \"2151-12-26 00:00:00\",\n    \"2151-12-31 00:00:00\",\n    \"2152-01-01 00:00:00\",\n    \"2152-01-06 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-04-24 00:00:00\",\n    \"2152-05-01 00:00:00\",\n    \"2152-06-01 00:00:00\",\n    \"2152-06-06 00:00:00\",\n    \"2152-06-23 00:00:00\",\n    \"2152-12-24 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2152-12-31 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-01-06 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-16 00:00:00\",\n    \"2153-05-01 00:00:00\",\n    \"2153-05-24 00:00:00\",\n    \"2153-06-06 00:00:00\",\n    \"2153-06-22 00:00:00\",\n    \"2153-12-24 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2153-12-31 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-01-06 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-01 00:00:00\",\n    \"2154-05-01 00:00:00\",\n    \"2154-05-09 00:00:00\",\n    \"2154-06-06 00:00:00\",\n    \"2154-06-21 00:00:00\",\n    \"2154-12-24 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2154-12-31 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-01-06 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-04-21 00:00:00\",\n    \"2155-05-01 00:00:00\",\n    \"2155-05-29 00:00:00\",\n    \"2155-06-06 00:00:00\",\n    \"2155-06-20 00:00:00\",\n    \"2155-12-24 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2155-12-31 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-01-06 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-12 00:00:00\",\n    \"2156-05-01 00:00:00\",\n    \"2156-05-20 00:00:00\",\n    \"2156-06-06 00:00:00\",\n    \"2156-06-25 00:00:00\",\n    \"2156-12-24 00:00:00\",\n    \"2156-12-25 00:00:00\",\n    \"2156-12-26 00:00:00\",\n    \"2156-12-31 00:00:00\",\n    \"2157-01-01 00:00:00\",\n    \"2157-01-06 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-03-28 00:00:00\",\n    \"2157-05-01 00:00:00\",\n    \"2157-05-05 00:00:00\",\n    \"2157-06-06 00:00:00\",\n    \"2157-06-24 00:00:00\",\n    \"2157-12-24 00:00:00\",\n    \"2157-12-25 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2157-12-31 00:00:00\",\n    \"2158-01-01 00:00:00\",\n    \"2158-01-06 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-17 00:00:00\",\n    \"2158-05-01 00:00:00\",\n    \"2158-05-25 00:00:00\",\n    \"2158-06-06 00:00:00\",\n    \"2158-06-23 00:00:00\",\n    \"2158-12-24 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2158-12-31 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-01-06 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-09 00:00:00\",\n    \"2159-05-01 00:00:00\",\n    \"2159-05-17 00:00:00\",\n    \"2159-06-06 00:00:00\",\n    \"2159-06-22 00:00:00\",\n    \"2159-12-24 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2159-12-31 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-01-06 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-03-24 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-06-06 00:00:00\",\n    \"2160-06-20 00:00:00\",\n    \"2160-12-24 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2160-12-31 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-01-06 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-13 00:00:00\",\n    \"2161-05-01 00:00:00\",\n    \"2161-05-21 00:00:00\",\n    \"2161-06-06 00:00:00\",\n    \"2161-06-19 00:00:00\",\n    \"2161-12-24 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-26 00:00:00\",\n    \"2161-12-31 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-01-06 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-05 00:00:00\",\n    \"2162-05-01 00:00:00\",\n    \"2162-05-13 00:00:00\",\n    \"2162-06-06 00:00:00\",\n    \"2162-06-25 00:00:00\",\n    \"2162-12-24 00:00:00\",\n    \"2162-12-25 00:00:00\",\n    \"2162-12-26 00:00:00\",\n    \"2162-12-31 00:00:00\",\n    \"2163-01-01 00:00:00\",\n    \"2163-01-06 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-05-01 00:00:00\",\n    \"2163-06-02 00:00:00\",\n    \"2163-06-06 00:00:00\",\n    \"2163-06-24 00:00:00\",\n    \"2163-12-24 00:00:00\",\n    \"2163-12-25 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2163-12-31 00:00:00\",\n    \"2164-01-01 00:00:00\",\n    \"2164-01-06 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-09 00:00:00\",\n    \"2164-05-01 00:00:00\",\n    \"2164-05-17 00:00:00\",\n    \"2164-06-06 00:00:00\",\n    \"2164-06-22 00:00:00\",\n    \"2164-12-24 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2164-12-31 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-01-06 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-01 00:00:00\",\n    \"2165-05-01 00:00:00\",\n    \"2165-05-09 00:00:00\",\n    \"2165-06-06 00:00:00\",\n    \"2165-06-21 00:00:00\",\n    \"2165-12-24 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2165-12-31 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-01-06 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-04-21 00:00:00\",\n    \"2166-05-01 00:00:00\",\n    \"2166-05-29 00:00:00\",\n    \"2166-06-06 00:00:00\",\n    \"2166-06-20 00:00:00\",\n    \"2166-12-24 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2166-12-31 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-01-06 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-06 00:00:00\",\n    \"2167-05-01 00:00:00\",\n    \"2167-05-14 00:00:00\",\n    \"2167-06-06 00:00:00\",\n    \"2167-06-19 00:00:00\",\n    \"2167-12-24 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-26 00:00:00\",\n    \"2167-12-31 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-01-06 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-03-28 00:00:00\",\n    \"2168-05-01 00:00:00\",\n    \"2168-05-05 00:00:00\",\n    \"2168-06-06 00:00:00\",\n    \"2168-06-24 00:00:00\",\n    \"2168-12-24 00:00:00\",\n    \"2168-12-25 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2168-12-31 00:00:00\",\n    \"2169-01-01 00:00:00\",\n    \"2169-01-06 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-17 00:00:00\",\n    \"2169-05-01 00:00:00\",\n    \"2169-05-25 00:00:00\",\n    \"2169-06-06 00:00:00\",\n    \"2169-06-23 00:00:00\",\n    \"2169-12-24 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2169-12-31 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-01-06 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-02 00:00:00\",\n    \"2170-05-01 00:00:00\",\n    \"2170-05-10 00:00:00\",\n    \"2170-06-06 00:00:00\",\n    \"2170-06-22 00:00:00\",\n    \"2170-12-24 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2170-12-31 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-01-06 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-04-22 00:00:00\",\n    \"2171-05-01 00:00:00\",\n    \"2171-05-30 00:00:00\",\n    \"2171-06-06 00:00:00\",\n    \"2171-06-21 00:00:00\",\n    \"2171-12-24 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2171-12-31 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-01-06 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-13 00:00:00\",\n    \"2172-05-01 00:00:00\",\n    \"2172-05-21 00:00:00\",\n    \"2172-06-06 00:00:00\",\n    \"2172-06-19 00:00:00\",\n    \"2172-12-24 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-26 00:00:00\",\n    \"2172-12-31 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-01-06 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-05 00:00:00\",\n    \"2173-05-01 00:00:00\",\n    \"2173-05-13 00:00:00\",\n    \"2173-06-06 00:00:00\",\n    \"2173-06-25 00:00:00\",\n    \"2173-12-24 00:00:00\",\n    \"2173-12-25 00:00:00\",\n    \"2173-12-26 00:00:00\",\n    \"2173-12-31 00:00:00\",\n    \"2174-01-01 00:00:00\",\n    \"2174-01-06 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-04-18 00:00:00\",\n    \"2174-05-01 00:00:00\",\n    \"2174-05-26 00:00:00\",\n    \"2174-06-06 00:00:00\",\n    \"2174-06-24 00:00:00\",\n    \"2174-12-24 00:00:00\",\n    \"2174-12-25 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2174-12-31 00:00:00\",\n    \"2175-01-01 00:00:00\",\n    \"2175-01-06 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-10 00:00:00\",\n    \"2175-05-01 00:00:00\",\n    \"2175-05-18 00:00:00\",\n    \"2175-06-06 00:00:00\",\n    \"2175-06-23 00:00:00\",\n    \"2175-12-24 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2175-12-31 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-01-06 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-01 00:00:00\",\n    \"2176-05-01 00:00:00\",\n    \"2176-05-09 00:00:00\",\n    \"2176-06-06 00:00:00\",\n    \"2176-06-21 00:00:00\",\n    \"2176-12-24 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2176-12-31 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-01-06 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-04-21 00:00:00\",\n    \"2177-05-01 00:00:00\",\n    \"2177-05-29 00:00:00\",\n    \"2177-06-06 00:00:00\",\n    \"2177-06-20 00:00:00\",\n    \"2177-12-24 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2177-12-31 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-01-06 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-06 00:00:00\",\n    \"2178-05-01 00:00:00\",\n    \"2178-05-14 00:00:00\",\n    \"2178-06-06 00:00:00\",\n    \"2178-06-19 00:00:00\",\n    \"2178-12-24 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-26 00:00:00\",\n    \"2178-12-31 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-01-06 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-03-29 00:00:00\",\n    \"2179-05-01 00:00:00\",\n    \"2179-05-06 00:00:00\",\n    \"2179-06-06 00:00:00\",\n    \"2179-06-25 00:00:00\",\n    \"2179-12-24 00:00:00\",\n    \"2179-12-25 00:00:00\",\n    \"2179-12-26 00:00:00\",\n    \"2179-12-31 00:00:00\",\n    \"2180-01-01 00:00:00\",\n    \"2180-01-06 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-17 00:00:00\",\n    \"2180-05-01 00:00:00\",\n    \"2180-05-25 00:00:00\",\n    \"2180-06-06 00:00:00\",\n    \"2180-06-23 00:00:00\",\n    \"2180-12-24 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2180-12-31 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-01-06 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-02 00:00:00\",\n    \"2181-05-01 00:00:00\",\n    \"2181-05-10 00:00:00\",\n    \"2181-06-06 00:00:00\",\n    \"2181-06-22 00:00:00\",\n    \"2181-12-24 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2181-12-31 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-01-06 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-04-22 00:00:00\",\n    \"2182-05-01 00:00:00\",\n    \"2182-05-30 00:00:00\",\n    \"2182-06-06 00:00:00\",\n    \"2182-06-21 00:00:00\",\n    \"2182-12-24 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2182-12-31 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-01-06 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-05-01 00:00:00\",\n    \"2183-05-22 00:00:00\",\n    \"2183-06-06 00:00:00\",\n    \"2183-06-20 00:00:00\",\n    \"2183-12-24 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2183-12-31 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-01-06 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-03-29 00:00:00\",\n    \"2184-05-01 00:00:00\",\n    \"2184-05-06 00:00:00\",\n    \"2184-06-06 00:00:00\",\n    \"2184-06-25 00:00:00\",\n    \"2184-12-24 00:00:00\",\n    \"2184-12-25 00:00:00\",\n    \"2184-12-26 00:00:00\",\n    \"2184-12-31 00:00:00\",\n    \"2185-01-01 00:00:00\",\n    \"2185-01-06 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-04-18 00:00:00\",\n    \"2185-05-01 00:00:00\",\n    \"2185-05-26 00:00:00\",\n    \"2185-06-06 00:00:00\",\n    \"2185-06-24 00:00:00\",\n    \"2185-12-24 00:00:00\",\n    \"2185-12-25 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2185-12-31 00:00:00\",\n    \"2186-01-01 00:00:00\",\n    \"2186-01-06 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-10 00:00:00\",\n    \"2186-05-01 00:00:00\",\n    \"2186-05-18 00:00:00\",\n    \"2186-06-06 00:00:00\",\n    \"2186-06-23 00:00:00\",\n    \"2186-12-24 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2186-12-31 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-01-06 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-03-26 00:00:00\",\n    \"2187-05-01 00:00:00\",\n    \"2187-05-03 00:00:00\",\n    \"2187-06-06 00:00:00\",\n    \"2187-06-22 00:00:00\",\n    \"2187-12-24 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2187-12-31 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-01-06 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-05-01 00:00:00\",\n    \"2188-05-22 00:00:00\",\n    \"2188-06-06 00:00:00\",\n    \"2188-06-20 00:00:00\",\n    \"2188-12-24 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2188-12-31 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-01-06 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-06 00:00:00\",\n    \"2189-05-01 00:00:00\",\n    \"2189-05-14 00:00:00\",\n    \"2189-06-06 00:00:00\",\n    \"2189-06-19 00:00:00\",\n    \"2189-12-24 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-26 00:00:00\",\n    \"2189-12-31 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-01-06 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-05-01 00:00:00\",\n    \"2190-06-03 00:00:00\",\n    \"2190-06-06 00:00:00\",\n    \"2190-06-25 00:00:00\",\n    \"2190-12-24 00:00:00\",\n    \"2190-12-25 00:00:00\",\n    \"2190-12-26 00:00:00\",\n    \"2190-12-31 00:00:00\",\n    \"2191-01-01 00:00:00\",\n    \"2191-01-06 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-11 00:00:00\",\n    \"2191-05-01 00:00:00\",\n    \"2191-05-19 00:00:00\",\n    \"2191-06-06 00:00:00\",\n    \"2191-06-24 00:00:00\",\n    \"2191-12-24 00:00:00\",\n    \"2191-12-25 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2191-12-31 00:00:00\",\n    \"2192-01-01 00:00:00\",\n    \"2192-01-06 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-02 00:00:00\",\n    \"2192-05-01 00:00:00\",\n    \"2192-05-10 00:00:00\",\n    \"2192-06-06 00:00:00\",\n    \"2192-06-22 00:00:00\",\n    \"2192-12-24 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2192-12-31 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-01-06 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-04-22 00:00:00\",\n    \"2193-05-01 00:00:00\",\n    \"2193-05-30 00:00:00\",\n    \"2193-06-06 00:00:00\",\n    \"2193-06-21 00:00:00\",\n    \"2193-12-24 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2193-12-31 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-01-06 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-07 00:00:00\",\n    \"2194-05-01 00:00:00\",\n    \"2194-05-15 00:00:00\",\n    \"2194-06-06 00:00:00\",\n    \"2194-06-20 00:00:00\",\n    \"2194-12-24 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2194-12-31 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-01-06 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-03-30 00:00:00\",\n    \"2195-05-01 00:00:00\",\n    \"2195-05-07 00:00:00\",\n    \"2195-06-06 00:00:00\",\n    \"2195-06-19 00:00:00\",\n    \"2195-12-24 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-26 00:00:00\",\n    \"2195-12-31 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-01-06 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-04-18 00:00:00\",\n    \"2196-05-01 00:00:00\",\n    \"2196-05-26 00:00:00\",\n    \"2196-06-06 00:00:00\",\n    \"2196-06-24 00:00:00\",\n    \"2196-12-24 00:00:00\",\n    \"2196-12-25 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2196-12-31 00:00:00\",\n    \"2197-01-01 00:00:00\",\n    \"2197-01-06 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-10 00:00:00\",\n    \"2197-05-01 00:00:00\",\n    \"2197-05-18 00:00:00\",\n    \"2197-06-06 00:00:00\",\n    \"2197-06-23 00:00:00\",\n    \"2197-12-24 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2197-12-31 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-01-06 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-03-26 00:00:00\",\n    \"2198-05-01 00:00:00\",\n    \"2198-05-03 00:00:00\",\n    \"2198-06-06 00:00:00\",\n    \"2198-06-22 00:00:00\",\n    \"2198-12-24 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2198-12-31 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-01-06 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-15 00:00:00\",\n    \"2199-05-01 00:00:00\",\n    \"2199-05-23 00:00:00\",\n    \"2199-06-06 00:00:00\",\n    \"2199-06-21 00:00:00\",\n    \"2199-12-24 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2199-12-31 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-01-06 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-07 00:00:00\",\n    \"2200-05-01 00:00:00\",\n    \"2200-05-15 00:00:00\",\n    \"2200-06-06 00:00:00\",\n    \"2200-06-20 00:00:00\",\n    \"2200-12-24 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n    \"2200-12-31 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/stk_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pandas as pd\nfrom pandas.tseries.holiday import FR, AbstractHolidayCalendar, DateOffset, Holiday\nfrom pandas.tseries.offsets import CustomBusinessDay, Day, Easter\n\nRULES = [\n    Holiday(\"New Year's Day\", month=1, day=1),\n    Holiday(\"Epiphany\", month=1, day=6),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Easter Monday\", month=1, day=1, offset=[Easter(), Day(1)]),\n    Holiday(\"EU Labour Day\", month=5, day=1),\n    Holiday(\"Ascention Day\", month=1, day=1, offset=[Easter(), Day(39)]),\n    Holiday(\"Sweden National Day\", month=6, day=6),\n    Holiday(\"Swedish Midsummer\", month=6, day=25, offset=DateOffset(weekday=FR(-1))),\n    Holiday(\"Christmas Eve\", month=12, day=24),\n    Holiday(\"Christmas Day\", month=12, day=25),\n    Holiday(\"Boxing Day\", month=12, day=26),\n    Holiday(\"New Year's Eve\", month=12, day=31),\n]\n\nCALENDAR = CustomBusinessDay(\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/syd.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Sydney business day calendar, aligned with AONIA publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Jan 26 (Australia)\",\n//     \"Fri before Easter (Good Friday)\",\n//     \"Mon after Easter (Easter Monday)\",\n//     \"Apr 25 (Anzac)\",\n//     \"Jun 2nd Mon (Kings Bday)\",\n//     \"Dec 25 (Christmas)\",\n//     \"Dec 26 (Boxing Day)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-01-26 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-03-30 00:00:00\",\n    \"1970-04-25 00:00:00\",\n    \"1970-06-08 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-28 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-01-26 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-12 00:00:00\",\n    \"1971-04-25 00:00:00\",\n    \"1971-06-14 00:00:00\",\n    \"1971-12-27 00:00:00\",\n    \"1971-12-28 00:00:00\",\n    \"1972-01-03 00:00:00\",\n    \"1972-01-26 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-03 00:00:00\",\n    \"1972-04-25 00:00:00\",\n    \"1972-06-12 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-01-26 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-04-23 00:00:00\",\n    \"1973-04-25 00:00:00\",\n    \"1973-06-11 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-01-28 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-15 00:00:00\",\n    \"1974-04-25 00:00:00\",\n    \"1974-06-10 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-01-27 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-03-31 00:00:00\",\n    \"1975-04-25 00:00:00\",\n    \"1975-06-09 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-01-26 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-04-19 00:00:00\",\n    \"1976-04-25 00:00:00\",\n    \"1976-06-14 00:00:00\",\n    \"1976-12-27 00:00:00\",\n    \"1976-12-28 00:00:00\",\n    \"1977-01-03 00:00:00\",\n    \"1977-01-26 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-11 00:00:00\",\n    \"1977-04-25 00:00:00\",\n    \"1977-06-13 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1977-12-27 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-01-26 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-03-27 00:00:00\",\n    \"1978-04-25 00:00:00\",\n    \"1978-06-12 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-01-26 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-16 00:00:00\",\n    \"1979-04-25 00:00:00\",\n    \"1979-06-11 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-01-28 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-07 00:00:00\",\n    \"1980-04-25 00:00:00\",\n    \"1980-06-09 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-01-26 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-04-20 00:00:00\",\n    \"1981-04-25 00:00:00\",\n    \"1981-06-08 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-28 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-01-26 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-12 00:00:00\",\n    \"1982-04-25 00:00:00\",\n    \"1982-06-14 00:00:00\",\n    \"1982-12-27 00:00:00\",\n    \"1982-12-28 00:00:00\",\n    \"1983-01-03 00:00:00\",\n    \"1983-01-26 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-04 00:00:00\",\n    \"1983-04-25 00:00:00\",\n    \"1983-06-13 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1983-12-27 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-01-26 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-04-23 00:00:00\",\n    \"1984-04-25 00:00:00\",\n    \"1984-06-11 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-01-28 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-08 00:00:00\",\n    \"1985-04-25 00:00:00\",\n    \"1985-06-10 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-01-27 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-03-31 00:00:00\",\n    \"1986-04-25 00:00:00\",\n    \"1986-06-09 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-01-26 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-04-20 00:00:00\",\n    \"1987-04-25 00:00:00\",\n    \"1987-06-08 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-28 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-01-26 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-04 00:00:00\",\n    \"1988-04-25 00:00:00\",\n    \"1988-06-13 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1988-12-27 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-01-26 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-03-27 00:00:00\",\n    \"1989-04-25 00:00:00\",\n    \"1989-06-12 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-01-26 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-16 00:00:00\",\n    \"1990-04-25 00:00:00\",\n    \"1990-06-11 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-01-28 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-01 00:00:00\",\n    \"1991-04-25 00:00:00\",\n    \"1991-06-10 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-01-27 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-04-20 00:00:00\",\n    \"1992-04-25 00:00:00\",\n    \"1992-06-08 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-28 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-01-26 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-12 00:00:00\",\n    \"1993-04-25 00:00:00\",\n    \"1993-06-14 00:00:00\",\n    \"1993-12-27 00:00:00\",\n    \"1993-12-28 00:00:00\",\n    \"1994-01-03 00:00:00\",\n    \"1994-01-26 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-04 00:00:00\",\n    \"1994-04-25 00:00:00\",\n    \"1994-06-13 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1994-12-27 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-01-26 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-17 00:00:00\",\n    \"1995-04-25 00:00:00\",\n    \"1995-06-12 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-01-26 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-08 00:00:00\",\n    \"1996-04-25 00:00:00\",\n    \"1996-06-10 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-01-27 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-03-31 00:00:00\",\n    \"1997-04-25 00:00:00\",\n    \"1997-06-09 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-01-26 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-13 00:00:00\",\n    \"1998-04-25 00:00:00\",\n    \"1998-06-08 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-28 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-01-26 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-05 00:00:00\",\n    \"1999-04-25 00:00:00\",\n    \"1999-06-14 00:00:00\",\n    \"1999-12-27 00:00:00\",\n    \"1999-12-28 00:00:00\",\n    \"2000-01-03 00:00:00\",\n    \"2000-01-26 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-04-24 00:00:00\",\n    \"2000-04-25 00:00:00\",\n    \"2000-06-12 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-01-26 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-16 00:00:00\",\n    \"2001-04-25 00:00:00\",\n    \"2001-06-11 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-01-28 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-01 00:00:00\",\n    \"2002-04-25 00:00:00\",\n    \"2002-06-10 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-01-27 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-04-21 00:00:00\",\n    \"2003-04-25 00:00:00\",\n    \"2003-06-09 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-01-26 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-12 00:00:00\",\n    \"2004-04-25 00:00:00\",\n    \"2004-06-14 00:00:00\",\n    \"2004-12-27 00:00:00\",\n    \"2004-12-28 00:00:00\",\n    \"2005-01-03 00:00:00\",\n    \"2005-01-26 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-03-28 00:00:00\",\n    \"2005-04-25 00:00:00\",\n    \"2005-06-13 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2005-12-27 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-01-26 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-17 00:00:00\",\n    \"2006-04-25 00:00:00\",\n    \"2006-06-12 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-01-26 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-09 00:00:00\",\n    \"2007-04-25 00:00:00\",\n    \"2007-06-11 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-01-28 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-03-24 00:00:00\",\n    \"2008-04-25 00:00:00\",\n    \"2008-06-09 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-01-26 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-13 00:00:00\",\n    \"2009-04-25 00:00:00\",\n    \"2009-06-08 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-28 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-01-26 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-05 00:00:00\",\n    \"2010-04-25 00:00:00\",\n    \"2010-06-14 00:00:00\",\n    \"2010-12-27 00:00:00\",\n    \"2010-12-28 00:00:00\",\n    \"2011-01-03 00:00:00\",\n    \"2011-01-26 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-04-26 00:00:00\",\n    \"2011-06-13 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2011-12-27 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-01-26 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-09 00:00:00\",\n    \"2012-04-25 00:00:00\",\n    \"2012-06-11 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-01-28 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-01 00:00:00\",\n    \"2013-04-25 00:00:00\",\n    \"2013-06-10 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-01-27 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-04-21 00:00:00\",\n    \"2014-04-25 00:00:00\",\n    \"2014-06-09 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-01-26 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-06 00:00:00\",\n    \"2015-04-25 00:00:00\",\n    \"2015-06-08 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-28 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-01-26 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-03-28 00:00:00\",\n    \"2016-04-25 00:00:00\",\n    \"2016-06-13 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2016-12-27 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-01-26 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-17 00:00:00\",\n    \"2017-04-25 00:00:00\",\n    \"2017-06-12 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-01-26 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-02 00:00:00\",\n    \"2018-04-25 00:00:00\",\n    \"2018-06-11 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-01-28 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-22 00:00:00\",\n    \"2019-04-25 00:00:00\",\n    \"2019-06-10 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-01-27 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-13 00:00:00\",\n    \"2020-04-25 00:00:00\",\n    \"2020-06-08 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-28 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-01-26 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-05 00:00:00\",\n    \"2021-04-25 00:00:00\",\n    \"2021-06-14 00:00:00\",\n    \"2021-12-27 00:00:00\",\n    \"2021-12-28 00:00:00\",\n    \"2022-01-03 00:00:00\",\n    \"2022-01-26 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-04-18 00:00:00\",\n    \"2022-04-25 00:00:00\",\n    \"2022-06-13 00:00:00\",\n    \"2022-09-22 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2022-12-27 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-01-26 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-10 00:00:00\",\n    \"2023-04-25 00:00:00\",\n    \"2023-06-12 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-01-26 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-04-25 00:00:00\",\n    \"2024-06-10 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-27 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-04-21 00:00:00\",\n    \"2025-04-25 00:00:00\",\n    \"2025-06-09 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-26 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-04-25 00:00:00\",\n    \"2026-06-08 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-28 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-26 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-03-29 00:00:00\",\n    \"2027-04-25 00:00:00\",\n    \"2027-06-14 00:00:00\",\n    \"2027-12-27 00:00:00\",\n    \"2027-12-28 00:00:00\",\n    \"2028-01-03 00:00:00\",\n    \"2028-01-26 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-17 00:00:00\",\n    \"2028-04-25 00:00:00\",\n    \"2028-06-12 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-26 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-02 00:00:00\",\n    \"2029-04-25 00:00:00\",\n    \"2029-06-11 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-28 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-04-22 00:00:00\",\n    \"2030-04-25 00:00:00\",\n    \"2030-06-10 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-27 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-04-25 00:00:00\",\n    \"2031-06-09 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-26 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-03-29 00:00:00\",\n    \"2032-04-25 00:00:00\",\n    \"2032-06-14 00:00:00\",\n    \"2032-12-27 00:00:00\",\n    \"2032-12-28 00:00:00\",\n    \"2033-01-03 00:00:00\",\n    \"2033-01-26 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-04-18 00:00:00\",\n    \"2033-04-25 00:00:00\",\n    \"2033-06-13 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2033-12-27 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-01-26 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-10 00:00:00\",\n    \"2034-04-25 00:00:00\",\n    \"2034-06-12 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-26 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-03-26 00:00:00\",\n    \"2035-04-25 00:00:00\",\n    \"2035-06-11 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-28 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-04-25 00:00:00\",\n    \"2036-06-09 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-26 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-04-25 00:00:00\",\n    \"2037-06-08 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-28 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-26 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-04-25 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-06-14 00:00:00\",\n    \"2038-12-27 00:00:00\",\n    \"2038-12-28 00:00:00\",\n    \"2039-01-03 00:00:00\",\n    \"2039-01-26 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-11 00:00:00\",\n    \"2039-04-25 00:00:00\",\n    \"2039-06-13 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2039-12-27 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-01-26 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-02 00:00:00\",\n    \"2040-04-25 00:00:00\",\n    \"2040-06-11 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-28 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-04-22 00:00:00\",\n    \"2041-04-25 00:00:00\",\n    \"2041-06-10 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-27 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-07 00:00:00\",\n    \"2042-04-25 00:00:00\",\n    \"2042-06-09 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-26 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-03-30 00:00:00\",\n    \"2043-04-25 00:00:00\",\n    \"2043-06-08 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-28 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-26 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-04-18 00:00:00\",\n    \"2044-04-25 00:00:00\",\n    \"2044-06-13 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2044-12-27 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-01-26 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-10 00:00:00\",\n    \"2045-04-25 00:00:00\",\n    \"2045-06-12 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-26 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-03-26 00:00:00\",\n    \"2046-04-25 00:00:00\",\n    \"2046-06-11 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-28 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-15 00:00:00\",\n    \"2047-04-25 00:00:00\",\n    \"2047-06-10 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-27 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-06 00:00:00\",\n    \"2048-04-25 00:00:00\",\n    \"2048-06-08 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-28 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-26 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-04-19 00:00:00\",\n    \"2049-04-25 00:00:00\",\n    \"2049-06-14 00:00:00\",\n    \"2049-12-27 00:00:00\",\n    \"2049-12-28 00:00:00\",\n    \"2050-01-03 00:00:00\",\n    \"2050-01-26 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-11 00:00:00\",\n    \"2050-04-25 00:00:00\",\n    \"2050-06-13 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2050-12-27 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-01-26 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-04-25 00:00:00\",\n    \"2051-06-12 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-26 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-04-22 00:00:00\",\n    \"2052-04-25 00:00:00\",\n    \"2052-06-10 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-27 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-07 00:00:00\",\n    \"2053-04-25 00:00:00\",\n    \"2053-06-09 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-26 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-03-30 00:00:00\",\n    \"2054-04-25 00:00:00\",\n    \"2054-06-08 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-28 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-26 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-04-19 00:00:00\",\n    \"2055-04-25 00:00:00\",\n    \"2055-06-14 00:00:00\",\n    \"2055-12-27 00:00:00\",\n    \"2055-12-28 00:00:00\",\n    \"2056-01-03 00:00:00\",\n    \"2056-01-26 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-04-25 00:00:00\",\n    \"2056-06-12 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-26 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-04-23 00:00:00\",\n    \"2057-04-25 00:00:00\",\n    \"2057-06-11 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-28 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-15 00:00:00\",\n    \"2058-04-25 00:00:00\",\n    \"2058-06-10 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-27 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-03-31 00:00:00\",\n    \"2059-04-25 00:00:00\",\n    \"2059-06-09 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-26 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-04-19 00:00:00\",\n    \"2060-04-25 00:00:00\",\n    \"2060-06-14 00:00:00\",\n    \"2060-12-27 00:00:00\",\n    \"2060-12-28 00:00:00\",\n    \"2061-01-03 00:00:00\",\n    \"2061-01-26 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-11 00:00:00\",\n    \"2061-04-25 00:00:00\",\n    \"2061-06-13 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2061-12-27 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-01-26 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-03-27 00:00:00\",\n    \"2062-04-25 00:00:00\",\n    \"2062-06-12 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-26 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-16 00:00:00\",\n    \"2063-04-25 00:00:00\",\n    \"2063-06-11 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-28 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-07 00:00:00\",\n    \"2064-04-25 00:00:00\",\n    \"2064-06-09 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-26 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-03-30 00:00:00\",\n    \"2065-04-25 00:00:00\",\n    \"2065-06-08 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-28 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-26 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-12 00:00:00\",\n    \"2066-04-25 00:00:00\",\n    \"2066-06-14 00:00:00\",\n    \"2066-12-27 00:00:00\",\n    \"2066-12-28 00:00:00\",\n    \"2067-01-03 00:00:00\",\n    \"2067-01-26 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-04-25 00:00:00\",\n    \"2067-06-13 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2067-12-27 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-01-26 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-04-23 00:00:00\",\n    \"2068-04-25 00:00:00\",\n    \"2068-06-11 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-28 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-15 00:00:00\",\n    \"2069-04-25 00:00:00\",\n    \"2069-06-10 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-27 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-03-31 00:00:00\",\n    \"2070-04-25 00:00:00\",\n    \"2070-06-09 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-26 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-04-20 00:00:00\",\n    \"2071-04-25 00:00:00\",\n    \"2071-06-08 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-28 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-26 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-11 00:00:00\",\n    \"2072-04-25 00:00:00\",\n    \"2072-06-13 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2072-12-27 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-01-26 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-03-27 00:00:00\",\n    \"2073-04-25 00:00:00\",\n    \"2073-06-12 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-26 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-16 00:00:00\",\n    \"2074-04-25 00:00:00\",\n    \"2074-06-11 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-28 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-08 00:00:00\",\n    \"2075-04-25 00:00:00\",\n    \"2075-06-10 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-27 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-04-20 00:00:00\",\n    \"2076-04-25 00:00:00\",\n    \"2076-06-08 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-28 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-26 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-12 00:00:00\",\n    \"2077-04-25 00:00:00\",\n    \"2077-06-14 00:00:00\",\n    \"2077-12-27 00:00:00\",\n    \"2077-12-28 00:00:00\",\n    \"2078-01-03 00:00:00\",\n    \"2078-01-26 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-04-25 00:00:00\",\n    \"2078-06-13 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2078-12-27 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-01-26 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-04-24 00:00:00\",\n    \"2079-04-25 00:00:00\",\n    \"2079-06-12 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-26 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-08 00:00:00\",\n    \"2080-04-25 00:00:00\",\n    \"2080-06-10 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-27 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-03-31 00:00:00\",\n    \"2081-04-25 00:00:00\",\n    \"2081-06-09 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-26 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-04-20 00:00:00\",\n    \"2082-04-25 00:00:00\",\n    \"2082-06-08 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-28 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-26 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-04-25 00:00:00\",\n    \"2083-06-14 00:00:00\",\n    \"2083-12-27 00:00:00\",\n    \"2083-12-28 00:00:00\",\n    \"2084-01-03 00:00:00\",\n    \"2084-01-26 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-03-27 00:00:00\",\n    \"2084-04-25 00:00:00\",\n    \"2084-06-12 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-26 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-16 00:00:00\",\n    \"2085-04-25 00:00:00\",\n    \"2085-06-11 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-28 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-01 00:00:00\",\n    \"2086-04-25 00:00:00\",\n    \"2086-06-10 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-27 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-04-21 00:00:00\",\n    \"2087-04-25 00:00:00\",\n    \"2087-06-09 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-26 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-12 00:00:00\",\n    \"2088-04-25 00:00:00\",\n    \"2088-06-14 00:00:00\",\n    \"2088-12-27 00:00:00\",\n    \"2088-12-28 00:00:00\",\n    \"2089-01-03 00:00:00\",\n    \"2089-01-26 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-04-25 00:00:00\",\n    \"2089-06-13 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2089-12-27 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-01-26 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-17 00:00:00\",\n    \"2090-04-25 00:00:00\",\n    \"2090-06-12 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-26 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-09 00:00:00\",\n    \"2091-04-25 00:00:00\",\n    \"2091-06-11 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-28 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-03-31 00:00:00\",\n    \"2092-04-25 00:00:00\",\n    \"2092-06-09 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-26 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-13 00:00:00\",\n    \"2093-04-25 00:00:00\",\n    \"2093-06-08 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-28 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-26 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-04-25 00:00:00\",\n    \"2094-06-14 00:00:00\",\n    \"2094-12-27 00:00:00\",\n    \"2094-12-28 00:00:00\",\n    \"2095-01-03 00:00:00\",\n    \"2095-01-26 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-06-13 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2095-12-27 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-01-26 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-16 00:00:00\",\n    \"2096-04-25 00:00:00\",\n    \"2096-06-11 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-28 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-01 00:00:00\",\n    \"2097-04-25 00:00:00\",\n    \"2097-06-10 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-27 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-04-21 00:00:00\",\n    \"2098-04-25 00:00:00\",\n    \"2098-06-09 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-26 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-13 00:00:00\",\n    \"2099-04-25 00:00:00\",\n    \"2099-06-08 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-28 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-26 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-03-29 00:00:00\",\n    \"2100-04-25 00:00:00\",\n    \"2100-06-14 00:00:00\",\n    \"2100-12-27 00:00:00\",\n    \"2100-12-28 00:00:00\",\n    \"2101-01-03 00:00:00\",\n    \"2101-01-26 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-04-18 00:00:00\",\n    \"2101-04-25 00:00:00\",\n    \"2101-06-13 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2101-12-27 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-01-26 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-10 00:00:00\",\n    \"2102-04-25 00:00:00\",\n    \"2102-06-12 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-01-26 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-03-26 00:00:00\",\n    \"2103-04-25 00:00:00\",\n    \"2103-06-11 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-01-28 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-04-25 00:00:00\",\n    \"2104-06-09 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-01-26 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-06 00:00:00\",\n    \"2105-04-25 00:00:00\",\n    \"2105-06-08 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-28 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-01-26 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-04-19 00:00:00\",\n    \"2106-04-25 00:00:00\",\n    \"2106-06-14 00:00:00\",\n    \"2106-12-27 00:00:00\",\n    \"2106-12-28 00:00:00\",\n    \"2107-01-03 00:00:00\",\n    \"2107-01-26 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-11 00:00:00\",\n    \"2107-04-25 00:00:00\",\n    \"2107-06-13 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2107-12-27 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-01-26 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-02 00:00:00\",\n    \"2108-04-25 00:00:00\",\n    \"2108-06-11 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-01-28 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-04-22 00:00:00\",\n    \"2109-04-25 00:00:00\",\n    \"2109-06-10 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-01-27 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-07 00:00:00\",\n    \"2110-04-25 00:00:00\",\n    \"2110-06-09 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-01-26 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-03-30 00:00:00\",\n    \"2111-04-25 00:00:00\",\n    \"2111-06-08 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-28 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-01-26 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-04-18 00:00:00\",\n    \"2112-04-25 00:00:00\",\n    \"2112-06-13 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2112-12-27 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-01-26 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-03 00:00:00\",\n    \"2113-04-25 00:00:00\",\n    \"2113-06-12 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-01-26 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-04-23 00:00:00\",\n    \"2114-04-25 00:00:00\",\n    \"2114-06-11 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-01-28 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-15 00:00:00\",\n    \"2115-04-25 00:00:00\",\n    \"2115-06-10 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-01-27 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-03-30 00:00:00\",\n    \"2116-04-25 00:00:00\",\n    \"2116-06-08 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-28 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-01-26 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-04-19 00:00:00\",\n    \"2117-04-25 00:00:00\",\n    \"2117-06-14 00:00:00\",\n    \"2117-12-27 00:00:00\",\n    \"2117-12-28 00:00:00\",\n    \"2118-01-03 00:00:00\",\n    \"2118-01-26 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-11 00:00:00\",\n    \"2118-04-25 00:00:00\",\n    \"2118-06-13 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2118-12-27 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-01-26 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-03-27 00:00:00\",\n    \"2119-04-25 00:00:00\",\n    \"2119-06-12 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-01-26 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-15 00:00:00\",\n    \"2120-04-25 00:00:00\",\n    \"2120-06-10 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-01-27 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-07 00:00:00\",\n    \"2121-04-25 00:00:00\",\n    \"2121-06-09 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-01-26 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-03-30 00:00:00\",\n    \"2122-04-25 00:00:00\",\n    \"2122-06-08 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-28 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-01-26 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-12 00:00:00\",\n    \"2123-04-25 00:00:00\",\n    \"2123-06-14 00:00:00\",\n    \"2123-12-27 00:00:00\",\n    \"2123-12-28 00:00:00\",\n    \"2124-01-03 00:00:00\",\n    \"2124-01-26 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-03 00:00:00\",\n    \"2124-04-25 00:00:00\",\n    \"2124-06-12 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-01-26 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-04-23 00:00:00\",\n    \"2125-04-25 00:00:00\",\n    \"2125-06-11 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-01-28 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-15 00:00:00\",\n    \"2126-04-25 00:00:00\",\n    \"2126-06-10 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-01-27 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-03-31 00:00:00\",\n    \"2127-04-25 00:00:00\",\n    \"2127-06-09 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-01-26 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-04-19 00:00:00\",\n    \"2128-04-25 00:00:00\",\n    \"2128-06-14 00:00:00\",\n    \"2128-12-27 00:00:00\",\n    \"2128-12-28 00:00:00\",\n    \"2129-01-03 00:00:00\",\n    \"2129-01-26 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-11 00:00:00\",\n    \"2129-04-25 00:00:00\",\n    \"2129-06-13 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2129-12-27 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-01-26 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-03-27 00:00:00\",\n    \"2130-04-25 00:00:00\",\n    \"2130-06-12 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-01-26 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-16 00:00:00\",\n    \"2131-04-25 00:00:00\",\n    \"2131-06-11 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-01-28 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-07 00:00:00\",\n    \"2132-04-25 00:00:00\",\n    \"2132-06-09 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-01-26 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-04-20 00:00:00\",\n    \"2133-04-25 00:00:00\",\n    \"2133-06-08 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-28 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-01-26 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-12 00:00:00\",\n    \"2134-04-25 00:00:00\",\n    \"2134-06-14 00:00:00\",\n    \"2134-12-27 00:00:00\",\n    \"2134-12-28 00:00:00\",\n    \"2135-01-03 00:00:00\",\n    \"2135-01-26 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-04 00:00:00\",\n    \"2135-04-25 00:00:00\",\n    \"2135-06-13 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2135-12-27 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-01-26 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-04-23 00:00:00\",\n    \"2136-04-25 00:00:00\",\n    \"2136-06-11 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-01-28 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-08 00:00:00\",\n    \"2137-04-25 00:00:00\",\n    \"2137-06-10 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-01-27 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-03-31 00:00:00\",\n    \"2138-04-25 00:00:00\",\n    \"2138-06-09 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-01-26 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-04-20 00:00:00\",\n    \"2139-04-25 00:00:00\",\n    \"2139-06-08 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-28 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-01-26 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-04 00:00:00\",\n    \"2140-04-25 00:00:00\",\n    \"2140-06-13 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2140-12-27 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-01-26 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-03-27 00:00:00\",\n    \"2141-04-25 00:00:00\",\n    \"2141-06-12 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-01-26 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-16 00:00:00\",\n    \"2142-04-25 00:00:00\",\n    \"2142-06-11 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-01-28 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-01 00:00:00\",\n    \"2143-04-25 00:00:00\",\n    \"2143-06-10 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-01-27 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-04-20 00:00:00\",\n    \"2144-04-25 00:00:00\",\n    \"2144-06-08 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-28 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-01-26 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-12 00:00:00\",\n    \"2145-04-25 00:00:00\",\n    \"2145-06-14 00:00:00\",\n    \"2145-12-27 00:00:00\",\n    \"2145-12-28 00:00:00\",\n    \"2146-01-03 00:00:00\",\n    \"2146-01-26 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-04 00:00:00\",\n    \"2146-04-25 00:00:00\",\n    \"2146-06-13 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2146-12-27 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-01-26 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-17 00:00:00\",\n    \"2147-04-25 00:00:00\",\n    \"2147-06-12 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-01-26 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-08 00:00:00\",\n    \"2148-04-25 00:00:00\",\n    \"2148-06-10 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-01-27 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-03-31 00:00:00\",\n    \"2149-04-25 00:00:00\",\n    \"2149-06-09 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-01-26 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-13 00:00:00\",\n    \"2150-04-25 00:00:00\",\n    \"2150-06-08 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-28 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-01-26 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-05 00:00:00\",\n    \"2151-04-25 00:00:00\",\n    \"2151-06-14 00:00:00\",\n    \"2151-12-27 00:00:00\",\n    \"2151-12-28 00:00:00\",\n    \"2152-01-03 00:00:00\",\n    \"2152-01-26 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-04-24 00:00:00\",\n    \"2152-04-25 00:00:00\",\n    \"2152-06-12 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-01-26 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-16 00:00:00\",\n    \"2153-04-25 00:00:00\",\n    \"2153-06-11 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-01-28 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-01 00:00:00\",\n    \"2154-04-25 00:00:00\",\n    \"2154-06-10 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-01-27 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-04-21 00:00:00\",\n    \"2155-04-25 00:00:00\",\n    \"2155-06-09 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-01-26 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-12 00:00:00\",\n    \"2156-04-25 00:00:00\",\n    \"2156-06-14 00:00:00\",\n    \"2156-12-27 00:00:00\",\n    \"2156-12-28 00:00:00\",\n    \"2157-01-03 00:00:00\",\n    \"2157-01-26 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-03-28 00:00:00\",\n    \"2157-04-25 00:00:00\",\n    \"2157-06-13 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2157-12-27 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-01-26 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-17 00:00:00\",\n    \"2158-04-25 00:00:00\",\n    \"2158-06-12 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-01-26 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-09 00:00:00\",\n    \"2159-04-25 00:00:00\",\n    \"2159-06-11 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-01-28 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-03-24 00:00:00\",\n    \"2160-04-25 00:00:00\",\n    \"2160-06-09 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-01-26 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-13 00:00:00\",\n    \"2161-04-25 00:00:00\",\n    \"2161-06-08 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-28 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-01-26 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-05 00:00:00\",\n    \"2162-04-25 00:00:00\",\n    \"2162-06-14 00:00:00\",\n    \"2162-12-27 00:00:00\",\n    \"2162-12-28 00:00:00\",\n    \"2163-01-03 00:00:00\",\n    \"2163-01-26 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-06-13 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2163-12-27 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-01-26 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-09 00:00:00\",\n    \"2164-04-25 00:00:00\",\n    \"2164-06-11 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-01-28 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-01 00:00:00\",\n    \"2165-04-25 00:00:00\",\n    \"2165-06-10 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-01-27 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-04-21 00:00:00\",\n    \"2166-04-25 00:00:00\",\n    \"2166-06-09 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-01-26 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-06 00:00:00\",\n    \"2167-04-25 00:00:00\",\n    \"2167-06-08 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-28 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-01-26 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-03-28 00:00:00\",\n    \"2168-04-25 00:00:00\",\n    \"2168-06-13 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2168-12-27 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-01-26 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-17 00:00:00\",\n    \"2169-04-25 00:00:00\",\n    \"2169-06-12 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-01-26 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-02 00:00:00\",\n    \"2170-04-25 00:00:00\",\n    \"2170-06-11 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-01-28 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-04-22 00:00:00\",\n    \"2171-04-25 00:00:00\",\n    \"2171-06-10 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-01-27 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-13 00:00:00\",\n    \"2172-04-25 00:00:00\",\n    \"2172-06-08 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-28 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-01-26 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-05 00:00:00\",\n    \"2173-04-25 00:00:00\",\n    \"2173-06-14 00:00:00\",\n    \"2173-12-27 00:00:00\",\n    \"2173-12-28 00:00:00\",\n    \"2174-01-03 00:00:00\",\n    \"2174-01-26 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-04-18 00:00:00\",\n    \"2174-04-25 00:00:00\",\n    \"2174-06-13 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2174-12-27 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-01-26 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-10 00:00:00\",\n    \"2175-04-25 00:00:00\",\n    \"2175-06-12 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-01-26 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-01 00:00:00\",\n    \"2176-04-25 00:00:00\",\n    \"2176-06-10 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-01-27 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-04-21 00:00:00\",\n    \"2177-04-25 00:00:00\",\n    \"2177-06-09 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-01-26 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-06 00:00:00\",\n    \"2178-04-25 00:00:00\",\n    \"2178-06-08 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-28 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-01-26 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-03-29 00:00:00\",\n    \"2179-04-25 00:00:00\",\n    \"2179-06-14 00:00:00\",\n    \"2179-12-27 00:00:00\",\n    \"2179-12-28 00:00:00\",\n    \"2180-01-03 00:00:00\",\n    \"2180-01-26 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-17 00:00:00\",\n    \"2180-04-25 00:00:00\",\n    \"2180-06-12 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-01-26 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-02 00:00:00\",\n    \"2181-04-25 00:00:00\",\n    \"2181-06-11 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-01-28 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-04-22 00:00:00\",\n    \"2182-04-25 00:00:00\",\n    \"2182-06-10 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-01-27 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-04-25 00:00:00\",\n    \"2183-06-09 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-01-26 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-03-29 00:00:00\",\n    \"2184-04-25 00:00:00\",\n    \"2184-06-14 00:00:00\",\n    \"2184-12-27 00:00:00\",\n    \"2184-12-28 00:00:00\",\n    \"2185-01-03 00:00:00\",\n    \"2185-01-26 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-04-18 00:00:00\",\n    \"2185-04-25 00:00:00\",\n    \"2185-06-13 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2185-12-27 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-01-26 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-10 00:00:00\",\n    \"2186-04-25 00:00:00\",\n    \"2186-06-12 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-01-26 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-03-26 00:00:00\",\n    \"2187-04-25 00:00:00\",\n    \"2187-06-11 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-01-28 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-04-25 00:00:00\",\n    \"2188-06-09 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-01-26 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-06 00:00:00\",\n    \"2189-04-25 00:00:00\",\n    \"2189-06-08 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-28 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-01-26 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-04-25 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-06-14 00:00:00\",\n    \"2190-12-27 00:00:00\",\n    \"2190-12-28 00:00:00\",\n    \"2191-01-03 00:00:00\",\n    \"2191-01-26 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-11 00:00:00\",\n    \"2191-04-25 00:00:00\",\n    \"2191-06-13 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2191-12-27 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-01-26 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-02 00:00:00\",\n    \"2192-04-25 00:00:00\",\n    \"2192-06-11 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-01-28 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-04-22 00:00:00\",\n    \"2193-04-25 00:00:00\",\n    \"2193-06-10 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-01-27 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-07 00:00:00\",\n    \"2194-04-25 00:00:00\",\n    \"2194-06-09 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-01-26 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-03-30 00:00:00\",\n    \"2195-04-25 00:00:00\",\n    \"2195-06-08 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-28 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-01-26 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-04-18 00:00:00\",\n    \"2196-04-25 00:00:00\",\n    \"2196-06-13 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2196-12-27 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-01-26 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-10 00:00:00\",\n    \"2197-04-25 00:00:00\",\n    \"2197-06-12 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-01-26 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-03-26 00:00:00\",\n    \"2198-04-25 00:00:00\",\n    \"2198-06-11 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-01-28 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-15 00:00:00\",\n    \"2199-04-25 00:00:00\",\n    \"2199-06-10 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-01-27 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-07 00:00:00\",\n    \"2200-04-25 00:00:00\",\n    \"2200-06-09 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/syd_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pandas as pd\nfrom dateutil.relativedelta import MO\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Day,\n    Easter,\n    Holiday,\n    next_monday,\n    next_monday_or_tuesday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, DateOffset\n\nRULES = [\n    Holiday(\"New Year's Day\", month=1, day=1, observance=next_monday),\n    Holiday(\"Australia Day\", month=1, day=26, observance=next_monday),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Easter Monday\", month=1, day=1, offset=[Easter(), Day(1)]),\n    Holiday(\"Anzac Day\", month=4, day=25),\n    Holiday(\"King's Birthday\", month=6, day=1, offset=DateOffset(weekday=MO(2))),\n    Holiday(\"Christmas Day Holiday\", month=12, day=25, observance=next_monday),\n    Holiday(\"Boxing Day Holiday\", month=12, day=26, observance=next_monday_or_tuesday),\n    # One Off\n    Holiday(\"Memorial\", year=2022, month=9, day=22),\n    Holiday(\"Adhoc1-Anzac fix\", year=2011, month=4, day=26),\n]\n\nCALENDAR = CustomBusinessDay(  # type: ignore[call-arg]\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/tgt.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a European Target holiday calendar, aligned with ESTR publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Fri before Easter (Good Friday)\",\n//     \"Mon after Easter (Easter Monday)\",\n//     \"May 1 (EU Labour)\",\n//     \"Dec 25 (Christmas)\",\n//     \"Dec 26 (Boxing Day)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-03-30 00:00:00\",\n    \"1970-05-01 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-26 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-12 00:00:00\",\n    \"1971-05-01 00:00:00\",\n    \"1971-12-25 00:00:00\",\n    \"1971-12-26 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-03 00:00:00\",\n    \"1972-05-01 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-04-23 00:00:00\",\n    \"1973-05-01 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-15 00:00:00\",\n    \"1974-05-01 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-03-31 00:00:00\",\n    \"1975-05-01 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-04-19 00:00:00\",\n    \"1976-05-01 00:00:00\",\n    \"1976-12-25 00:00:00\",\n    \"1976-12-26 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-11 00:00:00\",\n    \"1977-05-01 00:00:00\",\n    \"1977-12-25 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1978-01-01 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-03-27 00:00:00\",\n    \"1978-05-01 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-16 00:00:00\",\n    \"1979-05-01 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-07 00:00:00\",\n    \"1980-05-01 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-04-20 00:00:00\",\n    \"1981-05-01 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-26 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-12 00:00:00\",\n    \"1982-05-01 00:00:00\",\n    \"1982-12-25 00:00:00\",\n    \"1982-12-26 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-04 00:00:00\",\n    \"1983-05-01 00:00:00\",\n    \"1983-12-25 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1984-01-01 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-04-23 00:00:00\",\n    \"1984-05-01 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-08 00:00:00\",\n    \"1985-05-01 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-03-31 00:00:00\",\n    \"1986-05-01 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-04-20 00:00:00\",\n    \"1987-05-01 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-26 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-04 00:00:00\",\n    \"1988-05-01 00:00:00\",\n    \"1988-12-25 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1989-01-01 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-03-27 00:00:00\",\n    \"1989-05-01 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-16 00:00:00\",\n    \"1990-05-01 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-01 00:00:00\",\n    \"1991-05-01 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-04-20 00:00:00\",\n    \"1992-05-01 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-26 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-12 00:00:00\",\n    \"1993-05-01 00:00:00\",\n    \"1993-12-25 00:00:00\",\n    \"1993-12-26 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-04 00:00:00\",\n    \"1994-05-01 00:00:00\",\n    \"1994-12-25 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1995-01-01 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-17 00:00:00\",\n    \"1995-05-01 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-08 00:00:00\",\n    \"1996-05-01 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-03-31 00:00:00\",\n    \"1997-05-01 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-13 00:00:00\",\n    \"1998-05-01 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-26 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-05 00:00:00\",\n    \"1999-05-01 00:00:00\",\n    \"1999-12-25 00:00:00\",\n    \"1999-12-26 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-04-24 00:00:00\",\n    \"2000-05-01 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-16 00:00:00\",\n    \"2001-05-01 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-01 00:00:00\",\n    \"2002-05-01 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-04-21 00:00:00\",\n    \"2003-05-01 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-12 00:00:00\",\n    \"2004-05-01 00:00:00\",\n    \"2004-12-25 00:00:00\",\n    \"2004-12-26 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-03-28 00:00:00\",\n    \"2005-05-01 00:00:00\",\n    \"2005-12-25 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2006-01-01 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-17 00:00:00\",\n    \"2006-05-01 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-09 00:00:00\",\n    \"2007-05-01 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-03-24 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-13 00:00:00\",\n    \"2009-05-01 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-26 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-05 00:00:00\",\n    \"2010-05-01 00:00:00\",\n    \"2010-12-25 00:00:00\",\n    \"2010-12-26 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-05-01 00:00:00\",\n    \"2011-12-25 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2012-01-01 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-09 00:00:00\",\n    \"2012-05-01 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-01 00:00:00\",\n    \"2013-05-01 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-04-21 00:00:00\",\n    \"2014-05-01 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-06 00:00:00\",\n    \"2015-05-01 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-26 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-03-28 00:00:00\",\n    \"2016-05-01 00:00:00\",\n    \"2016-12-25 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2017-01-01 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-17 00:00:00\",\n    \"2017-05-01 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-02 00:00:00\",\n    \"2018-05-01 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-22 00:00:00\",\n    \"2019-05-01 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-13 00:00:00\",\n    \"2020-05-01 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-26 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-05 00:00:00\",\n    \"2021-05-01 00:00:00\",\n    \"2021-12-25 00:00:00\",\n    \"2021-12-26 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-04-18 00:00:00\",\n    \"2022-05-01 00:00:00\",\n    \"2022-12-25 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2023-01-01 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-10 00:00:00\",\n    \"2023-05-01 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-05-01 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-04-21 00:00:00\",\n    \"2025-05-01 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-05-01 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-26 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-03-29 00:00:00\",\n    \"2027-05-01 00:00:00\",\n    \"2027-12-25 00:00:00\",\n    \"2027-12-26 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-17 00:00:00\",\n    \"2028-05-01 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-02 00:00:00\",\n    \"2029-05-01 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-04-22 00:00:00\",\n    \"2030-05-01 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-05-01 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-03-29 00:00:00\",\n    \"2032-05-01 00:00:00\",\n    \"2032-12-25 00:00:00\",\n    \"2032-12-26 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-04-18 00:00:00\",\n    \"2033-05-01 00:00:00\",\n    \"2033-12-25 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2034-01-01 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-10 00:00:00\",\n    \"2034-05-01 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-03-26 00:00:00\",\n    \"2035-05-01 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-05-01 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-05-01 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-26 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-05-01 00:00:00\",\n    \"2038-12-25 00:00:00\",\n    \"2038-12-26 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-11 00:00:00\",\n    \"2039-05-01 00:00:00\",\n    \"2039-12-25 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2040-01-01 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-02 00:00:00\",\n    \"2040-05-01 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-04-22 00:00:00\",\n    \"2041-05-01 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-07 00:00:00\",\n    \"2042-05-01 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-03-30 00:00:00\",\n    \"2043-05-01 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-26 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-04-18 00:00:00\",\n    \"2044-05-01 00:00:00\",\n    \"2044-12-25 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2045-01-01 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-10 00:00:00\",\n    \"2045-05-01 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-03-26 00:00:00\",\n    \"2046-05-01 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-15 00:00:00\",\n    \"2047-05-01 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-06 00:00:00\",\n    \"2048-05-01 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-26 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-04-19 00:00:00\",\n    \"2049-05-01 00:00:00\",\n    \"2049-12-25 00:00:00\",\n    \"2049-12-26 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-11 00:00:00\",\n    \"2050-05-01 00:00:00\",\n    \"2050-12-25 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2051-01-01 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-05-01 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-04-22 00:00:00\",\n    \"2052-05-01 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-07 00:00:00\",\n    \"2053-05-01 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-03-30 00:00:00\",\n    \"2054-05-01 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-26 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-04-19 00:00:00\",\n    \"2055-05-01 00:00:00\",\n    \"2055-12-25 00:00:00\",\n    \"2055-12-26 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-05-01 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-04-23 00:00:00\",\n    \"2057-05-01 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-15 00:00:00\",\n    \"2058-05-01 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-03-31 00:00:00\",\n    \"2059-05-01 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-04-19 00:00:00\",\n    \"2060-05-01 00:00:00\",\n    \"2060-12-25 00:00:00\",\n    \"2060-12-26 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-11 00:00:00\",\n    \"2061-05-01 00:00:00\",\n    \"2061-12-25 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2062-01-01 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-03-27 00:00:00\",\n    \"2062-05-01 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-16 00:00:00\",\n    \"2063-05-01 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-07 00:00:00\",\n    \"2064-05-01 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-03-30 00:00:00\",\n    \"2065-05-01 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-26 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-12 00:00:00\",\n    \"2066-05-01 00:00:00\",\n    \"2066-12-25 00:00:00\",\n    \"2066-12-26 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-05-01 00:00:00\",\n    \"2067-12-25 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2068-01-01 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-04-23 00:00:00\",\n    \"2068-05-01 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-15 00:00:00\",\n    \"2069-05-01 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-03-31 00:00:00\",\n    \"2070-05-01 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-04-20 00:00:00\",\n    \"2071-05-01 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-26 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-11 00:00:00\",\n    \"2072-05-01 00:00:00\",\n    \"2072-12-25 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2073-01-01 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-03-27 00:00:00\",\n    \"2073-05-01 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-16 00:00:00\",\n    \"2074-05-01 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-08 00:00:00\",\n    \"2075-05-01 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-04-20 00:00:00\",\n    \"2076-05-01 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-26 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-12 00:00:00\",\n    \"2077-05-01 00:00:00\",\n    \"2077-12-25 00:00:00\",\n    \"2077-12-26 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-05-01 00:00:00\",\n    \"2078-12-25 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2079-01-01 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-04-24 00:00:00\",\n    \"2079-05-01 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-08 00:00:00\",\n    \"2080-05-01 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-03-31 00:00:00\",\n    \"2081-05-01 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-04-20 00:00:00\",\n    \"2082-05-01 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-26 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-05-01 00:00:00\",\n    \"2083-12-25 00:00:00\",\n    \"2083-12-26 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-03-27 00:00:00\",\n    \"2084-05-01 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-16 00:00:00\",\n    \"2085-05-01 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-01 00:00:00\",\n    \"2086-05-01 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-04-21 00:00:00\",\n    \"2087-05-01 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-12 00:00:00\",\n    \"2088-05-01 00:00:00\",\n    \"2088-12-25 00:00:00\",\n    \"2088-12-26 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-05-01 00:00:00\",\n    \"2089-12-25 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2090-01-01 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-17 00:00:00\",\n    \"2090-05-01 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-09 00:00:00\",\n    \"2091-05-01 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-03-31 00:00:00\",\n    \"2092-05-01 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-13 00:00:00\",\n    \"2093-05-01 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-26 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-05-01 00:00:00\",\n    \"2094-12-25 00:00:00\",\n    \"2094-12-26 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-05-01 00:00:00\",\n    \"2095-12-25 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2096-01-01 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-16 00:00:00\",\n    \"2096-05-01 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-01 00:00:00\",\n    \"2097-05-01 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-04-21 00:00:00\",\n    \"2098-05-01 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-13 00:00:00\",\n    \"2099-05-01 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-26 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-03-29 00:00:00\",\n    \"2100-05-01 00:00:00\",\n    \"2100-12-25 00:00:00\",\n    \"2100-12-26 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-04-18 00:00:00\",\n    \"2101-05-01 00:00:00\",\n    \"2101-12-25 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2102-01-01 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-10 00:00:00\",\n    \"2102-05-01 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-03-26 00:00:00\",\n    \"2103-05-01 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-05-01 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-06 00:00:00\",\n    \"2105-05-01 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-26 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-04-19 00:00:00\",\n    \"2106-05-01 00:00:00\",\n    \"2106-12-25 00:00:00\",\n    \"2106-12-26 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-11 00:00:00\",\n    \"2107-05-01 00:00:00\",\n    \"2107-12-25 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2108-01-01 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-02 00:00:00\",\n    \"2108-05-01 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-04-22 00:00:00\",\n    \"2109-05-01 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-07 00:00:00\",\n    \"2110-05-01 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-03-30 00:00:00\",\n    \"2111-05-01 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-26 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-04-18 00:00:00\",\n    \"2112-05-01 00:00:00\",\n    \"2112-12-25 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2113-01-01 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-03 00:00:00\",\n    \"2113-05-01 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-04-23 00:00:00\",\n    \"2114-05-01 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-15 00:00:00\",\n    \"2115-05-01 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-03-30 00:00:00\",\n    \"2116-05-01 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-26 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-04-19 00:00:00\",\n    \"2117-05-01 00:00:00\",\n    \"2117-12-25 00:00:00\",\n    \"2117-12-26 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-11 00:00:00\",\n    \"2118-05-01 00:00:00\",\n    \"2118-12-25 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2119-01-01 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-03-27 00:00:00\",\n    \"2119-05-01 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-15 00:00:00\",\n    \"2120-05-01 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-07 00:00:00\",\n    \"2121-05-01 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-03-30 00:00:00\",\n    \"2122-05-01 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-26 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-12 00:00:00\",\n    \"2123-05-01 00:00:00\",\n    \"2123-12-25 00:00:00\",\n    \"2123-12-26 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-03 00:00:00\",\n    \"2124-05-01 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-04-23 00:00:00\",\n    \"2125-05-01 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-15 00:00:00\",\n    \"2126-05-01 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-03-31 00:00:00\",\n    \"2127-05-01 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-04-19 00:00:00\",\n    \"2128-05-01 00:00:00\",\n    \"2128-12-25 00:00:00\",\n    \"2128-12-26 00:00:00\",\n    \"2129-01-01 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-11 00:00:00\",\n    \"2129-05-01 00:00:00\",\n    \"2129-12-25 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2130-01-01 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-03-27 00:00:00\",\n    \"2130-05-01 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-16 00:00:00\",\n    \"2131-05-01 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-07 00:00:00\",\n    \"2132-05-01 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-04-20 00:00:00\",\n    \"2133-05-01 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-26 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-12 00:00:00\",\n    \"2134-05-01 00:00:00\",\n    \"2134-12-25 00:00:00\",\n    \"2134-12-26 00:00:00\",\n    \"2135-01-01 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-04 00:00:00\",\n    \"2135-05-01 00:00:00\",\n    \"2135-12-25 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2136-01-01 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-04-23 00:00:00\",\n    \"2136-05-01 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-08 00:00:00\",\n    \"2137-05-01 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-03-31 00:00:00\",\n    \"2138-05-01 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-04-20 00:00:00\",\n    \"2139-05-01 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-26 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-04 00:00:00\",\n    \"2140-05-01 00:00:00\",\n    \"2140-12-25 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2141-01-01 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-03-27 00:00:00\",\n    \"2141-05-01 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-16 00:00:00\",\n    \"2142-05-01 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-01 00:00:00\",\n    \"2143-05-01 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-04-20 00:00:00\",\n    \"2144-05-01 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-26 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-12 00:00:00\",\n    \"2145-05-01 00:00:00\",\n    \"2145-12-25 00:00:00\",\n    \"2145-12-26 00:00:00\",\n    \"2146-01-01 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-04 00:00:00\",\n    \"2146-05-01 00:00:00\",\n    \"2146-12-25 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2147-01-01 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-17 00:00:00\",\n    \"2147-05-01 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-08 00:00:00\",\n    \"2148-05-01 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-03-31 00:00:00\",\n    \"2149-05-01 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-13 00:00:00\",\n    \"2150-05-01 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-26 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-05 00:00:00\",\n    \"2151-05-01 00:00:00\",\n    \"2151-12-25 00:00:00\",\n    \"2151-12-26 00:00:00\",\n    \"2152-01-01 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-04-24 00:00:00\",\n    \"2152-05-01 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-16 00:00:00\",\n    \"2153-05-01 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-01 00:00:00\",\n    \"2154-05-01 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-04-21 00:00:00\",\n    \"2155-05-01 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-12 00:00:00\",\n    \"2156-05-01 00:00:00\",\n    \"2156-12-25 00:00:00\",\n    \"2156-12-26 00:00:00\",\n    \"2157-01-01 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-03-28 00:00:00\",\n    \"2157-05-01 00:00:00\",\n    \"2157-12-25 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2158-01-01 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-17 00:00:00\",\n    \"2158-05-01 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-09 00:00:00\",\n    \"2159-05-01 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-03-24 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-13 00:00:00\",\n    \"2161-05-01 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-26 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-05 00:00:00\",\n    \"2162-05-01 00:00:00\",\n    \"2162-12-25 00:00:00\",\n    \"2162-12-26 00:00:00\",\n    \"2163-01-01 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-05-01 00:00:00\",\n    \"2163-12-25 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2164-01-01 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-09 00:00:00\",\n    \"2164-05-01 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-01 00:00:00\",\n    \"2165-05-01 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-04-21 00:00:00\",\n    \"2166-05-01 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-06 00:00:00\",\n    \"2167-05-01 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-26 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-03-28 00:00:00\",\n    \"2168-05-01 00:00:00\",\n    \"2168-12-25 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2169-01-01 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-17 00:00:00\",\n    \"2169-05-01 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-02 00:00:00\",\n    \"2170-05-01 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-04-22 00:00:00\",\n    \"2171-05-01 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-13 00:00:00\",\n    \"2172-05-01 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-26 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-05 00:00:00\",\n    \"2173-05-01 00:00:00\",\n    \"2173-12-25 00:00:00\",\n    \"2173-12-26 00:00:00\",\n    \"2174-01-01 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-04-18 00:00:00\",\n    \"2174-05-01 00:00:00\",\n    \"2174-12-25 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2175-01-01 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-10 00:00:00\",\n    \"2175-05-01 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-01 00:00:00\",\n    \"2176-05-01 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-04-21 00:00:00\",\n    \"2177-05-01 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-06 00:00:00\",\n    \"2178-05-01 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-26 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-03-29 00:00:00\",\n    \"2179-05-01 00:00:00\",\n    \"2179-12-25 00:00:00\",\n    \"2179-12-26 00:00:00\",\n    \"2180-01-01 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-17 00:00:00\",\n    \"2180-05-01 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-02 00:00:00\",\n    \"2181-05-01 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-04-22 00:00:00\",\n    \"2182-05-01 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-05-01 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-03-29 00:00:00\",\n    \"2184-05-01 00:00:00\",\n    \"2184-12-25 00:00:00\",\n    \"2184-12-26 00:00:00\",\n    \"2185-01-01 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-04-18 00:00:00\",\n    \"2185-05-01 00:00:00\",\n    \"2185-12-25 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2186-01-01 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-10 00:00:00\",\n    \"2186-05-01 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-03-26 00:00:00\",\n    \"2187-05-01 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-05-01 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-06 00:00:00\",\n    \"2189-05-01 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-26 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-05-01 00:00:00\",\n    \"2190-12-25 00:00:00\",\n    \"2190-12-26 00:00:00\",\n    \"2191-01-01 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-11 00:00:00\",\n    \"2191-05-01 00:00:00\",\n    \"2191-12-25 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2192-01-01 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-02 00:00:00\",\n    \"2192-05-01 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-04-22 00:00:00\",\n    \"2193-05-01 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-07 00:00:00\",\n    \"2194-05-01 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-03-30 00:00:00\",\n    \"2195-05-01 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-26 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-04-18 00:00:00\",\n    \"2196-05-01 00:00:00\",\n    \"2196-12-25 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2197-01-01 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-10 00:00:00\",\n    \"2197-05-01 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-03-26 00:00:00\",\n    \"2198-05-01 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-15 00:00:00\",\n    \"2199-05-01 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-07 00:00:00\",\n    \"2200-05-01 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/tgt_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pandas as pd\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, Day, Easter\n\nRULES = [\n    Holiday(\"New Year's Day\", month=1, day=1),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Easter Monday\", month=1, day=1, offset=[Easter(), Day(1)]),\n    Holiday(\"EU Labour Day\", month=5, day=1),\n    Holiday(\"Christmas Day\", month=12, day=25),\n    Holiday(\"Boxing Day\", month=12, day=26),\n]\n\nCALENDAR = CustomBusinessDay(\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/tro.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Toronto business day calendar, aligned with CORRA publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1: Sat,Sun->Mon (New Year)\",\n//     \"Feb 3rd Mon (Family)\",\n//     \"Fri before Easter (Easter Friday)\",\n//     \"Mon before May 24 (Victoria)\",\n//     \"Jul 1: Sat,Sun->Mon (National)\",\n//     \"Aug 1st Mon (Civic)\",\n//     \"Sep 1st Mon (Labour)\",\n//     \"Sep 30 (Truth and Reconciliation)\",\n//     \"Oct 2nd Mon (Thanksgiving)\",\n//     \"Nov 11: Sat,Sun->Mon (Remembrance)\",\n//     \"Dec 25: Sat,Sun->Mon (Christmas)\",\n//     \"Dec 26: Sat->Mon,Sun->Tue (Boxing Day)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-05-18 00:00:00\",\n    \"1970-07-01 00:00:00\",\n    \"1970-08-03 00:00:00\",\n    \"1970-09-07 00:00:00\",\n    \"1970-10-12 00:00:00\",\n    \"1970-11-11 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-28 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-05-24 00:00:00\",\n    \"1971-07-01 00:00:00\",\n    \"1971-08-02 00:00:00\",\n    \"1971-09-06 00:00:00\",\n    \"1971-10-11 00:00:00\",\n    \"1971-11-11 00:00:00\",\n    \"1971-12-27 00:00:00\",\n    \"1971-12-28 00:00:00\",\n    \"1972-01-03 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-05-22 00:00:00\",\n    \"1972-07-03 00:00:00\",\n    \"1972-08-07 00:00:00\",\n    \"1972-09-04 00:00:00\",\n    \"1972-10-09 00:00:00\",\n    \"1972-11-13 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-05-21 00:00:00\",\n    \"1973-07-02 00:00:00\",\n    \"1973-08-06 00:00:00\",\n    \"1973-09-03 00:00:00\",\n    \"1973-10-08 00:00:00\",\n    \"1973-11-12 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-05-20 00:00:00\",\n    \"1974-07-01 00:00:00\",\n    \"1974-08-05 00:00:00\",\n    \"1974-09-02 00:00:00\",\n    \"1974-10-14 00:00:00\",\n    \"1974-11-11 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-05-19 00:00:00\",\n    \"1975-07-01 00:00:00\",\n    \"1975-08-04 00:00:00\",\n    \"1975-09-01 00:00:00\",\n    \"1975-10-13 00:00:00\",\n    \"1975-11-11 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-05-24 00:00:00\",\n    \"1976-07-01 00:00:00\",\n    \"1976-08-02 00:00:00\",\n    \"1976-09-06 00:00:00\",\n    \"1976-10-11 00:00:00\",\n    \"1976-11-11 00:00:00\",\n    \"1976-12-27 00:00:00\",\n    \"1976-12-28 00:00:00\",\n    \"1977-01-03 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-05-23 00:00:00\",\n    \"1977-07-01 00:00:00\",\n    \"1977-08-01 00:00:00\",\n    \"1977-09-05 00:00:00\",\n    \"1977-10-10 00:00:00\",\n    \"1977-11-11 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1977-12-27 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-05-22 00:00:00\",\n    \"1978-07-03 00:00:00\",\n    \"1978-08-07 00:00:00\",\n    \"1978-09-04 00:00:00\",\n    \"1978-10-09 00:00:00\",\n    \"1978-11-13 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-05-21 00:00:00\",\n    \"1979-07-02 00:00:00\",\n    \"1979-08-06 00:00:00\",\n    \"1979-09-03 00:00:00\",\n    \"1979-10-08 00:00:00\",\n    \"1979-11-12 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-05-19 00:00:00\",\n    \"1980-07-01 00:00:00\",\n    \"1980-08-04 00:00:00\",\n    \"1980-09-01 00:00:00\",\n    \"1980-10-13 00:00:00\",\n    \"1980-11-11 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-05-18 00:00:00\",\n    \"1981-07-01 00:00:00\",\n    \"1981-08-03 00:00:00\",\n    \"1981-09-07 00:00:00\",\n    \"1981-10-12 00:00:00\",\n    \"1981-11-11 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-28 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-05-24 00:00:00\",\n    \"1982-07-01 00:00:00\",\n    \"1982-08-02 00:00:00\",\n    \"1982-09-06 00:00:00\",\n    \"1982-10-11 00:00:00\",\n    \"1982-11-11 00:00:00\",\n    \"1982-12-27 00:00:00\",\n    \"1982-12-28 00:00:00\",\n    \"1983-01-03 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-05-23 00:00:00\",\n    \"1983-07-01 00:00:00\",\n    \"1983-08-01 00:00:00\",\n    \"1983-09-05 00:00:00\",\n    \"1983-10-10 00:00:00\",\n    \"1983-11-11 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1983-12-27 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-05-21 00:00:00\",\n    \"1984-07-02 00:00:00\",\n    \"1984-08-06 00:00:00\",\n    \"1984-09-03 00:00:00\",\n    \"1984-10-08 00:00:00\",\n    \"1984-11-12 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-05-20 00:00:00\",\n    \"1985-07-01 00:00:00\",\n    \"1985-08-05 00:00:00\",\n    \"1985-09-02 00:00:00\",\n    \"1985-10-14 00:00:00\",\n    \"1985-11-11 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-05-19 00:00:00\",\n    \"1986-07-01 00:00:00\",\n    \"1986-08-04 00:00:00\",\n    \"1986-09-01 00:00:00\",\n    \"1986-10-13 00:00:00\",\n    \"1986-11-11 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-05-18 00:00:00\",\n    \"1987-07-01 00:00:00\",\n    \"1987-08-03 00:00:00\",\n    \"1987-09-07 00:00:00\",\n    \"1987-10-12 00:00:00\",\n    \"1987-11-11 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-28 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-05-23 00:00:00\",\n    \"1988-07-01 00:00:00\",\n    \"1988-08-01 00:00:00\",\n    \"1988-09-05 00:00:00\",\n    \"1988-10-10 00:00:00\",\n    \"1988-11-11 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1988-12-27 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-05-22 00:00:00\",\n    \"1989-07-03 00:00:00\",\n    \"1989-08-07 00:00:00\",\n    \"1989-09-04 00:00:00\",\n    \"1989-10-09 00:00:00\",\n    \"1989-11-13 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-05-21 00:00:00\",\n    \"1990-07-02 00:00:00\",\n    \"1990-08-06 00:00:00\",\n    \"1990-09-03 00:00:00\",\n    \"1990-10-08 00:00:00\",\n    \"1990-11-12 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-05-20 00:00:00\",\n    \"1991-07-01 00:00:00\",\n    \"1991-08-05 00:00:00\",\n    \"1991-09-02 00:00:00\",\n    \"1991-10-14 00:00:00\",\n    \"1991-11-11 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-05-18 00:00:00\",\n    \"1992-07-01 00:00:00\",\n    \"1992-08-03 00:00:00\",\n    \"1992-09-07 00:00:00\",\n    \"1992-10-12 00:00:00\",\n    \"1992-11-11 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-28 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-05-24 00:00:00\",\n    \"1993-07-01 00:00:00\",\n    \"1993-08-02 00:00:00\",\n    \"1993-09-06 00:00:00\",\n    \"1993-10-11 00:00:00\",\n    \"1993-11-11 00:00:00\",\n    \"1993-12-27 00:00:00\",\n    \"1993-12-28 00:00:00\",\n    \"1994-01-03 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-05-23 00:00:00\",\n    \"1994-07-01 00:00:00\",\n    \"1994-08-01 00:00:00\",\n    \"1994-09-05 00:00:00\",\n    \"1994-10-10 00:00:00\",\n    \"1994-11-11 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1994-12-27 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-05-22 00:00:00\",\n    \"1995-07-03 00:00:00\",\n    \"1995-08-07 00:00:00\",\n    \"1995-09-04 00:00:00\",\n    \"1995-10-09 00:00:00\",\n    \"1995-11-13 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-05-20 00:00:00\",\n    \"1996-07-01 00:00:00\",\n    \"1996-08-05 00:00:00\",\n    \"1996-09-02 00:00:00\",\n    \"1996-10-14 00:00:00\",\n    \"1996-11-11 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-05-19 00:00:00\",\n    \"1997-07-01 00:00:00\",\n    \"1997-08-04 00:00:00\",\n    \"1997-08-13 00:00:00\",\n    \"1997-08-14 00:00:00\",\n    \"1997-08-15 00:00:00\",\n    \"1997-08-29 00:00:00\",\n    \"1997-09-01 00:00:00\",\n    \"1997-10-13 00:00:00\",\n    \"1997-11-11 00:00:00\",\n    \"1997-12-22 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-04-09 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-29 00:00:00\",\n    \"1998-05-18 00:00:00\",\n    \"1998-07-01 00:00:00\",\n    \"1998-08-03 00:00:00\",\n    \"1998-09-07 00:00:00\",\n    \"1998-10-12 00:00:00\",\n    \"1998-11-11 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-28 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-05-24 00:00:00\",\n    \"1999-07-01 00:00:00\",\n    \"1999-08-02 00:00:00\",\n    \"1999-09-06 00:00:00\",\n    \"1999-10-11 00:00:00\",\n    \"1999-11-11 00:00:00\",\n    \"1999-12-27 00:00:00\",\n    \"1999-12-28 00:00:00\",\n    \"2000-01-03 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-05-22 00:00:00\",\n    \"2000-07-03 00:00:00\",\n    \"2000-08-07 00:00:00\",\n    \"2000-09-04 00:00:00\",\n    \"2000-10-09 00:00:00\",\n    \"2000-11-13 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-05-21 00:00:00\",\n    \"2001-07-02 00:00:00\",\n    \"2001-08-06 00:00:00\",\n    \"2001-09-03 00:00:00\",\n    \"2001-10-08 00:00:00\",\n    \"2001-11-12 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-05-20 00:00:00\",\n    \"2002-07-01 00:00:00\",\n    \"2002-08-05 00:00:00\",\n    \"2002-09-02 00:00:00\",\n    \"2002-10-14 00:00:00\",\n    \"2002-11-11 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-05-19 00:00:00\",\n    \"2003-07-01 00:00:00\",\n    \"2003-08-04 00:00:00\",\n    \"2003-09-01 00:00:00\",\n    \"2003-10-13 00:00:00\",\n    \"2003-11-11 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-05-24 00:00:00\",\n    \"2004-07-01 00:00:00\",\n    \"2004-08-02 00:00:00\",\n    \"2004-09-06 00:00:00\",\n    \"2004-10-11 00:00:00\",\n    \"2004-11-11 00:00:00\",\n    \"2004-12-27 00:00:00\",\n    \"2004-12-28 00:00:00\",\n    \"2005-01-03 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-05-23 00:00:00\",\n    \"2005-07-01 00:00:00\",\n    \"2005-08-01 00:00:00\",\n    \"2005-09-05 00:00:00\",\n    \"2005-10-10 00:00:00\",\n    \"2005-11-11 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2005-12-27 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-05-22 00:00:00\",\n    \"2006-07-03 00:00:00\",\n    \"2006-08-07 00:00:00\",\n    \"2006-09-04 00:00:00\",\n    \"2006-10-09 00:00:00\",\n    \"2006-11-13 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-05-21 00:00:00\",\n    \"2007-07-02 00:00:00\",\n    \"2007-08-06 00:00:00\",\n    \"2007-09-03 00:00:00\",\n    \"2007-10-08 00:00:00\",\n    \"2007-11-12 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-02-18 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-05-19 00:00:00\",\n    \"2008-07-01 00:00:00\",\n    \"2008-08-04 00:00:00\",\n    \"2008-09-01 00:00:00\",\n    \"2008-10-13 00:00:00\",\n    \"2008-11-11 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-02-16 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-05-18 00:00:00\",\n    \"2009-07-01 00:00:00\",\n    \"2009-08-03 00:00:00\",\n    \"2009-09-07 00:00:00\",\n    \"2009-10-12 00:00:00\",\n    \"2009-11-11 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-28 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-02-15 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-05-24 00:00:00\",\n    \"2010-07-01 00:00:00\",\n    \"2010-08-02 00:00:00\",\n    \"2010-09-06 00:00:00\",\n    \"2010-10-11 00:00:00\",\n    \"2010-11-11 00:00:00\",\n    \"2010-12-27 00:00:00\",\n    \"2010-12-28 00:00:00\",\n    \"2011-01-03 00:00:00\",\n    \"2011-02-21 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-05-23 00:00:00\",\n    \"2011-07-01 00:00:00\",\n    \"2011-08-01 00:00:00\",\n    \"2011-09-05 00:00:00\",\n    \"2011-10-10 00:00:00\",\n    \"2011-11-11 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2011-12-27 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-02-20 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-05-21 00:00:00\",\n    \"2012-07-02 00:00:00\",\n    \"2012-08-06 00:00:00\",\n    \"2012-09-03 00:00:00\",\n    \"2012-10-08 00:00:00\",\n    \"2012-11-12 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-02-18 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-05-20 00:00:00\",\n    \"2013-07-01 00:00:00\",\n    \"2013-08-05 00:00:00\",\n    \"2013-09-02 00:00:00\",\n    \"2013-10-14 00:00:00\",\n    \"2013-11-11 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-02-17 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-05-19 00:00:00\",\n    \"2014-07-01 00:00:00\",\n    \"2014-08-04 00:00:00\",\n    \"2014-09-01 00:00:00\",\n    \"2014-10-13 00:00:00\",\n    \"2014-11-11 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-02-16 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-05-18 00:00:00\",\n    \"2015-07-01 00:00:00\",\n    \"2015-08-03 00:00:00\",\n    \"2015-09-07 00:00:00\",\n    \"2015-10-12 00:00:00\",\n    \"2015-11-11 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-28 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-02-15 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-05-23 00:00:00\",\n    \"2016-07-01 00:00:00\",\n    \"2016-08-01 00:00:00\",\n    \"2016-09-05 00:00:00\",\n    \"2016-10-10 00:00:00\",\n    \"2016-11-11 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2016-12-27 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-02-20 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-05-22 00:00:00\",\n    \"2017-07-03 00:00:00\",\n    \"2017-08-07 00:00:00\",\n    \"2017-09-04 00:00:00\",\n    \"2017-10-09 00:00:00\",\n    \"2017-11-13 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-02-19 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-05-21 00:00:00\",\n    \"2018-07-02 00:00:00\",\n    \"2018-08-06 00:00:00\",\n    \"2018-09-03 00:00:00\",\n    \"2018-10-08 00:00:00\",\n    \"2018-11-12 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-02-18 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-05-20 00:00:00\",\n    \"2019-07-01 00:00:00\",\n    \"2019-08-05 00:00:00\",\n    \"2019-09-02 00:00:00\",\n    \"2019-10-14 00:00:00\",\n    \"2019-11-11 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-02-17 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-05-18 00:00:00\",\n    \"2020-07-01 00:00:00\",\n    \"2020-08-03 00:00:00\",\n    \"2020-09-07 00:00:00\",\n    \"2020-10-12 00:00:00\",\n    \"2020-11-11 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-28 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-02-15 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-05-24 00:00:00\",\n    \"2021-07-01 00:00:00\",\n    \"2021-08-02 00:00:00\",\n    \"2021-09-06 00:00:00\",\n    \"2021-09-30 00:00:00\",\n    \"2021-10-11 00:00:00\",\n    \"2021-11-11 00:00:00\",\n    \"2021-12-27 00:00:00\",\n    \"2021-12-28 00:00:00\",\n    \"2022-01-03 00:00:00\",\n    \"2022-02-21 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-05-23 00:00:00\",\n    \"2022-07-01 00:00:00\",\n    \"2022-08-01 00:00:00\",\n    \"2022-09-05 00:00:00\",\n    \"2022-09-30 00:00:00\",\n    \"2022-10-10 00:00:00\",\n    \"2022-11-11 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2022-12-27 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-02-20 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-05-22 00:00:00\",\n    \"2023-07-03 00:00:00\",\n    \"2023-08-07 00:00:00\",\n    \"2023-09-04 00:00:00\",\n    \"2023-09-30 00:00:00\",\n    \"2023-10-09 00:00:00\",\n    \"2023-11-13 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-02-19 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-05-20 00:00:00\",\n    \"2024-07-01 00:00:00\",\n    \"2024-08-05 00:00:00\",\n    \"2024-09-02 00:00:00\",\n    \"2024-09-30 00:00:00\",\n    \"2024-10-14 00:00:00\",\n    \"2024-11-11 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-02-17 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-05-19 00:00:00\",\n    \"2025-07-01 00:00:00\",\n    \"2025-08-04 00:00:00\",\n    \"2025-09-01 00:00:00\",\n    \"2025-09-30 00:00:00\",\n    \"2025-10-13 00:00:00\",\n    \"2025-11-11 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-02-16 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-05-18 00:00:00\",\n    \"2026-07-01 00:00:00\",\n    \"2026-08-03 00:00:00\",\n    \"2026-09-07 00:00:00\",\n    \"2026-09-30 00:00:00\",\n    \"2026-10-12 00:00:00\",\n    \"2026-11-11 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-28 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-02-15 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-05-24 00:00:00\",\n    \"2027-07-01 00:00:00\",\n    \"2027-08-02 00:00:00\",\n    \"2027-09-06 00:00:00\",\n    \"2027-09-30 00:00:00\",\n    \"2027-10-11 00:00:00\",\n    \"2027-11-11 00:00:00\",\n    \"2027-12-27 00:00:00\",\n    \"2027-12-28 00:00:00\",\n    \"2028-01-03 00:00:00\",\n    \"2028-02-21 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-05-22 00:00:00\",\n    \"2028-07-03 00:00:00\",\n    \"2028-08-07 00:00:00\",\n    \"2028-09-04 00:00:00\",\n    \"2028-09-30 00:00:00\",\n    \"2028-10-09 00:00:00\",\n    \"2028-11-13 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-02-19 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-05-21 00:00:00\",\n    \"2029-07-02 00:00:00\",\n    \"2029-08-06 00:00:00\",\n    \"2029-09-03 00:00:00\",\n    \"2029-09-30 00:00:00\",\n    \"2029-10-08 00:00:00\",\n    \"2029-11-12 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-02-18 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-05-20 00:00:00\",\n    \"2030-07-01 00:00:00\",\n    \"2030-08-05 00:00:00\",\n    \"2030-09-02 00:00:00\",\n    \"2030-09-30 00:00:00\",\n    \"2030-10-14 00:00:00\",\n    \"2030-11-11 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-02-17 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-05-19 00:00:00\",\n    \"2031-07-01 00:00:00\",\n    \"2031-08-04 00:00:00\",\n    \"2031-09-01 00:00:00\",\n    \"2031-09-30 00:00:00\",\n    \"2031-10-13 00:00:00\",\n    \"2031-11-11 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-02-16 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-05-24 00:00:00\",\n    \"2032-07-01 00:00:00\",\n    \"2032-08-02 00:00:00\",\n    \"2032-09-06 00:00:00\",\n    \"2032-09-30 00:00:00\",\n    \"2032-10-11 00:00:00\",\n    \"2032-11-11 00:00:00\",\n    \"2032-12-27 00:00:00\",\n    \"2032-12-28 00:00:00\",\n    \"2033-01-03 00:00:00\",\n    \"2033-02-21 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-05-23 00:00:00\",\n    \"2033-07-01 00:00:00\",\n    \"2033-08-01 00:00:00\",\n    \"2033-09-05 00:00:00\",\n    \"2033-09-30 00:00:00\",\n    \"2033-10-10 00:00:00\",\n    \"2033-11-11 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2033-12-27 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-02-20 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-05-22 00:00:00\",\n    \"2034-07-03 00:00:00\",\n    \"2034-08-07 00:00:00\",\n    \"2034-09-04 00:00:00\",\n    \"2034-09-30 00:00:00\",\n    \"2034-10-09 00:00:00\",\n    \"2034-11-13 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-02-19 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-05-21 00:00:00\",\n    \"2035-07-02 00:00:00\",\n    \"2035-08-06 00:00:00\",\n    \"2035-09-03 00:00:00\",\n    \"2035-09-30 00:00:00\",\n    \"2035-10-08 00:00:00\",\n    \"2035-11-12 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-02-18 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-05-19 00:00:00\",\n    \"2036-07-01 00:00:00\",\n    \"2036-08-04 00:00:00\",\n    \"2036-09-01 00:00:00\",\n    \"2036-09-30 00:00:00\",\n    \"2036-10-13 00:00:00\",\n    \"2036-11-11 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-02-16 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-05-18 00:00:00\",\n    \"2037-07-01 00:00:00\",\n    \"2037-08-03 00:00:00\",\n    \"2037-09-07 00:00:00\",\n    \"2037-09-30 00:00:00\",\n    \"2037-10-12 00:00:00\",\n    \"2037-11-11 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-28 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-02-15 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-05-24 00:00:00\",\n    \"2038-07-01 00:00:00\",\n    \"2038-08-02 00:00:00\",\n    \"2038-09-06 00:00:00\",\n    \"2038-09-30 00:00:00\",\n    \"2038-10-11 00:00:00\",\n    \"2038-11-11 00:00:00\",\n    \"2038-12-27 00:00:00\",\n    \"2038-12-28 00:00:00\",\n    \"2039-01-03 00:00:00\",\n    \"2039-02-21 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-05-23 00:00:00\",\n    \"2039-07-01 00:00:00\",\n    \"2039-08-01 00:00:00\",\n    \"2039-09-05 00:00:00\",\n    \"2039-09-30 00:00:00\",\n    \"2039-10-10 00:00:00\",\n    \"2039-11-11 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2039-12-27 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-02-20 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-05-21 00:00:00\",\n    \"2040-07-02 00:00:00\",\n    \"2040-08-06 00:00:00\",\n    \"2040-09-03 00:00:00\",\n    \"2040-09-30 00:00:00\",\n    \"2040-10-08 00:00:00\",\n    \"2040-11-12 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-02-18 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-05-20 00:00:00\",\n    \"2041-07-01 00:00:00\",\n    \"2041-08-05 00:00:00\",\n    \"2041-09-02 00:00:00\",\n    \"2041-09-30 00:00:00\",\n    \"2041-10-14 00:00:00\",\n    \"2041-11-11 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-02-17 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-05-19 00:00:00\",\n    \"2042-07-01 00:00:00\",\n    \"2042-08-04 00:00:00\",\n    \"2042-09-01 00:00:00\",\n    \"2042-09-30 00:00:00\",\n    \"2042-10-13 00:00:00\",\n    \"2042-11-11 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-02-16 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-05-18 00:00:00\",\n    \"2043-07-01 00:00:00\",\n    \"2043-08-03 00:00:00\",\n    \"2043-09-07 00:00:00\",\n    \"2043-09-30 00:00:00\",\n    \"2043-10-12 00:00:00\",\n    \"2043-11-11 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-28 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-02-15 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-05-23 00:00:00\",\n    \"2044-07-01 00:00:00\",\n    \"2044-08-01 00:00:00\",\n    \"2044-09-05 00:00:00\",\n    \"2044-09-30 00:00:00\",\n    \"2044-10-10 00:00:00\",\n    \"2044-11-11 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2044-12-27 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-02-20 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-05-22 00:00:00\",\n    \"2045-07-03 00:00:00\",\n    \"2045-08-07 00:00:00\",\n    \"2045-09-04 00:00:00\",\n    \"2045-09-30 00:00:00\",\n    \"2045-10-09 00:00:00\",\n    \"2045-11-13 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-02-19 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-05-21 00:00:00\",\n    \"2046-07-02 00:00:00\",\n    \"2046-08-06 00:00:00\",\n    \"2046-09-03 00:00:00\",\n    \"2046-09-30 00:00:00\",\n    \"2046-10-08 00:00:00\",\n    \"2046-11-12 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-02-18 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-05-20 00:00:00\",\n    \"2047-07-01 00:00:00\",\n    \"2047-08-05 00:00:00\",\n    \"2047-09-02 00:00:00\",\n    \"2047-09-30 00:00:00\",\n    \"2047-10-14 00:00:00\",\n    \"2047-11-11 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-02-17 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-05-18 00:00:00\",\n    \"2048-07-01 00:00:00\",\n    \"2048-08-03 00:00:00\",\n    \"2048-09-07 00:00:00\",\n    \"2048-09-30 00:00:00\",\n    \"2048-10-12 00:00:00\",\n    \"2048-11-11 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-28 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-02-15 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-05-24 00:00:00\",\n    \"2049-07-01 00:00:00\",\n    \"2049-08-02 00:00:00\",\n    \"2049-09-06 00:00:00\",\n    \"2049-09-30 00:00:00\",\n    \"2049-10-11 00:00:00\",\n    \"2049-11-11 00:00:00\",\n    \"2049-12-27 00:00:00\",\n    \"2049-12-28 00:00:00\",\n    \"2050-01-03 00:00:00\",\n    \"2050-02-21 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-05-23 00:00:00\",\n    \"2050-07-01 00:00:00\",\n    \"2050-08-01 00:00:00\",\n    \"2050-09-05 00:00:00\",\n    \"2050-09-30 00:00:00\",\n    \"2050-10-10 00:00:00\",\n    \"2050-11-11 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2050-12-27 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-02-20 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-05-22 00:00:00\",\n    \"2051-07-03 00:00:00\",\n    \"2051-08-07 00:00:00\",\n    \"2051-09-04 00:00:00\",\n    \"2051-09-30 00:00:00\",\n    \"2051-10-09 00:00:00\",\n    \"2051-11-13 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-02-19 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-05-20 00:00:00\",\n    \"2052-07-01 00:00:00\",\n    \"2052-08-05 00:00:00\",\n    \"2052-09-02 00:00:00\",\n    \"2052-09-30 00:00:00\",\n    \"2052-10-14 00:00:00\",\n    \"2052-11-11 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-02-17 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-05-19 00:00:00\",\n    \"2053-07-01 00:00:00\",\n    \"2053-08-04 00:00:00\",\n    \"2053-09-01 00:00:00\",\n    \"2053-09-30 00:00:00\",\n    \"2053-10-13 00:00:00\",\n    \"2053-11-11 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-02-16 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-05-18 00:00:00\",\n    \"2054-07-01 00:00:00\",\n    \"2054-08-03 00:00:00\",\n    \"2054-09-07 00:00:00\",\n    \"2054-09-30 00:00:00\",\n    \"2054-10-12 00:00:00\",\n    \"2054-11-11 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-28 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-02-15 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-05-24 00:00:00\",\n    \"2055-07-01 00:00:00\",\n    \"2055-08-02 00:00:00\",\n    \"2055-09-06 00:00:00\",\n    \"2055-09-30 00:00:00\",\n    \"2055-10-11 00:00:00\",\n    \"2055-11-11 00:00:00\",\n    \"2055-12-27 00:00:00\",\n    \"2055-12-28 00:00:00\",\n    \"2056-01-03 00:00:00\",\n    \"2056-02-21 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-05-22 00:00:00\",\n    \"2056-07-03 00:00:00\",\n    \"2056-08-07 00:00:00\",\n    \"2056-09-04 00:00:00\",\n    \"2056-09-30 00:00:00\",\n    \"2056-10-09 00:00:00\",\n    \"2056-11-13 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-02-19 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-05-21 00:00:00\",\n    \"2057-07-02 00:00:00\",\n    \"2057-08-06 00:00:00\",\n    \"2057-09-03 00:00:00\",\n    \"2057-09-30 00:00:00\",\n    \"2057-10-08 00:00:00\",\n    \"2057-11-12 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-02-18 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-05-20 00:00:00\",\n    \"2058-07-01 00:00:00\",\n    \"2058-08-05 00:00:00\",\n    \"2058-09-02 00:00:00\",\n    \"2058-09-30 00:00:00\",\n    \"2058-10-14 00:00:00\",\n    \"2058-11-11 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-02-17 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-05-19 00:00:00\",\n    \"2059-07-01 00:00:00\",\n    \"2059-08-04 00:00:00\",\n    \"2059-09-01 00:00:00\",\n    \"2059-09-30 00:00:00\",\n    \"2059-10-13 00:00:00\",\n    \"2059-11-11 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-02-16 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-05-24 00:00:00\",\n    \"2060-07-01 00:00:00\",\n    \"2060-08-02 00:00:00\",\n    \"2060-09-06 00:00:00\",\n    \"2060-09-30 00:00:00\",\n    \"2060-10-11 00:00:00\",\n    \"2060-11-11 00:00:00\",\n    \"2060-12-27 00:00:00\",\n    \"2060-12-28 00:00:00\",\n    \"2061-01-03 00:00:00\",\n    \"2061-02-21 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-05-23 00:00:00\",\n    \"2061-07-01 00:00:00\",\n    \"2061-08-01 00:00:00\",\n    \"2061-09-05 00:00:00\",\n    \"2061-09-30 00:00:00\",\n    \"2061-10-10 00:00:00\",\n    \"2061-11-11 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2061-12-27 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-02-20 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-05-22 00:00:00\",\n    \"2062-07-03 00:00:00\",\n    \"2062-08-07 00:00:00\",\n    \"2062-09-04 00:00:00\",\n    \"2062-09-30 00:00:00\",\n    \"2062-10-09 00:00:00\",\n    \"2062-11-13 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-02-19 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-05-21 00:00:00\",\n    \"2063-07-02 00:00:00\",\n    \"2063-08-06 00:00:00\",\n    \"2063-09-03 00:00:00\",\n    \"2063-09-30 00:00:00\",\n    \"2063-10-08 00:00:00\",\n    \"2063-11-12 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-02-18 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-05-19 00:00:00\",\n    \"2064-07-01 00:00:00\",\n    \"2064-08-04 00:00:00\",\n    \"2064-09-01 00:00:00\",\n    \"2064-09-30 00:00:00\",\n    \"2064-10-13 00:00:00\",\n    \"2064-11-11 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-02-16 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-05-18 00:00:00\",\n    \"2065-07-01 00:00:00\",\n    \"2065-08-03 00:00:00\",\n    \"2065-09-07 00:00:00\",\n    \"2065-09-30 00:00:00\",\n    \"2065-10-12 00:00:00\",\n    \"2065-11-11 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-28 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-02-15 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-05-24 00:00:00\",\n    \"2066-07-01 00:00:00\",\n    \"2066-08-02 00:00:00\",\n    \"2066-09-06 00:00:00\",\n    \"2066-09-30 00:00:00\",\n    \"2066-10-11 00:00:00\",\n    \"2066-11-11 00:00:00\",\n    \"2066-12-27 00:00:00\",\n    \"2066-12-28 00:00:00\",\n    \"2067-01-03 00:00:00\",\n    \"2067-02-21 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-05-23 00:00:00\",\n    \"2067-07-01 00:00:00\",\n    \"2067-08-01 00:00:00\",\n    \"2067-09-05 00:00:00\",\n    \"2067-09-30 00:00:00\",\n    \"2067-10-10 00:00:00\",\n    \"2067-11-11 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2067-12-27 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-02-20 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-05-21 00:00:00\",\n    \"2068-07-02 00:00:00\",\n    \"2068-08-06 00:00:00\",\n    \"2068-09-03 00:00:00\",\n    \"2068-09-30 00:00:00\",\n    \"2068-10-08 00:00:00\",\n    \"2068-11-12 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-02-18 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-05-20 00:00:00\",\n    \"2069-07-01 00:00:00\",\n    \"2069-08-05 00:00:00\",\n    \"2069-09-02 00:00:00\",\n    \"2069-09-30 00:00:00\",\n    \"2069-10-14 00:00:00\",\n    \"2069-11-11 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-02-17 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-05-19 00:00:00\",\n    \"2070-07-01 00:00:00\",\n    \"2070-08-04 00:00:00\",\n    \"2070-09-01 00:00:00\",\n    \"2070-09-30 00:00:00\",\n    \"2070-10-13 00:00:00\",\n    \"2070-11-11 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-02-16 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-05-18 00:00:00\",\n    \"2071-07-01 00:00:00\",\n    \"2071-08-03 00:00:00\",\n    \"2071-09-07 00:00:00\",\n    \"2071-09-30 00:00:00\",\n    \"2071-10-12 00:00:00\",\n    \"2071-11-11 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-28 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-02-15 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-05-23 00:00:00\",\n    \"2072-07-01 00:00:00\",\n    \"2072-08-01 00:00:00\",\n    \"2072-09-05 00:00:00\",\n    \"2072-09-30 00:00:00\",\n    \"2072-10-10 00:00:00\",\n    \"2072-11-11 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2072-12-27 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-02-20 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-05-22 00:00:00\",\n    \"2073-07-03 00:00:00\",\n    \"2073-08-07 00:00:00\",\n    \"2073-09-04 00:00:00\",\n    \"2073-09-30 00:00:00\",\n    \"2073-10-09 00:00:00\",\n    \"2073-11-13 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-02-19 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-05-21 00:00:00\",\n    \"2074-07-02 00:00:00\",\n    \"2074-08-06 00:00:00\",\n    \"2074-09-03 00:00:00\",\n    \"2074-09-30 00:00:00\",\n    \"2074-10-08 00:00:00\",\n    \"2074-11-12 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-02-18 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-05-20 00:00:00\",\n    \"2075-07-01 00:00:00\",\n    \"2075-08-05 00:00:00\",\n    \"2075-09-02 00:00:00\",\n    \"2075-09-30 00:00:00\",\n    \"2075-10-14 00:00:00\",\n    \"2075-11-11 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-02-17 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-05-18 00:00:00\",\n    \"2076-07-01 00:00:00\",\n    \"2076-08-03 00:00:00\",\n    \"2076-09-07 00:00:00\",\n    \"2076-09-30 00:00:00\",\n    \"2076-10-12 00:00:00\",\n    \"2076-11-11 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-28 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-02-15 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-05-24 00:00:00\",\n    \"2077-07-01 00:00:00\",\n    \"2077-08-02 00:00:00\",\n    \"2077-09-06 00:00:00\",\n    \"2077-09-30 00:00:00\",\n    \"2077-10-11 00:00:00\",\n    \"2077-11-11 00:00:00\",\n    \"2077-12-27 00:00:00\",\n    \"2077-12-28 00:00:00\",\n    \"2078-01-03 00:00:00\",\n    \"2078-02-21 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-05-23 00:00:00\",\n    \"2078-07-01 00:00:00\",\n    \"2078-08-01 00:00:00\",\n    \"2078-09-05 00:00:00\",\n    \"2078-09-30 00:00:00\",\n    \"2078-10-10 00:00:00\",\n    \"2078-11-11 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2078-12-27 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-02-20 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-05-22 00:00:00\",\n    \"2079-07-03 00:00:00\",\n    \"2079-08-07 00:00:00\",\n    \"2079-09-04 00:00:00\",\n    \"2079-09-30 00:00:00\",\n    \"2079-10-09 00:00:00\",\n    \"2079-11-13 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-02-19 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-05-20 00:00:00\",\n    \"2080-07-01 00:00:00\",\n    \"2080-08-05 00:00:00\",\n    \"2080-09-02 00:00:00\",\n    \"2080-09-30 00:00:00\",\n    \"2080-10-14 00:00:00\",\n    \"2080-11-11 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-02-17 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-05-19 00:00:00\",\n    \"2081-07-01 00:00:00\",\n    \"2081-08-04 00:00:00\",\n    \"2081-09-01 00:00:00\",\n    \"2081-09-30 00:00:00\",\n    \"2081-10-13 00:00:00\",\n    \"2081-11-11 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-02-16 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-05-18 00:00:00\",\n    \"2082-07-01 00:00:00\",\n    \"2082-08-03 00:00:00\",\n    \"2082-09-07 00:00:00\",\n    \"2082-09-30 00:00:00\",\n    \"2082-10-12 00:00:00\",\n    \"2082-11-11 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-28 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-02-15 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-05-24 00:00:00\",\n    \"2083-07-01 00:00:00\",\n    \"2083-08-02 00:00:00\",\n    \"2083-09-06 00:00:00\",\n    \"2083-09-30 00:00:00\",\n    \"2083-10-11 00:00:00\",\n    \"2083-11-11 00:00:00\",\n    \"2083-12-27 00:00:00\",\n    \"2083-12-28 00:00:00\",\n    \"2084-01-03 00:00:00\",\n    \"2084-02-21 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-05-22 00:00:00\",\n    \"2084-07-03 00:00:00\",\n    \"2084-08-07 00:00:00\",\n    \"2084-09-04 00:00:00\",\n    \"2084-09-30 00:00:00\",\n    \"2084-10-09 00:00:00\",\n    \"2084-11-13 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-02-19 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-05-21 00:00:00\",\n    \"2085-07-02 00:00:00\",\n    \"2085-08-06 00:00:00\",\n    \"2085-09-03 00:00:00\",\n    \"2085-09-30 00:00:00\",\n    \"2085-10-08 00:00:00\",\n    \"2085-11-12 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-02-18 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-05-20 00:00:00\",\n    \"2086-07-01 00:00:00\",\n    \"2086-08-05 00:00:00\",\n    \"2086-09-02 00:00:00\",\n    \"2086-09-30 00:00:00\",\n    \"2086-10-14 00:00:00\",\n    \"2086-11-11 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-02-17 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-05-19 00:00:00\",\n    \"2087-07-01 00:00:00\",\n    \"2087-08-04 00:00:00\",\n    \"2087-09-01 00:00:00\",\n    \"2087-09-30 00:00:00\",\n    \"2087-10-13 00:00:00\",\n    \"2087-11-11 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-02-16 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-05-24 00:00:00\",\n    \"2088-07-01 00:00:00\",\n    \"2088-08-02 00:00:00\",\n    \"2088-09-06 00:00:00\",\n    \"2088-09-30 00:00:00\",\n    \"2088-10-11 00:00:00\",\n    \"2088-11-11 00:00:00\",\n    \"2088-12-27 00:00:00\",\n    \"2088-12-28 00:00:00\",\n    \"2089-01-03 00:00:00\",\n    \"2089-02-21 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-05-23 00:00:00\",\n    \"2089-07-01 00:00:00\",\n    \"2089-08-01 00:00:00\",\n    \"2089-09-05 00:00:00\",\n    \"2089-09-30 00:00:00\",\n    \"2089-10-10 00:00:00\",\n    \"2089-11-11 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2089-12-27 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-02-20 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-05-22 00:00:00\",\n    \"2090-07-03 00:00:00\",\n    \"2090-08-07 00:00:00\",\n    \"2090-09-04 00:00:00\",\n    \"2090-09-30 00:00:00\",\n    \"2090-10-09 00:00:00\",\n    \"2090-11-13 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-02-19 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-05-21 00:00:00\",\n    \"2091-07-02 00:00:00\",\n    \"2091-08-06 00:00:00\",\n    \"2091-09-03 00:00:00\",\n    \"2091-09-30 00:00:00\",\n    \"2091-10-08 00:00:00\",\n    \"2091-11-12 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-02-18 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-05-19 00:00:00\",\n    \"2092-07-01 00:00:00\",\n    \"2092-08-04 00:00:00\",\n    \"2092-09-01 00:00:00\",\n    \"2092-09-30 00:00:00\",\n    \"2092-10-13 00:00:00\",\n    \"2092-11-11 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-02-16 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-05-18 00:00:00\",\n    \"2093-07-01 00:00:00\",\n    \"2093-08-03 00:00:00\",\n    \"2093-09-07 00:00:00\",\n    \"2093-09-30 00:00:00\",\n    \"2093-10-12 00:00:00\",\n    \"2093-11-11 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-28 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-02-15 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-05-24 00:00:00\",\n    \"2094-07-01 00:00:00\",\n    \"2094-08-02 00:00:00\",\n    \"2094-09-06 00:00:00\",\n    \"2094-09-30 00:00:00\",\n    \"2094-10-11 00:00:00\",\n    \"2094-11-11 00:00:00\",\n    \"2094-12-27 00:00:00\",\n    \"2094-12-28 00:00:00\",\n    \"2095-01-03 00:00:00\",\n    \"2095-02-21 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-05-23 00:00:00\",\n    \"2095-07-01 00:00:00\",\n    \"2095-08-01 00:00:00\",\n    \"2095-09-05 00:00:00\",\n    \"2095-09-30 00:00:00\",\n    \"2095-10-10 00:00:00\",\n    \"2095-11-11 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2095-12-27 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-02-20 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-05-21 00:00:00\",\n    \"2096-07-02 00:00:00\",\n    \"2096-08-06 00:00:00\",\n    \"2096-09-03 00:00:00\",\n    \"2096-09-30 00:00:00\",\n    \"2096-10-08 00:00:00\",\n    \"2096-11-12 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-02-18 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-05-20 00:00:00\",\n    \"2097-07-01 00:00:00\",\n    \"2097-08-05 00:00:00\",\n    \"2097-09-02 00:00:00\",\n    \"2097-09-30 00:00:00\",\n    \"2097-10-14 00:00:00\",\n    \"2097-11-11 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-02-17 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-05-19 00:00:00\",\n    \"2098-07-01 00:00:00\",\n    \"2098-08-04 00:00:00\",\n    \"2098-09-01 00:00:00\",\n    \"2098-09-30 00:00:00\",\n    \"2098-10-13 00:00:00\",\n    \"2098-11-11 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-02-16 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-05-18 00:00:00\",\n    \"2099-07-01 00:00:00\",\n    \"2099-08-03 00:00:00\",\n    \"2099-09-07 00:00:00\",\n    \"2099-09-30 00:00:00\",\n    \"2099-10-12 00:00:00\",\n    \"2099-11-11 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-28 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-02-15 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-05-24 00:00:00\",\n    \"2100-07-01 00:00:00\",\n    \"2100-08-02 00:00:00\",\n    \"2100-09-06 00:00:00\",\n    \"2100-09-30 00:00:00\",\n    \"2100-10-11 00:00:00\",\n    \"2100-11-11 00:00:00\",\n    \"2100-12-27 00:00:00\",\n    \"2100-12-28 00:00:00\",\n    \"2101-01-03 00:00:00\",\n    \"2101-02-21 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-05-23 00:00:00\",\n    \"2101-07-01 00:00:00\",\n    \"2101-08-01 00:00:00\",\n    \"2101-09-05 00:00:00\",\n    \"2101-09-30 00:00:00\",\n    \"2101-10-10 00:00:00\",\n    \"2101-11-11 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2101-12-27 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-02-20 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-05-22 00:00:00\",\n    \"2102-07-03 00:00:00\",\n    \"2102-08-07 00:00:00\",\n    \"2102-09-04 00:00:00\",\n    \"2102-09-30 00:00:00\",\n    \"2102-10-09 00:00:00\",\n    \"2102-11-13 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-02-19 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-05-21 00:00:00\",\n    \"2103-07-02 00:00:00\",\n    \"2103-08-06 00:00:00\",\n    \"2103-09-03 00:00:00\",\n    \"2103-09-30 00:00:00\",\n    \"2103-10-08 00:00:00\",\n    \"2103-11-12 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-02-18 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-05-19 00:00:00\",\n    \"2104-07-01 00:00:00\",\n    \"2104-08-04 00:00:00\",\n    \"2104-09-01 00:00:00\",\n    \"2104-09-30 00:00:00\",\n    \"2104-10-13 00:00:00\",\n    \"2104-11-11 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-02-16 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-05-18 00:00:00\",\n    \"2105-07-01 00:00:00\",\n    \"2105-08-03 00:00:00\",\n    \"2105-09-07 00:00:00\",\n    \"2105-09-30 00:00:00\",\n    \"2105-10-12 00:00:00\",\n    \"2105-11-11 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-28 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-02-15 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-05-24 00:00:00\",\n    \"2106-07-01 00:00:00\",\n    \"2106-08-02 00:00:00\",\n    \"2106-09-06 00:00:00\",\n    \"2106-09-30 00:00:00\",\n    \"2106-10-11 00:00:00\",\n    \"2106-11-11 00:00:00\",\n    \"2106-12-27 00:00:00\",\n    \"2106-12-28 00:00:00\",\n    \"2107-01-03 00:00:00\",\n    \"2107-02-21 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-05-23 00:00:00\",\n    \"2107-07-01 00:00:00\",\n    \"2107-08-01 00:00:00\",\n    \"2107-09-05 00:00:00\",\n    \"2107-09-30 00:00:00\",\n    \"2107-10-10 00:00:00\",\n    \"2107-11-11 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2107-12-27 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-02-20 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-05-21 00:00:00\",\n    \"2108-07-02 00:00:00\",\n    \"2108-08-06 00:00:00\",\n    \"2108-09-03 00:00:00\",\n    \"2108-09-30 00:00:00\",\n    \"2108-10-08 00:00:00\",\n    \"2108-11-12 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-02-18 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-05-20 00:00:00\",\n    \"2109-07-01 00:00:00\",\n    \"2109-08-05 00:00:00\",\n    \"2109-09-02 00:00:00\",\n    \"2109-09-30 00:00:00\",\n    \"2109-10-14 00:00:00\",\n    \"2109-11-11 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-02-17 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-05-19 00:00:00\",\n    \"2110-07-01 00:00:00\",\n    \"2110-08-04 00:00:00\",\n    \"2110-09-01 00:00:00\",\n    \"2110-09-30 00:00:00\",\n    \"2110-10-13 00:00:00\",\n    \"2110-11-11 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-02-16 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-05-18 00:00:00\",\n    \"2111-07-01 00:00:00\",\n    \"2111-08-03 00:00:00\",\n    \"2111-09-07 00:00:00\",\n    \"2111-09-30 00:00:00\",\n    \"2111-10-12 00:00:00\",\n    \"2111-11-11 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-28 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-02-15 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-05-23 00:00:00\",\n    \"2112-07-01 00:00:00\",\n    \"2112-08-01 00:00:00\",\n    \"2112-09-05 00:00:00\",\n    \"2112-09-30 00:00:00\",\n    \"2112-10-10 00:00:00\",\n    \"2112-11-11 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2112-12-27 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-02-20 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-05-22 00:00:00\",\n    \"2113-07-03 00:00:00\",\n    \"2113-08-07 00:00:00\",\n    \"2113-09-04 00:00:00\",\n    \"2113-09-30 00:00:00\",\n    \"2113-10-09 00:00:00\",\n    \"2113-11-13 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-02-19 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-05-21 00:00:00\",\n    \"2114-07-02 00:00:00\",\n    \"2114-08-06 00:00:00\",\n    \"2114-09-03 00:00:00\",\n    \"2114-09-30 00:00:00\",\n    \"2114-10-08 00:00:00\",\n    \"2114-11-12 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-02-18 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-05-20 00:00:00\",\n    \"2115-07-01 00:00:00\",\n    \"2115-08-05 00:00:00\",\n    \"2115-09-02 00:00:00\",\n    \"2115-09-30 00:00:00\",\n    \"2115-10-14 00:00:00\",\n    \"2115-11-11 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-02-17 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-05-18 00:00:00\",\n    \"2116-07-01 00:00:00\",\n    \"2116-08-03 00:00:00\",\n    \"2116-09-07 00:00:00\",\n    \"2116-09-30 00:00:00\",\n    \"2116-10-12 00:00:00\",\n    \"2116-11-11 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-28 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-02-15 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-05-24 00:00:00\",\n    \"2117-07-01 00:00:00\",\n    \"2117-08-02 00:00:00\",\n    \"2117-09-06 00:00:00\",\n    \"2117-09-30 00:00:00\",\n    \"2117-10-11 00:00:00\",\n    \"2117-11-11 00:00:00\",\n    \"2117-12-27 00:00:00\",\n    \"2117-12-28 00:00:00\",\n    \"2118-01-03 00:00:00\",\n    \"2118-02-21 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-05-23 00:00:00\",\n    \"2118-07-01 00:00:00\",\n    \"2118-08-01 00:00:00\",\n    \"2118-09-05 00:00:00\",\n    \"2118-09-30 00:00:00\",\n    \"2118-10-10 00:00:00\",\n    \"2118-11-11 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2118-12-27 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-02-20 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-05-22 00:00:00\",\n    \"2119-07-03 00:00:00\",\n    \"2119-08-07 00:00:00\",\n    \"2119-09-04 00:00:00\",\n    \"2119-09-30 00:00:00\",\n    \"2119-10-09 00:00:00\",\n    \"2119-11-13 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-02-19 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-05-20 00:00:00\",\n    \"2120-07-01 00:00:00\",\n    \"2120-08-05 00:00:00\",\n    \"2120-09-02 00:00:00\",\n    \"2120-09-30 00:00:00\",\n    \"2120-10-14 00:00:00\",\n    \"2120-11-11 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-02-17 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-05-19 00:00:00\",\n    \"2121-07-01 00:00:00\",\n    \"2121-08-04 00:00:00\",\n    \"2121-09-01 00:00:00\",\n    \"2121-09-30 00:00:00\",\n    \"2121-10-13 00:00:00\",\n    \"2121-11-11 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-02-16 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-05-18 00:00:00\",\n    \"2122-07-01 00:00:00\",\n    \"2122-08-03 00:00:00\",\n    \"2122-09-07 00:00:00\",\n    \"2122-09-30 00:00:00\",\n    \"2122-10-12 00:00:00\",\n    \"2122-11-11 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-28 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-02-15 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-05-24 00:00:00\",\n    \"2123-07-01 00:00:00\",\n    \"2123-08-02 00:00:00\",\n    \"2123-09-06 00:00:00\",\n    \"2123-09-30 00:00:00\",\n    \"2123-10-11 00:00:00\",\n    \"2123-11-11 00:00:00\",\n    \"2123-12-27 00:00:00\",\n    \"2123-12-28 00:00:00\",\n    \"2124-01-03 00:00:00\",\n    \"2124-02-21 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-05-22 00:00:00\",\n    \"2124-07-03 00:00:00\",\n    \"2124-08-07 00:00:00\",\n    \"2124-09-04 00:00:00\",\n    \"2124-09-30 00:00:00\",\n    \"2124-10-09 00:00:00\",\n    \"2124-11-13 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-02-19 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-05-21 00:00:00\",\n    \"2125-07-02 00:00:00\",\n    \"2125-08-06 00:00:00\",\n    \"2125-09-03 00:00:00\",\n    \"2125-09-30 00:00:00\",\n    \"2125-10-08 00:00:00\",\n    \"2125-11-12 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-02-18 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-05-20 00:00:00\",\n    \"2126-07-01 00:00:00\",\n    \"2126-08-05 00:00:00\",\n    \"2126-09-02 00:00:00\",\n    \"2126-09-30 00:00:00\",\n    \"2126-10-14 00:00:00\",\n    \"2126-11-11 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-02-17 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-05-19 00:00:00\",\n    \"2127-07-01 00:00:00\",\n    \"2127-08-04 00:00:00\",\n    \"2127-09-01 00:00:00\",\n    \"2127-09-30 00:00:00\",\n    \"2127-10-13 00:00:00\",\n    \"2127-11-11 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-02-16 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-05-24 00:00:00\",\n    \"2128-07-01 00:00:00\",\n    \"2128-08-02 00:00:00\",\n    \"2128-09-06 00:00:00\",\n    \"2128-09-30 00:00:00\",\n    \"2128-10-11 00:00:00\",\n    \"2128-11-11 00:00:00\",\n    \"2128-12-27 00:00:00\",\n    \"2128-12-28 00:00:00\",\n    \"2129-01-03 00:00:00\",\n    \"2129-02-21 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-05-23 00:00:00\",\n    \"2129-07-01 00:00:00\",\n    \"2129-08-01 00:00:00\",\n    \"2129-09-05 00:00:00\",\n    \"2129-09-30 00:00:00\",\n    \"2129-10-10 00:00:00\",\n    \"2129-11-11 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2129-12-27 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-02-20 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-05-22 00:00:00\",\n    \"2130-07-03 00:00:00\",\n    \"2130-08-07 00:00:00\",\n    \"2130-09-04 00:00:00\",\n    \"2130-09-30 00:00:00\",\n    \"2130-10-09 00:00:00\",\n    \"2130-11-13 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-02-19 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-05-21 00:00:00\",\n    \"2131-07-02 00:00:00\",\n    \"2131-08-06 00:00:00\",\n    \"2131-09-03 00:00:00\",\n    \"2131-09-30 00:00:00\",\n    \"2131-10-08 00:00:00\",\n    \"2131-11-12 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-02-18 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-05-19 00:00:00\",\n    \"2132-07-01 00:00:00\",\n    \"2132-08-04 00:00:00\",\n    \"2132-09-01 00:00:00\",\n    \"2132-09-30 00:00:00\",\n    \"2132-10-13 00:00:00\",\n    \"2132-11-11 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-02-16 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-05-18 00:00:00\",\n    \"2133-07-01 00:00:00\",\n    \"2133-08-03 00:00:00\",\n    \"2133-09-07 00:00:00\",\n    \"2133-09-30 00:00:00\",\n    \"2133-10-12 00:00:00\",\n    \"2133-11-11 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-28 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-02-15 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-05-24 00:00:00\",\n    \"2134-07-01 00:00:00\",\n    \"2134-08-02 00:00:00\",\n    \"2134-09-06 00:00:00\",\n    \"2134-09-30 00:00:00\",\n    \"2134-10-11 00:00:00\",\n    \"2134-11-11 00:00:00\",\n    \"2134-12-27 00:00:00\",\n    \"2134-12-28 00:00:00\",\n    \"2135-01-03 00:00:00\",\n    \"2135-02-21 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-05-23 00:00:00\",\n    \"2135-07-01 00:00:00\",\n    \"2135-08-01 00:00:00\",\n    \"2135-09-05 00:00:00\",\n    \"2135-09-30 00:00:00\",\n    \"2135-10-10 00:00:00\",\n    \"2135-11-11 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2135-12-27 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-02-20 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-05-21 00:00:00\",\n    \"2136-07-02 00:00:00\",\n    \"2136-08-06 00:00:00\",\n    \"2136-09-03 00:00:00\",\n    \"2136-09-30 00:00:00\",\n    \"2136-10-08 00:00:00\",\n    \"2136-11-12 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-02-18 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-05-20 00:00:00\",\n    \"2137-07-01 00:00:00\",\n    \"2137-08-05 00:00:00\",\n    \"2137-09-02 00:00:00\",\n    \"2137-09-30 00:00:00\",\n    \"2137-10-14 00:00:00\",\n    \"2137-11-11 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-02-17 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-05-19 00:00:00\",\n    \"2138-07-01 00:00:00\",\n    \"2138-08-04 00:00:00\",\n    \"2138-09-01 00:00:00\",\n    \"2138-09-30 00:00:00\",\n    \"2138-10-13 00:00:00\",\n    \"2138-11-11 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-02-16 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-05-18 00:00:00\",\n    \"2139-07-01 00:00:00\",\n    \"2139-08-03 00:00:00\",\n    \"2139-09-07 00:00:00\",\n    \"2139-09-30 00:00:00\",\n    \"2139-10-12 00:00:00\",\n    \"2139-11-11 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-28 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-02-15 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-05-23 00:00:00\",\n    \"2140-07-01 00:00:00\",\n    \"2140-08-01 00:00:00\",\n    \"2140-09-05 00:00:00\",\n    \"2140-09-30 00:00:00\",\n    \"2140-10-10 00:00:00\",\n    \"2140-11-11 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2140-12-27 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-02-20 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-05-22 00:00:00\",\n    \"2141-07-03 00:00:00\",\n    \"2141-08-07 00:00:00\",\n    \"2141-09-04 00:00:00\",\n    \"2141-09-30 00:00:00\",\n    \"2141-10-09 00:00:00\",\n    \"2141-11-13 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-02-19 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-05-21 00:00:00\",\n    \"2142-07-02 00:00:00\",\n    \"2142-08-06 00:00:00\",\n    \"2142-09-03 00:00:00\",\n    \"2142-09-30 00:00:00\",\n    \"2142-10-08 00:00:00\",\n    \"2142-11-12 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-02-18 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-05-20 00:00:00\",\n    \"2143-07-01 00:00:00\",\n    \"2143-08-05 00:00:00\",\n    \"2143-09-02 00:00:00\",\n    \"2143-09-30 00:00:00\",\n    \"2143-10-14 00:00:00\",\n    \"2143-11-11 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-02-17 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-05-18 00:00:00\",\n    \"2144-07-01 00:00:00\",\n    \"2144-08-03 00:00:00\",\n    \"2144-09-07 00:00:00\",\n    \"2144-09-30 00:00:00\",\n    \"2144-10-12 00:00:00\",\n    \"2144-11-11 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-28 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-02-15 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-05-24 00:00:00\",\n    \"2145-07-01 00:00:00\",\n    \"2145-08-02 00:00:00\",\n    \"2145-09-06 00:00:00\",\n    \"2145-09-30 00:00:00\",\n    \"2145-10-11 00:00:00\",\n    \"2145-11-11 00:00:00\",\n    \"2145-12-27 00:00:00\",\n    \"2145-12-28 00:00:00\",\n    \"2146-01-03 00:00:00\",\n    \"2146-02-21 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-05-23 00:00:00\",\n    \"2146-07-01 00:00:00\",\n    \"2146-08-01 00:00:00\",\n    \"2146-09-05 00:00:00\",\n    \"2146-09-30 00:00:00\",\n    \"2146-10-10 00:00:00\",\n    \"2146-11-11 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2146-12-27 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-02-20 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-05-22 00:00:00\",\n    \"2147-07-03 00:00:00\",\n    \"2147-08-07 00:00:00\",\n    \"2147-09-04 00:00:00\",\n    \"2147-09-30 00:00:00\",\n    \"2147-10-09 00:00:00\",\n    \"2147-11-13 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-02-19 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-05-20 00:00:00\",\n    \"2148-07-01 00:00:00\",\n    \"2148-08-05 00:00:00\",\n    \"2148-09-02 00:00:00\",\n    \"2148-09-30 00:00:00\",\n    \"2148-10-14 00:00:00\",\n    \"2148-11-11 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-02-17 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-05-19 00:00:00\",\n    \"2149-07-01 00:00:00\",\n    \"2149-08-04 00:00:00\",\n    \"2149-09-01 00:00:00\",\n    \"2149-09-30 00:00:00\",\n    \"2149-10-13 00:00:00\",\n    \"2149-11-11 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-02-16 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-05-18 00:00:00\",\n    \"2150-07-01 00:00:00\",\n    \"2150-08-03 00:00:00\",\n    \"2150-09-07 00:00:00\",\n    \"2150-09-30 00:00:00\",\n    \"2150-10-12 00:00:00\",\n    \"2150-11-11 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-28 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-02-15 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-05-24 00:00:00\",\n    \"2151-07-01 00:00:00\",\n    \"2151-08-02 00:00:00\",\n    \"2151-09-06 00:00:00\",\n    \"2151-09-30 00:00:00\",\n    \"2151-10-11 00:00:00\",\n    \"2151-11-11 00:00:00\",\n    \"2151-12-27 00:00:00\",\n    \"2151-12-28 00:00:00\",\n    \"2152-01-03 00:00:00\",\n    \"2152-02-21 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-05-22 00:00:00\",\n    \"2152-07-03 00:00:00\",\n    \"2152-08-07 00:00:00\",\n    \"2152-09-04 00:00:00\",\n    \"2152-09-30 00:00:00\",\n    \"2152-10-09 00:00:00\",\n    \"2152-11-13 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-02-19 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-05-21 00:00:00\",\n    \"2153-07-02 00:00:00\",\n    \"2153-08-06 00:00:00\",\n    \"2153-09-03 00:00:00\",\n    \"2153-09-30 00:00:00\",\n    \"2153-10-08 00:00:00\",\n    \"2153-11-12 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-02-18 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-05-20 00:00:00\",\n    \"2154-07-01 00:00:00\",\n    \"2154-08-05 00:00:00\",\n    \"2154-09-02 00:00:00\",\n    \"2154-09-30 00:00:00\",\n    \"2154-10-14 00:00:00\",\n    \"2154-11-11 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-02-17 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-05-19 00:00:00\",\n    \"2155-07-01 00:00:00\",\n    \"2155-08-04 00:00:00\",\n    \"2155-09-01 00:00:00\",\n    \"2155-09-30 00:00:00\",\n    \"2155-10-13 00:00:00\",\n    \"2155-11-11 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-02-16 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-05-24 00:00:00\",\n    \"2156-07-01 00:00:00\",\n    \"2156-08-02 00:00:00\",\n    \"2156-09-06 00:00:00\",\n    \"2156-09-30 00:00:00\",\n    \"2156-10-11 00:00:00\",\n    \"2156-11-11 00:00:00\",\n    \"2156-12-27 00:00:00\",\n    \"2156-12-28 00:00:00\",\n    \"2157-01-03 00:00:00\",\n    \"2157-02-21 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-05-23 00:00:00\",\n    \"2157-07-01 00:00:00\",\n    \"2157-08-01 00:00:00\",\n    \"2157-09-05 00:00:00\",\n    \"2157-09-30 00:00:00\",\n    \"2157-10-10 00:00:00\",\n    \"2157-11-11 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2157-12-27 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-02-20 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-05-22 00:00:00\",\n    \"2158-07-03 00:00:00\",\n    \"2158-08-07 00:00:00\",\n    \"2158-09-04 00:00:00\",\n    \"2158-09-30 00:00:00\",\n    \"2158-10-09 00:00:00\",\n    \"2158-11-13 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-02-19 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-05-21 00:00:00\",\n    \"2159-07-02 00:00:00\",\n    \"2159-08-06 00:00:00\",\n    \"2159-09-03 00:00:00\",\n    \"2159-09-30 00:00:00\",\n    \"2159-10-08 00:00:00\",\n    \"2159-11-12 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-02-18 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-05-19 00:00:00\",\n    \"2160-07-01 00:00:00\",\n    \"2160-08-04 00:00:00\",\n    \"2160-09-01 00:00:00\",\n    \"2160-09-30 00:00:00\",\n    \"2160-10-13 00:00:00\",\n    \"2160-11-11 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-02-16 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-05-18 00:00:00\",\n    \"2161-07-01 00:00:00\",\n    \"2161-08-03 00:00:00\",\n    \"2161-09-07 00:00:00\",\n    \"2161-09-30 00:00:00\",\n    \"2161-10-12 00:00:00\",\n    \"2161-11-11 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-28 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-02-15 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-05-24 00:00:00\",\n    \"2162-07-01 00:00:00\",\n    \"2162-08-02 00:00:00\",\n    \"2162-09-06 00:00:00\",\n    \"2162-09-30 00:00:00\",\n    \"2162-10-11 00:00:00\",\n    \"2162-11-11 00:00:00\",\n    \"2162-12-27 00:00:00\",\n    \"2162-12-28 00:00:00\",\n    \"2163-01-03 00:00:00\",\n    \"2163-02-21 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-05-23 00:00:00\",\n    \"2163-07-01 00:00:00\",\n    \"2163-08-01 00:00:00\",\n    \"2163-09-05 00:00:00\",\n    \"2163-09-30 00:00:00\",\n    \"2163-10-10 00:00:00\",\n    \"2163-11-11 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2163-12-27 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-02-20 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-05-21 00:00:00\",\n    \"2164-07-02 00:00:00\",\n    \"2164-08-06 00:00:00\",\n    \"2164-09-03 00:00:00\",\n    \"2164-09-30 00:00:00\",\n    \"2164-10-08 00:00:00\",\n    \"2164-11-12 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-02-18 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-05-20 00:00:00\",\n    \"2165-07-01 00:00:00\",\n    \"2165-08-05 00:00:00\",\n    \"2165-09-02 00:00:00\",\n    \"2165-09-30 00:00:00\",\n    \"2165-10-14 00:00:00\",\n    \"2165-11-11 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-02-17 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-05-19 00:00:00\",\n    \"2166-07-01 00:00:00\",\n    \"2166-08-04 00:00:00\",\n    \"2166-09-01 00:00:00\",\n    \"2166-09-30 00:00:00\",\n    \"2166-10-13 00:00:00\",\n    \"2166-11-11 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-02-16 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-05-18 00:00:00\",\n    \"2167-07-01 00:00:00\",\n    \"2167-08-03 00:00:00\",\n    \"2167-09-07 00:00:00\",\n    \"2167-09-30 00:00:00\",\n    \"2167-10-12 00:00:00\",\n    \"2167-11-11 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-28 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-02-15 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-05-23 00:00:00\",\n    \"2168-07-01 00:00:00\",\n    \"2168-08-01 00:00:00\",\n    \"2168-09-05 00:00:00\",\n    \"2168-09-30 00:00:00\",\n    \"2168-10-10 00:00:00\",\n    \"2168-11-11 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2168-12-27 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-02-20 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-05-22 00:00:00\",\n    \"2169-07-03 00:00:00\",\n    \"2169-08-07 00:00:00\",\n    \"2169-09-04 00:00:00\",\n    \"2169-09-30 00:00:00\",\n    \"2169-10-09 00:00:00\",\n    \"2169-11-13 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-02-19 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-05-21 00:00:00\",\n    \"2170-07-02 00:00:00\",\n    \"2170-08-06 00:00:00\",\n    \"2170-09-03 00:00:00\",\n    \"2170-09-30 00:00:00\",\n    \"2170-10-08 00:00:00\",\n    \"2170-11-12 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-02-18 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-05-20 00:00:00\",\n    \"2171-07-01 00:00:00\",\n    \"2171-08-05 00:00:00\",\n    \"2171-09-02 00:00:00\",\n    \"2171-09-30 00:00:00\",\n    \"2171-10-14 00:00:00\",\n    \"2171-11-11 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-02-17 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-05-18 00:00:00\",\n    \"2172-07-01 00:00:00\",\n    \"2172-08-03 00:00:00\",\n    \"2172-09-07 00:00:00\",\n    \"2172-09-30 00:00:00\",\n    \"2172-10-12 00:00:00\",\n    \"2172-11-11 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-28 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-02-15 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-05-24 00:00:00\",\n    \"2173-07-01 00:00:00\",\n    \"2173-08-02 00:00:00\",\n    \"2173-09-06 00:00:00\",\n    \"2173-09-30 00:00:00\",\n    \"2173-10-11 00:00:00\",\n    \"2173-11-11 00:00:00\",\n    \"2173-12-27 00:00:00\",\n    \"2173-12-28 00:00:00\",\n    \"2174-01-03 00:00:00\",\n    \"2174-02-21 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-05-23 00:00:00\",\n    \"2174-07-01 00:00:00\",\n    \"2174-08-01 00:00:00\",\n    \"2174-09-05 00:00:00\",\n    \"2174-09-30 00:00:00\",\n    \"2174-10-10 00:00:00\",\n    \"2174-11-11 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2174-12-27 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-02-20 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-05-22 00:00:00\",\n    \"2175-07-03 00:00:00\",\n    \"2175-08-07 00:00:00\",\n    \"2175-09-04 00:00:00\",\n    \"2175-09-30 00:00:00\",\n    \"2175-10-09 00:00:00\",\n    \"2175-11-13 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-02-19 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-05-20 00:00:00\",\n    \"2176-07-01 00:00:00\",\n    \"2176-08-05 00:00:00\",\n    \"2176-09-02 00:00:00\",\n    \"2176-09-30 00:00:00\",\n    \"2176-10-14 00:00:00\",\n    \"2176-11-11 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-02-17 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-05-19 00:00:00\",\n    \"2177-07-01 00:00:00\",\n    \"2177-08-04 00:00:00\",\n    \"2177-09-01 00:00:00\",\n    \"2177-09-30 00:00:00\",\n    \"2177-10-13 00:00:00\",\n    \"2177-11-11 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-02-16 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-05-18 00:00:00\",\n    \"2178-07-01 00:00:00\",\n    \"2178-08-03 00:00:00\",\n    \"2178-09-07 00:00:00\",\n    \"2178-09-30 00:00:00\",\n    \"2178-10-12 00:00:00\",\n    \"2178-11-11 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-28 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-02-15 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-05-24 00:00:00\",\n    \"2179-07-01 00:00:00\",\n    \"2179-08-02 00:00:00\",\n    \"2179-09-06 00:00:00\",\n    \"2179-09-30 00:00:00\",\n    \"2179-10-11 00:00:00\",\n    \"2179-11-11 00:00:00\",\n    \"2179-12-27 00:00:00\",\n    \"2179-12-28 00:00:00\",\n    \"2180-01-03 00:00:00\",\n    \"2180-02-21 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-05-22 00:00:00\",\n    \"2180-07-03 00:00:00\",\n    \"2180-08-07 00:00:00\",\n    \"2180-09-04 00:00:00\",\n    \"2180-09-30 00:00:00\",\n    \"2180-10-09 00:00:00\",\n    \"2180-11-13 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-02-19 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-05-21 00:00:00\",\n    \"2181-07-02 00:00:00\",\n    \"2181-08-06 00:00:00\",\n    \"2181-09-03 00:00:00\",\n    \"2181-09-30 00:00:00\",\n    \"2181-10-08 00:00:00\",\n    \"2181-11-12 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-02-18 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-05-20 00:00:00\",\n    \"2182-07-01 00:00:00\",\n    \"2182-08-05 00:00:00\",\n    \"2182-09-02 00:00:00\",\n    \"2182-09-30 00:00:00\",\n    \"2182-10-14 00:00:00\",\n    \"2182-11-11 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-02-17 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-05-19 00:00:00\",\n    \"2183-07-01 00:00:00\",\n    \"2183-08-04 00:00:00\",\n    \"2183-09-01 00:00:00\",\n    \"2183-09-30 00:00:00\",\n    \"2183-10-13 00:00:00\",\n    \"2183-11-11 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-02-16 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-05-24 00:00:00\",\n    \"2184-07-01 00:00:00\",\n    \"2184-08-02 00:00:00\",\n    \"2184-09-06 00:00:00\",\n    \"2184-09-30 00:00:00\",\n    \"2184-10-11 00:00:00\",\n    \"2184-11-11 00:00:00\",\n    \"2184-12-27 00:00:00\",\n    \"2184-12-28 00:00:00\",\n    \"2185-01-03 00:00:00\",\n    \"2185-02-21 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-05-23 00:00:00\",\n    \"2185-07-01 00:00:00\",\n    \"2185-08-01 00:00:00\",\n    \"2185-09-05 00:00:00\",\n    \"2185-09-30 00:00:00\",\n    \"2185-10-10 00:00:00\",\n    \"2185-11-11 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2185-12-27 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-02-20 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-05-22 00:00:00\",\n    \"2186-07-03 00:00:00\",\n    \"2186-08-07 00:00:00\",\n    \"2186-09-04 00:00:00\",\n    \"2186-09-30 00:00:00\",\n    \"2186-10-09 00:00:00\",\n    \"2186-11-13 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-02-19 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-05-21 00:00:00\",\n    \"2187-07-02 00:00:00\",\n    \"2187-08-06 00:00:00\",\n    \"2187-09-03 00:00:00\",\n    \"2187-09-30 00:00:00\",\n    \"2187-10-08 00:00:00\",\n    \"2187-11-12 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-02-18 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-05-19 00:00:00\",\n    \"2188-07-01 00:00:00\",\n    \"2188-08-04 00:00:00\",\n    \"2188-09-01 00:00:00\",\n    \"2188-09-30 00:00:00\",\n    \"2188-10-13 00:00:00\",\n    \"2188-11-11 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-02-16 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-05-18 00:00:00\",\n    \"2189-07-01 00:00:00\",\n    \"2189-08-03 00:00:00\",\n    \"2189-09-07 00:00:00\",\n    \"2189-09-30 00:00:00\",\n    \"2189-10-12 00:00:00\",\n    \"2189-11-11 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-28 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-02-15 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-05-24 00:00:00\",\n    \"2190-07-01 00:00:00\",\n    \"2190-08-02 00:00:00\",\n    \"2190-09-06 00:00:00\",\n    \"2190-09-30 00:00:00\",\n    \"2190-10-11 00:00:00\",\n    \"2190-11-11 00:00:00\",\n    \"2190-12-27 00:00:00\",\n    \"2190-12-28 00:00:00\",\n    \"2191-01-03 00:00:00\",\n    \"2191-02-21 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-05-23 00:00:00\",\n    \"2191-07-01 00:00:00\",\n    \"2191-08-01 00:00:00\",\n    \"2191-09-05 00:00:00\",\n    \"2191-09-30 00:00:00\",\n    \"2191-10-10 00:00:00\",\n    \"2191-11-11 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2191-12-27 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-02-20 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-05-21 00:00:00\",\n    \"2192-07-02 00:00:00\",\n    \"2192-08-06 00:00:00\",\n    \"2192-09-03 00:00:00\",\n    \"2192-09-30 00:00:00\",\n    \"2192-10-08 00:00:00\",\n    \"2192-11-12 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-02-18 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-05-20 00:00:00\",\n    \"2193-07-01 00:00:00\",\n    \"2193-08-05 00:00:00\",\n    \"2193-09-02 00:00:00\",\n    \"2193-09-30 00:00:00\",\n    \"2193-10-14 00:00:00\",\n    \"2193-11-11 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-02-17 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-05-19 00:00:00\",\n    \"2194-07-01 00:00:00\",\n    \"2194-08-04 00:00:00\",\n    \"2194-09-01 00:00:00\",\n    \"2194-09-30 00:00:00\",\n    \"2194-10-13 00:00:00\",\n    \"2194-11-11 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-02-16 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-05-18 00:00:00\",\n    \"2195-07-01 00:00:00\",\n    \"2195-08-03 00:00:00\",\n    \"2195-09-07 00:00:00\",\n    \"2195-09-30 00:00:00\",\n    \"2195-10-12 00:00:00\",\n    \"2195-11-11 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-28 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-02-15 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-05-23 00:00:00\",\n    \"2196-07-01 00:00:00\",\n    \"2196-08-01 00:00:00\",\n    \"2196-09-05 00:00:00\",\n    \"2196-09-30 00:00:00\",\n    \"2196-10-10 00:00:00\",\n    \"2196-11-11 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2196-12-27 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-02-20 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-05-22 00:00:00\",\n    \"2197-07-03 00:00:00\",\n    \"2197-08-07 00:00:00\",\n    \"2197-09-04 00:00:00\",\n    \"2197-09-30 00:00:00\",\n    \"2197-10-09 00:00:00\",\n    \"2197-11-13 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-02-19 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-05-21 00:00:00\",\n    \"2198-07-02 00:00:00\",\n    \"2198-08-06 00:00:00\",\n    \"2198-09-03 00:00:00\",\n    \"2198-09-30 00:00:00\",\n    \"2198-10-08 00:00:00\",\n    \"2198-11-12 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-02-18 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-05-20 00:00:00\",\n    \"2199-07-01 00:00:00\",\n    \"2199-08-05 00:00:00\",\n    \"2199-09-02 00:00:00\",\n    \"2199-09-30 00:00:00\",\n    \"2199-10-14 00:00:00\",\n    \"2199-11-11 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-02-17 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-05-19 00:00:00\",\n    \"2200-07-01 00:00:00\",\n    \"2200-08-04 00:00:00\",\n    \"2200-09-01 00:00:00\",\n    \"2200-09-30 00:00:00\",\n    \"2200-10-13 00:00:00\",\n    \"2200-11-11 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/tro_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime\n\nimport pandas as pd\nfrom pandas.tseries.holiday import (\n    MO,\n    AbstractHolidayCalendar,\n    DateOffset,\n    Holiday,\n    next_monday,\n    next_monday_or_tuesday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, Day, Easter\n\nRULES = [\n    Holiday(\"New Year's Day Holiday\", month=1, day=1, observance=next_monday),\n    Holiday(\n        \"Family Day\",\n        month=2,\n        day=1,\n        offset=DateOffset(weekday=MO(3)),\n        start_date=datetime(2008, 1, 1),\n    ),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Victoria Day\", month=5, day=24, offset=DateOffset(weekday=MO(-1))),\n    Holiday(\"Canada Day\", month=7, day=1, observance=next_monday),\n    Holiday(\"Civic Holiday\", month=8, day=1, offset=DateOffset(weekday=MO(1))),\n    Holiday(\"CAD Labour Day\", month=9, day=1, offset=DateOffset(weekday=MO(1))),\n    Holiday(\"CAD Thanksgiving\", month=10, day=1, offset=DateOffset(weekday=MO(2))),\n    Holiday(\"Remembrance\", month=11, day=11, observance=next_monday),\n    Holiday(\"National Truth & Reconciliation\", month=9, day=30, start_date=datetime(2021, 1, 1)),\n    Holiday(\"Christmas Day Holiday\", month=12, day=25, observance=next_monday),\n    Holiday(\"Boxing Day Holiday\", month=12, day=26, observance=next_monday_or_tuesday),\n    # Ad hoc dates\n    Holiday(\"adhoc1\", year=1997, month=8, day=13),\n    Holiday(\"adhoc2\", year=1997, month=8, day=14),\n    Holiday(\"adhoc3\", year=1997, month=8, day=15),\n    Holiday(\"adhoc4\", year=1997, month=8, day=29),\n    Holiday(\"adhoc5\", year=1997, month=12, day=22),\n    Holiday(\"adhoc6\", year=1998, month=4, day=9),\n    Holiday(\"adhoc7\", year=1998, month=4, day=29),\n]\n\nCALENDAR = CustomBusinessDay(\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/tyo.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Tokyo business day calendar, aligned with TONA publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Jan 2 (New Year)\",\n//     \"Jan 3 (New Year)\",\n//     \"Jan 2nd Mon (Coming-of-Age)\",\n//     \"Feb 11: Sun->Mon (Foundation)\",\n//     \"Feb 23: Sun->Mon (Emperor Naruhito Birthday est. 2020)\",\n//     \"Mar 20/21: Sun->Mon (Vernal Equinox)\",\n//     \"Apr 29: Sun->Mon (Showa)\",\n//     \"May 3: Sun->Mon (Constitution)\",\n//     \"May 4: Sun->Mon->Tue (Greenery)\",\n//     \"May 5: Sun->Mon->Tue->Wed (Children)\",\n//     \"Jul 3rd Mon (Marine)\",\n//     \"Aug 11: Sun->Mon (Mountain est. 2016)\",\n//     \"Sep 3rd Mon (Respect Aged)\",\n//     \"Sep 22/23: Sun->Mon (Autumn Equinox)\",\n//     \"Oct 2nd Mon (Sports)\",\n//     \"Nov 3: Sun->Mon (Culture)\",\n//     \"Nov 23: Sun->Mon (Labor Thanksgiving)\",\n//     \"Dec 23: Sun->Mon (Emperor Akihito Birthday end. 2019)\",\n//     \"Dec 31 (New Year)\",\n//     \"Note: 2020 Olympics adjustments.\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-01-02 00:00:00\",\n    \"1970-01-03 00:00:00\",\n    \"1970-01-12 00:00:00\",\n    \"1970-02-11 00:00:00\",\n    \"1970-03-21 00:00:00\",\n    \"1970-04-29 00:00:00\",\n    \"1970-05-04 00:00:00\",\n    \"1970-05-05 00:00:00\",\n    \"1970-05-06 00:00:00\",\n    \"1970-07-20 00:00:00\",\n    \"1970-09-21 00:00:00\",\n    \"1970-09-23 00:00:00\",\n    \"1970-10-12 00:00:00\",\n    \"1970-11-03 00:00:00\",\n    \"1970-11-23 00:00:00\",\n    \"1970-12-23 00:00:00\",\n    \"1970-12-31 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-01-02 00:00:00\",\n    \"1971-01-03 00:00:00\",\n    \"1971-01-11 00:00:00\",\n    \"1971-02-11 00:00:00\",\n    \"1971-03-21 00:00:00\",\n    \"1971-04-29 00:00:00\",\n    \"1971-05-03 00:00:00\",\n    \"1971-05-04 00:00:00\",\n    \"1971-05-05 00:00:00\",\n    \"1971-07-19 00:00:00\",\n    \"1971-09-20 00:00:00\",\n    \"1971-09-23 00:00:00\",\n    \"1971-10-11 00:00:00\",\n    \"1971-11-03 00:00:00\",\n    \"1971-11-23 00:00:00\",\n    \"1971-12-23 00:00:00\",\n    \"1971-12-31 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-01-02 00:00:00\",\n    \"1972-01-03 00:00:00\",\n    \"1972-01-10 00:00:00\",\n    \"1972-02-11 00:00:00\",\n    \"1972-03-20 00:00:00\",\n    \"1972-04-29 00:00:00\",\n    \"1972-05-03 00:00:00\",\n    \"1972-05-04 00:00:00\",\n    \"1972-05-05 00:00:00\",\n    \"1972-07-17 00:00:00\",\n    \"1972-09-18 00:00:00\",\n    \"1972-09-22 00:00:00\",\n    \"1972-10-09 00:00:00\",\n    \"1972-11-03 00:00:00\",\n    \"1972-11-23 00:00:00\",\n    \"1972-12-23 00:00:00\",\n    \"1972-12-31 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-01-02 00:00:00\",\n    \"1973-01-03 00:00:00\",\n    \"1973-01-08 00:00:00\",\n    \"1973-02-12 00:00:00\",\n    \"1973-03-20 00:00:00\",\n    \"1973-04-30 00:00:00\",\n    \"1973-05-03 00:00:00\",\n    \"1973-05-04 00:00:00\",\n    \"1973-05-05 00:00:00\",\n    \"1973-07-16 00:00:00\",\n    \"1973-09-17 00:00:00\",\n    \"1973-09-23 00:00:00\",\n    \"1973-10-08 00:00:00\",\n    \"1973-11-03 00:00:00\",\n    \"1973-11-23 00:00:00\",\n    \"1973-12-24 00:00:00\",\n    \"1973-12-31 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-01-02 00:00:00\",\n    \"1974-01-03 00:00:00\",\n    \"1974-01-14 00:00:00\",\n    \"1974-02-11 00:00:00\",\n    \"1974-03-21 00:00:00\",\n    \"1974-04-29 00:00:00\",\n    \"1974-05-03 00:00:00\",\n    \"1974-05-04 00:00:00\",\n    \"1974-05-06 00:00:00\",\n    \"1974-07-15 00:00:00\",\n    \"1974-09-16 00:00:00\",\n    \"1974-09-23 00:00:00\",\n    \"1974-10-14 00:00:00\",\n    \"1974-11-04 00:00:00\",\n    \"1974-11-23 00:00:00\",\n    \"1974-12-23 00:00:00\",\n    \"1974-12-31 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-01-02 00:00:00\",\n    \"1975-01-03 00:00:00\",\n    \"1975-01-13 00:00:00\",\n    \"1975-02-11 00:00:00\",\n    \"1975-03-21 00:00:00\",\n    \"1975-04-29 00:00:00\",\n    \"1975-05-03 00:00:00\",\n    \"1975-05-05 00:00:00\",\n    \"1975-05-06 00:00:00\",\n    \"1975-07-21 00:00:00\",\n    \"1975-09-15 00:00:00\",\n    \"1975-09-23 00:00:00\",\n    \"1975-10-13 00:00:00\",\n    \"1975-11-03 00:00:00\",\n    \"1975-11-24 00:00:00\",\n    \"1975-12-23 00:00:00\",\n    \"1975-12-31 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-01-02 00:00:00\",\n    \"1976-01-03 00:00:00\",\n    \"1976-01-12 00:00:00\",\n    \"1976-02-11 00:00:00\",\n    \"1976-03-20 00:00:00\",\n    \"1976-04-29 00:00:00\",\n    \"1976-05-03 00:00:00\",\n    \"1976-05-04 00:00:00\",\n    \"1976-05-05 00:00:00\",\n    \"1976-07-19 00:00:00\",\n    \"1976-09-20 00:00:00\",\n    \"1976-09-22 00:00:00\",\n    \"1976-10-11 00:00:00\",\n    \"1976-11-03 00:00:00\",\n    \"1976-11-23 00:00:00\",\n    \"1976-12-23 00:00:00\",\n    \"1976-12-31 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-01-02 00:00:00\",\n    \"1977-01-03 00:00:00\",\n    \"1977-01-10 00:00:00\",\n    \"1977-02-11 00:00:00\",\n    \"1977-03-20 00:00:00\",\n    \"1977-04-29 00:00:00\",\n    \"1977-05-03 00:00:00\",\n    \"1977-05-04 00:00:00\",\n    \"1977-05-05 00:00:00\",\n    \"1977-07-18 00:00:00\",\n    \"1977-09-19 00:00:00\",\n    \"1977-09-23 00:00:00\",\n    \"1977-10-10 00:00:00\",\n    \"1977-11-03 00:00:00\",\n    \"1977-11-23 00:00:00\",\n    \"1977-12-23 00:00:00\",\n    \"1977-12-31 00:00:00\",\n    \"1978-01-01 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-01-03 00:00:00\",\n    \"1978-01-09 00:00:00\",\n    \"1978-02-11 00:00:00\",\n    \"1978-03-20 00:00:00\",\n    \"1978-04-29 00:00:00\",\n    \"1978-05-03 00:00:00\",\n    \"1978-05-04 00:00:00\",\n    \"1978-05-05 00:00:00\",\n    \"1978-07-17 00:00:00\",\n    \"1978-09-18 00:00:00\",\n    \"1978-09-23 00:00:00\",\n    \"1978-10-09 00:00:00\",\n    \"1978-11-03 00:00:00\",\n    \"1978-11-23 00:00:00\",\n    \"1978-12-23 00:00:00\",\n    \"1978-12-31 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-01-02 00:00:00\",\n    \"1979-01-03 00:00:00\",\n    \"1979-01-08 00:00:00\",\n    \"1979-02-12 00:00:00\",\n    \"1979-03-21 00:00:00\",\n    \"1979-04-30 00:00:00\",\n    \"1979-05-03 00:00:00\",\n    \"1979-05-04 00:00:00\",\n    \"1979-05-05 00:00:00\",\n    \"1979-07-16 00:00:00\",\n    \"1979-09-17 00:00:00\",\n    \"1979-09-23 00:00:00\",\n    \"1979-10-08 00:00:00\",\n    \"1979-11-03 00:00:00\",\n    \"1979-11-23 00:00:00\",\n    \"1979-12-24 00:00:00\",\n    \"1979-12-31 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-01-02 00:00:00\",\n    \"1980-01-03 00:00:00\",\n    \"1980-01-14 00:00:00\",\n    \"1980-02-11 00:00:00\",\n    \"1980-03-20 00:00:00\",\n    \"1980-04-29 00:00:00\",\n    \"1980-05-03 00:00:00\",\n    \"1980-05-05 00:00:00\",\n    \"1980-05-06 00:00:00\",\n    \"1980-07-21 00:00:00\",\n    \"1980-09-15 00:00:00\",\n    \"1980-09-22 00:00:00\",\n    \"1980-10-13 00:00:00\",\n    \"1980-11-03 00:00:00\",\n    \"1980-11-24 00:00:00\",\n    \"1980-12-23 00:00:00\",\n    \"1980-12-31 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-01-02 00:00:00\",\n    \"1981-01-03 00:00:00\",\n    \"1981-01-12 00:00:00\",\n    \"1981-02-11 00:00:00\",\n    \"1981-03-20 00:00:00\",\n    \"1981-04-29 00:00:00\",\n    \"1981-05-04 00:00:00\",\n    \"1981-05-05 00:00:00\",\n    \"1981-05-06 00:00:00\",\n    \"1981-07-20 00:00:00\",\n    \"1981-09-21 00:00:00\",\n    \"1981-09-23 00:00:00\",\n    \"1981-10-12 00:00:00\",\n    \"1981-11-03 00:00:00\",\n    \"1981-11-23 00:00:00\",\n    \"1981-12-23 00:00:00\",\n    \"1981-12-31 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-01-02 00:00:00\",\n    \"1982-01-03 00:00:00\",\n    \"1982-01-11 00:00:00\",\n    \"1982-02-11 00:00:00\",\n    \"1982-03-20 00:00:00\",\n    \"1982-04-29 00:00:00\",\n    \"1982-05-03 00:00:00\",\n    \"1982-05-04 00:00:00\",\n    \"1982-05-05 00:00:00\",\n    \"1982-07-19 00:00:00\",\n    \"1982-09-20 00:00:00\",\n    \"1982-09-23 00:00:00\",\n    \"1982-10-11 00:00:00\",\n    \"1982-11-03 00:00:00\",\n    \"1982-11-23 00:00:00\",\n    \"1982-12-23 00:00:00\",\n    \"1982-12-31 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-01-02 00:00:00\",\n    \"1983-01-03 00:00:00\",\n    \"1983-01-10 00:00:00\",\n    \"1983-02-11 00:00:00\",\n    \"1983-03-21 00:00:00\",\n    \"1983-04-29 00:00:00\",\n    \"1983-05-03 00:00:00\",\n    \"1983-05-04 00:00:00\",\n    \"1983-05-05 00:00:00\",\n    \"1983-07-18 00:00:00\",\n    \"1983-09-19 00:00:00\",\n    \"1983-09-23 00:00:00\",\n    \"1983-10-10 00:00:00\",\n    \"1983-11-03 00:00:00\",\n    \"1983-11-23 00:00:00\",\n    \"1983-12-23 00:00:00\",\n    \"1983-12-31 00:00:00\",\n    \"1984-01-01 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-01-03 00:00:00\",\n    \"1984-01-09 00:00:00\",\n    \"1984-02-11 00:00:00\",\n    \"1984-03-20 00:00:00\",\n    \"1984-04-30 00:00:00\",\n    \"1984-05-03 00:00:00\",\n    \"1984-05-04 00:00:00\",\n    \"1984-05-05 00:00:00\",\n    \"1984-07-16 00:00:00\",\n    \"1984-09-17 00:00:00\",\n    \"1984-09-22 00:00:00\",\n    \"1984-10-08 00:00:00\",\n    \"1984-11-03 00:00:00\",\n    \"1984-11-23 00:00:00\",\n    \"1984-12-24 00:00:00\",\n    \"1984-12-31 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-01-02 00:00:00\",\n    \"1985-01-03 00:00:00\",\n    \"1985-01-14 00:00:00\",\n    \"1985-02-11 00:00:00\",\n    \"1985-03-20 00:00:00\",\n    \"1985-04-29 00:00:00\",\n    \"1985-05-03 00:00:00\",\n    \"1985-05-04 00:00:00\",\n    \"1985-05-06 00:00:00\",\n    \"1985-07-15 00:00:00\",\n    \"1985-09-16 00:00:00\",\n    \"1985-09-23 00:00:00\",\n    \"1985-10-14 00:00:00\",\n    \"1985-11-04 00:00:00\",\n    \"1985-11-23 00:00:00\",\n    \"1985-12-23 00:00:00\",\n    \"1985-12-31 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-01-02 00:00:00\",\n    \"1986-01-03 00:00:00\",\n    \"1986-01-13 00:00:00\",\n    \"1986-02-11 00:00:00\",\n    \"1986-03-20 00:00:00\",\n    \"1986-04-29 00:00:00\",\n    \"1986-05-03 00:00:00\",\n    \"1986-05-05 00:00:00\",\n    \"1986-05-06 00:00:00\",\n    \"1986-07-21 00:00:00\",\n    \"1986-09-15 00:00:00\",\n    \"1986-09-23 00:00:00\",\n    \"1986-10-13 00:00:00\",\n    \"1986-11-03 00:00:00\",\n    \"1986-11-24 00:00:00\",\n    \"1986-12-23 00:00:00\",\n    \"1986-12-31 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-01-02 00:00:00\",\n    \"1987-01-03 00:00:00\",\n    \"1987-01-12 00:00:00\",\n    \"1987-02-11 00:00:00\",\n    \"1987-03-21 00:00:00\",\n    \"1987-04-29 00:00:00\",\n    \"1987-05-04 00:00:00\",\n    \"1987-05-05 00:00:00\",\n    \"1987-05-06 00:00:00\",\n    \"1987-07-20 00:00:00\",\n    \"1987-09-21 00:00:00\",\n    \"1987-09-23 00:00:00\",\n    \"1987-10-12 00:00:00\",\n    \"1987-11-03 00:00:00\",\n    \"1987-11-23 00:00:00\",\n    \"1987-12-23 00:00:00\",\n    \"1987-12-31 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-01-02 00:00:00\",\n    \"1988-01-03 00:00:00\",\n    \"1988-01-11 00:00:00\",\n    \"1988-02-11 00:00:00\",\n    \"1988-03-20 00:00:00\",\n    \"1988-04-29 00:00:00\",\n    \"1988-05-03 00:00:00\",\n    \"1988-05-04 00:00:00\",\n    \"1988-05-05 00:00:00\",\n    \"1988-07-18 00:00:00\",\n    \"1988-09-19 00:00:00\",\n    \"1988-09-22 00:00:00\",\n    \"1988-10-10 00:00:00\",\n    \"1988-11-03 00:00:00\",\n    \"1988-11-23 00:00:00\",\n    \"1988-12-23 00:00:00\",\n    \"1988-12-31 00:00:00\",\n    \"1989-01-01 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-01-03 00:00:00\",\n    \"1989-01-09 00:00:00\",\n    \"1989-02-11 00:00:00\",\n    \"1989-03-20 00:00:00\",\n    \"1989-04-29 00:00:00\",\n    \"1989-05-03 00:00:00\",\n    \"1989-05-04 00:00:00\",\n    \"1989-05-05 00:00:00\",\n    \"1989-07-17 00:00:00\",\n    \"1989-09-18 00:00:00\",\n    \"1989-09-23 00:00:00\",\n    \"1989-10-09 00:00:00\",\n    \"1989-11-03 00:00:00\",\n    \"1989-11-23 00:00:00\",\n    \"1989-12-23 00:00:00\",\n    \"1989-12-31 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-01-02 00:00:00\",\n    \"1990-01-03 00:00:00\",\n    \"1990-01-08 00:00:00\",\n    \"1990-02-12 00:00:00\",\n    \"1990-03-20 00:00:00\",\n    \"1990-04-30 00:00:00\",\n    \"1990-05-03 00:00:00\",\n    \"1990-05-04 00:00:00\",\n    \"1990-05-05 00:00:00\",\n    \"1990-07-16 00:00:00\",\n    \"1990-09-17 00:00:00\",\n    \"1990-09-23 00:00:00\",\n    \"1990-10-08 00:00:00\",\n    \"1990-11-03 00:00:00\",\n    \"1990-11-23 00:00:00\",\n    \"1990-12-24 00:00:00\",\n    \"1990-12-31 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-01-02 00:00:00\",\n    \"1991-01-03 00:00:00\",\n    \"1991-01-14 00:00:00\",\n    \"1991-02-11 00:00:00\",\n    \"1991-03-21 00:00:00\",\n    \"1991-04-29 00:00:00\",\n    \"1991-05-03 00:00:00\",\n    \"1991-05-04 00:00:00\",\n    \"1991-05-06 00:00:00\",\n    \"1991-07-15 00:00:00\",\n    \"1991-09-16 00:00:00\",\n    \"1991-09-23 00:00:00\",\n    \"1991-10-14 00:00:00\",\n    \"1991-11-04 00:00:00\",\n    \"1991-11-23 00:00:00\",\n    \"1991-12-23 00:00:00\",\n    \"1991-12-31 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-01-02 00:00:00\",\n    \"1992-01-03 00:00:00\",\n    \"1992-01-13 00:00:00\",\n    \"1992-02-11 00:00:00\",\n    \"1992-03-20 00:00:00\",\n    \"1992-04-29 00:00:00\",\n    \"1992-05-04 00:00:00\",\n    \"1992-05-05 00:00:00\",\n    \"1992-05-06 00:00:00\",\n    \"1992-07-20 00:00:00\",\n    \"1992-09-21 00:00:00\",\n    \"1992-09-22 00:00:00\",\n    \"1992-10-12 00:00:00\",\n    \"1992-11-03 00:00:00\",\n    \"1992-11-23 00:00:00\",\n    \"1992-12-23 00:00:00\",\n    \"1992-12-31 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-01-02 00:00:00\",\n    \"1993-01-03 00:00:00\",\n    \"1993-01-11 00:00:00\",\n    \"1993-02-11 00:00:00\",\n    \"1993-03-20 00:00:00\",\n    \"1993-04-29 00:00:00\",\n    \"1993-05-03 00:00:00\",\n    \"1993-05-04 00:00:00\",\n    \"1993-05-05 00:00:00\",\n    \"1993-07-19 00:00:00\",\n    \"1993-09-20 00:00:00\",\n    \"1993-09-23 00:00:00\",\n    \"1993-10-11 00:00:00\",\n    \"1993-11-03 00:00:00\",\n    \"1993-11-23 00:00:00\",\n    \"1993-12-23 00:00:00\",\n    \"1993-12-31 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-01-02 00:00:00\",\n    \"1994-01-03 00:00:00\",\n    \"1994-01-10 00:00:00\",\n    \"1994-02-11 00:00:00\",\n    \"1994-03-20 00:00:00\",\n    \"1994-04-29 00:00:00\",\n    \"1994-05-03 00:00:00\",\n    \"1994-05-04 00:00:00\",\n    \"1994-05-05 00:00:00\",\n    \"1994-07-18 00:00:00\",\n    \"1994-09-19 00:00:00\",\n    \"1994-09-23 00:00:00\",\n    \"1994-10-10 00:00:00\",\n    \"1994-11-03 00:00:00\",\n    \"1994-11-23 00:00:00\",\n    \"1994-12-23 00:00:00\",\n    \"1994-12-31 00:00:00\",\n    \"1995-01-01 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-01-03 00:00:00\",\n    \"1995-01-09 00:00:00\",\n    \"1995-02-11 00:00:00\",\n    \"1995-03-21 00:00:00\",\n    \"1995-04-29 00:00:00\",\n    \"1995-05-03 00:00:00\",\n    \"1995-05-04 00:00:00\",\n    \"1995-05-05 00:00:00\",\n    \"1995-07-17 00:00:00\",\n    \"1995-09-18 00:00:00\",\n    \"1995-09-23 00:00:00\",\n    \"1995-10-09 00:00:00\",\n    \"1995-11-03 00:00:00\",\n    \"1995-11-23 00:00:00\",\n    \"1995-12-23 00:00:00\",\n    \"1995-12-31 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-01-02 00:00:00\",\n    \"1996-01-03 00:00:00\",\n    \"1996-01-08 00:00:00\",\n    \"1996-02-12 00:00:00\",\n    \"1996-03-20 00:00:00\",\n    \"1996-04-29 00:00:00\",\n    \"1996-05-03 00:00:00\",\n    \"1996-05-04 00:00:00\",\n    \"1996-05-06 00:00:00\",\n    \"1996-07-15 00:00:00\",\n    \"1996-09-16 00:00:00\",\n    \"1996-09-22 00:00:00\",\n    \"1996-10-14 00:00:00\",\n    \"1996-11-04 00:00:00\",\n    \"1996-11-23 00:00:00\",\n    \"1996-12-23 00:00:00\",\n    \"1996-12-31 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-01-02 00:00:00\",\n    \"1997-01-03 00:00:00\",\n    \"1997-01-13 00:00:00\",\n    \"1997-02-11 00:00:00\",\n    \"1997-03-20 00:00:00\",\n    \"1997-04-29 00:00:00\",\n    \"1997-05-03 00:00:00\",\n    \"1997-05-05 00:00:00\",\n    \"1997-05-06 00:00:00\",\n    \"1997-07-21 00:00:00\",\n    \"1997-09-15 00:00:00\",\n    \"1997-09-22 00:00:00\",\n    \"1997-10-13 00:00:00\",\n    \"1997-11-03 00:00:00\",\n    \"1997-11-24 00:00:00\",\n    \"1997-12-23 00:00:00\",\n    \"1997-12-31 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-01-02 00:00:00\",\n    \"1998-01-03 00:00:00\",\n    \"1998-01-12 00:00:00\",\n    \"1998-02-11 00:00:00\",\n    \"1998-03-20 00:00:00\",\n    \"1998-04-29 00:00:00\",\n    \"1998-05-04 00:00:00\",\n    \"1998-05-05 00:00:00\",\n    \"1998-05-06 00:00:00\",\n    \"1998-07-20 00:00:00\",\n    \"1998-09-21 00:00:00\",\n    \"1998-09-23 00:00:00\",\n    \"1998-10-12 00:00:00\",\n    \"1998-11-03 00:00:00\",\n    \"1998-11-23 00:00:00\",\n    \"1998-12-23 00:00:00\",\n    \"1998-12-31 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-01-02 00:00:00\",\n    \"1999-01-03 00:00:00\",\n    \"1999-01-11 00:00:00\",\n    \"1999-02-11 00:00:00\",\n    \"1999-03-21 00:00:00\",\n    \"1999-04-29 00:00:00\",\n    \"1999-05-03 00:00:00\",\n    \"1999-05-04 00:00:00\",\n    \"1999-05-05 00:00:00\",\n    \"1999-07-19 00:00:00\",\n    \"1999-09-20 00:00:00\",\n    \"1999-09-23 00:00:00\",\n    \"1999-10-11 00:00:00\",\n    \"1999-11-03 00:00:00\",\n    \"1999-11-23 00:00:00\",\n    \"1999-12-23 00:00:00\",\n    \"1999-12-31 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-01-02 00:00:00\",\n    \"2000-01-03 00:00:00\",\n    \"2000-01-10 00:00:00\",\n    \"2000-02-11 00:00:00\",\n    \"2000-03-20 00:00:00\",\n    \"2000-04-29 00:00:00\",\n    \"2000-05-03 00:00:00\",\n    \"2000-05-04 00:00:00\",\n    \"2000-05-05 00:00:00\",\n    \"2000-07-17 00:00:00\",\n    \"2000-09-18 00:00:00\",\n    \"2000-09-22 00:00:00\",\n    \"2000-10-09 00:00:00\",\n    \"2000-11-03 00:00:00\",\n    \"2000-11-23 00:00:00\",\n    \"2000-12-23 00:00:00\",\n    \"2000-12-31 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-01-02 00:00:00\",\n    \"2001-01-03 00:00:00\",\n    \"2001-01-08 00:00:00\",\n    \"2001-02-12 00:00:00\",\n    \"2001-03-20 00:00:00\",\n    \"2001-04-30 00:00:00\",\n    \"2001-05-03 00:00:00\",\n    \"2001-05-04 00:00:00\",\n    \"2001-05-05 00:00:00\",\n    \"2001-07-16 00:00:00\",\n    \"2001-09-17 00:00:00\",\n    \"2001-09-22 00:00:00\",\n    \"2001-10-08 00:00:00\",\n    \"2001-11-03 00:00:00\",\n    \"2001-11-23 00:00:00\",\n    \"2001-12-24 00:00:00\",\n    \"2001-12-31 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-01-02 00:00:00\",\n    \"2002-01-03 00:00:00\",\n    \"2002-01-14 00:00:00\",\n    \"2002-02-11 00:00:00\",\n    \"2002-03-20 00:00:00\",\n    \"2002-04-29 00:00:00\",\n    \"2002-05-03 00:00:00\",\n    \"2002-05-04 00:00:00\",\n    \"2002-05-06 00:00:00\",\n    \"2002-07-15 00:00:00\",\n    \"2002-09-16 00:00:00\",\n    \"2002-09-23 00:00:00\",\n    \"2002-10-14 00:00:00\",\n    \"2002-11-04 00:00:00\",\n    \"2002-11-23 00:00:00\",\n    \"2002-12-23 00:00:00\",\n    \"2002-12-31 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-01-02 00:00:00\",\n    \"2003-01-03 00:00:00\",\n    \"2003-01-13 00:00:00\",\n    \"2003-02-11 00:00:00\",\n    \"2003-03-21 00:00:00\",\n    \"2003-04-29 00:00:00\",\n    \"2003-05-03 00:00:00\",\n    \"2003-05-05 00:00:00\",\n    \"2003-05-06 00:00:00\",\n    \"2003-07-21 00:00:00\",\n    \"2003-09-15 00:00:00\",\n    \"2003-09-23 00:00:00\",\n    \"2003-10-13 00:00:00\",\n    \"2003-11-03 00:00:00\",\n    \"2003-11-24 00:00:00\",\n    \"2003-12-23 00:00:00\",\n    \"2003-12-31 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-01-02 00:00:00\",\n    \"2004-01-03 00:00:00\",\n    \"2004-01-12 00:00:00\",\n    \"2004-02-11 00:00:00\",\n    \"2004-03-20 00:00:00\",\n    \"2004-04-29 00:00:00\",\n    \"2004-05-03 00:00:00\",\n    \"2004-05-04 00:00:00\",\n    \"2004-05-05 00:00:00\",\n    \"2004-07-19 00:00:00\",\n    \"2004-09-20 00:00:00\",\n    \"2004-09-22 00:00:00\",\n    \"2004-10-11 00:00:00\",\n    \"2004-11-03 00:00:00\",\n    \"2004-11-23 00:00:00\",\n    \"2004-12-23 00:00:00\",\n    \"2004-12-31 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-01-02 00:00:00\",\n    \"2005-01-03 00:00:00\",\n    \"2005-01-10 00:00:00\",\n    \"2005-02-11 00:00:00\",\n    \"2005-03-20 00:00:00\",\n    \"2005-04-29 00:00:00\",\n    \"2005-05-03 00:00:00\",\n    \"2005-05-04 00:00:00\",\n    \"2005-05-05 00:00:00\",\n    \"2005-07-18 00:00:00\",\n    \"2005-09-19 00:00:00\",\n    \"2005-09-22 00:00:00\",\n    \"2005-10-10 00:00:00\",\n    \"2005-11-03 00:00:00\",\n    \"2005-11-23 00:00:00\",\n    \"2005-12-23 00:00:00\",\n    \"2005-12-31 00:00:00\",\n    \"2006-01-01 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-01-03 00:00:00\",\n    \"2006-01-09 00:00:00\",\n    \"2006-02-11 00:00:00\",\n    \"2006-03-20 00:00:00\",\n    \"2006-04-29 00:00:00\",\n    \"2006-05-03 00:00:00\",\n    \"2006-05-04 00:00:00\",\n    \"2006-05-05 00:00:00\",\n    \"2006-07-17 00:00:00\",\n    \"2006-09-18 00:00:00\",\n    \"2006-09-23 00:00:00\",\n    \"2006-10-09 00:00:00\",\n    \"2006-11-03 00:00:00\",\n    \"2006-11-23 00:00:00\",\n    \"2006-12-23 00:00:00\",\n    \"2006-12-31 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-01-02 00:00:00\",\n    \"2007-01-03 00:00:00\",\n    \"2007-01-08 00:00:00\",\n    \"2007-02-12 00:00:00\",\n    \"2007-03-21 00:00:00\",\n    \"2007-04-30 00:00:00\",\n    \"2007-05-03 00:00:00\",\n    \"2007-05-04 00:00:00\",\n    \"2007-05-05 00:00:00\",\n    \"2007-07-16 00:00:00\",\n    \"2007-09-17 00:00:00\",\n    \"2007-09-23 00:00:00\",\n    \"2007-10-08 00:00:00\",\n    \"2007-11-03 00:00:00\",\n    \"2007-11-23 00:00:00\",\n    \"2007-12-24 00:00:00\",\n    \"2007-12-31 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-01-02 00:00:00\",\n    \"2008-01-03 00:00:00\",\n    \"2008-01-14 00:00:00\",\n    \"2008-02-11 00:00:00\",\n    \"2008-03-20 00:00:00\",\n    \"2008-04-29 00:00:00\",\n    \"2008-05-03 00:00:00\",\n    \"2008-05-05 00:00:00\",\n    \"2008-05-06 00:00:00\",\n    \"2008-07-21 00:00:00\",\n    \"2008-09-15 00:00:00\",\n    \"2008-09-22 00:00:00\",\n    \"2008-10-13 00:00:00\",\n    \"2008-11-03 00:00:00\",\n    \"2008-11-24 00:00:00\",\n    \"2008-12-23 00:00:00\",\n    \"2008-12-31 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-01-02 00:00:00\",\n    \"2009-01-03 00:00:00\",\n    \"2009-01-12 00:00:00\",\n    \"2009-02-11 00:00:00\",\n    \"2009-03-20 00:00:00\",\n    \"2009-04-29 00:00:00\",\n    \"2009-05-04 00:00:00\",\n    \"2009-05-05 00:00:00\",\n    \"2009-05-06 00:00:00\",\n    \"2009-07-20 00:00:00\",\n    \"2009-09-21 00:00:00\",\n    \"2009-09-22 00:00:00\",\n    \"2009-10-12 00:00:00\",\n    \"2009-11-03 00:00:00\",\n    \"2009-11-23 00:00:00\",\n    \"2009-12-23 00:00:00\",\n    \"2009-12-31 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-01-02 00:00:00\",\n    \"2010-01-03 00:00:00\",\n    \"2010-01-11 00:00:00\",\n    \"2010-02-11 00:00:00\",\n    \"2010-03-20 00:00:00\",\n    \"2010-04-29 00:00:00\",\n    \"2010-05-03 00:00:00\",\n    \"2010-05-04 00:00:00\",\n    \"2010-05-05 00:00:00\",\n    \"2010-07-19 00:00:00\",\n    \"2010-09-20 00:00:00\",\n    \"2010-09-23 00:00:00\",\n    \"2010-10-11 00:00:00\",\n    \"2010-11-03 00:00:00\",\n    \"2010-11-23 00:00:00\",\n    \"2010-12-23 00:00:00\",\n    \"2010-12-31 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-01-02 00:00:00\",\n    \"2011-01-03 00:00:00\",\n    \"2011-01-10 00:00:00\",\n    \"2011-02-11 00:00:00\",\n    \"2011-03-20 00:00:00\",\n    \"2011-04-29 00:00:00\",\n    \"2011-05-03 00:00:00\",\n    \"2011-05-04 00:00:00\",\n    \"2011-05-05 00:00:00\",\n    \"2011-07-18 00:00:00\",\n    \"2011-09-19 00:00:00\",\n    \"2011-09-23 00:00:00\",\n    \"2011-10-10 00:00:00\",\n    \"2011-11-03 00:00:00\",\n    \"2011-11-23 00:00:00\",\n    \"2011-12-23 00:00:00\",\n    \"2011-12-31 00:00:00\",\n    \"2012-01-01 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-01-03 00:00:00\",\n    \"2012-01-09 00:00:00\",\n    \"2012-02-11 00:00:00\",\n    \"2012-03-20 00:00:00\",\n    \"2012-04-30 00:00:00\",\n    \"2012-05-03 00:00:00\",\n    \"2012-05-04 00:00:00\",\n    \"2012-05-05 00:00:00\",\n    \"2012-07-16 00:00:00\",\n    \"2012-09-17 00:00:00\",\n    \"2012-09-22 00:00:00\",\n    \"2012-10-08 00:00:00\",\n    \"2012-11-03 00:00:00\",\n    \"2012-11-23 00:00:00\",\n    \"2012-12-24 00:00:00\",\n    \"2012-12-31 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-01-02 00:00:00\",\n    \"2013-01-03 00:00:00\",\n    \"2013-01-14 00:00:00\",\n    \"2013-02-11 00:00:00\",\n    \"2013-03-20 00:00:00\",\n    \"2013-04-29 00:00:00\",\n    \"2013-05-03 00:00:00\",\n    \"2013-05-04 00:00:00\",\n    \"2013-05-06 00:00:00\",\n    \"2013-07-15 00:00:00\",\n    \"2013-09-16 00:00:00\",\n    \"2013-09-22 00:00:00\",\n    \"2013-10-14 00:00:00\",\n    \"2013-11-04 00:00:00\",\n    \"2013-11-23 00:00:00\",\n    \"2013-12-23 00:00:00\",\n    \"2013-12-31 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-01-02 00:00:00\",\n    \"2014-01-03 00:00:00\",\n    \"2014-01-13 00:00:00\",\n    \"2014-02-11 00:00:00\",\n    \"2014-03-20 00:00:00\",\n    \"2014-04-29 00:00:00\",\n    \"2014-05-03 00:00:00\",\n    \"2014-05-05 00:00:00\",\n    \"2014-05-06 00:00:00\",\n    \"2014-07-21 00:00:00\",\n    \"2014-09-15 00:00:00\",\n    \"2014-09-23 00:00:00\",\n    \"2014-10-13 00:00:00\",\n    \"2014-11-03 00:00:00\",\n    \"2014-11-24 00:00:00\",\n    \"2014-12-23 00:00:00\",\n    \"2014-12-31 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-01-02 00:00:00\",\n    \"2015-01-03 00:00:00\",\n    \"2015-01-12 00:00:00\",\n    \"2015-02-11 00:00:00\",\n    \"2015-03-20 00:00:00\",\n    \"2015-04-29 00:00:00\",\n    \"2015-05-04 00:00:00\",\n    \"2015-05-05 00:00:00\",\n    \"2015-05-06 00:00:00\",\n    \"2015-07-20 00:00:00\",\n    \"2015-09-21 00:00:00\",\n    \"2015-09-22 00:00:00\",\n    \"2015-09-23 00:00:00\",\n    \"2015-10-12 00:00:00\",\n    \"2015-11-03 00:00:00\",\n    \"2015-11-23 00:00:00\",\n    \"2015-12-23 00:00:00\",\n    \"2015-12-31 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-01-02 00:00:00\",\n    \"2016-01-03 00:00:00\",\n    \"2016-01-11 00:00:00\",\n    \"2016-02-11 00:00:00\",\n    \"2016-03-21 00:00:00\",\n    \"2016-04-29 00:00:00\",\n    \"2016-05-03 00:00:00\",\n    \"2016-05-04 00:00:00\",\n    \"2016-05-05 00:00:00\",\n    \"2016-07-18 00:00:00\",\n    \"2016-08-11 00:00:00\",\n    \"2016-09-19 00:00:00\",\n    \"2016-09-22 00:00:00\",\n    \"2016-10-10 00:00:00\",\n    \"2016-11-03 00:00:00\",\n    \"2016-11-23 00:00:00\",\n    \"2016-12-23 00:00:00\",\n    \"2016-12-31 00:00:00\",\n    \"2017-01-01 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-01-03 00:00:00\",\n    \"2017-01-09 00:00:00\",\n    \"2017-02-11 00:00:00\",\n    \"2017-03-20 00:00:00\",\n    \"2017-04-29 00:00:00\",\n    \"2017-05-03 00:00:00\",\n    \"2017-05-04 00:00:00\",\n    \"2017-05-05 00:00:00\",\n    \"2017-07-17 00:00:00\",\n    \"2017-08-11 00:00:00\",\n    \"2017-09-18 00:00:00\",\n    \"2017-09-23 00:00:00\",\n    \"2017-10-09 00:00:00\",\n    \"2017-11-03 00:00:00\",\n    \"2017-11-23 00:00:00\",\n    \"2017-12-23 00:00:00\",\n    \"2017-12-31 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-01-02 00:00:00\",\n    \"2018-01-03 00:00:00\",\n    \"2018-01-08 00:00:00\",\n    \"2018-02-12 00:00:00\",\n    \"2018-03-21 00:00:00\",\n    \"2018-04-30 00:00:00\",\n    \"2018-05-03 00:00:00\",\n    \"2018-05-04 00:00:00\",\n    \"2018-05-05 00:00:00\",\n    \"2018-07-16 00:00:00\",\n    \"2018-08-11 00:00:00\",\n    \"2018-09-17 00:00:00\",\n    \"2018-09-24 00:00:00\",\n    \"2018-10-08 00:00:00\",\n    \"2018-11-03 00:00:00\",\n    \"2018-11-23 00:00:00\",\n    \"2018-12-24 00:00:00\",\n    \"2018-12-31 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-01-02 00:00:00\",\n    \"2019-01-03 00:00:00\",\n    \"2019-01-14 00:00:00\",\n    \"2019-02-11 00:00:00\",\n    \"2019-03-21 00:00:00\",\n    \"2019-04-29 00:00:00\",\n    \"2019-04-30 00:00:00\",\n    \"2019-05-01 00:00:00\",\n    \"2019-05-02 00:00:00\",\n    \"2019-05-03 00:00:00\",\n    \"2019-05-04 00:00:00\",\n    \"2019-05-06 00:00:00\",\n    \"2019-07-15 00:00:00\",\n    \"2019-08-12 00:00:00\",\n    \"2019-09-16 00:00:00\",\n    \"2019-09-23 00:00:00\",\n    \"2019-10-14 00:00:00\",\n    \"2019-10-22 00:00:00\",\n    \"2019-11-04 00:00:00\",\n    \"2019-11-23 00:00:00\",\n    \"2019-12-31 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-01-02 00:00:00\",\n    \"2020-01-03 00:00:00\",\n    \"2020-01-13 00:00:00\",\n    \"2020-02-11 00:00:00\",\n    \"2020-02-24 00:00:00\",\n    \"2020-03-20 00:00:00\",\n    \"2020-04-29 00:00:00\",\n    \"2020-05-04 00:00:00\",\n    \"2020-05-05 00:00:00\",\n    \"2020-05-06 00:00:00\",\n    \"2020-07-23 00:00:00\",\n    \"2020-07-24 00:00:00\",\n    \"2020-08-10 00:00:00\",\n    \"2020-09-21 00:00:00\",\n    \"2020-09-22 00:00:00\",\n    \"2020-11-03 00:00:00\",\n    \"2020-11-23 00:00:00\",\n    \"2020-12-31 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-01-02 00:00:00\",\n    \"2021-01-03 00:00:00\",\n    \"2021-01-11 00:00:00\",\n    \"2021-02-11 00:00:00\",\n    \"2021-02-23 00:00:00\",\n    \"2021-03-21 00:00:00\",\n    \"2021-04-29 00:00:00\",\n    \"2021-05-03 00:00:00\",\n    \"2021-05-04 00:00:00\",\n    \"2021-05-05 00:00:00\",\n    \"2021-07-22 00:00:00\",\n    \"2021-07-23 00:00:00\",\n    \"2021-08-09 00:00:00\",\n    \"2021-09-20 00:00:00\",\n    \"2021-09-23 00:00:00\",\n    \"2021-11-03 00:00:00\",\n    \"2021-11-23 00:00:00\",\n    \"2021-12-31 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-01-02 00:00:00\",\n    \"2022-01-03 00:00:00\",\n    \"2022-01-10 00:00:00\",\n    \"2022-02-11 00:00:00\",\n    \"2022-02-23 00:00:00\",\n    \"2022-03-21 00:00:00\",\n    \"2022-04-29 00:00:00\",\n    \"2022-05-03 00:00:00\",\n    \"2022-05-04 00:00:00\",\n    \"2022-05-05 00:00:00\",\n    \"2022-07-18 00:00:00\",\n    \"2022-08-11 00:00:00\",\n    \"2022-09-19 00:00:00\",\n    \"2022-09-23 00:00:00\",\n    \"2022-10-10 00:00:00\",\n    \"2022-11-03 00:00:00\",\n    \"2022-11-23 00:00:00\",\n    \"2022-12-31 00:00:00\",\n    \"2023-01-01 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-01-03 00:00:00\",\n    \"2023-01-09 00:00:00\",\n    \"2023-02-11 00:00:00\",\n    \"2023-02-23 00:00:00\",\n    \"2023-03-21 00:00:00\",\n    \"2023-04-29 00:00:00\",\n    \"2023-05-03 00:00:00\",\n    \"2023-05-04 00:00:00\",\n    \"2023-05-05 00:00:00\",\n    \"2023-07-17 00:00:00\",\n    \"2023-08-11 00:00:00\",\n    \"2023-09-18 00:00:00\",\n    \"2023-09-23 00:00:00\",\n    \"2023-10-09 00:00:00\",\n    \"2023-11-03 00:00:00\",\n    \"2023-11-23 00:00:00\",\n    \"2023-12-31 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-01-02 00:00:00\",\n    \"2024-01-03 00:00:00\",\n    \"2024-01-08 00:00:00\",\n    \"2024-02-12 00:00:00\",\n    \"2024-02-23 00:00:00\",\n    \"2024-03-20 00:00:00\",\n    \"2024-04-29 00:00:00\",\n    \"2024-05-03 00:00:00\",\n    \"2024-05-04 00:00:00\",\n    \"2024-05-06 00:00:00\",\n    \"2024-07-15 00:00:00\",\n    \"2024-08-12 00:00:00\",\n    \"2024-09-16 00:00:00\",\n    \"2024-09-23 00:00:00\",\n    \"2024-10-14 00:00:00\",\n    \"2024-11-04 00:00:00\",\n    \"2024-11-23 00:00:00\",\n    \"2024-12-31 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-02 00:00:00\",\n    \"2025-01-03 00:00:00\",\n    \"2025-01-13 00:00:00\",\n    \"2025-02-11 00:00:00\",\n    \"2025-02-24 00:00:00\",\n    \"2025-03-20 00:00:00\",\n    \"2025-04-29 00:00:00\",\n    \"2025-05-03 00:00:00\",\n    \"2025-05-05 00:00:00\",\n    \"2025-05-06 00:00:00\",\n    \"2025-07-21 00:00:00\",\n    \"2025-08-11 00:00:00\",\n    \"2025-09-15 00:00:00\",\n    \"2025-09-23 00:00:00\",\n    \"2025-10-13 00:00:00\",\n    \"2025-11-03 00:00:00\",\n    \"2025-11-24 00:00:00\",\n    \"2025-12-31 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-02 00:00:00\",\n    \"2026-01-03 00:00:00\",\n    \"2026-01-12 00:00:00\",\n    \"2026-02-11 00:00:00\",\n    \"2026-02-23 00:00:00\",\n    \"2026-03-20 00:00:00\",\n    \"2026-04-29 00:00:00\",\n    \"2026-05-04 00:00:00\",\n    \"2026-05-05 00:00:00\",\n    \"2026-05-06 00:00:00\",\n    \"2026-07-20 00:00:00\",\n    \"2026-08-11 00:00:00\",\n    \"2026-09-21 00:00:00\",\n    \"2026-09-22 00:00:00\",\n    \"2026-09-23 00:00:00\",\n    \"2026-10-12 00:00:00\",\n    \"2026-11-03 00:00:00\",\n    \"2026-11-23 00:00:00\",\n    \"2026-12-31 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-02 00:00:00\",\n    \"2027-01-03 00:00:00\",\n    \"2027-01-11 00:00:00\",\n    \"2027-02-11 00:00:00\",\n    \"2027-02-23 00:00:00\",\n    \"2027-03-20 00:00:00\",\n    \"2027-04-29 00:00:00\",\n    \"2027-05-03 00:00:00\",\n    \"2027-05-04 00:00:00\",\n    \"2027-05-05 00:00:00\",\n    \"2027-07-19 00:00:00\",\n    \"2027-08-11 00:00:00\",\n    \"2027-09-20 00:00:00\",\n    \"2027-09-23 00:00:00\",\n    \"2027-10-11 00:00:00\",\n    \"2027-11-03 00:00:00\",\n    \"2027-11-23 00:00:00\",\n    \"2027-12-31 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-01-02 00:00:00\",\n    \"2028-01-03 00:00:00\",\n    \"2028-01-10 00:00:00\",\n    \"2028-02-11 00:00:00\",\n    \"2028-02-23 00:00:00\",\n    \"2028-03-20 00:00:00\",\n    \"2028-04-29 00:00:00\",\n    \"2028-05-03 00:00:00\",\n    \"2028-05-04 00:00:00\",\n    \"2028-05-05 00:00:00\",\n    \"2028-07-17 00:00:00\",\n    \"2028-08-11 00:00:00\",\n    \"2028-09-18 00:00:00\",\n    \"2028-09-22 00:00:00\",\n    \"2028-10-09 00:00:00\",\n    \"2028-11-03 00:00:00\",\n    \"2028-11-23 00:00:00\",\n    \"2028-12-31 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-02 00:00:00\",\n    \"2029-01-03 00:00:00\",\n    \"2029-01-08 00:00:00\",\n    \"2029-02-12 00:00:00\",\n    \"2029-02-23 00:00:00\",\n    \"2029-03-20 00:00:00\",\n    \"2029-04-30 00:00:00\",\n    \"2029-05-03 00:00:00\",\n    \"2029-05-04 00:00:00\",\n    \"2029-05-05 00:00:00\",\n    \"2029-07-16 00:00:00\",\n    \"2029-08-11 00:00:00\",\n    \"2029-09-17 00:00:00\",\n    \"2029-09-22 00:00:00\",\n    \"2029-10-08 00:00:00\",\n    \"2029-11-03 00:00:00\",\n    \"2029-11-23 00:00:00\",\n    \"2029-12-31 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-02 00:00:00\",\n    \"2030-01-03 00:00:00\",\n    \"2030-01-14 00:00:00\",\n    \"2030-02-11 00:00:00\",\n    \"2030-02-23 00:00:00\",\n    \"2030-03-20 00:00:00\",\n    \"2030-04-29 00:00:00\",\n    \"2030-05-03 00:00:00\",\n    \"2030-05-04 00:00:00\",\n    \"2030-05-06 00:00:00\",\n    \"2030-07-15 00:00:00\",\n    \"2030-08-12 00:00:00\",\n    \"2030-09-16 00:00:00\",\n    \"2030-09-22 00:00:00\",\n    \"2030-10-14 00:00:00\",\n    \"2030-11-04 00:00:00\",\n    \"2030-11-23 00:00:00\",\n    \"2030-12-31 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-02 00:00:00\",\n    \"2031-01-03 00:00:00\",\n    \"2031-01-13 00:00:00\",\n    \"2031-02-11 00:00:00\",\n    \"2031-02-24 00:00:00\",\n    \"2031-03-20 00:00:00\",\n    \"2031-04-29 00:00:00\",\n    \"2031-05-03 00:00:00\",\n    \"2031-05-05 00:00:00\",\n    \"2031-05-06 00:00:00\",\n    \"2031-07-21 00:00:00\",\n    \"2031-08-11 00:00:00\",\n    \"2031-09-15 00:00:00\",\n    \"2031-09-23 00:00:00\",\n    \"2031-10-13 00:00:00\",\n    \"2031-11-03 00:00:00\",\n    \"2031-11-24 00:00:00\",\n    \"2031-12-31 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-02 00:00:00\",\n    \"2032-01-03 00:00:00\",\n    \"2032-01-12 00:00:00\",\n    \"2032-02-11 00:00:00\",\n    \"2032-02-23 00:00:00\",\n    \"2032-03-20 00:00:00\",\n    \"2032-04-29 00:00:00\",\n    \"2032-05-03 00:00:00\",\n    \"2032-05-04 00:00:00\",\n    \"2032-05-05 00:00:00\",\n    \"2032-07-19 00:00:00\",\n    \"2032-08-11 00:00:00\",\n    \"2032-09-20 00:00:00\",\n    \"2032-09-22 00:00:00\",\n    \"2032-10-11 00:00:00\",\n    \"2032-11-03 00:00:00\",\n    \"2032-11-23 00:00:00\",\n    \"2032-12-31 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-01-02 00:00:00\",\n    \"2033-01-03 00:00:00\",\n    \"2033-01-10 00:00:00\",\n    \"2033-02-11 00:00:00\",\n    \"2033-02-23 00:00:00\",\n    \"2033-03-20 00:00:00\",\n    \"2033-04-29 00:00:00\",\n    \"2033-05-03 00:00:00\",\n    \"2033-05-04 00:00:00\",\n    \"2033-05-05 00:00:00\",\n    \"2033-07-18 00:00:00\",\n    \"2033-08-11 00:00:00\",\n    \"2033-09-19 00:00:00\",\n    \"2033-09-22 00:00:00\",\n    \"2033-10-10 00:00:00\",\n    \"2033-11-03 00:00:00\",\n    \"2033-11-23 00:00:00\",\n    \"2033-12-31 00:00:00\",\n    \"2034-01-01 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-01-03 00:00:00\",\n    \"2034-01-09 00:00:00\",\n    \"2034-02-11 00:00:00\",\n    \"2034-02-23 00:00:00\",\n    \"2034-03-20 00:00:00\",\n    \"2034-04-29 00:00:00\",\n    \"2034-05-03 00:00:00\",\n    \"2034-05-04 00:00:00\",\n    \"2034-05-05 00:00:00\",\n    \"2034-07-17 00:00:00\",\n    \"2034-08-11 00:00:00\",\n    \"2034-09-18 00:00:00\",\n    \"2034-09-22 00:00:00\",\n    \"2034-10-09 00:00:00\",\n    \"2034-11-03 00:00:00\",\n    \"2034-11-23 00:00:00\",\n    \"2034-12-31 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-02 00:00:00\",\n    \"2035-01-03 00:00:00\",\n    \"2035-01-08 00:00:00\",\n    \"2035-02-12 00:00:00\",\n    \"2035-02-23 00:00:00\",\n    \"2035-03-20 00:00:00\",\n    \"2035-04-30 00:00:00\",\n    \"2035-05-03 00:00:00\",\n    \"2035-05-04 00:00:00\",\n    \"2035-05-05 00:00:00\",\n    \"2035-07-16 00:00:00\",\n    \"2035-08-11 00:00:00\",\n    \"2035-09-17 00:00:00\",\n    \"2035-09-23 00:00:00\",\n    \"2035-10-08 00:00:00\",\n    \"2035-11-03 00:00:00\",\n    \"2035-11-23 00:00:00\",\n    \"2035-12-31 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-02 00:00:00\",\n    \"2036-01-03 00:00:00\",\n    \"2036-01-14 00:00:00\",\n    \"2036-02-11 00:00:00\",\n    \"2036-02-23 00:00:00\",\n    \"2036-03-20 00:00:00\",\n    \"2036-04-29 00:00:00\",\n    \"2036-05-03 00:00:00\",\n    \"2036-05-05 00:00:00\",\n    \"2036-05-06 00:00:00\",\n    \"2036-07-21 00:00:00\",\n    \"2036-08-11 00:00:00\",\n    \"2036-09-15 00:00:00\",\n    \"2036-09-22 00:00:00\",\n    \"2036-10-13 00:00:00\",\n    \"2036-11-03 00:00:00\",\n    \"2036-11-24 00:00:00\",\n    \"2036-12-31 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-02 00:00:00\",\n    \"2037-01-03 00:00:00\",\n    \"2037-01-12 00:00:00\",\n    \"2037-02-11 00:00:00\",\n    \"2037-02-23 00:00:00\",\n    \"2037-03-20 00:00:00\",\n    \"2037-04-29 00:00:00\",\n    \"2037-05-04 00:00:00\",\n    \"2037-05-05 00:00:00\",\n    \"2037-05-06 00:00:00\",\n    \"2037-07-20 00:00:00\",\n    \"2037-08-11 00:00:00\",\n    \"2037-09-21 00:00:00\",\n    \"2037-09-22 00:00:00\",\n    \"2037-10-12 00:00:00\",\n    \"2037-11-03 00:00:00\",\n    \"2037-11-23 00:00:00\",\n    \"2037-12-31 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-02 00:00:00\",\n    \"2038-01-03 00:00:00\",\n    \"2038-01-11 00:00:00\",\n    \"2038-02-11 00:00:00\",\n    \"2038-02-23 00:00:00\",\n    \"2038-03-20 00:00:00\",\n    \"2038-04-29 00:00:00\",\n    \"2038-05-03 00:00:00\",\n    \"2038-05-04 00:00:00\",\n    \"2038-05-05 00:00:00\",\n    \"2038-07-19 00:00:00\",\n    \"2038-08-11 00:00:00\",\n    \"2038-09-20 00:00:00\",\n    \"2038-09-22 00:00:00\",\n    \"2038-10-11 00:00:00\",\n    \"2038-11-03 00:00:00\",\n    \"2038-11-23 00:00:00\",\n    \"2038-12-31 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-01-02 00:00:00\",\n    \"2039-01-03 00:00:00\",\n    \"2039-01-10 00:00:00\",\n    \"2039-02-11 00:00:00\",\n    \"2039-02-23 00:00:00\",\n    \"2039-03-20 00:00:00\",\n    \"2039-04-29 00:00:00\",\n    \"2039-05-03 00:00:00\",\n    \"2039-05-04 00:00:00\",\n    \"2039-05-05 00:00:00\",\n    \"2039-07-18 00:00:00\",\n    \"2039-08-11 00:00:00\",\n    \"2039-09-19 00:00:00\",\n    \"2039-09-23 00:00:00\",\n    \"2039-10-10 00:00:00\",\n    \"2039-11-03 00:00:00\",\n    \"2039-11-23 00:00:00\",\n    \"2039-12-31 00:00:00\",\n    \"2040-01-01 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-01-03 00:00:00\",\n    \"2040-01-09 00:00:00\",\n    \"2040-02-11 00:00:00\",\n    \"2040-02-23 00:00:00\",\n    \"2040-03-20 00:00:00\",\n    \"2040-04-30 00:00:00\",\n    \"2040-05-03 00:00:00\",\n    \"2040-05-04 00:00:00\",\n    \"2040-05-05 00:00:00\",\n    \"2040-07-16 00:00:00\",\n    \"2040-08-11 00:00:00\",\n    \"2040-09-17 00:00:00\",\n    \"2040-09-22 00:00:00\",\n    \"2040-10-08 00:00:00\",\n    \"2040-11-03 00:00:00\",\n    \"2040-11-23 00:00:00\",\n    \"2040-12-31 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-02 00:00:00\",\n    \"2041-01-03 00:00:00\",\n    \"2041-01-14 00:00:00\",\n    \"2041-02-11 00:00:00\",\n    \"2041-02-23 00:00:00\",\n    \"2041-03-20 00:00:00\",\n    \"2041-04-29 00:00:00\",\n    \"2041-05-03 00:00:00\",\n    \"2041-05-04 00:00:00\",\n    \"2041-05-06 00:00:00\",\n    \"2041-07-15 00:00:00\",\n    \"2041-08-12 00:00:00\",\n    \"2041-09-16 00:00:00\",\n    \"2041-09-22 00:00:00\",\n    \"2041-10-14 00:00:00\",\n    \"2041-11-04 00:00:00\",\n    \"2041-11-23 00:00:00\",\n    \"2041-12-31 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-02 00:00:00\",\n    \"2042-01-03 00:00:00\",\n    \"2042-01-13 00:00:00\",\n    \"2042-02-11 00:00:00\",\n    \"2042-02-24 00:00:00\",\n    \"2042-03-20 00:00:00\",\n    \"2042-04-29 00:00:00\",\n    \"2042-05-03 00:00:00\",\n    \"2042-05-05 00:00:00\",\n    \"2042-05-06 00:00:00\",\n    \"2042-07-21 00:00:00\",\n    \"2042-08-11 00:00:00\",\n    \"2042-09-15 00:00:00\",\n    \"2042-09-22 00:00:00\",\n    \"2042-10-13 00:00:00\",\n    \"2042-11-03 00:00:00\",\n    \"2042-11-24 00:00:00\",\n    \"2042-12-31 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-02 00:00:00\",\n    \"2043-01-03 00:00:00\",\n    \"2043-01-12 00:00:00\",\n    \"2043-02-11 00:00:00\",\n    \"2043-02-23 00:00:00\",\n    \"2043-03-20 00:00:00\",\n    \"2043-04-29 00:00:00\",\n    \"2043-05-04 00:00:00\",\n    \"2043-05-05 00:00:00\",\n    \"2043-05-06 00:00:00\",\n    \"2043-07-20 00:00:00\",\n    \"2043-08-11 00:00:00\",\n    \"2043-09-21 00:00:00\",\n    \"2043-09-23 00:00:00\",\n    \"2043-10-12 00:00:00\",\n    \"2043-11-03 00:00:00\",\n    \"2043-11-23 00:00:00\",\n    \"2043-12-31 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-02 00:00:00\",\n    \"2044-01-03 00:00:00\",\n    \"2044-01-11 00:00:00\",\n    \"2044-02-11 00:00:00\",\n    \"2044-02-23 00:00:00\",\n    \"2044-03-19 00:00:00\",\n    \"2044-04-29 00:00:00\",\n    \"2044-05-03 00:00:00\",\n    \"2044-05-04 00:00:00\",\n    \"2044-05-05 00:00:00\",\n    \"2044-07-18 00:00:00\",\n    \"2044-08-11 00:00:00\",\n    \"2044-09-19 00:00:00\",\n    \"2044-09-22 00:00:00\",\n    \"2044-10-10 00:00:00\",\n    \"2044-11-03 00:00:00\",\n    \"2044-11-23 00:00:00\",\n    \"2044-12-31 00:00:00\",\n    \"2045-01-01 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-01-03 00:00:00\",\n    \"2045-01-09 00:00:00\",\n    \"2045-02-11 00:00:00\",\n    \"2045-02-23 00:00:00\",\n    \"2045-03-20 00:00:00\",\n    \"2045-04-29 00:00:00\",\n    \"2045-05-03 00:00:00\",\n    \"2045-05-04 00:00:00\",\n    \"2045-05-05 00:00:00\",\n    \"2045-07-17 00:00:00\",\n    \"2045-08-11 00:00:00\",\n    \"2045-09-18 00:00:00\",\n    \"2045-09-22 00:00:00\",\n    \"2045-10-09 00:00:00\",\n    \"2045-11-03 00:00:00\",\n    \"2045-11-23 00:00:00\",\n    \"2045-12-31 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-02 00:00:00\",\n    \"2046-01-03 00:00:00\",\n    \"2046-01-08 00:00:00\",\n    \"2046-02-12 00:00:00\",\n    \"2046-02-23 00:00:00\",\n    \"2046-03-20 00:00:00\",\n    \"2046-04-30 00:00:00\",\n    \"2046-05-03 00:00:00\",\n    \"2046-05-04 00:00:00\",\n    \"2046-05-05 00:00:00\",\n    \"2046-07-16 00:00:00\",\n    \"2046-08-11 00:00:00\",\n    \"2046-09-17 00:00:00\",\n    \"2046-09-22 00:00:00\",\n    \"2046-10-08 00:00:00\",\n    \"2046-11-03 00:00:00\",\n    \"2046-11-23 00:00:00\",\n    \"2046-12-31 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-02 00:00:00\",\n    \"2047-01-03 00:00:00\",\n    \"2047-01-14 00:00:00\",\n    \"2047-02-11 00:00:00\",\n    \"2047-02-23 00:00:00\",\n    \"2047-03-20 00:00:00\",\n    \"2047-04-29 00:00:00\",\n    \"2047-05-03 00:00:00\",\n    \"2047-05-04 00:00:00\",\n    \"2047-05-06 00:00:00\",\n    \"2047-07-15 00:00:00\",\n    \"2047-08-12 00:00:00\",\n    \"2047-09-16 00:00:00\",\n    \"2047-09-23 00:00:00\",\n    \"2047-10-14 00:00:00\",\n    \"2047-11-04 00:00:00\",\n    \"2047-11-23 00:00:00\",\n    \"2047-12-31 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-02 00:00:00\",\n    \"2048-01-03 00:00:00\",\n    \"2048-01-13 00:00:00\",\n    \"2048-02-11 00:00:00\",\n    \"2048-02-24 00:00:00\",\n    \"2048-03-19 00:00:00\",\n    \"2048-04-29 00:00:00\",\n    \"2048-05-04 00:00:00\",\n    \"2048-05-05 00:00:00\",\n    \"2048-05-06 00:00:00\",\n    \"2048-07-20 00:00:00\",\n    \"2048-08-11 00:00:00\",\n    \"2048-09-21 00:00:00\",\n    \"2048-09-22 00:00:00\",\n    \"2048-10-12 00:00:00\",\n    \"2048-11-03 00:00:00\",\n    \"2048-11-23 00:00:00\",\n    \"2048-12-31 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-02 00:00:00\",\n    \"2049-01-03 00:00:00\",\n    \"2049-01-11 00:00:00\",\n    \"2049-02-11 00:00:00\",\n    \"2049-02-23 00:00:00\",\n    \"2049-03-20 00:00:00\",\n    \"2049-04-29 00:00:00\",\n    \"2049-05-03 00:00:00\",\n    \"2049-05-04 00:00:00\",\n    \"2049-05-05 00:00:00\",\n    \"2049-07-19 00:00:00\",\n    \"2049-08-11 00:00:00\",\n    \"2049-09-20 00:00:00\",\n    \"2049-09-22 00:00:00\",\n    \"2049-10-11 00:00:00\",\n    \"2049-11-03 00:00:00\",\n    \"2049-11-23 00:00:00\",\n    \"2049-12-31 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-01-02 00:00:00\",\n    \"2050-01-03 00:00:00\",\n    \"2050-01-10 00:00:00\",\n    \"2050-02-11 00:00:00\",\n    \"2050-02-23 00:00:00\",\n    \"2050-03-20 00:00:00\",\n    \"2050-04-29 00:00:00\",\n    \"2050-05-03 00:00:00\",\n    \"2050-05-04 00:00:00\",\n    \"2050-05-05 00:00:00\",\n    \"2050-07-18 00:00:00\",\n    \"2050-08-11 00:00:00\",\n    \"2050-09-19 00:00:00\",\n    \"2050-09-22 00:00:00\",\n    \"2050-10-10 00:00:00\",\n    \"2050-11-03 00:00:00\",\n    \"2050-11-23 00:00:00\",\n    \"2050-12-31 00:00:00\",\n    \"2051-01-01 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-01-03 00:00:00\",\n    \"2051-01-09 00:00:00\",\n    \"2051-02-11 00:00:00\",\n    \"2051-02-23 00:00:00\",\n    \"2051-03-20 00:00:00\",\n    \"2051-04-29 00:00:00\",\n    \"2051-05-03 00:00:00\",\n    \"2051-05-04 00:00:00\",\n    \"2051-05-05 00:00:00\",\n    \"2051-07-17 00:00:00\",\n    \"2051-08-11 00:00:00\",\n    \"2051-09-18 00:00:00\",\n    \"2051-09-23 00:00:00\",\n    \"2051-10-09 00:00:00\",\n    \"2051-11-03 00:00:00\",\n    \"2051-11-23 00:00:00\",\n    \"2051-12-31 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-02 00:00:00\",\n    \"2052-01-03 00:00:00\",\n    \"2052-01-08 00:00:00\",\n    \"2052-02-12 00:00:00\",\n    \"2052-02-23 00:00:00\",\n    \"2052-03-19 00:00:00\",\n    \"2052-04-29 00:00:00\",\n    \"2052-05-03 00:00:00\",\n    \"2052-05-04 00:00:00\",\n    \"2052-05-06 00:00:00\",\n    \"2052-07-15 00:00:00\",\n    \"2052-08-12 00:00:00\",\n    \"2052-09-16 00:00:00\",\n    \"2052-09-22 00:00:00\",\n    \"2052-10-14 00:00:00\",\n    \"2052-11-04 00:00:00\",\n    \"2052-11-23 00:00:00\",\n    \"2052-12-31 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-02 00:00:00\",\n    \"2053-01-03 00:00:00\",\n    \"2053-01-13 00:00:00\",\n    \"2053-02-11 00:00:00\",\n    \"2053-02-24 00:00:00\",\n    \"2053-03-20 00:00:00\",\n    \"2053-04-29 00:00:00\",\n    \"2053-05-03 00:00:00\",\n    \"2053-05-05 00:00:00\",\n    \"2053-05-06 00:00:00\",\n    \"2053-07-21 00:00:00\",\n    \"2053-08-11 00:00:00\",\n    \"2053-09-15 00:00:00\",\n    \"2053-09-22 00:00:00\",\n    \"2053-10-13 00:00:00\",\n    \"2053-11-03 00:00:00\",\n    \"2053-11-24 00:00:00\",\n    \"2053-12-31 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-02 00:00:00\",\n    \"2054-01-03 00:00:00\",\n    \"2054-01-12 00:00:00\",\n    \"2054-02-11 00:00:00\",\n    \"2054-02-23 00:00:00\",\n    \"2054-03-20 00:00:00\",\n    \"2054-04-29 00:00:00\",\n    \"2054-05-04 00:00:00\",\n    \"2054-05-05 00:00:00\",\n    \"2054-05-06 00:00:00\",\n    \"2054-07-20 00:00:00\",\n    \"2054-08-11 00:00:00\",\n    \"2054-09-21 00:00:00\",\n    \"2054-09-22 00:00:00\",\n    \"2054-10-12 00:00:00\",\n    \"2054-11-03 00:00:00\",\n    \"2054-11-23 00:00:00\",\n    \"2054-12-31 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-02 00:00:00\",\n    \"2055-01-03 00:00:00\",\n    \"2055-01-11 00:00:00\",\n    \"2055-02-11 00:00:00\",\n    \"2055-02-23 00:00:00\",\n    \"2055-03-20 00:00:00\",\n    \"2055-04-29 00:00:00\",\n    \"2055-05-03 00:00:00\",\n    \"2055-05-04 00:00:00\",\n    \"2055-05-05 00:00:00\",\n    \"2055-07-19 00:00:00\",\n    \"2055-08-11 00:00:00\",\n    \"2055-09-20 00:00:00\",\n    \"2055-09-23 00:00:00\",\n    \"2055-10-11 00:00:00\",\n    \"2055-11-03 00:00:00\",\n    \"2055-11-23 00:00:00\",\n    \"2055-12-31 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-01-02 00:00:00\",\n    \"2056-01-03 00:00:00\",\n    \"2056-01-10 00:00:00\",\n    \"2056-02-11 00:00:00\",\n    \"2056-02-23 00:00:00\",\n    \"2056-03-19 00:00:00\",\n    \"2056-04-29 00:00:00\",\n    \"2056-05-03 00:00:00\",\n    \"2056-05-04 00:00:00\",\n    \"2056-05-05 00:00:00\",\n    \"2056-07-17 00:00:00\",\n    \"2056-08-11 00:00:00\",\n    \"2056-09-18 00:00:00\",\n    \"2056-09-22 00:00:00\",\n    \"2056-10-09 00:00:00\",\n    \"2056-11-03 00:00:00\",\n    \"2056-11-23 00:00:00\",\n    \"2056-12-31 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-02 00:00:00\",\n    \"2057-01-03 00:00:00\",\n    \"2057-01-08 00:00:00\",\n    \"2057-02-12 00:00:00\",\n    \"2057-02-23 00:00:00\",\n    \"2057-03-20 00:00:00\",\n    \"2057-04-30 00:00:00\",\n    \"2057-05-03 00:00:00\",\n    \"2057-05-04 00:00:00\",\n    \"2057-05-05 00:00:00\",\n    \"2057-07-16 00:00:00\",\n    \"2057-08-11 00:00:00\",\n    \"2057-09-17 00:00:00\",\n    \"2057-09-22 00:00:00\",\n    \"2057-10-08 00:00:00\",\n    \"2057-11-03 00:00:00\",\n    \"2057-11-23 00:00:00\",\n    \"2057-12-31 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-02 00:00:00\",\n    \"2058-01-03 00:00:00\",\n    \"2058-01-14 00:00:00\",\n    \"2058-02-11 00:00:00\",\n    \"2058-02-23 00:00:00\",\n    \"2058-03-20 00:00:00\",\n    \"2058-04-29 00:00:00\",\n    \"2058-05-03 00:00:00\",\n    \"2058-05-04 00:00:00\",\n    \"2058-05-06 00:00:00\",\n    \"2058-07-15 00:00:00\",\n    \"2058-08-12 00:00:00\",\n    \"2058-09-16 00:00:00\",\n    \"2058-09-22 00:00:00\",\n    \"2058-10-14 00:00:00\",\n    \"2058-11-04 00:00:00\",\n    \"2058-11-23 00:00:00\",\n    \"2058-12-31 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-02 00:00:00\",\n    \"2059-01-03 00:00:00\",\n    \"2059-01-13 00:00:00\",\n    \"2059-02-11 00:00:00\",\n    \"2059-02-24 00:00:00\",\n    \"2059-03-20 00:00:00\",\n    \"2059-04-29 00:00:00\",\n    \"2059-05-03 00:00:00\",\n    \"2059-05-05 00:00:00\",\n    \"2059-05-06 00:00:00\",\n    \"2059-07-21 00:00:00\",\n    \"2059-08-11 00:00:00\",\n    \"2059-09-15 00:00:00\",\n    \"2059-09-23 00:00:00\",\n    \"2059-10-13 00:00:00\",\n    \"2059-11-03 00:00:00\",\n    \"2059-11-24 00:00:00\",\n    \"2059-12-31 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-02 00:00:00\",\n    \"2060-01-03 00:00:00\",\n    \"2060-01-12 00:00:00\",\n    \"2060-02-11 00:00:00\",\n    \"2060-02-23 00:00:00\",\n    \"2060-03-19 00:00:00\",\n    \"2060-04-29 00:00:00\",\n    \"2060-05-03 00:00:00\",\n    \"2060-05-04 00:00:00\",\n    \"2060-05-05 00:00:00\",\n    \"2060-07-19 00:00:00\",\n    \"2060-08-11 00:00:00\",\n    \"2060-09-20 00:00:00\",\n    \"2060-09-22 00:00:00\",\n    \"2060-10-11 00:00:00\",\n    \"2060-11-03 00:00:00\",\n    \"2060-11-23 00:00:00\",\n    \"2060-12-31 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-01-02 00:00:00\",\n    \"2061-01-03 00:00:00\",\n    \"2061-01-10 00:00:00\",\n    \"2061-02-11 00:00:00\",\n    \"2061-02-23 00:00:00\",\n    \"2061-03-20 00:00:00\",\n    \"2061-04-29 00:00:00\",\n    \"2061-05-03 00:00:00\",\n    \"2061-05-04 00:00:00\",\n    \"2061-05-05 00:00:00\",\n    \"2061-07-18 00:00:00\",\n    \"2061-08-11 00:00:00\",\n    \"2061-09-19 00:00:00\",\n    \"2061-09-22 00:00:00\",\n    \"2061-10-10 00:00:00\",\n    \"2061-11-03 00:00:00\",\n    \"2061-11-23 00:00:00\",\n    \"2061-12-31 00:00:00\",\n    \"2062-01-01 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-01-03 00:00:00\",\n    \"2062-01-09 00:00:00\",\n    \"2062-02-11 00:00:00\",\n    \"2062-02-23 00:00:00\",\n    \"2062-03-20 00:00:00\",\n    \"2062-04-29 00:00:00\",\n    \"2062-05-03 00:00:00\",\n    \"2062-05-04 00:00:00\",\n    \"2062-05-05 00:00:00\",\n    \"2062-07-17 00:00:00\",\n    \"2062-08-11 00:00:00\",\n    \"2062-09-18 00:00:00\",\n    \"2062-09-22 00:00:00\",\n    \"2062-10-09 00:00:00\",\n    \"2062-11-03 00:00:00\",\n    \"2062-11-23 00:00:00\",\n    \"2062-12-31 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-02 00:00:00\",\n    \"2063-01-03 00:00:00\",\n    \"2063-01-08 00:00:00\",\n    \"2063-02-12 00:00:00\",\n    \"2063-02-23 00:00:00\",\n    \"2063-03-20 00:00:00\",\n    \"2063-04-30 00:00:00\",\n    \"2063-05-03 00:00:00\",\n    \"2063-05-04 00:00:00\",\n    \"2063-05-05 00:00:00\",\n    \"2063-07-16 00:00:00\",\n    \"2063-08-11 00:00:00\",\n    \"2063-09-17 00:00:00\",\n    \"2063-09-22 00:00:00\",\n    \"2063-10-08 00:00:00\",\n    \"2063-11-03 00:00:00\",\n    \"2063-11-23 00:00:00\",\n    \"2063-12-31 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-02 00:00:00\",\n    \"2064-01-03 00:00:00\",\n    \"2064-01-14 00:00:00\",\n    \"2064-02-11 00:00:00\",\n    \"2064-02-23 00:00:00\",\n    \"2064-03-19 00:00:00\",\n    \"2064-04-29 00:00:00\",\n    \"2064-05-03 00:00:00\",\n    \"2064-05-05 00:00:00\",\n    \"2064-05-06 00:00:00\",\n    \"2064-07-21 00:00:00\",\n    \"2064-08-11 00:00:00\",\n    \"2064-09-15 00:00:00\",\n    \"2064-09-22 00:00:00\",\n    \"2064-10-13 00:00:00\",\n    \"2064-11-03 00:00:00\",\n    \"2064-11-24 00:00:00\",\n    \"2064-12-31 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-02 00:00:00\",\n    \"2065-01-03 00:00:00\",\n    \"2065-01-12 00:00:00\",\n    \"2065-02-11 00:00:00\",\n    \"2065-02-23 00:00:00\",\n    \"2065-03-20 00:00:00\",\n    \"2065-04-29 00:00:00\",\n    \"2065-05-04 00:00:00\",\n    \"2065-05-05 00:00:00\",\n    \"2065-05-06 00:00:00\",\n    \"2065-07-20 00:00:00\",\n    \"2065-08-11 00:00:00\",\n    \"2065-09-21 00:00:00\",\n    \"2065-09-22 00:00:00\",\n    \"2065-10-12 00:00:00\",\n    \"2065-11-03 00:00:00\",\n    \"2065-11-23 00:00:00\",\n    \"2065-12-31 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-02 00:00:00\",\n    \"2066-01-03 00:00:00\",\n    \"2066-01-11 00:00:00\",\n    \"2066-02-11 00:00:00\",\n    \"2066-02-23 00:00:00\",\n    \"2066-03-20 00:00:00\",\n    \"2066-04-29 00:00:00\",\n    \"2066-05-03 00:00:00\",\n    \"2066-05-04 00:00:00\",\n    \"2066-05-05 00:00:00\",\n    \"2066-07-19 00:00:00\",\n    \"2066-08-11 00:00:00\",\n    \"2066-09-20 00:00:00\",\n    \"2066-09-22 00:00:00\",\n    \"2066-10-11 00:00:00\",\n    \"2066-11-03 00:00:00\",\n    \"2066-11-23 00:00:00\",\n    \"2066-12-31 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-01-02 00:00:00\",\n    \"2067-01-03 00:00:00\",\n    \"2067-01-10 00:00:00\",\n    \"2067-02-11 00:00:00\",\n    \"2067-02-23 00:00:00\",\n    \"2067-03-20 00:00:00\",\n    \"2067-04-29 00:00:00\",\n    \"2067-05-03 00:00:00\",\n    \"2067-05-04 00:00:00\",\n    \"2067-05-05 00:00:00\",\n    \"2067-07-18 00:00:00\",\n    \"2067-08-11 00:00:00\",\n    \"2067-09-19 00:00:00\",\n    \"2067-09-22 00:00:00\",\n    \"2067-10-10 00:00:00\",\n    \"2067-11-03 00:00:00\",\n    \"2067-11-23 00:00:00\",\n    \"2067-12-31 00:00:00\",\n    \"2068-01-01 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-01-03 00:00:00\",\n    \"2068-01-09 00:00:00\",\n    \"2068-02-11 00:00:00\",\n    \"2068-02-23 00:00:00\",\n    \"2068-03-19 00:00:00\",\n    \"2068-04-30 00:00:00\",\n    \"2068-05-03 00:00:00\",\n    \"2068-05-04 00:00:00\",\n    \"2068-05-05 00:00:00\",\n    \"2068-07-16 00:00:00\",\n    \"2068-08-11 00:00:00\",\n    \"2068-09-17 00:00:00\",\n    \"2068-09-22 00:00:00\",\n    \"2068-10-08 00:00:00\",\n    \"2068-11-03 00:00:00\",\n    \"2068-11-23 00:00:00\",\n    \"2068-12-31 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-02 00:00:00\",\n    \"2069-01-03 00:00:00\",\n    \"2069-01-14 00:00:00\",\n    \"2069-02-11 00:00:00\",\n    \"2069-02-23 00:00:00\",\n    \"2069-03-20 00:00:00\",\n    \"2069-04-29 00:00:00\",\n    \"2069-05-03 00:00:00\",\n    \"2069-05-04 00:00:00\",\n    \"2069-05-06 00:00:00\",\n    \"2069-07-15 00:00:00\",\n    \"2069-08-12 00:00:00\",\n    \"2069-09-16 00:00:00\",\n    \"2069-09-22 00:00:00\",\n    \"2069-10-14 00:00:00\",\n    \"2069-11-04 00:00:00\",\n    \"2069-11-23 00:00:00\",\n    \"2069-12-31 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-02 00:00:00\",\n    \"2070-01-03 00:00:00\",\n    \"2070-01-13 00:00:00\",\n    \"2070-02-11 00:00:00\",\n    \"2070-02-24 00:00:00\",\n    \"2070-03-20 00:00:00\",\n    \"2070-04-29 00:00:00\",\n    \"2070-05-03 00:00:00\",\n    \"2070-05-05 00:00:00\",\n    \"2070-05-06 00:00:00\",\n    \"2070-07-21 00:00:00\",\n    \"2070-08-11 00:00:00\",\n    \"2070-09-15 00:00:00\",\n    \"2070-09-22 00:00:00\",\n    \"2070-10-13 00:00:00\",\n    \"2070-11-03 00:00:00\",\n    \"2070-11-24 00:00:00\",\n    \"2070-12-31 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-02 00:00:00\",\n    \"2071-01-03 00:00:00\",\n    \"2071-01-12 00:00:00\",\n    \"2071-02-11 00:00:00\",\n    \"2071-02-23 00:00:00\",\n    \"2071-03-20 00:00:00\",\n    \"2071-04-29 00:00:00\",\n    \"2071-05-04 00:00:00\",\n    \"2071-05-05 00:00:00\",\n    \"2071-05-06 00:00:00\",\n    \"2071-07-20 00:00:00\",\n    \"2071-08-11 00:00:00\",\n    \"2071-09-21 00:00:00\",\n    \"2071-09-22 00:00:00\",\n    \"2071-10-12 00:00:00\",\n    \"2071-11-03 00:00:00\",\n    \"2071-11-23 00:00:00\",\n    \"2071-12-31 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-02 00:00:00\",\n    \"2072-01-03 00:00:00\",\n    \"2072-01-11 00:00:00\",\n    \"2072-02-11 00:00:00\",\n    \"2072-02-23 00:00:00\",\n    \"2072-03-19 00:00:00\",\n    \"2072-04-29 00:00:00\",\n    \"2072-05-03 00:00:00\",\n    \"2072-05-04 00:00:00\",\n    \"2072-05-05 00:00:00\",\n    \"2072-07-18 00:00:00\",\n    \"2072-08-11 00:00:00\",\n    \"2072-09-19 00:00:00\",\n    \"2072-09-22 00:00:00\",\n    \"2072-10-10 00:00:00\",\n    \"2072-11-03 00:00:00\",\n    \"2072-11-23 00:00:00\",\n    \"2072-12-31 00:00:00\",\n    \"2073-01-01 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-01-03 00:00:00\",\n    \"2073-01-09 00:00:00\",\n    \"2073-02-11 00:00:00\",\n    \"2073-02-23 00:00:00\",\n    \"2073-03-20 00:00:00\",\n    \"2073-04-29 00:00:00\",\n    \"2073-05-03 00:00:00\",\n    \"2073-05-04 00:00:00\",\n    \"2073-05-05 00:00:00\",\n    \"2073-07-17 00:00:00\",\n    \"2073-08-11 00:00:00\",\n    \"2073-09-18 00:00:00\",\n    \"2073-09-22 00:00:00\",\n    \"2073-10-09 00:00:00\",\n    \"2073-11-03 00:00:00\",\n    \"2073-11-23 00:00:00\",\n    \"2073-12-31 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-02 00:00:00\",\n    \"2074-01-03 00:00:00\",\n    \"2074-01-08 00:00:00\",\n    \"2074-02-12 00:00:00\",\n    \"2074-02-23 00:00:00\",\n    \"2074-03-20 00:00:00\",\n    \"2074-04-30 00:00:00\",\n    \"2074-05-03 00:00:00\",\n    \"2074-05-04 00:00:00\",\n    \"2074-05-05 00:00:00\",\n    \"2074-07-16 00:00:00\",\n    \"2074-08-11 00:00:00\",\n    \"2074-09-17 00:00:00\",\n    \"2074-09-22 00:00:00\",\n    \"2074-10-08 00:00:00\",\n    \"2074-11-03 00:00:00\",\n    \"2074-11-23 00:00:00\",\n    \"2074-12-31 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-02 00:00:00\",\n    \"2075-01-03 00:00:00\",\n    \"2075-01-14 00:00:00\",\n    \"2075-02-11 00:00:00\",\n    \"2075-02-23 00:00:00\",\n    \"2075-03-20 00:00:00\",\n    \"2075-04-29 00:00:00\",\n    \"2075-05-03 00:00:00\",\n    \"2075-05-04 00:00:00\",\n    \"2075-05-06 00:00:00\",\n    \"2075-07-15 00:00:00\",\n    \"2075-08-12 00:00:00\",\n    \"2075-09-16 00:00:00\",\n    \"2075-09-22 00:00:00\",\n    \"2075-10-14 00:00:00\",\n    \"2075-11-04 00:00:00\",\n    \"2075-11-23 00:00:00\",\n    \"2075-12-31 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-02 00:00:00\",\n    \"2076-01-03 00:00:00\",\n    \"2076-01-13 00:00:00\",\n    \"2076-02-11 00:00:00\",\n    \"2076-02-24 00:00:00\",\n    \"2076-03-19 00:00:00\",\n    \"2076-04-29 00:00:00\",\n    \"2076-05-04 00:00:00\",\n    \"2076-05-05 00:00:00\",\n    \"2076-05-06 00:00:00\",\n    \"2076-07-20 00:00:00\",\n    \"2076-08-11 00:00:00\",\n    \"2076-09-21 00:00:00\",\n    \"2076-09-22 00:00:00\",\n    \"2076-10-12 00:00:00\",\n    \"2076-11-03 00:00:00\",\n    \"2076-11-23 00:00:00\",\n    \"2076-12-31 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-02 00:00:00\",\n    \"2077-01-03 00:00:00\",\n    \"2077-01-11 00:00:00\",\n    \"2077-02-11 00:00:00\",\n    \"2077-02-23 00:00:00\",\n    \"2077-03-19 00:00:00\",\n    \"2077-04-29 00:00:00\",\n    \"2077-05-03 00:00:00\",\n    \"2077-05-04 00:00:00\",\n    \"2077-05-05 00:00:00\",\n    \"2077-07-19 00:00:00\",\n    \"2077-08-11 00:00:00\",\n    \"2077-09-20 00:00:00\",\n    \"2077-09-22 00:00:00\",\n    \"2077-10-11 00:00:00\",\n    \"2077-11-03 00:00:00\",\n    \"2077-11-23 00:00:00\",\n    \"2077-12-31 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-01-02 00:00:00\",\n    \"2078-01-03 00:00:00\",\n    \"2078-01-10 00:00:00\",\n    \"2078-02-11 00:00:00\",\n    \"2078-02-23 00:00:00\",\n    \"2078-03-20 00:00:00\",\n    \"2078-04-29 00:00:00\",\n    \"2078-05-03 00:00:00\",\n    \"2078-05-04 00:00:00\",\n    \"2078-05-05 00:00:00\",\n    \"2078-07-18 00:00:00\",\n    \"2078-08-11 00:00:00\",\n    \"2078-09-19 00:00:00\",\n    \"2078-09-22 00:00:00\",\n    \"2078-10-10 00:00:00\",\n    \"2078-11-03 00:00:00\",\n    \"2078-11-23 00:00:00\",\n    \"2078-12-31 00:00:00\",\n    \"2079-01-01 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-01-03 00:00:00\",\n    \"2079-01-09 00:00:00\",\n    \"2079-02-11 00:00:00\",\n    \"2079-02-23 00:00:00\",\n    \"2079-03-20 00:00:00\",\n    \"2079-04-29 00:00:00\",\n    \"2079-05-03 00:00:00\",\n    \"2079-05-04 00:00:00\",\n    \"2079-05-05 00:00:00\",\n    \"2079-07-17 00:00:00\",\n    \"2079-08-11 00:00:00\",\n    \"2079-09-18 00:00:00\",\n    \"2079-09-22 00:00:00\",\n    \"2079-10-09 00:00:00\",\n    \"2079-11-03 00:00:00\",\n    \"2079-11-23 00:00:00\",\n    \"2079-12-31 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-02 00:00:00\",\n    \"2080-01-03 00:00:00\",\n    \"2080-01-08 00:00:00\",\n    \"2080-02-12 00:00:00\",\n    \"2080-02-23 00:00:00\",\n    \"2080-03-19 00:00:00\",\n    \"2080-04-29 00:00:00\",\n    \"2080-05-03 00:00:00\",\n    \"2080-05-04 00:00:00\",\n    \"2080-05-06 00:00:00\",\n    \"2080-07-15 00:00:00\",\n    \"2080-08-12 00:00:00\",\n    \"2080-09-16 00:00:00\",\n    \"2080-09-22 00:00:00\",\n    \"2080-10-14 00:00:00\",\n    \"2080-11-04 00:00:00\",\n    \"2080-11-23 00:00:00\",\n    \"2080-12-31 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-02 00:00:00\",\n    \"2081-01-03 00:00:00\",\n    \"2081-01-13 00:00:00\",\n    \"2081-02-11 00:00:00\",\n    \"2081-02-24 00:00:00\",\n    \"2081-03-19 00:00:00\",\n    \"2081-04-29 00:00:00\",\n    \"2081-05-03 00:00:00\",\n    \"2081-05-05 00:00:00\",\n    \"2081-05-06 00:00:00\",\n    \"2081-07-21 00:00:00\",\n    \"2081-08-11 00:00:00\",\n    \"2081-09-15 00:00:00\",\n    \"2081-09-22 00:00:00\",\n    \"2081-10-13 00:00:00\",\n    \"2081-11-03 00:00:00\",\n    \"2081-11-24 00:00:00\",\n    \"2081-12-31 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-02 00:00:00\",\n    \"2082-01-03 00:00:00\",\n    \"2082-01-12 00:00:00\",\n    \"2082-02-11 00:00:00\",\n    \"2082-02-23 00:00:00\",\n    \"2082-03-20 00:00:00\",\n    \"2082-04-29 00:00:00\",\n    \"2082-05-04 00:00:00\",\n    \"2082-05-05 00:00:00\",\n    \"2082-05-06 00:00:00\",\n    \"2082-07-20 00:00:00\",\n    \"2082-08-11 00:00:00\",\n    \"2082-09-21 00:00:00\",\n    \"2082-09-22 00:00:00\",\n    \"2082-10-12 00:00:00\",\n    \"2082-11-03 00:00:00\",\n    \"2082-11-23 00:00:00\",\n    \"2082-12-31 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-02 00:00:00\",\n    \"2083-01-03 00:00:00\",\n    \"2083-01-11 00:00:00\",\n    \"2083-02-11 00:00:00\",\n    \"2083-02-23 00:00:00\",\n    \"2083-03-20 00:00:00\",\n    \"2083-04-29 00:00:00\",\n    \"2083-05-03 00:00:00\",\n    \"2083-05-04 00:00:00\",\n    \"2083-05-05 00:00:00\",\n    \"2083-07-19 00:00:00\",\n    \"2083-08-11 00:00:00\",\n    \"2083-09-20 00:00:00\",\n    \"2083-09-22 00:00:00\",\n    \"2083-10-11 00:00:00\",\n    \"2083-11-03 00:00:00\",\n    \"2083-11-23 00:00:00\",\n    \"2083-12-31 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-01-02 00:00:00\",\n    \"2084-01-03 00:00:00\",\n    \"2084-01-10 00:00:00\",\n    \"2084-02-11 00:00:00\",\n    \"2084-02-23 00:00:00\",\n    \"2084-03-19 00:00:00\",\n    \"2084-04-29 00:00:00\",\n    \"2084-05-03 00:00:00\",\n    \"2084-05-04 00:00:00\",\n    \"2084-05-05 00:00:00\",\n    \"2084-07-17 00:00:00\",\n    \"2084-08-11 00:00:00\",\n    \"2084-09-18 00:00:00\",\n    \"2084-09-22 00:00:00\",\n    \"2084-10-09 00:00:00\",\n    \"2084-11-03 00:00:00\",\n    \"2084-11-23 00:00:00\",\n    \"2084-12-31 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-02 00:00:00\",\n    \"2085-01-03 00:00:00\",\n    \"2085-01-08 00:00:00\",\n    \"2085-02-12 00:00:00\",\n    \"2085-02-23 00:00:00\",\n    \"2085-03-19 00:00:00\",\n    \"2085-04-30 00:00:00\",\n    \"2085-05-03 00:00:00\",\n    \"2085-05-04 00:00:00\",\n    \"2085-05-05 00:00:00\",\n    \"2085-07-16 00:00:00\",\n    \"2085-08-11 00:00:00\",\n    \"2085-09-17 00:00:00\",\n    \"2085-09-22 00:00:00\",\n    \"2085-10-08 00:00:00\",\n    \"2085-11-03 00:00:00\",\n    \"2085-11-23 00:00:00\",\n    \"2085-12-31 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-02 00:00:00\",\n    \"2086-01-03 00:00:00\",\n    \"2086-01-14 00:00:00\",\n    \"2086-02-11 00:00:00\",\n    \"2086-02-23 00:00:00\",\n    \"2086-03-20 00:00:00\",\n    \"2086-04-29 00:00:00\",\n    \"2086-05-03 00:00:00\",\n    \"2086-05-04 00:00:00\",\n    \"2086-05-06 00:00:00\",\n    \"2086-07-15 00:00:00\",\n    \"2086-08-12 00:00:00\",\n    \"2086-09-16 00:00:00\",\n    \"2086-09-22 00:00:00\",\n    \"2086-10-14 00:00:00\",\n    \"2086-11-04 00:00:00\",\n    \"2086-11-23 00:00:00\",\n    \"2086-12-31 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-02 00:00:00\",\n    \"2087-01-03 00:00:00\",\n    \"2087-01-13 00:00:00\",\n    \"2087-02-11 00:00:00\",\n    \"2087-02-24 00:00:00\",\n    \"2087-03-20 00:00:00\",\n    \"2087-04-29 00:00:00\",\n    \"2087-05-03 00:00:00\",\n    \"2087-05-05 00:00:00\",\n    \"2087-05-06 00:00:00\",\n    \"2087-07-21 00:00:00\",\n    \"2087-08-11 00:00:00\",\n    \"2087-09-15 00:00:00\",\n    \"2087-09-22 00:00:00\",\n    \"2087-10-13 00:00:00\",\n    \"2087-11-03 00:00:00\",\n    \"2087-11-24 00:00:00\",\n    \"2087-12-31 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-02 00:00:00\",\n    \"2088-01-03 00:00:00\",\n    \"2088-01-12 00:00:00\",\n    \"2088-02-11 00:00:00\",\n    \"2088-02-23 00:00:00\",\n    \"2088-03-19 00:00:00\",\n    \"2088-04-29 00:00:00\",\n    \"2088-05-03 00:00:00\",\n    \"2088-05-04 00:00:00\",\n    \"2088-05-05 00:00:00\",\n    \"2088-07-19 00:00:00\",\n    \"2088-08-11 00:00:00\",\n    \"2088-09-20 00:00:00\",\n    \"2088-09-22 00:00:00\",\n    \"2088-10-11 00:00:00\",\n    \"2088-11-03 00:00:00\",\n    \"2088-11-23 00:00:00\",\n    \"2088-12-31 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-01-02 00:00:00\",\n    \"2089-01-03 00:00:00\",\n    \"2089-01-10 00:00:00\",\n    \"2089-02-11 00:00:00\",\n    \"2089-02-23 00:00:00\",\n    \"2089-03-19 00:00:00\",\n    \"2089-04-29 00:00:00\",\n    \"2089-05-03 00:00:00\",\n    \"2089-05-04 00:00:00\",\n    \"2089-05-05 00:00:00\",\n    \"2089-07-18 00:00:00\",\n    \"2089-08-11 00:00:00\",\n    \"2089-09-19 00:00:00\",\n    \"2089-09-22 00:00:00\",\n    \"2089-10-10 00:00:00\",\n    \"2089-11-03 00:00:00\",\n    \"2089-11-23 00:00:00\",\n    \"2089-12-31 00:00:00\",\n    \"2090-01-01 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-01-03 00:00:00\",\n    \"2090-01-09 00:00:00\",\n    \"2090-02-11 00:00:00\",\n    \"2090-02-23 00:00:00\",\n    \"2090-03-20 00:00:00\",\n    \"2090-04-29 00:00:00\",\n    \"2090-05-03 00:00:00\",\n    \"2090-05-04 00:00:00\",\n    \"2090-05-05 00:00:00\",\n    \"2090-07-17 00:00:00\",\n    \"2090-08-11 00:00:00\",\n    \"2090-09-18 00:00:00\",\n    \"2090-09-22 00:00:00\",\n    \"2090-10-09 00:00:00\",\n    \"2090-11-03 00:00:00\",\n    \"2090-11-23 00:00:00\",\n    \"2090-12-31 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-02 00:00:00\",\n    \"2091-01-03 00:00:00\",\n    \"2091-01-08 00:00:00\",\n    \"2091-02-12 00:00:00\",\n    \"2091-02-23 00:00:00\",\n    \"2091-03-20 00:00:00\",\n    \"2091-04-30 00:00:00\",\n    \"2091-05-03 00:00:00\",\n    \"2091-05-04 00:00:00\",\n    \"2091-05-05 00:00:00\",\n    \"2091-07-16 00:00:00\",\n    \"2091-08-11 00:00:00\",\n    \"2091-09-17 00:00:00\",\n    \"2091-09-22 00:00:00\",\n    \"2091-10-08 00:00:00\",\n    \"2091-11-03 00:00:00\",\n    \"2091-11-23 00:00:00\",\n    \"2091-12-31 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-02 00:00:00\",\n    \"2092-01-03 00:00:00\",\n    \"2092-01-14 00:00:00\",\n    \"2092-02-11 00:00:00\",\n    \"2092-02-23 00:00:00\",\n    \"2092-03-19 00:00:00\",\n    \"2092-04-29 00:00:00\",\n    \"2092-05-03 00:00:00\",\n    \"2092-05-05 00:00:00\",\n    \"2092-05-06 00:00:00\",\n    \"2092-07-21 00:00:00\",\n    \"2092-08-11 00:00:00\",\n    \"2092-09-15 00:00:00\",\n    \"2092-09-21 00:00:00\",\n    \"2092-10-13 00:00:00\",\n    \"2092-11-03 00:00:00\",\n    \"2092-11-24 00:00:00\",\n    \"2092-12-31 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-02 00:00:00\",\n    \"2093-01-03 00:00:00\",\n    \"2093-01-12 00:00:00\",\n    \"2093-02-11 00:00:00\",\n    \"2093-02-23 00:00:00\",\n    \"2093-03-19 00:00:00\",\n    \"2093-04-29 00:00:00\",\n    \"2093-05-04 00:00:00\",\n    \"2093-05-05 00:00:00\",\n    \"2093-05-06 00:00:00\",\n    \"2093-07-20 00:00:00\",\n    \"2093-08-11 00:00:00\",\n    \"2093-09-21 00:00:00\",\n    \"2093-09-22 00:00:00\",\n    \"2093-10-12 00:00:00\",\n    \"2093-11-03 00:00:00\",\n    \"2093-11-23 00:00:00\",\n    \"2093-12-31 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-02 00:00:00\",\n    \"2094-01-03 00:00:00\",\n    \"2094-01-11 00:00:00\",\n    \"2094-02-11 00:00:00\",\n    \"2094-02-23 00:00:00\",\n    \"2094-03-20 00:00:00\",\n    \"2094-04-29 00:00:00\",\n    \"2094-05-03 00:00:00\",\n    \"2094-05-04 00:00:00\",\n    \"2094-05-05 00:00:00\",\n    \"2094-07-19 00:00:00\",\n    \"2094-08-11 00:00:00\",\n    \"2094-09-20 00:00:00\",\n    \"2094-09-22 00:00:00\",\n    \"2094-10-11 00:00:00\",\n    \"2094-11-03 00:00:00\",\n    \"2094-11-23 00:00:00\",\n    \"2094-12-31 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-01-02 00:00:00\",\n    \"2095-01-03 00:00:00\",\n    \"2095-01-10 00:00:00\",\n    \"2095-02-11 00:00:00\",\n    \"2095-02-23 00:00:00\",\n    \"2095-03-20 00:00:00\",\n    \"2095-04-29 00:00:00\",\n    \"2095-05-03 00:00:00\",\n    \"2095-05-04 00:00:00\",\n    \"2095-05-05 00:00:00\",\n    \"2095-07-18 00:00:00\",\n    \"2095-08-11 00:00:00\",\n    \"2095-09-19 00:00:00\",\n    \"2095-09-22 00:00:00\",\n    \"2095-10-10 00:00:00\",\n    \"2095-11-03 00:00:00\",\n    \"2095-11-23 00:00:00\",\n    \"2095-12-31 00:00:00\",\n    \"2096-01-01 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-01-03 00:00:00\",\n    \"2096-01-09 00:00:00\",\n    \"2096-02-11 00:00:00\",\n    \"2096-02-23 00:00:00\",\n    \"2096-03-19 00:00:00\",\n    \"2096-04-30 00:00:00\",\n    \"2096-05-03 00:00:00\",\n    \"2096-05-04 00:00:00\",\n    \"2096-05-05 00:00:00\",\n    \"2096-07-16 00:00:00\",\n    \"2096-08-11 00:00:00\",\n    \"2096-09-17 00:00:00\",\n    \"2096-09-21 00:00:00\",\n    \"2096-10-08 00:00:00\",\n    \"2096-11-03 00:00:00\",\n    \"2096-11-23 00:00:00\",\n    \"2096-12-31 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-02 00:00:00\",\n    \"2097-01-03 00:00:00\",\n    \"2097-01-14 00:00:00\",\n    \"2097-02-11 00:00:00\",\n    \"2097-02-23 00:00:00\",\n    \"2097-03-19 00:00:00\",\n    \"2097-04-29 00:00:00\",\n    \"2097-05-03 00:00:00\",\n    \"2097-05-04 00:00:00\",\n    \"2097-05-06 00:00:00\",\n    \"2097-07-15 00:00:00\",\n    \"2097-08-12 00:00:00\",\n    \"2097-09-16 00:00:00\",\n    \"2097-09-22 00:00:00\",\n    \"2097-10-14 00:00:00\",\n    \"2097-11-04 00:00:00\",\n    \"2097-11-23 00:00:00\",\n    \"2097-12-31 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-02 00:00:00\",\n    \"2098-01-03 00:00:00\",\n    \"2098-01-13 00:00:00\",\n    \"2098-02-11 00:00:00\",\n    \"2098-02-24 00:00:00\",\n    \"2098-03-20 00:00:00\",\n    \"2098-04-29 00:00:00\",\n    \"2098-05-03 00:00:00\",\n    \"2098-05-05 00:00:00\",\n    \"2098-05-06 00:00:00\",\n    \"2098-07-21 00:00:00\",\n    \"2098-08-11 00:00:00\",\n    \"2098-09-15 00:00:00\",\n    \"2098-09-22 00:00:00\",\n    \"2098-10-13 00:00:00\",\n    \"2098-11-03 00:00:00\",\n    \"2098-11-24 00:00:00\",\n    \"2098-12-31 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-02 00:00:00\",\n    \"2099-01-03 00:00:00\",\n    \"2099-01-12 00:00:00\",\n    \"2099-02-11 00:00:00\",\n    \"2099-02-23 00:00:00\",\n    \"2099-03-20 00:00:00\",\n    \"2099-04-29 00:00:00\",\n    \"2099-05-04 00:00:00\",\n    \"2099-05-05 00:00:00\",\n    \"2099-05-06 00:00:00\",\n    \"2099-07-20 00:00:00\",\n    \"2099-08-11 00:00:00\",\n    \"2099-09-21 00:00:00\",\n    \"2099-09-22 00:00:00\",\n    \"2099-10-12 00:00:00\",\n    \"2099-11-03 00:00:00\",\n    \"2099-11-23 00:00:00\",\n    \"2099-12-31 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-02 00:00:00\",\n    \"2100-01-03 00:00:00\",\n    \"2100-01-11 00:00:00\",\n    \"2100-02-11 00:00:00\",\n    \"2100-02-23 00:00:00\",\n    \"2100-03-20 00:00:00\",\n    \"2100-04-29 00:00:00\",\n    \"2100-05-03 00:00:00\",\n    \"2100-05-04 00:00:00\",\n    \"2100-05-05 00:00:00\",\n    \"2100-07-19 00:00:00\",\n    \"2100-08-11 00:00:00\",\n    \"2100-09-20 00:00:00\",\n    \"2100-09-22 00:00:00\",\n    \"2100-10-11 00:00:00\",\n    \"2100-11-03 00:00:00\",\n    \"2100-11-23 00:00:00\",\n    \"2100-12-31 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-01-02 00:00:00\",\n    \"2101-01-03 00:00:00\",\n    \"2101-01-10 00:00:00\",\n    \"2101-02-11 00:00:00\",\n    \"2101-02-23 00:00:00\",\n    \"2101-03-20 00:00:00\",\n    \"2101-04-29 00:00:00\",\n    \"2101-05-03 00:00:00\",\n    \"2101-05-04 00:00:00\",\n    \"2101-05-05 00:00:00\",\n    \"2101-07-18 00:00:00\",\n    \"2101-08-11 00:00:00\",\n    \"2101-09-19 00:00:00\",\n    \"2101-09-23 00:00:00\",\n    \"2101-10-10 00:00:00\",\n    \"2101-11-03 00:00:00\",\n    \"2101-11-23 00:00:00\",\n    \"2101-12-31 00:00:00\",\n    \"2102-01-01 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-01-03 00:00:00\",\n    \"2102-01-09 00:00:00\",\n    \"2102-02-11 00:00:00\",\n    \"2102-02-23 00:00:00\",\n    \"2102-03-21 00:00:00\",\n    \"2102-04-29 00:00:00\",\n    \"2102-05-03 00:00:00\",\n    \"2102-05-04 00:00:00\",\n    \"2102-05-05 00:00:00\",\n    \"2102-07-17 00:00:00\",\n    \"2102-08-11 00:00:00\",\n    \"2102-09-18 00:00:00\",\n    \"2102-09-23 00:00:00\",\n    \"2102-10-09 00:00:00\",\n    \"2102-11-03 00:00:00\",\n    \"2102-11-23 00:00:00\",\n    \"2102-12-31 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-01-02 00:00:00\",\n    \"2103-01-03 00:00:00\",\n    \"2103-01-08 00:00:00\",\n    \"2103-02-12 00:00:00\",\n    \"2103-02-23 00:00:00\",\n    \"2103-03-21 00:00:00\",\n    \"2103-04-30 00:00:00\",\n    \"2103-05-03 00:00:00\",\n    \"2103-05-04 00:00:00\",\n    \"2103-05-05 00:00:00\",\n    \"2103-07-16 00:00:00\",\n    \"2103-08-11 00:00:00\",\n    \"2103-09-17 00:00:00\",\n    \"2103-09-23 00:00:00\",\n    \"2103-10-08 00:00:00\",\n    \"2103-11-03 00:00:00\",\n    \"2103-11-23 00:00:00\",\n    \"2103-12-31 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-01-02 00:00:00\",\n    \"2104-01-03 00:00:00\",\n    \"2104-01-14 00:00:00\",\n    \"2104-02-11 00:00:00\",\n    \"2104-02-23 00:00:00\",\n    \"2104-03-20 00:00:00\",\n    \"2104-04-29 00:00:00\",\n    \"2104-05-03 00:00:00\",\n    \"2104-05-05 00:00:00\",\n    \"2104-05-06 00:00:00\",\n    \"2104-07-21 00:00:00\",\n    \"2104-08-11 00:00:00\",\n    \"2104-09-15 00:00:00\",\n    \"2104-09-22 00:00:00\",\n    \"2104-10-13 00:00:00\",\n    \"2104-11-03 00:00:00\",\n    \"2104-11-24 00:00:00\",\n    \"2104-12-31 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-01-02 00:00:00\",\n    \"2105-01-03 00:00:00\",\n    \"2105-01-12 00:00:00\",\n    \"2105-02-11 00:00:00\",\n    \"2105-02-23 00:00:00\",\n    \"2105-03-20 00:00:00\",\n    \"2105-04-29 00:00:00\",\n    \"2105-05-04 00:00:00\",\n    \"2105-05-05 00:00:00\",\n    \"2105-05-06 00:00:00\",\n    \"2105-07-20 00:00:00\",\n    \"2105-08-11 00:00:00\",\n    \"2105-09-21 00:00:00\",\n    \"2105-09-23 00:00:00\",\n    \"2105-10-12 00:00:00\",\n    \"2105-11-03 00:00:00\",\n    \"2105-11-23 00:00:00\",\n    \"2105-12-31 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-01-02 00:00:00\",\n    \"2106-01-03 00:00:00\",\n    \"2106-01-11 00:00:00\",\n    \"2106-02-11 00:00:00\",\n    \"2106-02-23 00:00:00\",\n    \"2106-03-21 00:00:00\",\n    \"2106-04-29 00:00:00\",\n    \"2106-05-03 00:00:00\",\n    \"2106-05-04 00:00:00\",\n    \"2106-05-05 00:00:00\",\n    \"2106-07-19 00:00:00\",\n    \"2106-08-11 00:00:00\",\n    \"2106-09-20 00:00:00\",\n    \"2106-09-23 00:00:00\",\n    \"2106-10-11 00:00:00\",\n    \"2106-11-03 00:00:00\",\n    \"2106-11-23 00:00:00\",\n    \"2106-12-31 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-01-02 00:00:00\",\n    \"2107-01-03 00:00:00\",\n    \"2107-01-10 00:00:00\",\n    \"2107-02-11 00:00:00\",\n    \"2107-02-23 00:00:00\",\n    \"2107-03-21 00:00:00\",\n    \"2107-04-29 00:00:00\",\n    \"2107-05-03 00:00:00\",\n    \"2107-05-04 00:00:00\",\n    \"2107-05-05 00:00:00\",\n    \"2107-07-18 00:00:00\",\n    \"2107-08-11 00:00:00\",\n    \"2107-09-19 00:00:00\",\n    \"2107-09-23 00:00:00\",\n    \"2107-10-10 00:00:00\",\n    \"2107-11-03 00:00:00\",\n    \"2107-11-23 00:00:00\",\n    \"2107-12-31 00:00:00\",\n    \"2108-01-01 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-01-03 00:00:00\",\n    \"2108-01-09 00:00:00\",\n    \"2108-02-11 00:00:00\",\n    \"2108-02-23 00:00:00\",\n    \"2108-03-20 00:00:00\",\n    \"2108-04-30 00:00:00\",\n    \"2108-05-03 00:00:00\",\n    \"2108-05-04 00:00:00\",\n    \"2108-05-05 00:00:00\",\n    \"2108-07-16 00:00:00\",\n    \"2108-08-11 00:00:00\",\n    \"2108-09-17 00:00:00\",\n    \"2108-09-22 00:00:00\",\n    \"2108-10-08 00:00:00\",\n    \"2108-11-03 00:00:00\",\n    \"2108-11-23 00:00:00\",\n    \"2108-12-31 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-01-02 00:00:00\",\n    \"2109-01-03 00:00:00\",\n    \"2109-01-14 00:00:00\",\n    \"2109-02-11 00:00:00\",\n    \"2109-02-23 00:00:00\",\n    \"2109-03-20 00:00:00\",\n    \"2109-04-29 00:00:00\",\n    \"2109-05-03 00:00:00\",\n    \"2109-05-04 00:00:00\",\n    \"2109-05-06 00:00:00\",\n    \"2109-07-15 00:00:00\",\n    \"2109-08-12 00:00:00\",\n    \"2109-09-16 00:00:00\",\n    \"2109-09-23 00:00:00\",\n    \"2109-10-14 00:00:00\",\n    \"2109-11-04 00:00:00\",\n    \"2109-11-23 00:00:00\",\n    \"2109-12-31 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-01-02 00:00:00\",\n    \"2110-01-03 00:00:00\",\n    \"2110-01-13 00:00:00\",\n    \"2110-02-11 00:00:00\",\n    \"2110-02-24 00:00:00\",\n    \"2110-03-20 00:00:00\",\n    \"2110-04-29 00:00:00\",\n    \"2110-05-03 00:00:00\",\n    \"2110-05-05 00:00:00\",\n    \"2110-05-06 00:00:00\",\n    \"2110-07-21 00:00:00\",\n    \"2110-08-11 00:00:00\",\n    \"2110-09-15 00:00:00\",\n    \"2110-09-23 00:00:00\",\n    \"2110-10-13 00:00:00\",\n    \"2110-11-03 00:00:00\",\n    \"2110-11-24 00:00:00\",\n    \"2110-12-31 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-01-02 00:00:00\",\n    \"2111-01-03 00:00:00\",\n    \"2111-01-12 00:00:00\",\n    \"2111-02-11 00:00:00\",\n    \"2111-02-23 00:00:00\",\n    \"2111-03-21 00:00:00\",\n    \"2111-04-29 00:00:00\",\n    \"2111-05-04 00:00:00\",\n    \"2111-05-05 00:00:00\",\n    \"2111-05-06 00:00:00\",\n    \"2111-07-20 00:00:00\",\n    \"2111-08-11 00:00:00\",\n    \"2111-09-21 00:00:00\",\n    \"2111-09-23 00:00:00\",\n    \"2111-10-12 00:00:00\",\n    \"2111-11-03 00:00:00\",\n    \"2111-11-23 00:00:00\",\n    \"2111-12-31 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-01-02 00:00:00\",\n    \"2112-01-03 00:00:00\",\n    \"2112-01-11 00:00:00\",\n    \"2112-02-11 00:00:00\",\n    \"2112-02-23 00:00:00\",\n    \"2112-03-20 00:00:00\",\n    \"2112-04-29 00:00:00\",\n    \"2112-05-03 00:00:00\",\n    \"2112-05-04 00:00:00\",\n    \"2112-05-05 00:00:00\",\n    \"2112-07-18 00:00:00\",\n    \"2112-08-11 00:00:00\",\n    \"2112-09-19 00:00:00\",\n    \"2112-09-22 00:00:00\",\n    \"2112-10-10 00:00:00\",\n    \"2112-11-03 00:00:00\",\n    \"2112-11-23 00:00:00\",\n    \"2112-12-31 00:00:00\",\n    \"2113-01-01 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-01-03 00:00:00\",\n    \"2113-01-09 00:00:00\",\n    \"2113-02-11 00:00:00\",\n    \"2113-02-23 00:00:00\",\n    \"2113-03-20 00:00:00\",\n    \"2113-04-29 00:00:00\",\n    \"2113-05-03 00:00:00\",\n    \"2113-05-04 00:00:00\",\n    \"2113-05-05 00:00:00\",\n    \"2113-07-17 00:00:00\",\n    \"2113-08-11 00:00:00\",\n    \"2113-09-18 00:00:00\",\n    \"2113-09-23 00:00:00\",\n    \"2113-10-09 00:00:00\",\n    \"2113-11-03 00:00:00\",\n    \"2113-11-23 00:00:00\",\n    \"2113-12-31 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-01-02 00:00:00\",\n    \"2114-01-03 00:00:00\",\n    \"2114-01-08 00:00:00\",\n    \"2114-02-12 00:00:00\",\n    \"2114-02-23 00:00:00\",\n    \"2114-03-20 00:00:00\",\n    \"2114-04-30 00:00:00\",\n    \"2114-05-03 00:00:00\",\n    \"2114-05-04 00:00:00\",\n    \"2114-05-05 00:00:00\",\n    \"2114-07-16 00:00:00\",\n    \"2114-08-11 00:00:00\",\n    \"2114-09-17 00:00:00\",\n    \"2114-09-23 00:00:00\",\n    \"2114-10-08 00:00:00\",\n    \"2114-11-03 00:00:00\",\n    \"2114-11-23 00:00:00\",\n    \"2114-12-31 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-01-02 00:00:00\",\n    \"2115-01-03 00:00:00\",\n    \"2115-01-14 00:00:00\",\n    \"2115-02-11 00:00:00\",\n    \"2115-02-23 00:00:00\",\n    \"2115-03-21 00:00:00\",\n    \"2115-04-29 00:00:00\",\n    \"2115-05-03 00:00:00\",\n    \"2115-05-04 00:00:00\",\n    \"2115-05-06 00:00:00\",\n    \"2115-07-15 00:00:00\",\n    \"2115-08-12 00:00:00\",\n    \"2115-09-16 00:00:00\",\n    \"2115-09-23 00:00:00\",\n    \"2115-10-14 00:00:00\",\n    \"2115-11-04 00:00:00\",\n    \"2115-11-23 00:00:00\",\n    \"2115-12-31 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-01-02 00:00:00\",\n    \"2116-01-03 00:00:00\",\n    \"2116-01-13 00:00:00\",\n    \"2116-02-11 00:00:00\",\n    \"2116-02-24 00:00:00\",\n    \"2116-03-20 00:00:00\",\n    \"2116-04-29 00:00:00\",\n    \"2116-05-04 00:00:00\",\n    \"2116-05-05 00:00:00\",\n    \"2116-05-06 00:00:00\",\n    \"2116-07-20 00:00:00\",\n    \"2116-08-11 00:00:00\",\n    \"2116-09-21 00:00:00\",\n    \"2116-09-22 00:00:00\",\n    \"2116-10-12 00:00:00\",\n    \"2116-11-03 00:00:00\",\n    \"2116-11-23 00:00:00\",\n    \"2116-12-31 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-01-02 00:00:00\",\n    \"2117-01-03 00:00:00\",\n    \"2117-01-11 00:00:00\",\n    \"2117-02-11 00:00:00\",\n    \"2117-02-23 00:00:00\",\n    \"2117-03-20 00:00:00\",\n    \"2117-04-29 00:00:00\",\n    \"2117-05-03 00:00:00\",\n    \"2117-05-04 00:00:00\",\n    \"2117-05-05 00:00:00\",\n    \"2117-07-19 00:00:00\",\n    \"2117-08-11 00:00:00\",\n    \"2117-09-20 00:00:00\",\n    \"2117-09-23 00:00:00\",\n    \"2117-10-11 00:00:00\",\n    \"2117-11-03 00:00:00\",\n    \"2117-11-23 00:00:00\",\n    \"2117-12-31 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-01-02 00:00:00\",\n    \"2118-01-03 00:00:00\",\n    \"2118-01-10 00:00:00\",\n    \"2118-02-11 00:00:00\",\n    \"2118-02-23 00:00:00\",\n    \"2118-03-20 00:00:00\",\n    \"2118-04-29 00:00:00\",\n    \"2118-05-03 00:00:00\",\n    \"2118-05-04 00:00:00\",\n    \"2118-05-05 00:00:00\",\n    \"2118-07-18 00:00:00\",\n    \"2118-08-11 00:00:00\",\n    \"2118-09-19 00:00:00\",\n    \"2118-09-23 00:00:00\",\n    \"2118-10-10 00:00:00\",\n    \"2118-11-03 00:00:00\",\n    \"2118-11-23 00:00:00\",\n    \"2118-12-31 00:00:00\",\n    \"2119-01-01 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-01-03 00:00:00\",\n    \"2119-01-09 00:00:00\",\n    \"2119-02-11 00:00:00\",\n    \"2119-02-23 00:00:00\",\n    \"2119-03-21 00:00:00\",\n    \"2119-04-29 00:00:00\",\n    \"2119-05-03 00:00:00\",\n    \"2119-05-04 00:00:00\",\n    \"2119-05-05 00:00:00\",\n    \"2119-07-17 00:00:00\",\n    \"2119-08-11 00:00:00\",\n    \"2119-09-18 00:00:00\",\n    \"2119-09-23 00:00:00\",\n    \"2119-10-09 00:00:00\",\n    \"2119-11-03 00:00:00\",\n    \"2119-11-23 00:00:00\",\n    \"2119-12-31 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-01-02 00:00:00\",\n    \"2120-01-03 00:00:00\",\n    \"2120-01-08 00:00:00\",\n    \"2120-02-12 00:00:00\",\n    \"2120-02-23 00:00:00\",\n    \"2120-03-20 00:00:00\",\n    \"2120-04-29 00:00:00\",\n    \"2120-05-03 00:00:00\",\n    \"2120-05-04 00:00:00\",\n    \"2120-05-06 00:00:00\",\n    \"2120-07-15 00:00:00\",\n    \"2120-08-12 00:00:00\",\n    \"2120-09-16 00:00:00\",\n    \"2120-09-22 00:00:00\",\n    \"2120-10-14 00:00:00\",\n    \"2120-11-04 00:00:00\",\n    \"2120-11-23 00:00:00\",\n    \"2120-12-31 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-01-02 00:00:00\",\n    \"2121-01-03 00:00:00\",\n    \"2121-01-13 00:00:00\",\n    \"2121-02-11 00:00:00\",\n    \"2121-02-24 00:00:00\",\n    \"2121-03-20 00:00:00\",\n    \"2121-04-29 00:00:00\",\n    \"2121-05-03 00:00:00\",\n    \"2121-05-05 00:00:00\",\n    \"2121-05-06 00:00:00\",\n    \"2121-07-21 00:00:00\",\n    \"2121-08-11 00:00:00\",\n    \"2121-09-15 00:00:00\",\n    \"2121-09-22 00:00:00\",\n    \"2121-10-13 00:00:00\",\n    \"2121-11-03 00:00:00\",\n    \"2121-11-24 00:00:00\",\n    \"2121-12-31 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-01-02 00:00:00\",\n    \"2122-01-03 00:00:00\",\n    \"2122-01-12 00:00:00\",\n    \"2122-02-11 00:00:00\",\n    \"2122-02-23 00:00:00\",\n    \"2122-03-20 00:00:00\",\n    \"2122-04-29 00:00:00\",\n    \"2122-05-04 00:00:00\",\n    \"2122-05-05 00:00:00\",\n    \"2122-05-06 00:00:00\",\n    \"2122-07-20 00:00:00\",\n    \"2122-08-11 00:00:00\",\n    \"2122-09-21 00:00:00\",\n    \"2122-09-23 00:00:00\",\n    \"2122-10-12 00:00:00\",\n    \"2122-11-03 00:00:00\",\n    \"2122-11-23 00:00:00\",\n    \"2122-12-31 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-01-02 00:00:00\",\n    \"2123-01-03 00:00:00\",\n    \"2123-01-11 00:00:00\",\n    \"2123-02-11 00:00:00\",\n    \"2123-02-23 00:00:00\",\n    \"2123-03-21 00:00:00\",\n    \"2123-04-29 00:00:00\",\n    \"2123-05-03 00:00:00\",\n    \"2123-05-04 00:00:00\",\n    \"2123-05-05 00:00:00\",\n    \"2123-07-19 00:00:00\",\n    \"2123-08-11 00:00:00\",\n    \"2123-09-20 00:00:00\",\n    \"2123-09-23 00:00:00\",\n    \"2123-10-11 00:00:00\",\n    \"2123-11-03 00:00:00\",\n    \"2123-11-23 00:00:00\",\n    \"2123-12-31 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-01-02 00:00:00\",\n    \"2124-01-03 00:00:00\",\n    \"2124-01-10 00:00:00\",\n    \"2124-02-11 00:00:00\",\n    \"2124-02-23 00:00:00\",\n    \"2124-03-20 00:00:00\",\n    \"2124-04-29 00:00:00\",\n    \"2124-05-03 00:00:00\",\n    \"2124-05-04 00:00:00\",\n    \"2124-05-05 00:00:00\",\n    \"2124-07-17 00:00:00\",\n    \"2124-08-11 00:00:00\",\n    \"2124-09-18 00:00:00\",\n    \"2124-09-22 00:00:00\",\n    \"2124-10-09 00:00:00\",\n    \"2124-11-03 00:00:00\",\n    \"2124-11-23 00:00:00\",\n    \"2124-12-31 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-01-02 00:00:00\",\n    \"2125-01-03 00:00:00\",\n    \"2125-01-08 00:00:00\",\n    \"2125-02-12 00:00:00\",\n    \"2125-02-23 00:00:00\",\n    \"2125-03-20 00:00:00\",\n    \"2125-04-30 00:00:00\",\n    \"2125-05-03 00:00:00\",\n    \"2125-05-04 00:00:00\",\n    \"2125-05-05 00:00:00\",\n    \"2125-07-16 00:00:00\",\n    \"2125-08-11 00:00:00\",\n    \"2125-09-17 00:00:00\",\n    \"2125-09-22 00:00:00\",\n    \"2125-10-08 00:00:00\",\n    \"2125-11-03 00:00:00\",\n    \"2125-11-23 00:00:00\",\n    \"2125-12-31 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-01-02 00:00:00\",\n    \"2126-01-03 00:00:00\",\n    \"2126-01-14 00:00:00\",\n    \"2126-02-11 00:00:00\",\n    \"2126-02-23 00:00:00\",\n    \"2126-03-20 00:00:00\",\n    \"2126-04-29 00:00:00\",\n    \"2126-05-03 00:00:00\",\n    \"2126-05-04 00:00:00\",\n    \"2126-05-06 00:00:00\",\n    \"2126-07-15 00:00:00\",\n    \"2126-08-12 00:00:00\",\n    \"2126-09-16 00:00:00\",\n    \"2126-09-23 00:00:00\",\n    \"2126-10-14 00:00:00\",\n    \"2126-11-04 00:00:00\",\n    \"2126-11-23 00:00:00\",\n    \"2126-12-31 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-01-02 00:00:00\",\n    \"2127-01-03 00:00:00\",\n    \"2127-01-13 00:00:00\",\n    \"2127-02-11 00:00:00\",\n    \"2127-02-24 00:00:00\",\n    \"2127-03-21 00:00:00\",\n    \"2127-04-29 00:00:00\",\n    \"2127-05-03 00:00:00\",\n    \"2127-05-05 00:00:00\",\n    \"2127-05-06 00:00:00\",\n    \"2127-07-21 00:00:00\",\n    \"2127-08-11 00:00:00\",\n    \"2127-09-15 00:00:00\",\n    \"2127-09-23 00:00:00\",\n    \"2127-10-13 00:00:00\",\n    \"2127-11-03 00:00:00\",\n    \"2127-11-24 00:00:00\",\n    \"2127-12-31 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-01-02 00:00:00\",\n    \"2128-01-03 00:00:00\",\n    \"2128-01-12 00:00:00\",\n    \"2128-02-11 00:00:00\",\n    \"2128-02-23 00:00:00\",\n    \"2128-03-20 00:00:00\",\n    \"2128-04-29 00:00:00\",\n    \"2128-05-03 00:00:00\",\n    \"2128-05-04 00:00:00\",\n    \"2128-05-05 00:00:00\",\n    \"2128-07-19 00:00:00\",\n    \"2128-08-11 00:00:00\",\n    \"2128-09-20 00:00:00\",\n    \"2128-09-22 00:00:00\",\n    \"2128-10-11 00:00:00\",\n    \"2128-11-03 00:00:00\",\n    \"2128-11-23 00:00:00\",\n    \"2128-12-31 00:00:00\",\n    \"2129-01-01 00:00:00\",\n    \"2129-01-02 00:00:00\",\n    \"2129-01-03 00:00:00\",\n    \"2129-01-10 00:00:00\",\n    \"2129-02-11 00:00:00\",\n    \"2129-02-23 00:00:00\",\n    \"2129-03-20 00:00:00\",\n    \"2129-04-29 00:00:00\",\n    \"2129-05-03 00:00:00\",\n    \"2129-05-04 00:00:00\",\n    \"2129-05-05 00:00:00\",\n    \"2129-07-18 00:00:00\",\n    \"2129-08-11 00:00:00\",\n    \"2129-09-19 00:00:00\",\n    \"2129-09-22 00:00:00\",\n    \"2129-10-10 00:00:00\",\n    \"2129-11-03 00:00:00\",\n    \"2129-11-23 00:00:00\",\n    \"2129-12-31 00:00:00\",\n    \"2130-01-01 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-01-03 00:00:00\",\n    \"2130-01-09 00:00:00\",\n    \"2130-02-11 00:00:00\",\n    \"2130-02-23 00:00:00\",\n    \"2130-03-20 00:00:00\",\n    \"2130-04-29 00:00:00\",\n    \"2130-05-03 00:00:00\",\n    \"2130-05-04 00:00:00\",\n    \"2130-05-05 00:00:00\",\n    \"2130-07-17 00:00:00\",\n    \"2130-08-11 00:00:00\",\n    \"2130-09-18 00:00:00\",\n    \"2130-09-23 00:00:00\",\n    \"2130-10-09 00:00:00\",\n    \"2130-11-03 00:00:00\",\n    \"2130-11-23 00:00:00\",\n    \"2130-12-31 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-01-02 00:00:00\",\n    \"2131-01-03 00:00:00\",\n    \"2131-01-08 00:00:00\",\n    \"2131-02-12 00:00:00\",\n    \"2131-02-23 00:00:00\",\n    \"2131-03-21 00:00:00\",\n    \"2131-04-30 00:00:00\",\n    \"2131-05-03 00:00:00\",\n    \"2131-05-04 00:00:00\",\n    \"2131-05-05 00:00:00\",\n    \"2131-07-16 00:00:00\",\n    \"2131-08-11 00:00:00\",\n    \"2131-09-17 00:00:00\",\n    \"2131-09-23 00:00:00\",\n    \"2131-10-08 00:00:00\",\n    \"2131-11-03 00:00:00\",\n    \"2131-11-23 00:00:00\",\n    \"2131-12-31 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-01-02 00:00:00\",\n    \"2132-01-03 00:00:00\",\n    \"2132-01-14 00:00:00\",\n    \"2132-02-11 00:00:00\",\n    \"2132-02-23 00:00:00\",\n    \"2132-03-20 00:00:00\",\n    \"2132-04-29 00:00:00\",\n    \"2132-05-03 00:00:00\",\n    \"2132-05-05 00:00:00\",\n    \"2132-05-06 00:00:00\",\n    \"2132-07-21 00:00:00\",\n    \"2132-08-11 00:00:00\",\n    \"2132-09-15 00:00:00\",\n    \"2132-09-22 00:00:00\",\n    \"2132-10-13 00:00:00\",\n    \"2132-11-03 00:00:00\",\n    \"2132-11-24 00:00:00\",\n    \"2132-12-31 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-01-02 00:00:00\",\n    \"2133-01-03 00:00:00\",\n    \"2133-01-12 00:00:00\",\n    \"2133-02-11 00:00:00\",\n    \"2133-02-23 00:00:00\",\n    \"2133-03-20 00:00:00\",\n    \"2133-04-29 00:00:00\",\n    \"2133-05-04 00:00:00\",\n    \"2133-05-05 00:00:00\",\n    \"2133-05-06 00:00:00\",\n    \"2133-07-20 00:00:00\",\n    \"2133-08-11 00:00:00\",\n    \"2133-09-21 00:00:00\",\n    \"2133-09-22 00:00:00\",\n    \"2133-10-12 00:00:00\",\n    \"2133-11-03 00:00:00\",\n    \"2133-11-23 00:00:00\",\n    \"2133-12-31 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-01-02 00:00:00\",\n    \"2134-01-03 00:00:00\",\n    \"2134-01-11 00:00:00\",\n    \"2134-02-11 00:00:00\",\n    \"2134-02-23 00:00:00\",\n    \"2134-03-20 00:00:00\",\n    \"2134-04-29 00:00:00\",\n    \"2134-05-03 00:00:00\",\n    \"2134-05-04 00:00:00\",\n    \"2134-05-05 00:00:00\",\n    \"2134-07-19 00:00:00\",\n    \"2134-08-11 00:00:00\",\n    \"2134-09-20 00:00:00\",\n    \"2134-09-23 00:00:00\",\n    \"2134-10-11 00:00:00\",\n    \"2134-11-03 00:00:00\",\n    \"2134-11-23 00:00:00\",\n    \"2134-12-31 00:00:00\",\n    \"2135-01-01 00:00:00\",\n    \"2135-01-02 00:00:00\",\n    \"2135-01-03 00:00:00\",\n    \"2135-01-10 00:00:00\",\n    \"2135-02-11 00:00:00\",\n    \"2135-02-23 00:00:00\",\n    \"2135-03-21 00:00:00\",\n    \"2135-04-29 00:00:00\",\n    \"2135-05-03 00:00:00\",\n    \"2135-05-04 00:00:00\",\n    \"2135-05-05 00:00:00\",\n    \"2135-07-18 00:00:00\",\n    \"2135-08-11 00:00:00\",\n    \"2135-09-19 00:00:00\",\n    \"2135-09-23 00:00:00\",\n    \"2135-10-10 00:00:00\",\n    \"2135-11-03 00:00:00\",\n    \"2135-11-23 00:00:00\",\n    \"2135-12-31 00:00:00\",\n    \"2136-01-01 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-01-03 00:00:00\",\n    \"2136-01-09 00:00:00\",\n    \"2136-02-11 00:00:00\",\n    \"2136-02-23 00:00:00\",\n    \"2136-03-20 00:00:00\",\n    \"2136-04-30 00:00:00\",\n    \"2136-05-03 00:00:00\",\n    \"2136-05-04 00:00:00\",\n    \"2136-05-05 00:00:00\",\n    \"2136-07-16 00:00:00\",\n    \"2136-08-11 00:00:00\",\n    \"2136-09-17 00:00:00\",\n    \"2136-09-22 00:00:00\",\n    \"2136-10-08 00:00:00\",\n    \"2136-11-03 00:00:00\",\n    \"2136-11-23 00:00:00\",\n    \"2136-12-31 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-01-02 00:00:00\",\n    \"2137-01-03 00:00:00\",\n    \"2137-01-14 00:00:00\",\n    \"2137-02-11 00:00:00\",\n    \"2137-02-23 00:00:00\",\n    \"2137-03-20 00:00:00\",\n    \"2137-04-29 00:00:00\",\n    \"2137-05-03 00:00:00\",\n    \"2137-05-04 00:00:00\",\n    \"2137-05-06 00:00:00\",\n    \"2137-07-15 00:00:00\",\n    \"2137-08-12 00:00:00\",\n    \"2137-09-16 00:00:00\",\n    \"2137-09-22 00:00:00\",\n    \"2137-10-14 00:00:00\",\n    \"2137-11-04 00:00:00\",\n    \"2137-11-23 00:00:00\",\n    \"2137-12-31 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-01-02 00:00:00\",\n    \"2138-01-03 00:00:00\",\n    \"2138-01-13 00:00:00\",\n    \"2138-02-11 00:00:00\",\n    \"2138-02-24 00:00:00\",\n    \"2138-03-20 00:00:00\",\n    \"2138-04-29 00:00:00\",\n    \"2138-05-03 00:00:00\",\n    \"2138-05-05 00:00:00\",\n    \"2138-05-06 00:00:00\",\n    \"2138-07-21 00:00:00\",\n    \"2138-08-11 00:00:00\",\n    \"2138-09-15 00:00:00\",\n    \"2138-09-23 00:00:00\",\n    \"2138-10-13 00:00:00\",\n    \"2138-11-03 00:00:00\",\n    \"2138-11-24 00:00:00\",\n    \"2138-12-31 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-01-02 00:00:00\",\n    \"2139-01-03 00:00:00\",\n    \"2139-01-12 00:00:00\",\n    \"2139-02-11 00:00:00\",\n    \"2139-02-23 00:00:00\",\n    \"2139-03-20 00:00:00\",\n    \"2139-04-29 00:00:00\",\n    \"2139-05-04 00:00:00\",\n    \"2139-05-05 00:00:00\",\n    \"2139-05-06 00:00:00\",\n    \"2139-07-20 00:00:00\",\n    \"2139-08-11 00:00:00\",\n    \"2139-09-21 00:00:00\",\n    \"2139-09-23 00:00:00\",\n    \"2139-10-12 00:00:00\",\n    \"2139-11-03 00:00:00\",\n    \"2139-11-23 00:00:00\",\n    \"2139-12-31 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-01-02 00:00:00\",\n    \"2140-01-03 00:00:00\",\n    \"2140-01-11 00:00:00\",\n    \"2140-02-11 00:00:00\",\n    \"2140-02-23 00:00:00\",\n    \"2140-03-20 00:00:00\",\n    \"2140-04-29 00:00:00\",\n    \"2140-05-03 00:00:00\",\n    \"2140-05-04 00:00:00\",\n    \"2140-05-05 00:00:00\",\n    \"2140-07-18 00:00:00\",\n    \"2140-08-11 00:00:00\",\n    \"2140-09-19 00:00:00\",\n    \"2140-09-22 00:00:00\",\n    \"2140-10-10 00:00:00\",\n    \"2140-11-03 00:00:00\",\n    \"2140-11-23 00:00:00\",\n    \"2140-12-31 00:00:00\",\n    \"2141-01-01 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-01-03 00:00:00\",\n    \"2141-01-09 00:00:00\",\n    \"2141-02-11 00:00:00\",\n    \"2141-02-23 00:00:00\",\n    \"2141-03-20 00:00:00\",\n    \"2141-04-29 00:00:00\",\n    \"2141-05-03 00:00:00\",\n    \"2141-05-04 00:00:00\",\n    \"2141-05-05 00:00:00\",\n    \"2141-07-17 00:00:00\",\n    \"2141-08-11 00:00:00\",\n    \"2141-09-18 00:00:00\",\n    \"2141-09-22 00:00:00\",\n    \"2141-10-09 00:00:00\",\n    \"2141-11-03 00:00:00\",\n    \"2141-11-23 00:00:00\",\n    \"2141-12-31 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-01-02 00:00:00\",\n    \"2142-01-03 00:00:00\",\n    \"2142-01-08 00:00:00\",\n    \"2142-02-12 00:00:00\",\n    \"2142-02-23 00:00:00\",\n    \"2142-03-20 00:00:00\",\n    \"2142-04-30 00:00:00\",\n    \"2142-05-03 00:00:00\",\n    \"2142-05-04 00:00:00\",\n    \"2142-05-05 00:00:00\",\n    \"2142-07-16 00:00:00\",\n    \"2142-08-11 00:00:00\",\n    \"2142-09-17 00:00:00\",\n    \"2142-09-23 00:00:00\",\n    \"2142-10-08 00:00:00\",\n    \"2142-11-03 00:00:00\",\n    \"2142-11-23 00:00:00\",\n    \"2142-12-31 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-01-02 00:00:00\",\n    \"2143-01-03 00:00:00\",\n    \"2143-01-14 00:00:00\",\n    \"2143-02-11 00:00:00\",\n    \"2143-02-23 00:00:00\",\n    \"2143-03-20 00:00:00\",\n    \"2143-04-29 00:00:00\",\n    \"2143-05-03 00:00:00\",\n    \"2143-05-04 00:00:00\",\n    \"2143-05-06 00:00:00\",\n    \"2143-07-15 00:00:00\",\n    \"2143-08-12 00:00:00\",\n    \"2143-09-16 00:00:00\",\n    \"2143-09-23 00:00:00\",\n    \"2143-10-14 00:00:00\",\n    \"2143-11-04 00:00:00\",\n    \"2143-11-23 00:00:00\",\n    \"2143-12-31 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-01-02 00:00:00\",\n    \"2144-01-03 00:00:00\",\n    \"2144-01-13 00:00:00\",\n    \"2144-02-11 00:00:00\",\n    \"2144-02-24 00:00:00\",\n    \"2144-03-20 00:00:00\",\n    \"2144-04-29 00:00:00\",\n    \"2144-05-04 00:00:00\",\n    \"2144-05-05 00:00:00\",\n    \"2144-05-06 00:00:00\",\n    \"2144-07-20 00:00:00\",\n    \"2144-08-11 00:00:00\",\n    \"2144-09-21 00:00:00\",\n    \"2144-09-22 00:00:00\",\n    \"2144-10-12 00:00:00\",\n    \"2144-11-03 00:00:00\",\n    \"2144-11-23 00:00:00\",\n    \"2144-12-31 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-01-02 00:00:00\",\n    \"2145-01-03 00:00:00\",\n    \"2145-01-11 00:00:00\",\n    \"2145-02-11 00:00:00\",\n    \"2145-02-23 00:00:00\",\n    \"2145-03-20 00:00:00\",\n    \"2145-04-29 00:00:00\",\n    \"2145-05-03 00:00:00\",\n    \"2145-05-04 00:00:00\",\n    \"2145-05-05 00:00:00\",\n    \"2145-07-19 00:00:00\",\n    \"2145-08-11 00:00:00\",\n    \"2145-09-20 00:00:00\",\n    \"2145-09-22 00:00:00\",\n    \"2145-10-11 00:00:00\",\n    \"2145-11-03 00:00:00\",\n    \"2145-11-23 00:00:00\",\n    \"2145-12-31 00:00:00\",\n    \"2146-01-01 00:00:00\",\n    \"2146-01-02 00:00:00\",\n    \"2146-01-03 00:00:00\",\n    \"2146-01-10 00:00:00\",\n    \"2146-02-11 00:00:00\",\n    \"2146-02-23 00:00:00\",\n    \"2146-03-20 00:00:00\",\n    \"2146-04-29 00:00:00\",\n    \"2146-05-03 00:00:00\",\n    \"2146-05-04 00:00:00\",\n    \"2146-05-05 00:00:00\",\n    \"2146-07-18 00:00:00\",\n    \"2146-08-11 00:00:00\",\n    \"2146-09-19 00:00:00\",\n    \"2146-09-23 00:00:00\",\n    \"2146-10-10 00:00:00\",\n    \"2146-11-03 00:00:00\",\n    \"2146-11-23 00:00:00\",\n    \"2146-12-31 00:00:00\",\n    \"2147-01-01 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-01-03 00:00:00\",\n    \"2147-01-09 00:00:00\",\n    \"2147-02-11 00:00:00\",\n    \"2147-02-23 00:00:00\",\n    \"2147-03-20 00:00:00\",\n    \"2147-04-29 00:00:00\",\n    \"2147-05-03 00:00:00\",\n    \"2147-05-04 00:00:00\",\n    \"2147-05-05 00:00:00\",\n    \"2147-07-17 00:00:00\",\n    \"2147-08-11 00:00:00\",\n    \"2147-09-18 00:00:00\",\n    \"2147-09-23 00:00:00\",\n    \"2147-10-09 00:00:00\",\n    \"2147-11-03 00:00:00\",\n    \"2147-11-23 00:00:00\",\n    \"2147-12-31 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-01-02 00:00:00\",\n    \"2148-01-03 00:00:00\",\n    \"2148-01-08 00:00:00\",\n    \"2148-02-12 00:00:00\",\n    \"2148-02-23 00:00:00\",\n    \"2148-03-20 00:00:00\",\n    \"2148-04-29 00:00:00\",\n    \"2148-05-03 00:00:00\",\n    \"2148-05-04 00:00:00\",\n    \"2148-05-06 00:00:00\",\n    \"2148-07-15 00:00:00\",\n    \"2148-08-12 00:00:00\",\n    \"2148-09-16 00:00:00\",\n    \"2148-09-22 00:00:00\",\n    \"2148-10-14 00:00:00\",\n    \"2148-11-04 00:00:00\",\n    \"2148-11-23 00:00:00\",\n    \"2148-12-31 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-01-02 00:00:00\",\n    \"2149-01-03 00:00:00\",\n    \"2149-01-13 00:00:00\",\n    \"2149-02-11 00:00:00\",\n    \"2149-02-24 00:00:00\",\n    \"2149-03-20 00:00:00\",\n    \"2149-04-29 00:00:00\",\n    \"2149-05-03 00:00:00\",\n    \"2149-05-05 00:00:00\",\n    \"2149-05-06 00:00:00\",\n    \"2149-07-21 00:00:00\",\n    \"2149-08-11 00:00:00\",\n    \"2149-09-15 00:00:00\",\n    \"2149-09-22 00:00:00\",\n    \"2149-10-13 00:00:00\",\n    \"2149-11-03 00:00:00\",\n    \"2149-11-24 00:00:00\",\n    \"2149-12-31 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-01-02 00:00:00\",\n    \"2150-01-03 00:00:00\",\n    \"2150-01-12 00:00:00\",\n    \"2150-02-11 00:00:00\",\n    \"2150-02-23 00:00:00\",\n    \"2150-03-20 00:00:00\",\n    \"2150-04-29 00:00:00\",\n    \"2150-05-04 00:00:00\",\n    \"2150-05-05 00:00:00\",\n    \"2150-05-06 00:00:00\",\n    \"2150-07-20 00:00:00\",\n    \"2150-08-11 00:00:00\",\n    \"2150-09-21 00:00:00\",\n    \"2150-09-23 00:00:00\",\n    \"2150-10-12 00:00:00\",\n    \"2150-11-03 00:00:00\",\n    \"2150-11-23 00:00:00\",\n    \"2150-12-31 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-01-02 00:00:00\",\n    \"2151-01-03 00:00:00\",\n    \"2151-01-11 00:00:00\",\n    \"2151-02-11 00:00:00\",\n    \"2151-02-23 00:00:00\",\n    \"2151-03-20 00:00:00\",\n    \"2151-04-29 00:00:00\",\n    \"2151-05-03 00:00:00\",\n    \"2151-05-04 00:00:00\",\n    \"2151-05-05 00:00:00\",\n    \"2151-07-19 00:00:00\",\n    \"2151-08-11 00:00:00\",\n    \"2151-09-20 00:00:00\",\n    \"2151-09-23 00:00:00\",\n    \"2151-10-11 00:00:00\",\n    \"2151-11-03 00:00:00\",\n    \"2151-11-23 00:00:00\",\n    \"2151-12-31 00:00:00\",\n    \"2152-01-01 00:00:00\",\n    \"2152-01-02 00:00:00\",\n    \"2152-01-03 00:00:00\",\n    \"2152-01-10 00:00:00\",\n    \"2152-02-11 00:00:00\",\n    \"2152-02-23 00:00:00\",\n    \"2152-03-20 00:00:00\",\n    \"2152-04-29 00:00:00\",\n    \"2152-05-03 00:00:00\",\n    \"2152-05-04 00:00:00\",\n    \"2152-05-05 00:00:00\",\n    \"2152-07-17 00:00:00\",\n    \"2152-08-11 00:00:00\",\n    \"2152-09-18 00:00:00\",\n    \"2152-09-22 00:00:00\",\n    \"2152-10-09 00:00:00\",\n    \"2152-11-03 00:00:00\",\n    \"2152-11-23 00:00:00\",\n    \"2152-12-31 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-01-02 00:00:00\",\n    \"2153-01-03 00:00:00\",\n    \"2153-01-08 00:00:00\",\n    \"2153-02-12 00:00:00\",\n    \"2153-02-23 00:00:00\",\n    \"2153-03-20 00:00:00\",\n    \"2153-04-30 00:00:00\",\n    \"2153-05-03 00:00:00\",\n    \"2153-05-04 00:00:00\",\n    \"2153-05-05 00:00:00\",\n    \"2153-07-16 00:00:00\",\n    \"2153-08-11 00:00:00\",\n    \"2153-09-17 00:00:00\",\n    \"2153-09-22 00:00:00\",\n    \"2153-10-08 00:00:00\",\n    \"2153-11-03 00:00:00\",\n    \"2153-11-23 00:00:00\",\n    \"2153-12-31 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-01-02 00:00:00\",\n    \"2154-01-03 00:00:00\",\n    \"2154-01-14 00:00:00\",\n    \"2154-02-11 00:00:00\",\n    \"2154-02-23 00:00:00\",\n    \"2154-03-20 00:00:00\",\n    \"2154-04-29 00:00:00\",\n    \"2154-05-03 00:00:00\",\n    \"2154-05-04 00:00:00\",\n    \"2154-05-06 00:00:00\",\n    \"2154-07-15 00:00:00\",\n    \"2154-08-12 00:00:00\",\n    \"2154-09-16 00:00:00\",\n    \"2154-09-22 00:00:00\",\n    \"2154-10-14 00:00:00\",\n    \"2154-11-04 00:00:00\",\n    \"2154-11-23 00:00:00\",\n    \"2154-12-31 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-01-02 00:00:00\",\n    \"2155-01-03 00:00:00\",\n    \"2155-01-13 00:00:00\",\n    \"2155-02-11 00:00:00\",\n    \"2155-02-24 00:00:00\",\n    \"2155-03-20 00:00:00\",\n    \"2155-04-29 00:00:00\",\n    \"2155-05-03 00:00:00\",\n    \"2155-05-05 00:00:00\",\n    \"2155-05-06 00:00:00\",\n    \"2155-07-21 00:00:00\",\n    \"2155-08-11 00:00:00\",\n    \"2155-09-15 00:00:00\",\n    \"2155-09-23 00:00:00\",\n    \"2155-10-13 00:00:00\",\n    \"2155-11-03 00:00:00\",\n    \"2155-11-24 00:00:00\",\n    \"2155-12-31 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-01-02 00:00:00\",\n    \"2156-01-03 00:00:00\",\n    \"2156-01-12 00:00:00\",\n    \"2156-02-11 00:00:00\",\n    \"2156-02-23 00:00:00\",\n    \"2156-03-20 00:00:00\",\n    \"2156-04-29 00:00:00\",\n    \"2156-05-03 00:00:00\",\n    \"2156-05-04 00:00:00\",\n    \"2156-05-05 00:00:00\",\n    \"2156-07-19 00:00:00\",\n    \"2156-08-11 00:00:00\",\n    \"2156-09-20 00:00:00\",\n    \"2156-09-22 00:00:00\",\n    \"2156-10-11 00:00:00\",\n    \"2156-11-03 00:00:00\",\n    \"2156-11-23 00:00:00\",\n    \"2156-12-31 00:00:00\",\n    \"2157-01-01 00:00:00\",\n    \"2157-01-02 00:00:00\",\n    \"2157-01-03 00:00:00\",\n    \"2157-01-10 00:00:00\",\n    \"2157-02-11 00:00:00\",\n    \"2157-02-23 00:00:00\",\n    \"2157-03-20 00:00:00\",\n    \"2157-04-29 00:00:00\",\n    \"2157-05-03 00:00:00\",\n    \"2157-05-04 00:00:00\",\n    \"2157-05-05 00:00:00\",\n    \"2157-07-18 00:00:00\",\n    \"2157-08-11 00:00:00\",\n    \"2157-09-19 00:00:00\",\n    \"2157-09-22 00:00:00\",\n    \"2157-10-10 00:00:00\",\n    \"2157-11-03 00:00:00\",\n    \"2157-11-23 00:00:00\",\n    \"2157-12-31 00:00:00\",\n    \"2158-01-01 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-01-03 00:00:00\",\n    \"2158-01-09 00:00:00\",\n    \"2158-02-11 00:00:00\",\n    \"2158-02-23 00:00:00\",\n    \"2158-03-20 00:00:00\",\n    \"2158-04-29 00:00:00\",\n    \"2158-05-03 00:00:00\",\n    \"2158-05-04 00:00:00\",\n    \"2158-05-05 00:00:00\",\n    \"2158-07-17 00:00:00\",\n    \"2158-08-11 00:00:00\",\n    \"2158-09-18 00:00:00\",\n    \"2158-09-22 00:00:00\",\n    \"2158-10-09 00:00:00\",\n    \"2158-11-03 00:00:00\",\n    \"2158-11-23 00:00:00\",\n    \"2158-12-31 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-01-02 00:00:00\",\n    \"2159-01-03 00:00:00\",\n    \"2159-01-08 00:00:00\",\n    \"2159-02-12 00:00:00\",\n    \"2159-02-23 00:00:00\",\n    \"2159-03-20 00:00:00\",\n    \"2159-04-30 00:00:00\",\n    \"2159-05-03 00:00:00\",\n    \"2159-05-04 00:00:00\",\n    \"2159-05-05 00:00:00\",\n    \"2159-07-16 00:00:00\",\n    \"2159-08-11 00:00:00\",\n    \"2159-09-17 00:00:00\",\n    \"2159-09-23 00:00:00\",\n    \"2159-10-08 00:00:00\",\n    \"2159-11-03 00:00:00\",\n    \"2159-11-23 00:00:00\",\n    \"2159-12-31 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-01-02 00:00:00\",\n    \"2160-01-03 00:00:00\",\n    \"2160-01-14 00:00:00\",\n    \"2160-02-11 00:00:00\",\n    \"2160-02-23 00:00:00\",\n    \"2160-03-20 00:00:00\",\n    \"2160-04-29 00:00:00\",\n    \"2160-05-03 00:00:00\",\n    \"2160-05-05 00:00:00\",\n    \"2160-05-06 00:00:00\",\n    \"2160-07-21 00:00:00\",\n    \"2160-08-11 00:00:00\",\n    \"2160-09-15 00:00:00\",\n    \"2160-09-22 00:00:00\",\n    \"2160-10-13 00:00:00\",\n    \"2160-11-03 00:00:00\",\n    \"2160-11-24 00:00:00\",\n    \"2160-12-31 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-01-02 00:00:00\",\n    \"2161-01-03 00:00:00\",\n    \"2161-01-12 00:00:00\",\n    \"2161-02-11 00:00:00\",\n    \"2161-02-23 00:00:00\",\n    \"2161-03-20 00:00:00\",\n    \"2161-04-29 00:00:00\",\n    \"2161-05-04 00:00:00\",\n    \"2161-05-05 00:00:00\",\n    \"2161-05-06 00:00:00\",\n    \"2161-07-20 00:00:00\",\n    \"2161-08-11 00:00:00\",\n    \"2161-09-21 00:00:00\",\n    \"2161-09-22 00:00:00\",\n    \"2161-10-12 00:00:00\",\n    \"2161-11-03 00:00:00\",\n    \"2161-11-23 00:00:00\",\n    \"2161-12-31 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-01-02 00:00:00\",\n    \"2162-01-03 00:00:00\",\n    \"2162-01-11 00:00:00\",\n    \"2162-02-11 00:00:00\",\n    \"2162-02-23 00:00:00\",\n    \"2162-03-20 00:00:00\",\n    \"2162-04-29 00:00:00\",\n    \"2162-05-03 00:00:00\",\n    \"2162-05-04 00:00:00\",\n    \"2162-05-05 00:00:00\",\n    \"2162-07-19 00:00:00\",\n    \"2162-08-11 00:00:00\",\n    \"2162-09-20 00:00:00\",\n    \"2162-09-22 00:00:00\",\n    \"2162-10-11 00:00:00\",\n    \"2162-11-03 00:00:00\",\n    \"2162-11-23 00:00:00\",\n    \"2162-12-31 00:00:00\",\n    \"2163-01-01 00:00:00\",\n    \"2163-01-02 00:00:00\",\n    \"2163-01-03 00:00:00\",\n    \"2163-01-10 00:00:00\",\n    \"2163-02-11 00:00:00\",\n    \"2163-02-23 00:00:00\",\n    \"2163-03-20 00:00:00\",\n    \"2163-04-29 00:00:00\",\n    \"2163-05-03 00:00:00\",\n    \"2163-05-04 00:00:00\",\n    \"2163-05-05 00:00:00\",\n    \"2163-07-18 00:00:00\",\n    \"2163-08-11 00:00:00\",\n    \"2163-09-19 00:00:00\",\n    \"2163-09-23 00:00:00\",\n    \"2163-10-10 00:00:00\",\n    \"2163-11-03 00:00:00\",\n    \"2163-11-23 00:00:00\",\n    \"2163-12-31 00:00:00\",\n    \"2164-01-01 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-01-03 00:00:00\",\n    \"2164-01-09 00:00:00\",\n    \"2164-02-11 00:00:00\",\n    \"2164-02-23 00:00:00\",\n    \"2164-03-20 00:00:00\",\n    \"2164-04-30 00:00:00\",\n    \"2164-05-03 00:00:00\",\n    \"2164-05-04 00:00:00\",\n    \"2164-05-05 00:00:00\",\n    \"2164-07-16 00:00:00\",\n    \"2164-08-11 00:00:00\",\n    \"2164-09-17 00:00:00\",\n    \"2164-09-22 00:00:00\",\n    \"2164-10-08 00:00:00\",\n    \"2164-11-03 00:00:00\",\n    \"2164-11-23 00:00:00\",\n    \"2164-12-31 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-01-02 00:00:00\",\n    \"2165-01-03 00:00:00\",\n    \"2165-01-14 00:00:00\",\n    \"2165-02-11 00:00:00\",\n    \"2165-02-23 00:00:00\",\n    \"2165-03-20 00:00:00\",\n    \"2165-04-29 00:00:00\",\n    \"2165-05-03 00:00:00\",\n    \"2165-05-04 00:00:00\",\n    \"2165-05-06 00:00:00\",\n    \"2165-07-15 00:00:00\",\n    \"2165-08-12 00:00:00\",\n    \"2165-09-16 00:00:00\",\n    \"2165-09-22 00:00:00\",\n    \"2165-10-14 00:00:00\",\n    \"2165-11-04 00:00:00\",\n    \"2165-11-23 00:00:00\",\n    \"2165-12-31 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-01-02 00:00:00\",\n    \"2166-01-03 00:00:00\",\n    \"2166-01-13 00:00:00\",\n    \"2166-02-11 00:00:00\",\n    \"2166-02-24 00:00:00\",\n    \"2166-03-20 00:00:00\",\n    \"2166-04-29 00:00:00\",\n    \"2166-05-03 00:00:00\",\n    \"2166-05-05 00:00:00\",\n    \"2166-05-06 00:00:00\",\n    \"2166-07-21 00:00:00\",\n    \"2166-08-11 00:00:00\",\n    \"2166-09-15 00:00:00\",\n    \"2166-09-22 00:00:00\",\n    \"2166-10-13 00:00:00\",\n    \"2166-11-03 00:00:00\",\n    \"2166-11-24 00:00:00\",\n    \"2166-12-31 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-01-02 00:00:00\",\n    \"2167-01-03 00:00:00\",\n    \"2167-01-12 00:00:00\",\n    \"2167-02-11 00:00:00\",\n    \"2167-02-23 00:00:00\",\n    \"2167-03-20 00:00:00\",\n    \"2167-04-29 00:00:00\",\n    \"2167-05-04 00:00:00\",\n    \"2167-05-05 00:00:00\",\n    \"2167-05-06 00:00:00\",\n    \"2167-07-20 00:00:00\",\n    \"2167-08-11 00:00:00\",\n    \"2167-09-21 00:00:00\",\n    \"2167-09-23 00:00:00\",\n    \"2167-10-12 00:00:00\",\n    \"2167-11-03 00:00:00\",\n    \"2167-11-23 00:00:00\",\n    \"2167-12-31 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-01-02 00:00:00\",\n    \"2168-01-03 00:00:00\",\n    \"2168-01-11 00:00:00\",\n    \"2168-02-11 00:00:00\",\n    \"2168-02-23 00:00:00\",\n    \"2168-03-20 00:00:00\",\n    \"2168-04-29 00:00:00\",\n    \"2168-05-03 00:00:00\",\n    \"2168-05-04 00:00:00\",\n    \"2168-05-05 00:00:00\",\n    \"2168-07-18 00:00:00\",\n    \"2168-08-11 00:00:00\",\n    \"2168-09-19 00:00:00\",\n    \"2168-09-22 00:00:00\",\n    \"2168-10-10 00:00:00\",\n    \"2168-11-03 00:00:00\",\n    \"2168-11-23 00:00:00\",\n    \"2168-12-31 00:00:00\",\n    \"2169-01-01 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-01-03 00:00:00\",\n    \"2169-01-09 00:00:00\",\n    \"2169-02-11 00:00:00\",\n    \"2169-02-23 00:00:00\",\n    \"2169-03-20 00:00:00\",\n    \"2169-04-29 00:00:00\",\n    \"2169-05-03 00:00:00\",\n    \"2169-05-04 00:00:00\",\n    \"2169-05-05 00:00:00\",\n    \"2169-07-17 00:00:00\",\n    \"2169-08-11 00:00:00\",\n    \"2169-09-18 00:00:00\",\n    \"2169-09-22 00:00:00\",\n    \"2169-10-09 00:00:00\",\n    \"2169-11-03 00:00:00\",\n    \"2169-11-23 00:00:00\",\n    \"2169-12-31 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-01-02 00:00:00\",\n    \"2170-01-03 00:00:00\",\n    \"2170-01-08 00:00:00\",\n    \"2170-02-12 00:00:00\",\n    \"2170-02-23 00:00:00\",\n    \"2170-03-20 00:00:00\",\n    \"2170-04-30 00:00:00\",\n    \"2170-05-03 00:00:00\",\n    \"2170-05-04 00:00:00\",\n    \"2170-05-05 00:00:00\",\n    \"2170-07-16 00:00:00\",\n    \"2170-08-11 00:00:00\",\n    \"2170-09-17 00:00:00\",\n    \"2170-09-22 00:00:00\",\n    \"2170-10-08 00:00:00\",\n    \"2170-11-03 00:00:00\",\n    \"2170-11-23 00:00:00\",\n    \"2170-12-31 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-01-02 00:00:00\",\n    \"2171-01-03 00:00:00\",\n    \"2171-01-14 00:00:00\",\n    \"2171-02-11 00:00:00\",\n    \"2171-02-23 00:00:00\",\n    \"2171-03-20 00:00:00\",\n    \"2171-04-29 00:00:00\",\n    \"2171-05-03 00:00:00\",\n    \"2171-05-04 00:00:00\",\n    \"2171-05-06 00:00:00\",\n    \"2171-07-15 00:00:00\",\n    \"2171-08-12 00:00:00\",\n    \"2171-09-16 00:00:00\",\n    \"2171-09-23 00:00:00\",\n    \"2171-10-14 00:00:00\",\n    \"2171-11-04 00:00:00\",\n    \"2171-11-23 00:00:00\",\n    \"2171-12-31 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-01-02 00:00:00\",\n    \"2172-01-03 00:00:00\",\n    \"2172-01-13 00:00:00\",\n    \"2172-02-11 00:00:00\",\n    \"2172-02-24 00:00:00\",\n    \"2172-03-19 00:00:00\",\n    \"2172-04-29 00:00:00\",\n    \"2172-05-04 00:00:00\",\n    \"2172-05-05 00:00:00\",\n    \"2172-05-06 00:00:00\",\n    \"2172-07-20 00:00:00\",\n    \"2172-08-11 00:00:00\",\n    \"2172-09-21 00:00:00\",\n    \"2172-09-22 00:00:00\",\n    \"2172-10-12 00:00:00\",\n    \"2172-11-03 00:00:00\",\n    \"2172-11-23 00:00:00\",\n    \"2172-12-31 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-01-02 00:00:00\",\n    \"2173-01-03 00:00:00\",\n    \"2173-01-11 00:00:00\",\n    \"2173-02-11 00:00:00\",\n    \"2173-02-23 00:00:00\",\n    \"2173-03-20 00:00:00\",\n    \"2173-04-29 00:00:00\",\n    \"2173-05-03 00:00:00\",\n    \"2173-05-04 00:00:00\",\n    \"2173-05-05 00:00:00\",\n    \"2173-07-19 00:00:00\",\n    \"2173-08-11 00:00:00\",\n    \"2173-09-20 00:00:00\",\n    \"2173-09-22 00:00:00\",\n    \"2173-10-11 00:00:00\",\n    \"2173-11-03 00:00:00\",\n    \"2173-11-23 00:00:00\",\n    \"2173-12-31 00:00:00\",\n    \"2174-01-01 00:00:00\",\n    \"2174-01-02 00:00:00\",\n    \"2174-01-03 00:00:00\",\n    \"2174-01-10 00:00:00\",\n    \"2174-02-11 00:00:00\",\n    \"2174-02-23 00:00:00\",\n    \"2174-03-20 00:00:00\",\n    \"2174-04-29 00:00:00\",\n    \"2174-05-03 00:00:00\",\n    \"2174-05-04 00:00:00\",\n    \"2174-05-05 00:00:00\",\n    \"2174-07-18 00:00:00\",\n    \"2174-08-11 00:00:00\",\n    \"2174-09-19 00:00:00\",\n    \"2174-09-22 00:00:00\",\n    \"2174-10-10 00:00:00\",\n    \"2174-11-03 00:00:00\",\n    \"2174-11-23 00:00:00\",\n    \"2174-12-31 00:00:00\",\n    \"2175-01-01 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-01-03 00:00:00\",\n    \"2175-01-09 00:00:00\",\n    \"2175-02-11 00:00:00\",\n    \"2175-02-23 00:00:00\",\n    \"2175-03-20 00:00:00\",\n    \"2175-04-29 00:00:00\",\n    \"2175-05-03 00:00:00\",\n    \"2175-05-04 00:00:00\",\n    \"2175-05-05 00:00:00\",\n    \"2175-07-17 00:00:00\",\n    \"2175-08-11 00:00:00\",\n    \"2175-09-18 00:00:00\",\n    \"2175-09-23 00:00:00\",\n    \"2175-10-09 00:00:00\",\n    \"2175-11-03 00:00:00\",\n    \"2175-11-23 00:00:00\",\n    \"2175-12-31 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-01-02 00:00:00\",\n    \"2176-01-03 00:00:00\",\n    \"2176-01-08 00:00:00\",\n    \"2176-02-12 00:00:00\",\n    \"2176-02-23 00:00:00\",\n    \"2176-03-19 00:00:00\",\n    \"2176-04-29 00:00:00\",\n    \"2176-05-03 00:00:00\",\n    \"2176-05-04 00:00:00\",\n    \"2176-05-06 00:00:00\",\n    \"2176-07-15 00:00:00\",\n    \"2176-08-12 00:00:00\",\n    \"2176-09-16 00:00:00\",\n    \"2176-09-22 00:00:00\",\n    \"2176-10-14 00:00:00\",\n    \"2176-11-04 00:00:00\",\n    \"2176-11-23 00:00:00\",\n    \"2176-12-31 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-01-02 00:00:00\",\n    \"2177-01-03 00:00:00\",\n    \"2177-01-13 00:00:00\",\n    \"2177-02-11 00:00:00\",\n    \"2177-02-24 00:00:00\",\n    \"2177-03-20 00:00:00\",\n    \"2177-04-29 00:00:00\",\n    \"2177-05-03 00:00:00\",\n    \"2177-05-05 00:00:00\",\n    \"2177-05-06 00:00:00\",\n    \"2177-07-21 00:00:00\",\n    \"2177-08-11 00:00:00\",\n    \"2177-09-15 00:00:00\",\n    \"2177-09-22 00:00:00\",\n    \"2177-10-13 00:00:00\",\n    \"2177-11-03 00:00:00\",\n    \"2177-11-24 00:00:00\",\n    \"2177-12-31 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-01-02 00:00:00\",\n    \"2178-01-03 00:00:00\",\n    \"2178-01-12 00:00:00\",\n    \"2178-02-11 00:00:00\",\n    \"2178-02-23 00:00:00\",\n    \"2178-03-20 00:00:00\",\n    \"2178-04-29 00:00:00\",\n    \"2178-05-04 00:00:00\",\n    \"2178-05-05 00:00:00\",\n    \"2178-05-06 00:00:00\",\n    \"2178-07-20 00:00:00\",\n    \"2178-08-11 00:00:00\",\n    \"2178-09-21 00:00:00\",\n    \"2178-09-22 00:00:00\",\n    \"2178-10-12 00:00:00\",\n    \"2178-11-03 00:00:00\",\n    \"2178-11-23 00:00:00\",\n    \"2178-12-31 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-01-02 00:00:00\",\n    \"2179-01-03 00:00:00\",\n    \"2179-01-11 00:00:00\",\n    \"2179-02-11 00:00:00\",\n    \"2179-02-23 00:00:00\",\n    \"2179-03-20 00:00:00\",\n    \"2179-04-29 00:00:00\",\n    \"2179-05-03 00:00:00\",\n    \"2179-05-04 00:00:00\",\n    \"2179-05-05 00:00:00\",\n    \"2179-07-19 00:00:00\",\n    \"2179-08-11 00:00:00\",\n    \"2179-09-20 00:00:00\",\n    \"2179-09-23 00:00:00\",\n    \"2179-10-11 00:00:00\",\n    \"2179-11-03 00:00:00\",\n    \"2179-11-23 00:00:00\",\n    \"2179-12-31 00:00:00\",\n    \"2180-01-01 00:00:00\",\n    \"2180-01-02 00:00:00\",\n    \"2180-01-03 00:00:00\",\n    \"2180-01-10 00:00:00\",\n    \"2180-02-11 00:00:00\",\n    \"2180-02-23 00:00:00\",\n    \"2180-03-19 00:00:00\",\n    \"2180-04-29 00:00:00\",\n    \"2180-05-03 00:00:00\",\n    \"2180-05-04 00:00:00\",\n    \"2180-05-05 00:00:00\",\n    \"2180-07-17 00:00:00\",\n    \"2180-08-11 00:00:00\",\n    \"2180-09-18 00:00:00\",\n    \"2180-09-22 00:00:00\",\n    \"2180-10-09 00:00:00\",\n    \"2180-11-03 00:00:00\",\n    \"2180-11-23 00:00:00\",\n    \"2180-12-31 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-01-02 00:00:00\",\n    \"2181-01-03 00:00:00\",\n    \"2181-01-08 00:00:00\",\n    \"2181-02-12 00:00:00\",\n    \"2181-02-23 00:00:00\",\n    \"2181-03-20 00:00:00\",\n    \"2181-04-30 00:00:00\",\n    \"2181-05-03 00:00:00\",\n    \"2181-05-04 00:00:00\",\n    \"2181-05-05 00:00:00\",\n    \"2181-07-16 00:00:00\",\n    \"2181-08-11 00:00:00\",\n    \"2181-09-17 00:00:00\",\n    \"2181-09-22 00:00:00\",\n    \"2181-10-08 00:00:00\",\n    \"2181-11-03 00:00:00\",\n    \"2181-11-23 00:00:00\",\n    \"2181-12-31 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-01-02 00:00:00\",\n    \"2182-01-03 00:00:00\",\n    \"2182-01-14 00:00:00\",\n    \"2182-02-11 00:00:00\",\n    \"2182-02-23 00:00:00\",\n    \"2182-03-20 00:00:00\",\n    \"2182-04-29 00:00:00\",\n    \"2182-05-03 00:00:00\",\n    \"2182-05-04 00:00:00\",\n    \"2182-05-06 00:00:00\",\n    \"2182-07-15 00:00:00\",\n    \"2182-08-12 00:00:00\",\n    \"2182-09-16 00:00:00\",\n    \"2182-09-22 00:00:00\",\n    \"2182-10-14 00:00:00\",\n    \"2182-11-04 00:00:00\",\n    \"2182-11-23 00:00:00\",\n    \"2182-12-31 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-01-02 00:00:00\",\n    \"2183-01-03 00:00:00\",\n    \"2183-01-13 00:00:00\",\n    \"2183-02-11 00:00:00\",\n    \"2183-02-24 00:00:00\",\n    \"2183-03-20 00:00:00\",\n    \"2183-04-29 00:00:00\",\n    \"2183-05-03 00:00:00\",\n    \"2183-05-05 00:00:00\",\n    \"2183-05-06 00:00:00\",\n    \"2183-07-21 00:00:00\",\n    \"2183-08-11 00:00:00\",\n    \"2183-09-15 00:00:00\",\n    \"2183-09-23 00:00:00\",\n    \"2183-10-13 00:00:00\",\n    \"2183-11-03 00:00:00\",\n    \"2183-11-24 00:00:00\",\n    \"2183-12-31 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-01-02 00:00:00\",\n    \"2184-01-03 00:00:00\",\n    \"2184-01-12 00:00:00\",\n    \"2184-02-11 00:00:00\",\n    \"2184-02-23 00:00:00\",\n    \"2184-03-19 00:00:00\",\n    \"2184-04-29 00:00:00\",\n    \"2184-05-03 00:00:00\",\n    \"2184-05-04 00:00:00\",\n    \"2184-05-05 00:00:00\",\n    \"2184-07-19 00:00:00\",\n    \"2184-08-11 00:00:00\",\n    \"2184-09-20 00:00:00\",\n    \"2184-09-22 00:00:00\",\n    \"2184-10-11 00:00:00\",\n    \"2184-11-03 00:00:00\",\n    \"2184-11-23 00:00:00\",\n    \"2184-12-31 00:00:00\",\n    \"2185-01-01 00:00:00\",\n    \"2185-01-02 00:00:00\",\n    \"2185-01-03 00:00:00\",\n    \"2185-01-10 00:00:00\",\n    \"2185-02-11 00:00:00\",\n    \"2185-02-23 00:00:00\",\n    \"2185-03-20 00:00:00\",\n    \"2185-04-29 00:00:00\",\n    \"2185-05-03 00:00:00\",\n    \"2185-05-04 00:00:00\",\n    \"2185-05-05 00:00:00\",\n    \"2185-07-18 00:00:00\",\n    \"2185-08-11 00:00:00\",\n    \"2185-09-19 00:00:00\",\n    \"2185-09-22 00:00:00\",\n    \"2185-10-10 00:00:00\",\n    \"2185-11-03 00:00:00\",\n    \"2185-11-23 00:00:00\",\n    \"2185-12-31 00:00:00\",\n    \"2186-01-01 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-01-03 00:00:00\",\n    \"2186-01-09 00:00:00\",\n    \"2186-02-11 00:00:00\",\n    \"2186-02-23 00:00:00\",\n    \"2186-03-20 00:00:00\",\n    \"2186-04-29 00:00:00\",\n    \"2186-05-03 00:00:00\",\n    \"2186-05-04 00:00:00\",\n    \"2186-05-05 00:00:00\",\n    \"2186-07-17 00:00:00\",\n    \"2186-08-11 00:00:00\",\n    \"2186-09-18 00:00:00\",\n    \"2186-09-22 00:00:00\",\n    \"2186-10-09 00:00:00\",\n    \"2186-11-03 00:00:00\",\n    \"2186-11-23 00:00:00\",\n    \"2186-12-31 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-01-02 00:00:00\",\n    \"2187-01-03 00:00:00\",\n    \"2187-01-08 00:00:00\",\n    \"2187-02-12 00:00:00\",\n    \"2187-02-23 00:00:00\",\n    \"2187-03-20 00:00:00\",\n    \"2187-04-30 00:00:00\",\n    \"2187-05-03 00:00:00\",\n    \"2187-05-04 00:00:00\",\n    \"2187-05-05 00:00:00\",\n    \"2187-07-16 00:00:00\",\n    \"2187-08-11 00:00:00\",\n    \"2187-09-17 00:00:00\",\n    \"2187-09-22 00:00:00\",\n    \"2187-10-08 00:00:00\",\n    \"2187-11-03 00:00:00\",\n    \"2187-11-23 00:00:00\",\n    \"2187-12-31 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-01-02 00:00:00\",\n    \"2188-01-03 00:00:00\",\n    \"2188-01-14 00:00:00\",\n    \"2188-02-11 00:00:00\",\n    \"2188-02-23 00:00:00\",\n    \"2188-03-19 00:00:00\",\n    \"2188-04-29 00:00:00\",\n    \"2188-05-03 00:00:00\",\n    \"2188-05-05 00:00:00\",\n    \"2188-05-06 00:00:00\",\n    \"2188-07-21 00:00:00\",\n    \"2188-08-11 00:00:00\",\n    \"2188-09-15 00:00:00\",\n    \"2188-09-22 00:00:00\",\n    \"2188-10-13 00:00:00\",\n    \"2188-11-03 00:00:00\",\n    \"2188-11-24 00:00:00\",\n    \"2188-12-31 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-01-02 00:00:00\",\n    \"2189-01-03 00:00:00\",\n    \"2189-01-12 00:00:00\",\n    \"2189-02-11 00:00:00\",\n    \"2189-02-23 00:00:00\",\n    \"2189-03-20 00:00:00\",\n    \"2189-04-29 00:00:00\",\n    \"2189-05-04 00:00:00\",\n    \"2189-05-05 00:00:00\",\n    \"2189-05-06 00:00:00\",\n    \"2189-07-20 00:00:00\",\n    \"2189-08-11 00:00:00\",\n    \"2189-09-21 00:00:00\",\n    \"2189-09-22 00:00:00\",\n    \"2189-10-12 00:00:00\",\n    \"2189-11-03 00:00:00\",\n    \"2189-11-23 00:00:00\",\n    \"2189-12-31 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-01-02 00:00:00\",\n    \"2190-01-03 00:00:00\",\n    \"2190-01-11 00:00:00\",\n    \"2190-02-11 00:00:00\",\n    \"2190-02-23 00:00:00\",\n    \"2190-03-20 00:00:00\",\n    \"2190-04-29 00:00:00\",\n    \"2190-05-03 00:00:00\",\n    \"2190-05-04 00:00:00\",\n    \"2190-05-05 00:00:00\",\n    \"2190-07-19 00:00:00\",\n    \"2190-08-11 00:00:00\",\n    \"2190-09-20 00:00:00\",\n    \"2190-09-22 00:00:00\",\n    \"2190-10-11 00:00:00\",\n    \"2190-11-03 00:00:00\",\n    \"2190-11-23 00:00:00\",\n    \"2190-12-31 00:00:00\",\n    \"2191-01-01 00:00:00\",\n    \"2191-01-02 00:00:00\",\n    \"2191-01-03 00:00:00\",\n    \"2191-01-10 00:00:00\",\n    \"2191-02-11 00:00:00\",\n    \"2191-02-23 00:00:00\",\n    \"2191-03-20 00:00:00\",\n    \"2191-04-29 00:00:00\",\n    \"2191-05-03 00:00:00\",\n    \"2191-05-04 00:00:00\",\n    \"2191-05-05 00:00:00\",\n    \"2191-07-18 00:00:00\",\n    \"2191-08-11 00:00:00\",\n    \"2191-09-19 00:00:00\",\n    \"2191-09-22 00:00:00\",\n    \"2191-10-10 00:00:00\",\n    \"2191-11-03 00:00:00\",\n    \"2191-11-23 00:00:00\",\n    \"2191-12-31 00:00:00\",\n    \"2192-01-01 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-01-03 00:00:00\",\n    \"2192-01-09 00:00:00\",\n    \"2192-02-11 00:00:00\",\n    \"2192-02-23 00:00:00\",\n    \"2192-03-19 00:00:00\",\n    \"2192-04-30 00:00:00\",\n    \"2192-05-03 00:00:00\",\n    \"2192-05-04 00:00:00\",\n    \"2192-05-05 00:00:00\",\n    \"2192-07-16 00:00:00\",\n    \"2192-08-11 00:00:00\",\n    \"2192-09-17 00:00:00\",\n    \"2192-09-22 00:00:00\",\n    \"2192-10-08 00:00:00\",\n    \"2192-11-03 00:00:00\",\n    \"2192-11-23 00:00:00\",\n    \"2192-12-31 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-01-02 00:00:00\",\n    \"2193-01-03 00:00:00\",\n    \"2193-01-14 00:00:00\",\n    \"2193-02-11 00:00:00\",\n    \"2193-02-23 00:00:00\",\n    \"2193-03-20 00:00:00\",\n    \"2193-04-29 00:00:00\",\n    \"2193-05-03 00:00:00\",\n    \"2193-05-04 00:00:00\",\n    \"2193-05-06 00:00:00\",\n    \"2193-07-15 00:00:00\",\n    \"2193-08-12 00:00:00\",\n    \"2193-09-16 00:00:00\",\n    \"2193-09-22 00:00:00\",\n    \"2193-10-14 00:00:00\",\n    \"2193-11-04 00:00:00\",\n    \"2193-11-23 00:00:00\",\n    \"2193-12-31 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-01-02 00:00:00\",\n    \"2194-01-03 00:00:00\",\n    \"2194-01-13 00:00:00\",\n    \"2194-02-11 00:00:00\",\n    \"2194-02-24 00:00:00\",\n    \"2194-03-20 00:00:00\",\n    \"2194-04-29 00:00:00\",\n    \"2194-05-03 00:00:00\",\n    \"2194-05-05 00:00:00\",\n    \"2194-05-06 00:00:00\",\n    \"2194-07-21 00:00:00\",\n    \"2194-08-11 00:00:00\",\n    \"2194-09-15 00:00:00\",\n    \"2194-09-22 00:00:00\",\n    \"2194-10-13 00:00:00\",\n    \"2194-11-03 00:00:00\",\n    \"2194-11-24 00:00:00\",\n    \"2194-12-31 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-01-02 00:00:00\",\n    \"2195-01-03 00:00:00\",\n    \"2195-01-12 00:00:00\",\n    \"2195-02-11 00:00:00\",\n    \"2195-02-23 00:00:00\",\n    \"2195-03-20 00:00:00\",\n    \"2195-04-29 00:00:00\",\n    \"2195-05-04 00:00:00\",\n    \"2195-05-05 00:00:00\",\n    \"2195-05-06 00:00:00\",\n    \"2195-07-20 00:00:00\",\n    \"2195-08-11 00:00:00\",\n    \"2195-09-21 00:00:00\",\n    \"2195-09-22 00:00:00\",\n    \"2195-10-12 00:00:00\",\n    \"2195-11-03 00:00:00\",\n    \"2195-11-23 00:00:00\",\n    \"2195-12-31 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-01-02 00:00:00\",\n    \"2196-01-03 00:00:00\",\n    \"2196-01-11 00:00:00\",\n    \"2196-02-11 00:00:00\",\n    \"2196-02-23 00:00:00\",\n    \"2196-03-19 00:00:00\",\n    \"2196-04-29 00:00:00\",\n    \"2196-05-03 00:00:00\",\n    \"2196-05-04 00:00:00\",\n    \"2196-05-05 00:00:00\",\n    \"2196-07-18 00:00:00\",\n    \"2196-08-11 00:00:00\",\n    \"2196-09-19 00:00:00\",\n    \"2196-09-22 00:00:00\",\n    \"2196-10-10 00:00:00\",\n    \"2196-11-03 00:00:00\",\n    \"2196-11-23 00:00:00\",\n    \"2196-12-31 00:00:00\",\n    \"2197-01-01 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-01-03 00:00:00\",\n    \"2197-01-09 00:00:00\",\n    \"2197-02-11 00:00:00\",\n    \"2197-02-23 00:00:00\",\n    \"2197-03-20 00:00:00\",\n    \"2197-04-29 00:00:00\",\n    \"2197-05-03 00:00:00\",\n    \"2197-05-04 00:00:00\",\n    \"2197-05-05 00:00:00\",\n    \"2197-07-17 00:00:00\",\n    \"2197-08-11 00:00:00\",\n    \"2197-09-18 00:00:00\",\n    \"2197-09-22 00:00:00\",\n    \"2197-10-09 00:00:00\",\n    \"2197-11-03 00:00:00\",\n    \"2197-11-23 00:00:00\",\n    \"2197-12-31 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-01-02 00:00:00\",\n    \"2198-01-03 00:00:00\",\n    \"2198-01-08 00:00:00\",\n    \"2198-02-12 00:00:00\",\n    \"2198-02-23 00:00:00\",\n    \"2198-03-20 00:00:00\",\n    \"2198-04-30 00:00:00\",\n    \"2198-05-03 00:00:00\",\n    \"2198-05-04 00:00:00\",\n    \"2198-05-05 00:00:00\",\n    \"2198-07-16 00:00:00\",\n    \"2198-08-11 00:00:00\",\n    \"2198-09-17 00:00:00\",\n    \"2198-09-22 00:00:00\",\n    \"2198-10-08 00:00:00\",\n    \"2198-11-03 00:00:00\",\n    \"2198-11-23 00:00:00\",\n    \"2198-12-31 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-01-02 00:00:00\",\n    \"2199-01-03 00:00:00\",\n    \"2199-01-14 00:00:00\",\n    \"2199-02-11 00:00:00\",\n    \"2199-02-23 00:00:00\",\n    \"2199-03-20 00:00:00\",\n    \"2199-04-29 00:00:00\",\n    \"2199-05-03 00:00:00\",\n    \"2199-05-04 00:00:00\",\n    \"2199-05-06 00:00:00\",\n    \"2199-07-15 00:00:00\",\n    \"2199-08-12 00:00:00\",\n    \"2199-09-16 00:00:00\",\n    \"2199-09-22 00:00:00\",\n    \"2199-10-14 00:00:00\",\n    \"2199-11-04 00:00:00\",\n    \"2199-11-23 00:00:00\",\n    \"2199-12-31 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-01-02 00:00:00\",\n    \"2200-01-03 00:00:00\",\n    \"2200-01-13 00:00:00\",\n    \"2200-02-11 00:00:00\",\n    \"2200-02-24 00:00:00\",\n    \"2200-03-20 00:00:00\",\n    \"2200-04-29 00:00:00\",\n    \"2200-05-03 00:00:00\",\n    \"2200-05-05 00:00:00\",\n    \"2200-05-06 00:00:00\",\n    \"2200-07-21 00:00:00\",\n    \"2200-08-11 00:00:00\",\n    \"2200-09-15 00:00:00\",\n    \"2200-09-23 00:00:00\",\n    \"2200-10-13 00:00:00\",\n    \"2200-11-03 00:00:00\",\n    \"2200-11-24 00:00:00\",\n    \"2200-12-31 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/tyo_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime, timedelta\n\nimport pandas as pd\nfrom dateutil.relativedelta import MO\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n    sunday_to_monday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, DateOffset\n\n\ndef sunday_to_monday_or_tuesday(dt: datetime) -> datetime:\n    \"\"\"\n    Used for Greenery Day\n    If holiday falls on Sunday, use following Monday instead;\n    if holiday falls on Monday, use the following Tuesday;\n    \"\"\"\n    dow = dt.weekday()\n    if dow in (6, 0):\n        return dt + timedelta(1)\n    return dt\n\n\ndef sunday_to_monday_or_tuesday_or_wednesday(dt: datetime) -> datetime:\n    \"\"\"\n    Used for Children's Day\n    If holiday falls on Sunday, use following Monday instead;\n    if holiday falls on Monday, use the following Tuesday;\n    if holiday falls on Tuesday, use the following Wednesday\n    \"\"\"\n    dow = dt.weekday()\n    if dow in (6, 0, 1):\n        return dt + timedelta(1)\n    return dt\n\n\n# # The below list of equinoxes were exported using the python library Ephem\n# import ephem\n# for i in range(1970, 2201):\n#     dt = ephem.next_equinox(f\"{i}/1/1\")\n#     print(f\"'{dt.datetime().strftime('%Y-%m-%d')}',\")\n\nvernal_equinox_date = [\n    \"1970-03-21\",\n    \"1971-03-21\",\n    \"1972-03-20\",\n    \"1973-03-20\",\n    \"1974-03-21\",\n    \"1975-03-21\",\n    \"1976-03-20\",\n    \"1977-03-20\",\n    \"1978-03-20\",\n    \"1979-03-21\",\n    \"1980-03-20\",\n    \"1981-03-20\",\n    \"1982-03-20\",\n    \"1983-03-21\",\n    \"1984-03-20\",\n    \"1985-03-20\",\n    \"1986-03-20\",\n    \"1987-03-21\",\n    \"1988-03-20\",\n    \"1989-03-20\",\n    \"1990-03-20\",\n    \"1991-03-21\",\n    \"1992-03-20\",\n    \"1993-03-20\",\n    \"1994-03-20\",\n    \"1995-03-21\",\n    \"1996-03-20\",\n    \"1997-03-20\",\n    \"1998-03-20\",\n    \"1999-03-21\",\n    \"2000-03-20\",\n    \"2001-03-20\",\n    \"2002-03-20\",\n    \"2003-03-21\",\n    \"2004-03-20\",\n    \"2005-03-20\",\n    \"2006-03-20\",\n    \"2007-03-21\",\n    \"2008-03-20\",\n    \"2009-03-20\",\n    \"2010-03-20\",\n    \"2011-03-20\",\n    \"2012-03-20\",\n    \"2013-03-20\",\n    \"2014-03-20\",\n    \"2015-03-20\",\n    \"2016-03-21\",  # Manual edit\n    \"2017-03-20\",\n    \"2018-03-21\",  # Manual edit\n    \"2019-03-20\",\n    \"2020-03-20\",\n    \"2021-03-21\",  # Manual edit\n    \"2022-03-21\",  # Manual edit\n    \"2023-03-21\",  # Manual edit\n    \"2024-03-20\",\n    \"2025-03-20\",\n    \"2026-03-20\",\n    \"2027-03-20\",\n    \"2028-03-20\",\n    \"2029-03-20\",\n    \"2030-03-20\",\n    \"2031-03-20\",\n    \"2032-03-20\",\n    \"2033-03-20\",\n    \"2034-03-20\",\n    \"2035-03-20\",\n    \"2036-03-20\",\n    \"2037-03-20\",\n    \"2038-03-20\",\n    \"2039-03-20\",\n    \"2040-03-20\",\n    \"2041-03-20\",\n    \"2042-03-20\",\n    \"2043-03-20\",\n    \"2044-03-19\",\n    \"2045-03-20\",\n    \"2046-03-20\",\n    \"2047-03-20\",\n    \"2048-03-19\",\n    \"2049-03-20\",\n    \"2050-03-20\",\n    \"2051-03-20\",\n    \"2052-03-19\",\n    \"2053-03-20\",\n    \"2054-03-20\",\n    \"2055-03-20\",\n    \"2056-03-19\",\n    \"2057-03-20\",\n    \"2058-03-20\",\n    \"2059-03-20\",\n    \"2060-03-19\",\n    \"2061-03-20\",\n    \"2062-03-20\",\n    \"2063-03-20\",\n    \"2064-03-19\",\n    \"2065-03-20\",\n    \"2066-03-20\",\n    \"2067-03-20\",\n    \"2068-03-19\",\n    \"2069-03-20\",\n    \"2070-03-20\",\n    \"2071-03-20\",\n    \"2072-03-19\",\n    \"2073-03-20\",\n    \"2074-03-20\",\n    \"2075-03-20\",\n    \"2076-03-19\",\n    \"2077-03-19\",\n    \"2078-03-20\",\n    \"2079-03-20\",\n    \"2080-03-19\",\n    \"2081-03-19\",\n    \"2082-03-20\",\n    \"2083-03-20\",\n    \"2084-03-19\",\n    \"2085-03-19\",\n    \"2086-03-20\",\n    \"2087-03-20\",\n    \"2088-03-19\",\n    \"2089-03-19\",\n    \"2090-03-20\",\n    \"2091-03-20\",\n    \"2092-03-19\",\n    \"2093-03-19\",\n    \"2094-03-20\",\n    \"2095-03-20\",\n    \"2096-03-19\",\n    \"2097-03-19\",\n    \"2098-03-20\",\n    \"2099-03-20\",\n    \"2100-03-20\",\n    \"2101-03-20\",\n    \"2102-03-21\",\n    \"2103-03-21\",\n    \"2104-03-20\",\n    \"2105-03-20\",\n    \"2106-03-21\",\n    \"2107-03-21\",\n    \"2108-03-20\",\n    \"2109-03-20\",\n    \"2110-03-20\",\n    \"2111-03-21\",\n    \"2112-03-20\",\n    \"2113-03-20\",\n    \"2114-03-20\",\n    \"2115-03-21\",\n    \"2116-03-20\",\n    \"2117-03-20\",\n    \"2118-03-20\",\n    \"2119-03-21\",\n    \"2120-03-20\",\n    \"2121-03-20\",\n    \"2122-03-20\",\n    \"2123-03-21\",\n    \"2124-03-20\",\n    \"2125-03-20\",\n    \"2126-03-20\",\n    \"2127-03-21\",\n    \"2128-03-20\",\n    \"2129-03-20\",\n    \"2130-03-20\",\n    \"2131-03-21\",\n    \"2132-03-20\",\n    \"2133-03-20\",\n    \"2134-03-20\",\n    \"2135-03-21\",\n    \"2136-03-20\",\n    \"2137-03-20\",\n    \"2138-03-20\",\n    \"2139-03-20\",\n    \"2140-03-20\",\n    \"2141-03-20\",\n    \"2142-03-20\",\n    \"2143-03-20\",\n    \"2144-03-20\",\n    \"2145-03-20\",\n    \"2146-03-20\",\n    \"2147-03-20\",\n    \"2148-03-20\",\n    \"2149-03-20\",\n    \"2150-03-20\",\n    \"2151-03-20\",\n    \"2152-03-20\",\n    \"2153-03-20\",\n    \"2154-03-20\",\n    \"2155-03-20\",\n    \"2156-03-20\",\n    \"2157-03-20\",\n    \"2158-03-20\",\n    \"2159-03-20\",\n    \"2160-03-20\",\n    \"2161-03-20\",\n    \"2162-03-20\",\n    \"2163-03-20\",\n    \"2164-03-20\",\n    \"2165-03-20\",\n    \"2166-03-20\",\n    \"2167-03-20\",\n    \"2168-03-20\",\n    \"2169-03-20\",\n    \"2170-03-20\",\n    \"2171-03-20\",\n    \"2172-03-19\",\n    \"2173-03-20\",\n    \"2174-03-20\",\n    \"2175-03-20\",\n    \"2176-03-19\",\n    \"2177-03-20\",\n    \"2178-03-20\",\n    \"2179-03-20\",\n    \"2180-03-19\",\n    \"2181-03-20\",\n    \"2182-03-20\",\n    \"2183-03-20\",\n    \"2184-03-19\",\n    \"2185-03-20\",\n    \"2186-03-20\",\n    \"2187-03-20\",\n    \"2188-03-19\",\n    \"2189-03-20\",\n    \"2190-03-20\",\n    \"2191-03-20\",\n    \"2192-03-19\",\n    \"2193-03-20\",\n    \"2194-03-20\",\n    \"2195-03-20\",\n    \"2196-03-19\",\n    \"2197-03-20\",\n    \"2198-03-20\",\n    \"2199-03-20\",\n    \"2200-03-20\",\n]\nvernal_equinox_dict = {\n    k + 1970: datetime.strptime(v, \"%Y-%m-%d\") for k, v in enumerate(vernal_equinox_date)\n}\n\nautumn_equinox_date = [\n    \"1970-09-23\",\n    \"1971-09-23\",\n    \"1972-09-22\",\n    \"1973-09-23\",\n    \"1974-09-23\",\n    \"1975-09-23\",\n    \"1976-09-22\",\n    \"1977-09-23\",\n    \"1978-09-23\",\n    \"1979-09-23\",\n    \"1980-09-22\",\n    \"1981-09-23\",\n    \"1982-09-23\",\n    \"1983-09-23\",\n    \"1984-09-22\",\n    \"1985-09-23\",\n    \"1986-09-23\",\n    \"1987-09-23\",\n    \"1988-09-22\",\n    \"1989-09-23\",\n    \"1990-09-23\",\n    \"1991-09-23\",\n    \"1992-09-22\",\n    \"1993-09-23\",\n    \"1994-09-23\",\n    \"1995-09-23\",\n    \"1996-09-22\",\n    \"1997-09-22\",\n    \"1998-09-23\",\n    \"1999-09-23\",\n    \"2000-09-22\",\n    \"2001-09-22\",\n    \"2002-09-23\",\n    \"2003-09-23\",\n    \"2004-09-22\",\n    \"2005-09-22\",\n    \"2006-09-23\",\n    \"2007-09-23\",\n    \"2008-09-22\",\n    \"2009-09-22\",\n    \"2010-09-23\",\n    \"2011-09-23\",\n    \"2012-09-22\",\n    \"2013-09-22\",\n    \"2014-09-23\",\n    \"2015-09-23\",\n    \"2016-09-22\",\n    \"2017-09-23\",  # Manual edit\n    \"2018-09-24\",  # Manual edit\n    \"2019-09-23\",\n    \"2020-09-22\",\n    \"2021-09-23\",  # Manual edit\n    \"2022-09-23\",\n    \"2023-09-23\",\n    \"2024-09-23\",  # Manual edit\n    \"2025-09-23\",  # Manual edit\n    \"2026-09-23\",\n    \"2027-09-23\",\n    \"2028-09-22\",\n    \"2029-09-22\",\n    \"2030-09-22\",\n    \"2031-09-23\",\n    \"2032-09-22\",\n    \"2033-09-22\",\n    \"2034-09-22\",\n    \"2035-09-23\",\n    \"2036-09-22\",\n    \"2037-09-22\",\n    \"2038-09-22\",\n    \"2039-09-23\",\n    \"2040-09-22\",\n    \"2041-09-22\",\n    \"2042-09-22\",\n    \"2043-09-23\",\n    \"2044-09-22\",\n    \"2045-09-22\",\n    \"2046-09-22\",\n    \"2047-09-23\",\n    \"2048-09-22\",\n    \"2049-09-22\",\n    \"2050-09-22\",\n    \"2051-09-23\",\n    \"2052-09-22\",\n    \"2053-09-22\",\n    \"2054-09-22\",\n    \"2055-09-23\",\n    \"2056-09-22\",\n    \"2057-09-22\",\n    \"2058-09-22\",\n    \"2059-09-23\",\n    \"2060-09-22\",\n    \"2061-09-22\",\n    \"2062-09-22\",\n    \"2063-09-22\",\n    \"2064-09-22\",\n    \"2065-09-22\",\n    \"2066-09-22\",\n    \"2067-09-22\",\n    \"2068-09-22\",\n    \"2069-09-22\",\n    \"2070-09-22\",\n    \"2071-09-22\",\n    \"2072-09-22\",\n    \"2073-09-22\",\n    \"2074-09-22\",\n    \"2075-09-22\",\n    \"2076-09-22\",\n    \"2077-09-22\",\n    \"2078-09-22\",\n    \"2079-09-22\",\n    \"2080-09-22\",\n    \"2081-09-22\",\n    \"2082-09-22\",\n    \"2083-09-22\",\n    \"2084-09-22\",\n    \"2085-09-22\",\n    \"2086-09-22\",\n    \"2087-09-22\",\n    \"2088-09-22\",\n    \"2089-09-22\",\n    \"2090-09-22\",\n    \"2091-09-22\",\n    \"2092-09-21\",\n    \"2093-09-22\",\n    \"2094-09-22\",\n    \"2095-09-22\",\n    \"2096-09-21\",\n    \"2097-09-22\",\n    \"2098-09-22\",\n    \"2099-09-22\",\n    \"2100-09-22\",\n    \"2101-09-23\",\n    \"2102-09-23\",\n    \"2103-09-23\",\n    \"2104-09-22\",\n    \"2105-09-23\",\n    \"2106-09-23\",\n    \"2107-09-23\",\n    \"2108-09-22\",\n    \"2109-09-23\",\n    \"2110-09-23\",\n    \"2111-09-23\",\n    \"2112-09-22\",\n    \"2113-09-23\",\n    \"2114-09-23\",\n    \"2115-09-23\",\n    \"2116-09-22\",\n    \"2117-09-23\",\n    \"2118-09-23\",\n    \"2119-09-23\",\n    \"2120-09-22\",\n    \"2121-09-22\",\n    \"2122-09-23\",\n    \"2123-09-23\",\n    \"2124-09-22\",\n    \"2125-09-22\",\n    \"2126-09-23\",\n    \"2127-09-23\",\n    \"2128-09-22\",\n    \"2129-09-22\",\n    \"2130-09-23\",\n    \"2131-09-23\",\n    \"2132-09-22\",\n    \"2133-09-22\",\n    \"2134-09-23\",\n    \"2135-09-23\",\n    \"2136-09-22\",\n    \"2137-09-22\",\n    \"2138-09-23\",\n    \"2139-09-23\",\n    \"2140-09-22\",\n    \"2141-09-22\",\n    \"2142-09-23\",\n    \"2143-09-23\",\n    \"2144-09-22\",\n    \"2145-09-22\",\n    \"2146-09-23\",\n    \"2147-09-23\",\n    \"2148-09-22\",\n    \"2149-09-22\",\n    \"2150-09-23\",\n    \"2151-09-23\",\n    \"2152-09-22\",\n    \"2153-09-22\",\n    \"2154-09-22\",\n    \"2155-09-23\",\n    \"2156-09-22\",\n    \"2157-09-22\",\n    \"2158-09-22\",\n    \"2159-09-23\",\n    \"2160-09-22\",\n    \"2161-09-22\",\n    \"2162-09-22\",\n    \"2163-09-23\",\n    \"2164-09-22\",\n    \"2165-09-22\",\n    \"2166-09-22\",\n    \"2167-09-23\",\n    \"2168-09-22\",\n    \"2169-09-22\",\n    \"2170-09-22\",\n    \"2171-09-23\",\n    \"2172-09-22\",\n    \"2173-09-22\",\n    \"2174-09-22\",\n    \"2175-09-23\",\n    \"2176-09-22\",\n    \"2177-09-22\",\n    \"2178-09-22\",\n    \"2179-09-23\",\n    \"2180-09-22\",\n    \"2181-09-22\",\n    \"2182-09-22\",\n    \"2183-09-23\",\n    \"2184-09-22\",\n    \"2185-09-22\",\n    \"2186-09-22\",\n    \"2187-09-22\",\n    \"2188-09-22\",\n    \"2189-09-22\",\n    \"2190-09-22\",\n    \"2191-09-22\",\n    \"2192-09-22\",\n    \"2193-09-22\",\n    \"2194-09-22\",\n    \"2195-09-22\",\n    \"2196-09-22\",\n    \"2197-09-22\",\n    \"2198-09-22\",\n    \"2199-09-22\",\n    \"2200-09-23\",\n]\nautumn_equinox_dict = {\n    k + 1970: datetime.strptime(v, \"%Y-%m-%d\") for k, v in enumerate(autumn_equinox_date)\n}\n\n\ndef vernal_equinox_sun_to_mon(dt: datetime) -> datetime:\n    try:\n        dt = vernal_equinox_dict[dt.year]\n    except KeyError:\n        return datetime(1900, 1, 1)\n    if dt.weekday == 6:\n        return dt + timedelta(1)\n    return dt\n\n\ndef autumn_equinox_sun_to_mon(dt: datetime) -> datetime:\n    try:\n        dt = autumn_equinox_dict[dt.year]\n    except KeyError:\n        return datetime(1900, 1, 1)\n    if dt.weekday == 6:\n        return dt + timedelta(1)\n    return dt\n\n\nRULES = [\n    Holiday(\"New Year's Day\", month=1, day=1),\n    Holiday(\"New Year's Bank holiday\", month=1, day=2),\n    Holiday(\"New Year's Bank holiday2\", month=1, day=3),\n    Holiday(\"Coming-of-Age Day\", month=1, day=1, offset=DateOffset(weekday=MO(2))),\n    Holiday(\"Foundation Day\", month=2, day=11, observance=sunday_to_monday),\n    Holiday(\n        \"Emperor Naruhito Birthday\",\n        month=2,\n        day=23,\n        observance=sunday_to_monday,\n        start_date=datetime(2020, 1, 1),\n    ),\n    Holiday(\"Vernal Equinox Day\", month=3, day=20, observance=vernal_equinox_sun_to_mon),\n    Holiday(\"Showa Day\", month=4, day=29, observance=sunday_to_monday),\n    Holiday(\"Constitution Day\", month=5, day=3, observance=sunday_to_monday),\n    Holiday(\"Greenery Day\", month=5, day=4, observance=sunday_to_monday_or_tuesday),\n    Holiday(\"Children's Day\", month=5, day=5, observance=sunday_to_monday_or_tuesday_or_wednesday),\n    Holiday(\n        \"Marine Day: Pre olympics\",\n        month=7,\n        day=1,\n        offset=DateOffset(weekday=MO(3)),\n        end_date=datetime(2019, 12, 31),\n    ),\n    Holiday(\n        \"Marine Day: Post olympics\",\n        month=7,\n        day=1,\n        offset=DateOffset(weekday=MO(3)),\n        start_date=datetime(2022, 1, 1),\n    ),\n    Holiday(\n        \"Mountain Day: Pre olympics\",\n        month=8,\n        day=11,\n        observance=sunday_to_monday,\n        start_date=datetime(2016, 1, 1),\n        end_date=datetime(2019, 12, 31),\n    ),\n    Holiday(\n        \"Mountain Day: Post olympics\",\n        month=8,\n        day=11,\n        observance=sunday_to_monday,\n        start_date=datetime(2022, 1, 1),\n    ),\n    Holiday(\"Respect the Aged Day\", month=9, day=1, offset=DateOffset(weekday=MO(3))),\n    Holiday(\"Autumn Equinox Day\", month=9, day=23, observance=autumn_equinox_sun_to_mon),\n    Holiday(\n        \"Sports Day: Pre Olympics\",\n        month=10,\n        day=1,\n        offset=DateOffset(weekday=MO(2)),\n        end_date=datetime(2019, 12, 31),\n    ),\n    Holiday(\n        \"Sports Day: Post olympics\",\n        month=10,\n        day=1,\n        offset=DateOffset(weekday=MO(2)),\n        start_date=datetime(2022, 1, 1),\n    ),\n    Holiday(\"Culture Day\", month=11, day=3, observance=sunday_to_monday),\n    Holiday(\"Labor Thanksgiving Day\", month=11, day=23, observance=sunday_to_monday),\n    Holiday(\n        \"Emperor Akihito Birthday\",\n        month=12,\n        day=23,\n        observance=sunday_to_monday,\n        end_date=datetime(2019, 1, 1),\n    ),\n    Holiday(\"End of Year\", month=12, day=31),\n    # One off\n    Holiday(\"New Emperor Ascention\", year=2019, month=5, day=1),\n    Holiday(\"Marine Day: During olympics\", year=2020, month=7, day=23),\n    Holiday(\"Marine Day: During paralympics\", year=2021, month=7, day=22),\n    Holiday(\"Mountain Day: During olmpypics\", year=2020, month=8, day=10),\n    Holiday(\"Mountain Day: During paralympics\", year=2021, month=8, day=9),\n    Holiday(\"Sports Day: Olympics\", year=2020, month=7, day=24),\n    Holiday(\"Sports Day: Paralympics\", year=2021, month=7, day=23),\n    Holiday(\"Citizens Holiday 2015\", year=2015, month=9, day=22),\n    Holiday(\"Citizens Holiday 2019\", year=2019, month=4, day=30),\n    Holiday(\"Citizens Holiday2 2019\", year=2019, month=5, day=2),\n    Holiday(\"New Emperor coronation\", year=2019, month=10, day=22),\n    Holiday(\"Bridge Holiday - Silver Week\", year=2026, month=9, day=22),\n]\n\nCALENDAR = CustomBusinessDay(  # type: ignore[call-arg]\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/wlg.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Wellington business day calendar, aligned with NZD rate publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Jan 2 (Day after New Year)\",\n//     \"Feb 6 (Waitangi)\",\n//     \"Fri before Easter (Good Friday)\",\n//     \"Mon after Easter (Easter Monday)\",\n//     \"Apr 25 (Anzac)\",\n//     \"Jun first Mon (Kings Bday)\",\n//     \"Matariki\",\n//     \"Oct last Mon (Labour)\",\n//     \"Dec 25 (Christmas)\",\n//     \"Dec 26 (Boxing Day)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-01-02 00:00:00\",\n    \"1970-01-19 00:00:00\",\n    \"1970-01-26 00:00:00\",\n    \"1970-02-06 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-03-30 00:00:00\",\n    \"1970-04-27 00:00:00\",\n    \"1970-06-01 00:00:00\",\n    \"1970-10-26 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-28 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-01-04 00:00:00\",\n    \"1971-01-25 00:00:00\",\n    \"1971-02-01 00:00:00\",\n    \"1971-02-08 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-12 00:00:00\",\n    \"1971-04-26 00:00:00\",\n    \"1971-06-07 00:00:00\",\n    \"1971-10-25 00:00:00\",\n    \"1971-12-27 00:00:00\",\n    \"1971-12-28 00:00:00\",\n    \"1972-01-03 00:00:00\",\n    \"1972-01-04 00:00:00\",\n    \"1972-01-24 00:00:00\",\n    \"1972-01-31 00:00:00\",\n    \"1972-02-07 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-03 00:00:00\",\n    \"1972-04-25 00:00:00\",\n    \"1972-06-05 00:00:00\",\n    \"1972-10-23 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-01-02 00:00:00\",\n    \"1973-01-22 00:00:00\",\n    \"1973-01-29 00:00:00\",\n    \"1973-02-06 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-04-23 00:00:00\",\n    \"1973-04-25 00:00:00\",\n    \"1973-06-04 00:00:00\",\n    \"1973-10-22 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-01-02 00:00:00\",\n    \"1974-01-21 00:00:00\",\n    \"1974-01-28 00:00:00\",\n    \"1974-02-06 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-15 00:00:00\",\n    \"1974-04-25 00:00:00\",\n    \"1974-06-03 00:00:00\",\n    \"1974-10-28 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-01-02 00:00:00\",\n    \"1975-01-20 00:00:00\",\n    \"1975-01-27 00:00:00\",\n    \"1975-02-06 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-03-31 00:00:00\",\n    \"1975-04-25 00:00:00\",\n    \"1975-06-02 00:00:00\",\n    \"1975-10-27 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-01-02 00:00:00\",\n    \"1976-01-19 00:00:00\",\n    \"1976-01-26 00:00:00\",\n    \"1976-02-06 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-04-19 00:00:00\",\n    \"1976-04-26 00:00:00\",\n    \"1976-06-07 00:00:00\",\n    \"1976-10-25 00:00:00\",\n    \"1976-12-27 00:00:00\",\n    \"1976-12-28 00:00:00\",\n    \"1977-01-03 00:00:00\",\n    \"1977-01-04 00:00:00\",\n    \"1977-01-24 00:00:00\",\n    \"1977-01-31 00:00:00\",\n    \"1977-02-07 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-11 00:00:00\",\n    \"1977-04-25 00:00:00\",\n    \"1977-06-06 00:00:00\",\n    \"1977-10-24 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1977-12-27 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-01-03 00:00:00\",\n    \"1978-01-23 00:00:00\",\n    \"1978-01-30 00:00:00\",\n    \"1978-02-06 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-03-27 00:00:00\",\n    \"1978-04-25 00:00:00\",\n    \"1978-06-05 00:00:00\",\n    \"1978-10-23 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-01-02 00:00:00\",\n    \"1979-01-22 00:00:00\",\n    \"1979-01-29 00:00:00\",\n    \"1979-02-06 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-16 00:00:00\",\n    \"1979-04-25 00:00:00\",\n    \"1979-06-04 00:00:00\",\n    \"1979-10-22 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-01-02 00:00:00\",\n    \"1980-01-21 00:00:00\",\n    \"1980-01-28 00:00:00\",\n    \"1980-02-06 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-07 00:00:00\",\n    \"1980-04-25 00:00:00\",\n    \"1980-06-02 00:00:00\",\n    \"1980-10-27 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-01-02 00:00:00\",\n    \"1981-01-19 00:00:00\",\n    \"1981-01-26 00:00:00\",\n    \"1981-02-06 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-04-20 00:00:00\",\n    \"1981-04-27 00:00:00\",\n    \"1981-06-01 00:00:00\",\n    \"1981-10-26 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-28 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-01-04 00:00:00\",\n    \"1982-01-25 00:00:00\",\n    \"1982-02-01 00:00:00\",\n    \"1982-02-08 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-12 00:00:00\",\n    \"1982-04-26 00:00:00\",\n    \"1982-06-07 00:00:00\",\n    \"1982-10-25 00:00:00\",\n    \"1982-12-27 00:00:00\",\n    \"1982-12-28 00:00:00\",\n    \"1983-01-03 00:00:00\",\n    \"1983-01-04 00:00:00\",\n    \"1983-01-24 00:00:00\",\n    \"1983-01-31 00:00:00\",\n    \"1983-02-07 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-04 00:00:00\",\n    \"1983-04-25 00:00:00\",\n    \"1983-06-06 00:00:00\",\n    \"1983-10-24 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1983-12-27 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-01-03 00:00:00\",\n    \"1984-01-23 00:00:00\",\n    \"1984-01-30 00:00:00\",\n    \"1984-02-06 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-04-23 00:00:00\",\n    \"1984-04-25 00:00:00\",\n    \"1984-06-04 00:00:00\",\n    \"1984-10-22 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-01-02 00:00:00\",\n    \"1985-01-21 00:00:00\",\n    \"1985-01-28 00:00:00\",\n    \"1985-02-06 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-08 00:00:00\",\n    \"1985-04-25 00:00:00\",\n    \"1985-06-03 00:00:00\",\n    \"1985-10-28 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-01-02 00:00:00\",\n    \"1986-01-20 00:00:00\",\n    \"1986-01-27 00:00:00\",\n    \"1986-02-06 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-03-31 00:00:00\",\n    \"1986-04-25 00:00:00\",\n    \"1986-06-02 00:00:00\",\n    \"1986-10-27 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-01-02 00:00:00\",\n    \"1987-01-19 00:00:00\",\n    \"1987-01-26 00:00:00\",\n    \"1987-02-06 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-04-20 00:00:00\",\n    \"1987-04-27 00:00:00\",\n    \"1987-06-01 00:00:00\",\n    \"1987-10-26 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-28 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-01-04 00:00:00\",\n    \"1988-01-25 00:00:00\",\n    \"1988-02-01 00:00:00\",\n    \"1988-02-08 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-04 00:00:00\",\n    \"1988-04-25 00:00:00\",\n    \"1988-06-06 00:00:00\",\n    \"1988-10-24 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1988-12-27 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-01-03 00:00:00\",\n    \"1989-01-23 00:00:00\",\n    \"1989-01-30 00:00:00\",\n    \"1989-02-06 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-03-27 00:00:00\",\n    \"1989-04-25 00:00:00\",\n    \"1989-06-05 00:00:00\",\n    \"1989-10-23 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-01-02 00:00:00\",\n    \"1990-01-22 00:00:00\",\n    \"1990-01-29 00:00:00\",\n    \"1990-02-06 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-16 00:00:00\",\n    \"1990-04-25 00:00:00\",\n    \"1990-06-04 00:00:00\",\n    \"1990-10-22 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-01-02 00:00:00\",\n    \"1991-01-21 00:00:00\",\n    \"1991-01-28 00:00:00\",\n    \"1991-02-06 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-01 00:00:00\",\n    \"1991-04-25 00:00:00\",\n    \"1991-06-03 00:00:00\",\n    \"1991-10-28 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-01-02 00:00:00\",\n    \"1992-01-20 00:00:00\",\n    \"1992-01-27 00:00:00\",\n    \"1992-02-06 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-04-20 00:00:00\",\n    \"1992-04-27 00:00:00\",\n    \"1992-06-01 00:00:00\",\n    \"1992-10-26 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-28 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-01-04 00:00:00\",\n    \"1993-01-25 00:00:00\",\n    \"1993-02-01 00:00:00\",\n    \"1993-02-08 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-12 00:00:00\",\n    \"1993-04-26 00:00:00\",\n    \"1993-06-07 00:00:00\",\n    \"1993-10-25 00:00:00\",\n    \"1993-12-27 00:00:00\",\n    \"1993-12-28 00:00:00\",\n    \"1994-01-03 00:00:00\",\n    \"1994-01-04 00:00:00\",\n    \"1994-01-24 00:00:00\",\n    \"1994-01-31 00:00:00\",\n    \"1994-02-07 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-04 00:00:00\",\n    \"1994-04-25 00:00:00\",\n    \"1994-06-06 00:00:00\",\n    \"1994-10-24 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1994-12-27 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-01-03 00:00:00\",\n    \"1995-01-23 00:00:00\",\n    \"1995-01-30 00:00:00\",\n    \"1995-02-06 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-17 00:00:00\",\n    \"1995-04-25 00:00:00\",\n    \"1995-06-05 00:00:00\",\n    \"1995-10-23 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-01-02 00:00:00\",\n    \"1996-01-22 00:00:00\",\n    \"1996-01-29 00:00:00\",\n    \"1996-02-06 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-08 00:00:00\",\n    \"1996-04-25 00:00:00\",\n    \"1996-06-03 00:00:00\",\n    \"1996-10-28 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-01-02 00:00:00\",\n    \"1997-01-20 00:00:00\",\n    \"1997-01-27 00:00:00\",\n    \"1997-02-06 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-03-31 00:00:00\",\n    \"1997-04-25 00:00:00\",\n    \"1997-06-02 00:00:00\",\n    \"1997-10-27 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-01-02 00:00:00\",\n    \"1998-01-19 00:00:00\",\n    \"1998-01-26 00:00:00\",\n    \"1998-02-06 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-13 00:00:00\",\n    \"1998-04-27 00:00:00\",\n    \"1998-06-01 00:00:00\",\n    \"1998-10-26 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-28 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-01-04 00:00:00\",\n    \"1999-01-25 00:00:00\",\n    \"1999-02-01 00:00:00\",\n    \"1999-02-08 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-05 00:00:00\",\n    \"1999-04-26 00:00:00\",\n    \"1999-06-07 00:00:00\",\n    \"1999-10-25 00:00:00\",\n    \"1999-12-27 00:00:00\",\n    \"1999-12-28 00:00:00\",\n    \"2000-01-03 00:00:00\",\n    \"2000-01-04 00:00:00\",\n    \"2000-01-24 00:00:00\",\n    \"2000-01-31 00:00:00\",\n    \"2000-02-07 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-04-24 00:00:00\",\n    \"2000-04-25 00:00:00\",\n    \"2000-06-05 00:00:00\",\n    \"2000-10-23 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-01-02 00:00:00\",\n    \"2001-01-22 00:00:00\",\n    \"2001-01-29 00:00:00\",\n    \"2001-02-06 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-16 00:00:00\",\n    \"2001-04-25 00:00:00\",\n    \"2001-06-04 00:00:00\",\n    \"2001-10-22 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-01-02 00:00:00\",\n    \"2002-01-21 00:00:00\",\n    \"2002-01-28 00:00:00\",\n    \"2002-02-06 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-01 00:00:00\",\n    \"2002-04-25 00:00:00\",\n    \"2002-06-03 00:00:00\",\n    \"2002-10-28 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-01-02 00:00:00\",\n    \"2003-01-20 00:00:00\",\n    \"2003-01-27 00:00:00\",\n    \"2003-02-06 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-04-21 00:00:00\",\n    \"2003-04-25 00:00:00\",\n    \"2003-06-02 00:00:00\",\n    \"2003-10-27 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-01-02 00:00:00\",\n    \"2004-01-19 00:00:00\",\n    \"2004-01-26 00:00:00\",\n    \"2004-02-06 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-12 00:00:00\",\n    \"2004-04-26 00:00:00\",\n    \"2004-06-07 00:00:00\",\n    \"2004-10-25 00:00:00\",\n    \"2004-12-27 00:00:00\",\n    \"2004-12-28 00:00:00\",\n    \"2005-01-03 00:00:00\",\n    \"2005-01-04 00:00:00\",\n    \"2005-01-24 00:00:00\",\n    \"2005-01-31 00:00:00\",\n    \"2005-02-07 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-03-28 00:00:00\",\n    \"2005-04-25 00:00:00\",\n    \"2005-06-06 00:00:00\",\n    \"2005-10-24 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2005-12-27 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-01-03 00:00:00\",\n    \"2006-01-23 00:00:00\",\n    \"2006-01-30 00:00:00\",\n    \"2006-02-06 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-17 00:00:00\",\n    \"2006-04-25 00:00:00\",\n    \"2006-06-05 00:00:00\",\n    \"2006-10-23 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-01-02 00:00:00\",\n    \"2007-01-22 00:00:00\",\n    \"2007-01-29 00:00:00\",\n    \"2007-02-06 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-09 00:00:00\",\n    \"2007-04-25 00:00:00\",\n    \"2007-06-04 00:00:00\",\n    \"2007-10-22 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-01-02 00:00:00\",\n    \"2008-01-21 00:00:00\",\n    \"2008-01-28 00:00:00\",\n    \"2008-02-06 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-03-24 00:00:00\",\n    \"2008-04-25 00:00:00\",\n    \"2008-06-02 00:00:00\",\n    \"2008-10-27 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-01-02 00:00:00\",\n    \"2009-01-19 00:00:00\",\n    \"2009-01-26 00:00:00\",\n    \"2009-02-06 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-13 00:00:00\",\n    \"2009-04-27 00:00:00\",\n    \"2009-06-01 00:00:00\",\n    \"2009-10-26 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-28 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-01-04 00:00:00\",\n    \"2010-01-25 00:00:00\",\n    \"2010-02-01 00:00:00\",\n    \"2010-02-08 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-05 00:00:00\",\n    \"2010-04-26 00:00:00\",\n    \"2010-06-07 00:00:00\",\n    \"2010-10-25 00:00:00\",\n    \"2010-12-27 00:00:00\",\n    \"2010-12-28 00:00:00\",\n    \"2011-01-03 00:00:00\",\n    \"2011-01-04 00:00:00\",\n    \"2011-01-24 00:00:00\",\n    \"2011-01-31 00:00:00\",\n    \"2011-02-07 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-06-06 00:00:00\",\n    \"2011-10-24 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2011-12-27 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-01-03 00:00:00\",\n    \"2012-01-23 00:00:00\",\n    \"2012-01-30 00:00:00\",\n    \"2012-02-06 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-09 00:00:00\",\n    \"2012-04-25 00:00:00\",\n    \"2012-06-04 00:00:00\",\n    \"2012-10-22 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-01-02 00:00:00\",\n    \"2013-01-21 00:00:00\",\n    \"2013-01-28 00:00:00\",\n    \"2013-02-06 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-01 00:00:00\",\n    \"2013-04-25 00:00:00\",\n    \"2013-06-03 00:00:00\",\n    \"2013-10-28 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-01-02 00:00:00\",\n    \"2014-01-20 00:00:00\",\n    \"2014-01-27 00:00:00\",\n    \"2014-02-06 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-04-21 00:00:00\",\n    \"2014-04-25 00:00:00\",\n    \"2014-06-02 00:00:00\",\n    \"2014-10-27 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-01-02 00:00:00\",\n    \"2015-01-19 00:00:00\",\n    \"2015-01-26 00:00:00\",\n    \"2015-02-06 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-06 00:00:00\",\n    \"2015-04-27 00:00:00\",\n    \"2015-06-01 00:00:00\",\n    \"2015-10-26 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-28 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-01-04 00:00:00\",\n    \"2016-01-25 00:00:00\",\n    \"2016-02-01 00:00:00\",\n    \"2016-02-08 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-03-28 00:00:00\",\n    \"2016-04-25 00:00:00\",\n    \"2016-06-06 00:00:00\",\n    \"2016-10-24 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2016-12-27 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-01-03 00:00:00\",\n    \"2017-01-23 00:00:00\",\n    \"2017-01-30 00:00:00\",\n    \"2017-02-06 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-17 00:00:00\",\n    \"2017-04-25 00:00:00\",\n    \"2017-06-05 00:00:00\",\n    \"2017-10-23 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-01-02 00:00:00\",\n    \"2018-01-22 00:00:00\",\n    \"2018-01-29 00:00:00\",\n    \"2018-02-06 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-02 00:00:00\",\n    \"2018-04-25 00:00:00\",\n    \"2018-06-04 00:00:00\",\n    \"2018-10-22 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-01-02 00:00:00\",\n    \"2019-01-21 00:00:00\",\n    \"2019-01-28 00:00:00\",\n    \"2019-02-06 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-22 00:00:00\",\n    \"2019-04-25 00:00:00\",\n    \"2019-06-03 00:00:00\",\n    \"2019-10-28 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-01-02 00:00:00\",\n    \"2020-01-20 00:00:00\",\n    \"2020-01-27 00:00:00\",\n    \"2020-02-06 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-13 00:00:00\",\n    \"2020-04-27 00:00:00\",\n    \"2020-06-01 00:00:00\",\n    \"2020-10-26 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-28 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-01-04 00:00:00\",\n    \"2021-01-25 00:00:00\",\n    \"2021-02-01 00:00:00\",\n    \"2021-02-08 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-05 00:00:00\",\n    \"2021-04-26 00:00:00\",\n    \"2021-06-07 00:00:00\",\n    \"2021-10-25 00:00:00\",\n    \"2021-12-27 00:00:00\",\n    \"2021-12-28 00:00:00\",\n    \"2022-01-03 00:00:00\",\n    \"2022-01-04 00:00:00\",\n    \"2022-01-24 00:00:00\",\n    \"2022-01-31 00:00:00\",\n    \"2022-02-07 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-04-18 00:00:00\",\n    \"2022-04-25 00:00:00\",\n    \"2022-06-06 00:00:00\",\n    \"2022-06-24 00:00:00\",\n    \"2022-09-26 00:00:00\",\n    \"2022-10-24 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2022-12-27 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-01-03 00:00:00\",\n    \"2023-01-23 00:00:00\",\n    \"2023-01-30 00:00:00\",\n    \"2023-02-06 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-10 00:00:00\",\n    \"2023-04-25 00:00:00\",\n    \"2023-06-05 00:00:00\",\n    \"2023-07-14 00:00:00\",\n    \"2023-10-23 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-01-02 00:00:00\",\n    \"2024-01-22 00:00:00\",\n    \"2024-01-29 00:00:00\",\n    \"2024-02-06 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-04-25 00:00:00\",\n    \"2024-06-03 00:00:00\",\n    \"2024-06-28 00:00:00\",\n    \"2024-10-28 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-02 00:00:00\",\n    \"2025-01-20 00:00:00\",\n    \"2025-01-27 00:00:00\",\n    \"2025-02-06 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-04-21 00:00:00\",\n    \"2025-04-25 00:00:00\",\n    \"2025-06-02 00:00:00\",\n    \"2025-06-20 00:00:00\",\n    \"2025-10-27 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-02 00:00:00\",\n    \"2026-01-19 00:00:00\",\n    \"2026-01-26 00:00:00\",\n    \"2026-02-06 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-04-27 00:00:00\",\n    \"2026-06-01 00:00:00\",\n    \"2026-07-10 00:00:00\",\n    \"2026-10-26 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-28 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-04 00:00:00\",\n    \"2027-01-25 00:00:00\",\n    \"2027-02-01 00:00:00\",\n    \"2027-02-08 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-03-29 00:00:00\",\n    \"2027-04-26 00:00:00\",\n    \"2027-06-07 00:00:00\",\n    \"2027-06-25 00:00:00\",\n    \"2027-10-25 00:00:00\",\n    \"2027-12-27 00:00:00\",\n    \"2027-12-28 00:00:00\",\n    \"2028-01-03 00:00:00\",\n    \"2028-01-04 00:00:00\",\n    \"2028-01-24 00:00:00\",\n    \"2028-01-31 00:00:00\",\n    \"2028-02-07 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-17 00:00:00\",\n    \"2028-04-25 00:00:00\",\n    \"2028-06-05 00:00:00\",\n    \"2028-07-14 00:00:00\",\n    \"2028-10-23 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-02 00:00:00\",\n    \"2029-01-22 00:00:00\",\n    \"2029-01-29 00:00:00\",\n    \"2029-02-06 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-02 00:00:00\",\n    \"2029-04-25 00:00:00\",\n    \"2029-06-04 00:00:00\",\n    \"2029-07-06 00:00:00\",\n    \"2029-10-22 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-02 00:00:00\",\n    \"2030-01-21 00:00:00\",\n    \"2030-01-28 00:00:00\",\n    \"2030-02-06 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-04-22 00:00:00\",\n    \"2030-04-25 00:00:00\",\n    \"2030-06-03 00:00:00\",\n    \"2030-06-21 00:00:00\",\n    \"2030-10-28 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-02 00:00:00\",\n    \"2031-01-20 00:00:00\",\n    \"2031-01-27 00:00:00\",\n    \"2031-02-06 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-04-25 00:00:00\",\n    \"2031-06-02 00:00:00\",\n    \"2031-07-11 00:00:00\",\n    \"2031-10-27 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-02 00:00:00\",\n    \"2032-01-19 00:00:00\",\n    \"2032-01-26 00:00:00\",\n    \"2032-02-06 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-03-29 00:00:00\",\n    \"2032-04-26 00:00:00\",\n    \"2032-06-07 00:00:00\",\n    \"2032-07-02 00:00:00\",\n    \"2032-10-25 00:00:00\",\n    \"2032-12-27 00:00:00\",\n    \"2032-12-28 00:00:00\",\n    \"2033-01-03 00:00:00\",\n    \"2033-01-04 00:00:00\",\n    \"2033-01-24 00:00:00\",\n    \"2033-01-31 00:00:00\",\n    \"2033-02-07 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-04-18 00:00:00\",\n    \"2033-04-25 00:00:00\",\n    \"2033-06-06 00:00:00\",\n    \"2033-06-24 00:00:00\",\n    \"2033-10-24 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2033-12-27 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-01-03 00:00:00\",\n    \"2034-01-23 00:00:00\",\n    \"2034-01-30 00:00:00\",\n    \"2034-02-06 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-10 00:00:00\",\n    \"2034-04-25 00:00:00\",\n    \"2034-06-05 00:00:00\",\n    \"2034-07-07 00:00:00\",\n    \"2034-10-23 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-02 00:00:00\",\n    \"2035-01-22 00:00:00\",\n    \"2035-01-29 00:00:00\",\n    \"2035-02-06 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-03-26 00:00:00\",\n    \"2035-04-25 00:00:00\",\n    \"2035-06-04 00:00:00\",\n    \"2035-06-29 00:00:00\",\n    \"2035-10-22 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-02 00:00:00\",\n    \"2036-01-21 00:00:00\",\n    \"2036-01-28 00:00:00\",\n    \"2036-02-06 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-04-25 00:00:00\",\n    \"2036-06-02 00:00:00\",\n    \"2036-07-18 00:00:00\",\n    \"2036-10-27 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-02 00:00:00\",\n    \"2037-01-19 00:00:00\",\n    \"2037-01-26 00:00:00\",\n    \"2037-02-06 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-04-27 00:00:00\",\n    \"2037-06-01 00:00:00\",\n    \"2037-07-10 00:00:00\",\n    \"2037-10-26 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-28 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-04 00:00:00\",\n    \"2038-01-25 00:00:00\",\n    \"2038-02-01 00:00:00\",\n    \"2038-02-08 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-06-07 00:00:00\",\n    \"2038-06-25 00:00:00\",\n    \"2038-10-25 00:00:00\",\n    \"2038-12-27 00:00:00\",\n    \"2038-12-28 00:00:00\",\n    \"2039-01-03 00:00:00\",\n    \"2039-01-04 00:00:00\",\n    \"2039-01-24 00:00:00\",\n    \"2039-01-31 00:00:00\",\n    \"2039-02-07 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-11 00:00:00\",\n    \"2039-04-25 00:00:00\",\n    \"2039-06-06 00:00:00\",\n    \"2039-07-15 00:00:00\",\n    \"2039-10-24 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2039-12-27 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-01-03 00:00:00\",\n    \"2040-01-23 00:00:00\",\n    \"2040-01-30 00:00:00\",\n    \"2040-02-06 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-02 00:00:00\",\n    \"2040-04-25 00:00:00\",\n    \"2040-06-04 00:00:00\",\n    \"2040-07-06 00:00:00\",\n    \"2040-10-22 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-02 00:00:00\",\n    \"2041-01-21 00:00:00\",\n    \"2041-01-28 00:00:00\",\n    \"2041-02-06 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-04-22 00:00:00\",\n    \"2041-04-25 00:00:00\",\n    \"2041-06-03 00:00:00\",\n    \"2041-07-19 00:00:00\",\n    \"2041-10-28 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-02 00:00:00\",\n    \"2042-01-20 00:00:00\",\n    \"2042-01-27 00:00:00\",\n    \"2042-02-06 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-07 00:00:00\",\n    \"2042-04-25 00:00:00\",\n    \"2042-06-02 00:00:00\",\n    \"2042-07-11 00:00:00\",\n    \"2042-10-27 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-02 00:00:00\",\n    \"2043-01-19 00:00:00\",\n    \"2043-01-26 00:00:00\",\n    \"2043-02-06 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-03-30 00:00:00\",\n    \"2043-04-27 00:00:00\",\n    \"2043-06-01 00:00:00\",\n    \"2043-07-03 00:00:00\",\n    \"2043-10-26 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-28 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-04 00:00:00\",\n    \"2044-01-25 00:00:00\",\n    \"2044-02-01 00:00:00\",\n    \"2044-02-08 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-04-18 00:00:00\",\n    \"2044-04-25 00:00:00\",\n    \"2044-06-06 00:00:00\",\n    \"2044-06-24 00:00:00\",\n    \"2044-10-24 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2044-12-27 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-01-03 00:00:00\",\n    \"2045-01-23 00:00:00\",\n    \"2045-01-30 00:00:00\",\n    \"2045-02-06 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-10 00:00:00\",\n    \"2045-04-25 00:00:00\",\n    \"2045-06-05 00:00:00\",\n    \"2045-07-07 00:00:00\",\n    \"2045-10-23 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-02 00:00:00\",\n    \"2046-01-22 00:00:00\",\n    \"2046-01-29 00:00:00\",\n    \"2046-02-06 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-03-26 00:00:00\",\n    \"2046-04-25 00:00:00\",\n    \"2046-06-04 00:00:00\",\n    \"2046-06-29 00:00:00\",\n    \"2046-10-22 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-02 00:00:00\",\n    \"2047-01-21 00:00:00\",\n    \"2047-01-28 00:00:00\",\n    \"2047-02-06 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-15 00:00:00\",\n    \"2047-04-25 00:00:00\",\n    \"2047-06-03 00:00:00\",\n    \"2047-07-19 00:00:00\",\n    \"2047-10-28 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-02 00:00:00\",\n    \"2048-01-20 00:00:00\",\n    \"2048-01-27 00:00:00\",\n    \"2048-02-06 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-06 00:00:00\",\n    \"2048-04-27 00:00:00\",\n    \"2048-06-01 00:00:00\",\n    \"2048-07-03 00:00:00\",\n    \"2048-10-26 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-28 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-04 00:00:00\",\n    \"2049-01-25 00:00:00\",\n    \"2049-02-01 00:00:00\",\n    \"2049-02-08 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-04-19 00:00:00\",\n    \"2049-04-26 00:00:00\",\n    \"2049-06-07 00:00:00\",\n    \"2049-06-25 00:00:00\",\n    \"2049-10-25 00:00:00\",\n    \"2049-12-27 00:00:00\",\n    \"2049-12-28 00:00:00\",\n    \"2050-01-03 00:00:00\",\n    \"2050-01-04 00:00:00\",\n    \"2050-01-24 00:00:00\",\n    \"2050-01-31 00:00:00\",\n    \"2050-02-07 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-11 00:00:00\",\n    \"2050-04-25 00:00:00\",\n    \"2050-06-06 00:00:00\",\n    \"2050-07-15 00:00:00\",\n    \"2050-10-24 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2050-12-27 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-01-03 00:00:00\",\n    \"2051-01-23 00:00:00\",\n    \"2051-01-30 00:00:00\",\n    \"2051-02-06 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-04-25 00:00:00\",\n    \"2051-06-05 00:00:00\",\n    \"2051-06-30 00:00:00\",\n    \"2051-10-23 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-02 00:00:00\",\n    \"2052-01-22 00:00:00\",\n    \"2052-01-29 00:00:00\",\n    \"2052-02-06 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-04-22 00:00:00\",\n    \"2052-04-25 00:00:00\",\n    \"2052-06-03 00:00:00\",\n    \"2052-06-21 00:00:00\",\n    \"2052-10-28 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-02 00:00:00\",\n    \"2053-01-20 00:00:00\",\n    \"2053-01-27 00:00:00\",\n    \"2053-02-06 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-07 00:00:00\",\n    \"2053-04-25 00:00:00\",\n    \"2053-06-02 00:00:00\",\n    \"2053-10-27 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-02 00:00:00\",\n    \"2054-01-19 00:00:00\",\n    \"2054-01-26 00:00:00\",\n    \"2054-02-06 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-03-30 00:00:00\",\n    \"2054-04-27 00:00:00\",\n    \"2054-06-01 00:00:00\",\n    \"2054-10-26 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-28 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-04 00:00:00\",\n    \"2055-01-25 00:00:00\",\n    \"2055-02-01 00:00:00\",\n    \"2055-02-08 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-04-19 00:00:00\",\n    \"2055-04-26 00:00:00\",\n    \"2055-06-07 00:00:00\",\n    \"2055-10-25 00:00:00\",\n    \"2055-12-27 00:00:00\",\n    \"2055-12-28 00:00:00\",\n    \"2056-01-03 00:00:00\",\n    \"2056-01-04 00:00:00\",\n    \"2056-01-24 00:00:00\",\n    \"2056-01-31 00:00:00\",\n    \"2056-02-07 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-04-25 00:00:00\",\n    \"2056-06-05 00:00:00\",\n    \"2056-10-23 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-02 00:00:00\",\n    \"2057-01-22 00:00:00\",\n    \"2057-01-29 00:00:00\",\n    \"2057-02-06 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-04-23 00:00:00\",\n    \"2057-04-25 00:00:00\",\n    \"2057-06-04 00:00:00\",\n    \"2057-10-22 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-02 00:00:00\",\n    \"2058-01-21 00:00:00\",\n    \"2058-01-28 00:00:00\",\n    \"2058-02-06 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-15 00:00:00\",\n    \"2058-04-25 00:00:00\",\n    \"2058-06-03 00:00:00\",\n    \"2058-10-28 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-02 00:00:00\",\n    \"2059-01-20 00:00:00\",\n    \"2059-01-27 00:00:00\",\n    \"2059-02-06 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-03-31 00:00:00\",\n    \"2059-04-25 00:00:00\",\n    \"2059-06-02 00:00:00\",\n    \"2059-10-27 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-02 00:00:00\",\n    \"2060-01-19 00:00:00\",\n    \"2060-01-26 00:00:00\",\n    \"2060-02-06 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-04-19 00:00:00\",\n    \"2060-04-26 00:00:00\",\n    \"2060-06-07 00:00:00\",\n    \"2060-10-25 00:00:00\",\n    \"2060-12-27 00:00:00\",\n    \"2060-12-28 00:00:00\",\n    \"2061-01-03 00:00:00\",\n    \"2061-01-04 00:00:00\",\n    \"2061-01-24 00:00:00\",\n    \"2061-01-31 00:00:00\",\n    \"2061-02-07 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-11 00:00:00\",\n    \"2061-04-25 00:00:00\",\n    \"2061-06-06 00:00:00\",\n    \"2061-10-24 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2061-12-27 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-01-03 00:00:00\",\n    \"2062-01-23 00:00:00\",\n    \"2062-01-30 00:00:00\",\n    \"2062-02-06 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-03-27 00:00:00\",\n    \"2062-04-25 00:00:00\",\n    \"2062-06-05 00:00:00\",\n    \"2062-10-23 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-02 00:00:00\",\n    \"2063-01-22 00:00:00\",\n    \"2063-01-29 00:00:00\",\n    \"2063-02-06 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-16 00:00:00\",\n    \"2063-04-25 00:00:00\",\n    \"2063-06-04 00:00:00\",\n    \"2063-10-22 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-02 00:00:00\",\n    \"2064-01-21 00:00:00\",\n    \"2064-01-28 00:00:00\",\n    \"2064-02-06 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-07 00:00:00\",\n    \"2064-04-25 00:00:00\",\n    \"2064-06-02 00:00:00\",\n    \"2064-10-27 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-02 00:00:00\",\n    \"2065-01-19 00:00:00\",\n    \"2065-01-26 00:00:00\",\n    \"2065-02-06 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-03-30 00:00:00\",\n    \"2065-04-27 00:00:00\",\n    \"2065-06-01 00:00:00\",\n    \"2065-10-26 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-28 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-04 00:00:00\",\n    \"2066-01-25 00:00:00\",\n    \"2066-02-01 00:00:00\",\n    \"2066-02-08 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-12 00:00:00\",\n    \"2066-04-26 00:00:00\",\n    \"2066-06-07 00:00:00\",\n    \"2066-10-25 00:00:00\",\n    \"2066-12-27 00:00:00\",\n    \"2066-12-28 00:00:00\",\n    \"2067-01-03 00:00:00\",\n    \"2067-01-04 00:00:00\",\n    \"2067-01-24 00:00:00\",\n    \"2067-01-31 00:00:00\",\n    \"2067-02-07 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-04-25 00:00:00\",\n    \"2067-06-06 00:00:00\",\n    \"2067-10-24 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2067-12-27 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-01-03 00:00:00\",\n    \"2068-01-23 00:00:00\",\n    \"2068-01-30 00:00:00\",\n    \"2068-02-06 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-04-23 00:00:00\",\n    \"2068-04-25 00:00:00\",\n    \"2068-06-04 00:00:00\",\n    \"2068-10-22 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-02 00:00:00\",\n    \"2069-01-21 00:00:00\",\n    \"2069-01-28 00:00:00\",\n    \"2069-02-06 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-15 00:00:00\",\n    \"2069-04-25 00:00:00\",\n    \"2069-06-03 00:00:00\",\n    \"2069-10-28 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-02 00:00:00\",\n    \"2070-01-20 00:00:00\",\n    \"2070-01-27 00:00:00\",\n    \"2070-02-06 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-03-31 00:00:00\",\n    \"2070-04-25 00:00:00\",\n    \"2070-06-02 00:00:00\",\n    \"2070-10-27 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-02 00:00:00\",\n    \"2071-01-19 00:00:00\",\n    \"2071-01-26 00:00:00\",\n    \"2071-02-06 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-04-20 00:00:00\",\n    \"2071-04-27 00:00:00\",\n    \"2071-06-01 00:00:00\",\n    \"2071-10-26 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-28 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-04 00:00:00\",\n    \"2072-01-25 00:00:00\",\n    \"2072-02-01 00:00:00\",\n    \"2072-02-08 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-11 00:00:00\",\n    \"2072-04-25 00:00:00\",\n    \"2072-06-06 00:00:00\",\n    \"2072-10-24 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2072-12-27 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-01-03 00:00:00\",\n    \"2073-01-23 00:00:00\",\n    \"2073-01-30 00:00:00\",\n    \"2073-02-06 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-03-27 00:00:00\",\n    \"2073-04-25 00:00:00\",\n    \"2073-06-05 00:00:00\",\n    \"2073-10-23 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-02 00:00:00\",\n    \"2074-01-22 00:00:00\",\n    \"2074-01-29 00:00:00\",\n    \"2074-02-06 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-16 00:00:00\",\n    \"2074-04-25 00:00:00\",\n    \"2074-06-04 00:00:00\",\n    \"2074-10-22 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-02 00:00:00\",\n    \"2075-01-21 00:00:00\",\n    \"2075-01-28 00:00:00\",\n    \"2075-02-06 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-08 00:00:00\",\n    \"2075-04-25 00:00:00\",\n    \"2075-06-03 00:00:00\",\n    \"2075-10-28 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-02 00:00:00\",\n    \"2076-01-20 00:00:00\",\n    \"2076-01-27 00:00:00\",\n    \"2076-02-06 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-04-20 00:00:00\",\n    \"2076-04-27 00:00:00\",\n    \"2076-06-01 00:00:00\",\n    \"2076-10-26 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-28 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-04 00:00:00\",\n    \"2077-01-25 00:00:00\",\n    \"2077-02-01 00:00:00\",\n    \"2077-02-08 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-12 00:00:00\",\n    \"2077-04-26 00:00:00\",\n    \"2077-06-07 00:00:00\",\n    \"2077-10-25 00:00:00\",\n    \"2077-12-27 00:00:00\",\n    \"2077-12-28 00:00:00\",\n    \"2078-01-03 00:00:00\",\n    \"2078-01-04 00:00:00\",\n    \"2078-01-24 00:00:00\",\n    \"2078-01-31 00:00:00\",\n    \"2078-02-07 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-04-25 00:00:00\",\n    \"2078-06-06 00:00:00\",\n    \"2078-10-24 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2078-12-27 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-01-03 00:00:00\",\n    \"2079-01-23 00:00:00\",\n    \"2079-01-30 00:00:00\",\n    \"2079-02-06 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-04-24 00:00:00\",\n    \"2079-04-25 00:00:00\",\n    \"2079-06-05 00:00:00\",\n    \"2079-10-23 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-02 00:00:00\",\n    \"2080-01-22 00:00:00\",\n    \"2080-01-29 00:00:00\",\n    \"2080-02-06 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-08 00:00:00\",\n    \"2080-04-25 00:00:00\",\n    \"2080-06-03 00:00:00\",\n    \"2080-10-28 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-02 00:00:00\",\n    \"2081-01-20 00:00:00\",\n    \"2081-01-27 00:00:00\",\n    \"2081-02-06 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-03-31 00:00:00\",\n    \"2081-04-25 00:00:00\",\n    \"2081-06-02 00:00:00\",\n    \"2081-10-27 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-02 00:00:00\",\n    \"2082-01-19 00:00:00\",\n    \"2082-01-26 00:00:00\",\n    \"2082-02-06 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-04-20 00:00:00\",\n    \"2082-04-27 00:00:00\",\n    \"2082-06-01 00:00:00\",\n    \"2082-10-26 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-28 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-04 00:00:00\",\n    \"2083-01-25 00:00:00\",\n    \"2083-02-01 00:00:00\",\n    \"2083-02-08 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-04-26 00:00:00\",\n    \"2083-06-07 00:00:00\",\n    \"2083-10-25 00:00:00\",\n    \"2083-12-27 00:00:00\",\n    \"2083-12-28 00:00:00\",\n    \"2084-01-03 00:00:00\",\n    \"2084-01-04 00:00:00\",\n    \"2084-01-24 00:00:00\",\n    \"2084-01-31 00:00:00\",\n    \"2084-02-07 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-03-27 00:00:00\",\n    \"2084-04-25 00:00:00\",\n    \"2084-06-05 00:00:00\",\n    \"2084-10-23 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-02 00:00:00\",\n    \"2085-01-22 00:00:00\",\n    \"2085-01-29 00:00:00\",\n    \"2085-02-06 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-16 00:00:00\",\n    \"2085-04-25 00:00:00\",\n    \"2085-06-04 00:00:00\",\n    \"2085-10-22 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-02 00:00:00\",\n    \"2086-01-21 00:00:00\",\n    \"2086-01-28 00:00:00\",\n    \"2086-02-06 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-01 00:00:00\",\n    \"2086-04-25 00:00:00\",\n    \"2086-06-03 00:00:00\",\n    \"2086-10-28 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-02 00:00:00\",\n    \"2087-01-20 00:00:00\",\n    \"2087-01-27 00:00:00\",\n    \"2087-02-06 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-04-21 00:00:00\",\n    \"2087-04-25 00:00:00\",\n    \"2087-06-02 00:00:00\",\n    \"2087-10-27 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-02 00:00:00\",\n    \"2088-01-19 00:00:00\",\n    \"2088-01-26 00:00:00\",\n    \"2088-02-06 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-12 00:00:00\",\n    \"2088-04-26 00:00:00\",\n    \"2088-06-07 00:00:00\",\n    \"2088-10-25 00:00:00\",\n    \"2088-12-27 00:00:00\",\n    \"2088-12-28 00:00:00\",\n    \"2089-01-03 00:00:00\",\n    \"2089-01-04 00:00:00\",\n    \"2089-01-24 00:00:00\",\n    \"2089-01-31 00:00:00\",\n    \"2089-02-07 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-04-25 00:00:00\",\n    \"2089-06-06 00:00:00\",\n    \"2089-10-24 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2089-12-27 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-01-03 00:00:00\",\n    \"2090-01-23 00:00:00\",\n    \"2090-01-30 00:00:00\",\n    \"2090-02-06 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-17 00:00:00\",\n    \"2090-04-25 00:00:00\",\n    \"2090-06-05 00:00:00\",\n    \"2090-10-23 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-02 00:00:00\",\n    \"2091-01-22 00:00:00\",\n    \"2091-01-29 00:00:00\",\n    \"2091-02-06 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-09 00:00:00\",\n    \"2091-04-25 00:00:00\",\n    \"2091-06-04 00:00:00\",\n    \"2091-10-22 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-02 00:00:00\",\n    \"2092-01-21 00:00:00\",\n    \"2092-01-28 00:00:00\",\n    \"2092-02-06 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-03-31 00:00:00\",\n    \"2092-04-25 00:00:00\",\n    \"2092-06-02 00:00:00\",\n    \"2092-10-27 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-02 00:00:00\",\n    \"2093-01-19 00:00:00\",\n    \"2093-01-26 00:00:00\",\n    \"2093-02-06 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-13 00:00:00\",\n    \"2093-04-27 00:00:00\",\n    \"2093-06-01 00:00:00\",\n    \"2093-10-26 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-28 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-04 00:00:00\",\n    \"2094-01-25 00:00:00\",\n    \"2094-02-01 00:00:00\",\n    \"2094-02-08 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-04-26 00:00:00\",\n    \"2094-06-07 00:00:00\",\n    \"2094-10-25 00:00:00\",\n    \"2094-12-27 00:00:00\",\n    \"2094-12-28 00:00:00\",\n    \"2095-01-03 00:00:00\",\n    \"2095-01-04 00:00:00\",\n    \"2095-01-24 00:00:00\",\n    \"2095-01-31 00:00:00\",\n    \"2095-02-07 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-06-06 00:00:00\",\n    \"2095-10-24 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2095-12-27 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-01-03 00:00:00\",\n    \"2096-01-23 00:00:00\",\n    \"2096-01-30 00:00:00\",\n    \"2096-02-06 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-16 00:00:00\",\n    \"2096-04-25 00:00:00\",\n    \"2096-06-04 00:00:00\",\n    \"2096-10-22 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-02 00:00:00\",\n    \"2097-01-21 00:00:00\",\n    \"2097-01-28 00:00:00\",\n    \"2097-02-06 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-01 00:00:00\",\n    \"2097-04-25 00:00:00\",\n    \"2097-06-03 00:00:00\",\n    \"2097-10-28 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-02 00:00:00\",\n    \"2098-01-20 00:00:00\",\n    \"2098-01-27 00:00:00\",\n    \"2098-02-06 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-04-21 00:00:00\",\n    \"2098-04-25 00:00:00\",\n    \"2098-06-02 00:00:00\",\n    \"2098-10-27 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-02 00:00:00\",\n    \"2099-01-19 00:00:00\",\n    \"2099-01-26 00:00:00\",\n    \"2099-02-06 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-13 00:00:00\",\n    \"2099-04-27 00:00:00\",\n    \"2099-06-01 00:00:00\",\n    \"2099-10-26 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-28 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-04 00:00:00\",\n    \"2100-01-25 00:00:00\",\n    \"2100-02-01 00:00:00\",\n    \"2100-02-08 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-03-29 00:00:00\",\n    \"2100-04-26 00:00:00\",\n    \"2100-06-07 00:00:00\",\n    \"2100-10-25 00:00:00\",\n    \"2100-12-27 00:00:00\",\n    \"2100-12-28 00:00:00\",\n    \"2101-01-03 00:00:00\",\n    \"2101-01-04 00:00:00\",\n    \"2101-01-24 00:00:00\",\n    \"2101-01-31 00:00:00\",\n    \"2101-02-07 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-04-18 00:00:00\",\n    \"2101-04-25 00:00:00\",\n    \"2101-06-06 00:00:00\",\n    \"2101-10-24 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2101-12-27 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-01-03 00:00:00\",\n    \"2102-01-23 00:00:00\",\n    \"2102-01-30 00:00:00\",\n    \"2102-02-06 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-10 00:00:00\",\n    \"2102-04-25 00:00:00\",\n    \"2102-06-05 00:00:00\",\n    \"2102-10-23 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-01-02 00:00:00\",\n    \"2103-01-22 00:00:00\",\n    \"2103-01-29 00:00:00\",\n    \"2103-02-06 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-03-26 00:00:00\",\n    \"2103-04-25 00:00:00\",\n    \"2103-06-04 00:00:00\",\n    \"2103-10-22 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-01-02 00:00:00\",\n    \"2104-01-21 00:00:00\",\n    \"2104-01-28 00:00:00\",\n    \"2104-02-06 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-04-25 00:00:00\",\n    \"2104-06-02 00:00:00\",\n    \"2104-10-27 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-01-02 00:00:00\",\n    \"2105-01-19 00:00:00\",\n    \"2105-01-26 00:00:00\",\n    \"2105-02-06 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-06 00:00:00\",\n    \"2105-04-27 00:00:00\",\n    \"2105-06-01 00:00:00\",\n    \"2105-10-26 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-28 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-01-04 00:00:00\",\n    \"2106-01-25 00:00:00\",\n    \"2106-02-01 00:00:00\",\n    \"2106-02-08 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-04-19 00:00:00\",\n    \"2106-04-26 00:00:00\",\n    \"2106-06-07 00:00:00\",\n    \"2106-10-25 00:00:00\",\n    \"2106-12-27 00:00:00\",\n    \"2106-12-28 00:00:00\",\n    \"2107-01-03 00:00:00\",\n    \"2107-01-04 00:00:00\",\n    \"2107-01-24 00:00:00\",\n    \"2107-01-31 00:00:00\",\n    \"2107-02-07 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-11 00:00:00\",\n    \"2107-04-25 00:00:00\",\n    \"2107-06-06 00:00:00\",\n    \"2107-10-24 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2107-12-27 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-01-03 00:00:00\",\n    \"2108-01-23 00:00:00\",\n    \"2108-01-30 00:00:00\",\n    \"2108-02-06 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-02 00:00:00\",\n    \"2108-04-25 00:00:00\",\n    \"2108-06-04 00:00:00\",\n    \"2108-10-22 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-01-02 00:00:00\",\n    \"2109-01-21 00:00:00\",\n    \"2109-01-28 00:00:00\",\n    \"2109-02-06 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-04-22 00:00:00\",\n    \"2109-04-25 00:00:00\",\n    \"2109-06-03 00:00:00\",\n    \"2109-10-28 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-01-02 00:00:00\",\n    \"2110-01-20 00:00:00\",\n    \"2110-01-27 00:00:00\",\n    \"2110-02-06 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-07 00:00:00\",\n    \"2110-04-25 00:00:00\",\n    \"2110-06-02 00:00:00\",\n    \"2110-10-27 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-01-02 00:00:00\",\n    \"2111-01-19 00:00:00\",\n    \"2111-01-26 00:00:00\",\n    \"2111-02-06 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-03-30 00:00:00\",\n    \"2111-04-27 00:00:00\",\n    \"2111-06-01 00:00:00\",\n    \"2111-10-26 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-28 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-01-04 00:00:00\",\n    \"2112-01-25 00:00:00\",\n    \"2112-02-01 00:00:00\",\n    \"2112-02-08 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-04-18 00:00:00\",\n    \"2112-04-25 00:00:00\",\n    \"2112-06-06 00:00:00\",\n    \"2112-10-24 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2112-12-27 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-01-03 00:00:00\",\n    \"2113-01-23 00:00:00\",\n    \"2113-01-30 00:00:00\",\n    \"2113-02-06 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-03 00:00:00\",\n    \"2113-04-25 00:00:00\",\n    \"2113-06-05 00:00:00\",\n    \"2113-10-23 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-01-02 00:00:00\",\n    \"2114-01-22 00:00:00\",\n    \"2114-01-29 00:00:00\",\n    \"2114-02-06 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-04-23 00:00:00\",\n    \"2114-04-25 00:00:00\",\n    \"2114-06-04 00:00:00\",\n    \"2114-10-22 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-01-02 00:00:00\",\n    \"2115-01-21 00:00:00\",\n    \"2115-01-28 00:00:00\",\n    \"2115-02-06 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-15 00:00:00\",\n    \"2115-04-25 00:00:00\",\n    \"2115-06-03 00:00:00\",\n    \"2115-10-28 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-01-02 00:00:00\",\n    \"2116-01-20 00:00:00\",\n    \"2116-01-27 00:00:00\",\n    \"2116-02-06 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-03-30 00:00:00\",\n    \"2116-04-27 00:00:00\",\n    \"2116-06-01 00:00:00\",\n    \"2116-10-26 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-28 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-01-04 00:00:00\",\n    \"2117-01-25 00:00:00\",\n    \"2117-02-01 00:00:00\",\n    \"2117-02-08 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-04-19 00:00:00\",\n    \"2117-04-26 00:00:00\",\n    \"2117-06-07 00:00:00\",\n    \"2117-10-25 00:00:00\",\n    \"2117-12-27 00:00:00\",\n    \"2117-12-28 00:00:00\",\n    \"2118-01-03 00:00:00\",\n    \"2118-01-04 00:00:00\",\n    \"2118-01-24 00:00:00\",\n    \"2118-01-31 00:00:00\",\n    \"2118-02-07 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-11 00:00:00\",\n    \"2118-04-25 00:00:00\",\n    \"2118-06-06 00:00:00\",\n    \"2118-10-24 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2118-12-27 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-01-03 00:00:00\",\n    \"2119-01-23 00:00:00\",\n    \"2119-01-30 00:00:00\",\n    \"2119-02-06 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-03-27 00:00:00\",\n    \"2119-04-25 00:00:00\",\n    \"2119-06-05 00:00:00\",\n    \"2119-10-23 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-01-02 00:00:00\",\n    \"2120-01-22 00:00:00\",\n    \"2120-01-29 00:00:00\",\n    \"2120-02-06 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-15 00:00:00\",\n    \"2120-04-25 00:00:00\",\n    \"2120-06-03 00:00:00\",\n    \"2120-10-28 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-01-02 00:00:00\",\n    \"2121-01-20 00:00:00\",\n    \"2121-01-27 00:00:00\",\n    \"2121-02-06 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-07 00:00:00\",\n    \"2121-04-25 00:00:00\",\n    \"2121-06-02 00:00:00\",\n    \"2121-10-27 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-01-02 00:00:00\",\n    \"2122-01-19 00:00:00\",\n    \"2122-01-26 00:00:00\",\n    \"2122-02-06 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-03-30 00:00:00\",\n    \"2122-04-27 00:00:00\",\n    \"2122-06-01 00:00:00\",\n    \"2122-10-26 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-28 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-01-04 00:00:00\",\n    \"2123-01-25 00:00:00\",\n    \"2123-02-01 00:00:00\",\n    \"2123-02-08 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-12 00:00:00\",\n    \"2123-04-26 00:00:00\",\n    \"2123-06-07 00:00:00\",\n    \"2123-10-25 00:00:00\",\n    \"2123-12-27 00:00:00\",\n    \"2123-12-28 00:00:00\",\n    \"2124-01-03 00:00:00\",\n    \"2124-01-04 00:00:00\",\n    \"2124-01-24 00:00:00\",\n    \"2124-01-31 00:00:00\",\n    \"2124-02-07 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-03 00:00:00\",\n    \"2124-04-25 00:00:00\",\n    \"2124-06-05 00:00:00\",\n    \"2124-10-23 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-01-02 00:00:00\",\n    \"2125-01-22 00:00:00\",\n    \"2125-01-29 00:00:00\",\n    \"2125-02-06 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-04-23 00:00:00\",\n    \"2125-04-25 00:00:00\",\n    \"2125-06-04 00:00:00\",\n    \"2125-10-22 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-01-02 00:00:00\",\n    \"2126-01-21 00:00:00\",\n    \"2126-01-28 00:00:00\",\n    \"2126-02-06 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-15 00:00:00\",\n    \"2126-04-25 00:00:00\",\n    \"2126-06-03 00:00:00\",\n    \"2126-10-28 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-01-02 00:00:00\",\n    \"2127-01-20 00:00:00\",\n    \"2127-01-27 00:00:00\",\n    \"2127-02-06 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-03-31 00:00:00\",\n    \"2127-04-25 00:00:00\",\n    \"2127-06-02 00:00:00\",\n    \"2127-10-27 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-01-02 00:00:00\",\n    \"2128-01-19 00:00:00\",\n    \"2128-01-26 00:00:00\",\n    \"2128-02-06 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-04-19 00:00:00\",\n    \"2128-04-26 00:00:00\",\n    \"2128-06-07 00:00:00\",\n    \"2128-10-25 00:00:00\",\n    \"2128-12-27 00:00:00\",\n    \"2128-12-28 00:00:00\",\n    \"2129-01-03 00:00:00\",\n    \"2129-01-04 00:00:00\",\n    \"2129-01-24 00:00:00\",\n    \"2129-01-31 00:00:00\",\n    \"2129-02-07 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-11 00:00:00\",\n    \"2129-04-25 00:00:00\",\n    \"2129-06-06 00:00:00\",\n    \"2129-10-24 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2129-12-27 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-01-03 00:00:00\",\n    \"2130-01-23 00:00:00\",\n    \"2130-01-30 00:00:00\",\n    \"2130-02-06 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-03-27 00:00:00\",\n    \"2130-04-25 00:00:00\",\n    \"2130-06-05 00:00:00\",\n    \"2130-10-23 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-01-02 00:00:00\",\n    \"2131-01-22 00:00:00\",\n    \"2131-01-29 00:00:00\",\n    \"2131-02-06 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-16 00:00:00\",\n    \"2131-04-25 00:00:00\",\n    \"2131-06-04 00:00:00\",\n    \"2131-10-22 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-01-02 00:00:00\",\n    \"2132-01-21 00:00:00\",\n    \"2132-01-28 00:00:00\",\n    \"2132-02-06 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-07 00:00:00\",\n    \"2132-04-25 00:00:00\",\n    \"2132-06-02 00:00:00\",\n    \"2132-10-27 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-01-02 00:00:00\",\n    \"2133-01-19 00:00:00\",\n    \"2133-01-26 00:00:00\",\n    \"2133-02-06 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-04-20 00:00:00\",\n    \"2133-04-27 00:00:00\",\n    \"2133-06-01 00:00:00\",\n    \"2133-10-26 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-28 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-01-04 00:00:00\",\n    \"2134-01-25 00:00:00\",\n    \"2134-02-01 00:00:00\",\n    \"2134-02-08 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-12 00:00:00\",\n    \"2134-04-26 00:00:00\",\n    \"2134-06-07 00:00:00\",\n    \"2134-10-25 00:00:00\",\n    \"2134-12-27 00:00:00\",\n    \"2134-12-28 00:00:00\",\n    \"2135-01-03 00:00:00\",\n    \"2135-01-04 00:00:00\",\n    \"2135-01-24 00:00:00\",\n    \"2135-01-31 00:00:00\",\n    \"2135-02-07 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-04 00:00:00\",\n    \"2135-04-25 00:00:00\",\n    \"2135-06-06 00:00:00\",\n    \"2135-10-24 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2135-12-27 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-01-03 00:00:00\",\n    \"2136-01-23 00:00:00\",\n    \"2136-01-30 00:00:00\",\n    \"2136-02-06 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-04-23 00:00:00\",\n    \"2136-04-25 00:00:00\",\n    \"2136-06-04 00:00:00\",\n    \"2136-10-22 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-01-02 00:00:00\",\n    \"2137-01-21 00:00:00\",\n    \"2137-01-28 00:00:00\",\n    \"2137-02-06 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-08 00:00:00\",\n    \"2137-04-25 00:00:00\",\n    \"2137-06-03 00:00:00\",\n    \"2137-10-28 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-01-02 00:00:00\",\n    \"2138-01-20 00:00:00\",\n    \"2138-01-27 00:00:00\",\n    \"2138-02-06 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-03-31 00:00:00\",\n    \"2138-04-25 00:00:00\",\n    \"2138-06-02 00:00:00\",\n    \"2138-10-27 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-01-02 00:00:00\",\n    \"2139-01-19 00:00:00\",\n    \"2139-01-26 00:00:00\",\n    \"2139-02-06 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-04-20 00:00:00\",\n    \"2139-04-27 00:00:00\",\n    \"2139-06-01 00:00:00\",\n    \"2139-10-26 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-28 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-01-04 00:00:00\",\n    \"2140-01-25 00:00:00\",\n    \"2140-02-01 00:00:00\",\n    \"2140-02-08 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-04 00:00:00\",\n    \"2140-04-25 00:00:00\",\n    \"2140-06-06 00:00:00\",\n    \"2140-10-24 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2140-12-27 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-01-03 00:00:00\",\n    \"2141-01-23 00:00:00\",\n    \"2141-01-30 00:00:00\",\n    \"2141-02-06 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-03-27 00:00:00\",\n    \"2141-04-25 00:00:00\",\n    \"2141-06-05 00:00:00\",\n    \"2141-10-23 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-01-02 00:00:00\",\n    \"2142-01-22 00:00:00\",\n    \"2142-01-29 00:00:00\",\n    \"2142-02-06 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-16 00:00:00\",\n    \"2142-04-25 00:00:00\",\n    \"2142-06-04 00:00:00\",\n    \"2142-10-22 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-01-02 00:00:00\",\n    \"2143-01-21 00:00:00\",\n    \"2143-01-28 00:00:00\",\n    \"2143-02-06 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-01 00:00:00\",\n    \"2143-04-25 00:00:00\",\n    \"2143-06-03 00:00:00\",\n    \"2143-10-28 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-01-02 00:00:00\",\n    \"2144-01-20 00:00:00\",\n    \"2144-01-27 00:00:00\",\n    \"2144-02-06 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-04-20 00:00:00\",\n    \"2144-04-27 00:00:00\",\n    \"2144-06-01 00:00:00\",\n    \"2144-10-26 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-28 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-01-04 00:00:00\",\n    \"2145-01-25 00:00:00\",\n    \"2145-02-01 00:00:00\",\n    \"2145-02-08 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-12 00:00:00\",\n    \"2145-04-26 00:00:00\",\n    \"2145-06-07 00:00:00\",\n    \"2145-10-25 00:00:00\",\n    \"2145-12-27 00:00:00\",\n    \"2145-12-28 00:00:00\",\n    \"2146-01-03 00:00:00\",\n    \"2146-01-04 00:00:00\",\n    \"2146-01-24 00:00:00\",\n    \"2146-01-31 00:00:00\",\n    \"2146-02-07 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-04 00:00:00\",\n    \"2146-04-25 00:00:00\",\n    \"2146-06-06 00:00:00\",\n    \"2146-10-24 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2146-12-27 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-01-03 00:00:00\",\n    \"2147-01-23 00:00:00\",\n    \"2147-01-30 00:00:00\",\n    \"2147-02-06 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-17 00:00:00\",\n    \"2147-04-25 00:00:00\",\n    \"2147-06-05 00:00:00\",\n    \"2147-10-23 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-01-02 00:00:00\",\n    \"2148-01-22 00:00:00\",\n    \"2148-01-29 00:00:00\",\n    \"2148-02-06 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-08 00:00:00\",\n    \"2148-04-25 00:00:00\",\n    \"2148-06-03 00:00:00\",\n    \"2148-10-28 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-01-02 00:00:00\",\n    \"2149-01-20 00:00:00\",\n    \"2149-01-27 00:00:00\",\n    \"2149-02-06 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-03-31 00:00:00\",\n    \"2149-04-25 00:00:00\",\n    \"2149-06-02 00:00:00\",\n    \"2149-10-27 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-01-02 00:00:00\",\n    \"2150-01-19 00:00:00\",\n    \"2150-01-26 00:00:00\",\n    \"2150-02-06 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-13 00:00:00\",\n    \"2150-04-27 00:00:00\",\n    \"2150-06-01 00:00:00\",\n    \"2150-10-26 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-28 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-01-04 00:00:00\",\n    \"2151-01-25 00:00:00\",\n    \"2151-02-01 00:00:00\",\n    \"2151-02-08 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-05 00:00:00\",\n    \"2151-04-26 00:00:00\",\n    \"2151-06-07 00:00:00\",\n    \"2151-10-25 00:00:00\",\n    \"2151-12-27 00:00:00\",\n    \"2151-12-28 00:00:00\",\n    \"2152-01-03 00:00:00\",\n    \"2152-01-04 00:00:00\",\n    \"2152-01-24 00:00:00\",\n    \"2152-01-31 00:00:00\",\n    \"2152-02-07 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-04-24 00:00:00\",\n    \"2152-04-25 00:00:00\",\n    \"2152-06-05 00:00:00\",\n    \"2152-10-23 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-01-02 00:00:00\",\n    \"2153-01-22 00:00:00\",\n    \"2153-01-29 00:00:00\",\n    \"2153-02-06 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-16 00:00:00\",\n    \"2153-04-25 00:00:00\",\n    \"2153-06-04 00:00:00\",\n    \"2153-10-22 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-01-02 00:00:00\",\n    \"2154-01-21 00:00:00\",\n    \"2154-01-28 00:00:00\",\n    \"2154-02-06 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-01 00:00:00\",\n    \"2154-04-25 00:00:00\",\n    \"2154-06-03 00:00:00\",\n    \"2154-10-28 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-01-02 00:00:00\",\n    \"2155-01-20 00:00:00\",\n    \"2155-01-27 00:00:00\",\n    \"2155-02-06 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-04-21 00:00:00\",\n    \"2155-04-25 00:00:00\",\n    \"2155-06-02 00:00:00\",\n    \"2155-10-27 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-01-02 00:00:00\",\n    \"2156-01-19 00:00:00\",\n    \"2156-01-26 00:00:00\",\n    \"2156-02-06 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-12 00:00:00\",\n    \"2156-04-26 00:00:00\",\n    \"2156-06-07 00:00:00\",\n    \"2156-10-25 00:00:00\",\n    \"2156-12-27 00:00:00\",\n    \"2156-12-28 00:00:00\",\n    \"2157-01-03 00:00:00\",\n    \"2157-01-04 00:00:00\",\n    \"2157-01-24 00:00:00\",\n    \"2157-01-31 00:00:00\",\n    \"2157-02-07 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-03-28 00:00:00\",\n    \"2157-04-25 00:00:00\",\n    \"2157-06-06 00:00:00\",\n    \"2157-10-24 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2157-12-27 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-01-03 00:00:00\",\n    \"2158-01-23 00:00:00\",\n    \"2158-01-30 00:00:00\",\n    \"2158-02-06 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-17 00:00:00\",\n    \"2158-04-25 00:00:00\",\n    \"2158-06-05 00:00:00\",\n    \"2158-10-23 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-01-02 00:00:00\",\n    \"2159-01-22 00:00:00\",\n    \"2159-01-29 00:00:00\",\n    \"2159-02-06 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-09 00:00:00\",\n    \"2159-04-25 00:00:00\",\n    \"2159-06-04 00:00:00\",\n    \"2159-10-22 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-01-02 00:00:00\",\n    \"2160-01-21 00:00:00\",\n    \"2160-01-28 00:00:00\",\n    \"2160-02-06 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-03-24 00:00:00\",\n    \"2160-04-25 00:00:00\",\n    \"2160-06-02 00:00:00\",\n    \"2160-10-27 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-01-02 00:00:00\",\n    \"2161-01-19 00:00:00\",\n    \"2161-01-26 00:00:00\",\n    \"2161-02-06 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-13 00:00:00\",\n    \"2161-04-27 00:00:00\",\n    \"2161-06-01 00:00:00\",\n    \"2161-10-26 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-28 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-01-04 00:00:00\",\n    \"2162-01-25 00:00:00\",\n    \"2162-02-01 00:00:00\",\n    \"2162-02-08 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-05 00:00:00\",\n    \"2162-04-26 00:00:00\",\n    \"2162-06-07 00:00:00\",\n    \"2162-10-25 00:00:00\",\n    \"2162-12-27 00:00:00\",\n    \"2162-12-28 00:00:00\",\n    \"2163-01-03 00:00:00\",\n    \"2163-01-04 00:00:00\",\n    \"2163-01-24 00:00:00\",\n    \"2163-01-31 00:00:00\",\n    \"2163-02-07 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-06-06 00:00:00\",\n    \"2163-10-24 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2163-12-27 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-01-03 00:00:00\",\n    \"2164-01-23 00:00:00\",\n    \"2164-01-30 00:00:00\",\n    \"2164-02-06 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-09 00:00:00\",\n    \"2164-04-25 00:00:00\",\n    \"2164-06-04 00:00:00\",\n    \"2164-10-22 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-01-02 00:00:00\",\n    \"2165-01-21 00:00:00\",\n    \"2165-01-28 00:00:00\",\n    \"2165-02-06 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-01 00:00:00\",\n    \"2165-04-25 00:00:00\",\n    \"2165-06-03 00:00:00\",\n    \"2165-10-28 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-01-02 00:00:00\",\n    \"2166-01-20 00:00:00\",\n    \"2166-01-27 00:00:00\",\n    \"2166-02-06 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-04-21 00:00:00\",\n    \"2166-04-25 00:00:00\",\n    \"2166-06-02 00:00:00\",\n    \"2166-10-27 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-01-02 00:00:00\",\n    \"2167-01-19 00:00:00\",\n    \"2167-01-26 00:00:00\",\n    \"2167-02-06 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-06 00:00:00\",\n    \"2167-04-27 00:00:00\",\n    \"2167-06-01 00:00:00\",\n    \"2167-10-26 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-28 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-01-04 00:00:00\",\n    \"2168-01-25 00:00:00\",\n    \"2168-02-01 00:00:00\",\n    \"2168-02-08 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-03-28 00:00:00\",\n    \"2168-04-25 00:00:00\",\n    \"2168-06-06 00:00:00\",\n    \"2168-10-24 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2168-12-27 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-01-03 00:00:00\",\n    \"2169-01-23 00:00:00\",\n    \"2169-01-30 00:00:00\",\n    \"2169-02-06 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-17 00:00:00\",\n    \"2169-04-25 00:00:00\",\n    \"2169-06-05 00:00:00\",\n    \"2169-10-23 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-01-02 00:00:00\",\n    \"2170-01-22 00:00:00\",\n    \"2170-01-29 00:00:00\",\n    \"2170-02-06 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-02 00:00:00\",\n    \"2170-04-25 00:00:00\",\n    \"2170-06-04 00:00:00\",\n    \"2170-10-22 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-01-02 00:00:00\",\n    \"2171-01-21 00:00:00\",\n    \"2171-01-28 00:00:00\",\n    \"2171-02-06 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-04-22 00:00:00\",\n    \"2171-04-25 00:00:00\",\n    \"2171-06-03 00:00:00\",\n    \"2171-10-28 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-01-02 00:00:00\",\n    \"2172-01-20 00:00:00\",\n    \"2172-01-27 00:00:00\",\n    \"2172-02-06 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-13 00:00:00\",\n    \"2172-04-27 00:00:00\",\n    \"2172-06-01 00:00:00\",\n    \"2172-10-26 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-28 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-01-04 00:00:00\",\n    \"2173-01-25 00:00:00\",\n    \"2173-02-01 00:00:00\",\n    \"2173-02-08 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-05 00:00:00\",\n    \"2173-04-26 00:00:00\",\n    \"2173-06-07 00:00:00\",\n    \"2173-10-25 00:00:00\",\n    \"2173-12-27 00:00:00\",\n    \"2173-12-28 00:00:00\",\n    \"2174-01-03 00:00:00\",\n    \"2174-01-04 00:00:00\",\n    \"2174-01-24 00:00:00\",\n    \"2174-01-31 00:00:00\",\n    \"2174-02-07 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-04-18 00:00:00\",\n    \"2174-04-25 00:00:00\",\n    \"2174-06-06 00:00:00\",\n    \"2174-10-24 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2174-12-27 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-01-03 00:00:00\",\n    \"2175-01-23 00:00:00\",\n    \"2175-01-30 00:00:00\",\n    \"2175-02-06 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-10 00:00:00\",\n    \"2175-04-25 00:00:00\",\n    \"2175-06-05 00:00:00\",\n    \"2175-10-23 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-01-02 00:00:00\",\n    \"2176-01-22 00:00:00\",\n    \"2176-01-29 00:00:00\",\n    \"2176-02-06 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-01 00:00:00\",\n    \"2176-04-25 00:00:00\",\n    \"2176-06-03 00:00:00\",\n    \"2176-10-28 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-01-02 00:00:00\",\n    \"2177-01-20 00:00:00\",\n    \"2177-01-27 00:00:00\",\n    \"2177-02-06 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-04-21 00:00:00\",\n    \"2177-04-25 00:00:00\",\n    \"2177-06-02 00:00:00\",\n    \"2177-10-27 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-01-02 00:00:00\",\n    \"2178-01-19 00:00:00\",\n    \"2178-01-26 00:00:00\",\n    \"2178-02-06 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-06 00:00:00\",\n    \"2178-04-27 00:00:00\",\n    \"2178-06-01 00:00:00\",\n    \"2178-10-26 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-28 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-01-04 00:00:00\",\n    \"2179-01-25 00:00:00\",\n    \"2179-02-01 00:00:00\",\n    \"2179-02-08 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-03-29 00:00:00\",\n    \"2179-04-26 00:00:00\",\n    \"2179-06-07 00:00:00\",\n    \"2179-10-25 00:00:00\",\n    \"2179-12-27 00:00:00\",\n    \"2179-12-28 00:00:00\",\n    \"2180-01-03 00:00:00\",\n    \"2180-01-04 00:00:00\",\n    \"2180-01-24 00:00:00\",\n    \"2180-01-31 00:00:00\",\n    \"2180-02-07 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-17 00:00:00\",\n    \"2180-04-25 00:00:00\",\n    \"2180-06-05 00:00:00\",\n    \"2180-10-23 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-01-02 00:00:00\",\n    \"2181-01-22 00:00:00\",\n    \"2181-01-29 00:00:00\",\n    \"2181-02-06 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-02 00:00:00\",\n    \"2181-04-25 00:00:00\",\n    \"2181-06-04 00:00:00\",\n    \"2181-10-22 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-01-02 00:00:00\",\n    \"2182-01-21 00:00:00\",\n    \"2182-01-28 00:00:00\",\n    \"2182-02-06 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-04-22 00:00:00\",\n    \"2182-04-25 00:00:00\",\n    \"2182-06-03 00:00:00\",\n    \"2182-10-28 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-01-02 00:00:00\",\n    \"2183-01-20 00:00:00\",\n    \"2183-01-27 00:00:00\",\n    \"2183-02-06 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-04-25 00:00:00\",\n    \"2183-06-02 00:00:00\",\n    \"2183-10-27 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-01-02 00:00:00\",\n    \"2184-01-19 00:00:00\",\n    \"2184-01-26 00:00:00\",\n    \"2184-02-06 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-03-29 00:00:00\",\n    \"2184-04-26 00:00:00\",\n    \"2184-06-07 00:00:00\",\n    \"2184-10-25 00:00:00\",\n    \"2184-12-27 00:00:00\",\n    \"2184-12-28 00:00:00\",\n    \"2185-01-03 00:00:00\",\n    \"2185-01-04 00:00:00\",\n    \"2185-01-24 00:00:00\",\n    \"2185-01-31 00:00:00\",\n    \"2185-02-07 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-04-18 00:00:00\",\n    \"2185-04-25 00:00:00\",\n    \"2185-06-06 00:00:00\",\n    \"2185-10-24 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2185-12-27 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-01-03 00:00:00\",\n    \"2186-01-23 00:00:00\",\n    \"2186-01-30 00:00:00\",\n    \"2186-02-06 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-10 00:00:00\",\n    \"2186-04-25 00:00:00\",\n    \"2186-06-05 00:00:00\",\n    \"2186-10-23 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-01-02 00:00:00\",\n    \"2187-01-22 00:00:00\",\n    \"2187-01-29 00:00:00\",\n    \"2187-02-06 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-03-26 00:00:00\",\n    \"2187-04-25 00:00:00\",\n    \"2187-06-04 00:00:00\",\n    \"2187-10-22 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-01-02 00:00:00\",\n    \"2188-01-21 00:00:00\",\n    \"2188-01-28 00:00:00\",\n    \"2188-02-06 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-04-25 00:00:00\",\n    \"2188-06-02 00:00:00\",\n    \"2188-10-27 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-01-02 00:00:00\",\n    \"2189-01-19 00:00:00\",\n    \"2189-01-26 00:00:00\",\n    \"2189-02-06 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-06 00:00:00\",\n    \"2189-04-27 00:00:00\",\n    \"2189-06-01 00:00:00\",\n    \"2189-10-26 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-28 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-01-04 00:00:00\",\n    \"2190-01-25 00:00:00\",\n    \"2190-02-01 00:00:00\",\n    \"2190-02-08 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-06-07 00:00:00\",\n    \"2190-10-25 00:00:00\",\n    \"2190-12-27 00:00:00\",\n    \"2190-12-28 00:00:00\",\n    \"2191-01-03 00:00:00\",\n    \"2191-01-04 00:00:00\",\n    \"2191-01-24 00:00:00\",\n    \"2191-01-31 00:00:00\",\n    \"2191-02-07 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-11 00:00:00\",\n    \"2191-04-25 00:00:00\",\n    \"2191-06-06 00:00:00\",\n    \"2191-10-24 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2191-12-27 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-01-03 00:00:00\",\n    \"2192-01-23 00:00:00\",\n    \"2192-01-30 00:00:00\",\n    \"2192-02-06 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-02 00:00:00\",\n    \"2192-04-25 00:00:00\",\n    \"2192-06-04 00:00:00\",\n    \"2192-10-22 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-01-02 00:00:00\",\n    \"2193-01-21 00:00:00\",\n    \"2193-01-28 00:00:00\",\n    \"2193-02-06 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-04-22 00:00:00\",\n    \"2193-04-25 00:00:00\",\n    \"2193-06-03 00:00:00\",\n    \"2193-10-28 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-01-02 00:00:00\",\n    \"2194-01-20 00:00:00\",\n    \"2194-01-27 00:00:00\",\n    \"2194-02-06 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-07 00:00:00\",\n    \"2194-04-25 00:00:00\",\n    \"2194-06-02 00:00:00\",\n    \"2194-10-27 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-01-02 00:00:00\",\n    \"2195-01-19 00:00:00\",\n    \"2195-01-26 00:00:00\",\n    \"2195-02-06 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-03-30 00:00:00\",\n    \"2195-04-27 00:00:00\",\n    \"2195-06-01 00:00:00\",\n    \"2195-10-26 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-28 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-01-04 00:00:00\",\n    \"2196-01-25 00:00:00\",\n    \"2196-02-01 00:00:00\",\n    \"2196-02-08 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-04-18 00:00:00\",\n    \"2196-04-25 00:00:00\",\n    \"2196-06-06 00:00:00\",\n    \"2196-10-24 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2196-12-27 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-01-03 00:00:00\",\n    \"2197-01-23 00:00:00\",\n    \"2197-01-30 00:00:00\",\n    \"2197-02-06 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-10 00:00:00\",\n    \"2197-04-25 00:00:00\",\n    \"2197-06-05 00:00:00\",\n    \"2197-10-23 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-01-02 00:00:00\",\n    \"2198-01-22 00:00:00\",\n    \"2198-01-29 00:00:00\",\n    \"2198-02-06 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-03-26 00:00:00\",\n    \"2198-04-25 00:00:00\",\n    \"2198-06-04 00:00:00\",\n    \"2198-10-22 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-01-02 00:00:00\",\n    \"2199-01-21 00:00:00\",\n    \"2199-01-28 00:00:00\",\n    \"2199-02-06 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-15 00:00:00\",\n    \"2199-04-25 00:00:00\",\n    \"2199-06-03 00:00:00\",\n    \"2199-10-28 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-01-02 00:00:00\",\n    \"2200-01-20 00:00:00\",\n    \"2200-01-27 00:00:00\",\n    \"2200-02-06 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-07 00:00:00\",\n    \"2200-04-25 00:00:00\",\n    \"2200-06-02 00:00:00\",\n    \"2200-10-27 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/wlg_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nfrom datetime import datetime, timedelta\n\nimport pandas as pd\nfrom dateutil.relativedelta import MO\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Day,\n    Easter,\n    Holiday,\n    next_monday,\n    next_monday_or_tuesday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, DateOffset\n\nmatariki_dates = [\n    \"2022-06-24\",\n    \"2023-07-14\",\n    \"2024-06-28\",\n    \"2025-06-20\",\n    \"2026-07-10\",\n    \"2027-06-25\",\n    \"2028-07-14\",\n    \"2029-07-06\",\n    \"2030-06-21\",\n    \"2031-07-11\",\n    \"2032-07-02\",\n    \"2033-06-24\",\n    \"2034-07-07\",\n    \"2035-06-29\",\n    \"2036-07-18\",\n    \"2037-07-10\",\n    \"2038-06-25\",\n    \"2039-07-15\",\n    \"2040-07-06\",\n    \"2041-07-19\",\n    \"2042-07-11\",\n    \"2043-07-03\",\n    \"2044-06-24\",\n    \"2045-07-07\",\n    \"2046-06-29\",\n    \"2047-07-19\",\n    \"2048-07-03\",\n    \"2049-06-25\",\n    \"2050-07-15\",\n    \"2051-06-30\",\n    \"2052-06-21\",\n]\n\nmatariki_dict = {k + 2022: datetime.strptime(v, \"%Y-%m-%d\") for k, v in enumerate(matariki_dates)}\n\n\ndef matariki_hol(dt: datetime) -> datetime:\n    try:\n        dt = matariki_dict[dt.year]\n    except KeyError:\n        return datetime(1900, 1, 1)\n    return dt\n\n\ndef monday_nearest(dt: datetime) -> datetime:\n    \"\"\"\n    Return the Monday nearest to the given date (dt);\n    Used for Wellington and Auckland anniversaries.\n    \"\"\"\n    # If already Monday\n    if dt.weekday() == 0:\n        return dt\n\n    # Previous Monday\n    prev_mon = dt - timedelta(days=dt.weekday())\n    # Next Monday\n    next_mon = prev_mon + timedelta(days=7)\n\n    # Pick whichever Monday is closer\n    if (dt - prev_mon) <= (next_mon - dt):\n        return prev_mon\n    else:\n        return next_mon\n\n\nRULES = [\n    Holiday(\"New Year's Day\", month=1, day=1, observance=next_monday),\n    Holiday(\"Day After New Year's Day\", month=1, day=2, observance=next_monday_or_tuesday),\n    Holiday(\"Wellington Anniversary Day\", month=1, day=22, observance=monday_nearest),\n    Holiday(\"Auckland Anniversary Day\", month=1, day=29, observance=monday_nearest),\n    Holiday(\"Waitangi Day\", month=2, day=6, observance=next_monday),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Easter Monday\", month=1, day=1, offset=[Easter(), Day(1)]),\n    Holiday(\"Anzac Day\", month=4, day=25, observance=next_monday),\n    Holiday(\"King's Birthday\", month=6, day=1, offset=DateOffset(weekday=MO(1))),\n    Holiday(\"Matariki\", month=1, day=1, observance=matariki_hol),\n    Holiday(\"Labour Day\", month=10, day=1, offset=DateOffset(weekday=MO(4))),\n    Holiday(\"Christmas Day Holiday\", month=12, day=25, observance=next_monday),\n    Holiday(\"Boxing Day Holiday\", month=12, day=26, observance=next_monday_or_tuesday),\n    # one off\n    Holiday(\"Queen Elizabeth II Memorial Day\", year=2022, month=9, day=26),\n]\n\nCALENDAR = CustomBusinessDay(  # type: ignore[call-arg]\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named/zur.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Define a Zurich business day calendar, aligned with SARON publication.\n\npub const WEEKMASK: &[u8] = &[5, 6]; // Saturday and Sunday weekend\n\n// pub const RULES: &[&str] = &[\n//     \"Jan 1 (New Year)\",\n//     \"Jan 2 (Berchtoldstag)\",\n//     \"Fri before Easter (Easter Friday)\",\n//     \"Mon after Easter (Easter Monday)\",\n//     \"May 1 (Labour)\",\n//     \"39 days after Easter (Ascention)\",\n//     \"50 days after Easter (Whit Monday)\",\n//     \"Aug 1 (National)\",\n//     \"Dec 25 (Christmas)\",\n//     \"Dec 26 (Boxing)\",\n// ];\n\npub const HOLIDAYS: &[&str] = &[\n    \"1970-01-01 00:00:00\",\n    \"1970-01-02 00:00:00\",\n    \"1970-03-27 00:00:00\",\n    \"1970-03-30 00:00:00\",\n    \"1970-05-01 00:00:00\",\n    \"1970-05-07 00:00:00\",\n    \"1970-05-18 00:00:00\",\n    \"1970-08-01 00:00:00\",\n    \"1970-12-25 00:00:00\",\n    \"1970-12-26 00:00:00\",\n    \"1971-01-01 00:00:00\",\n    \"1971-01-02 00:00:00\",\n    \"1971-04-09 00:00:00\",\n    \"1971-04-12 00:00:00\",\n    \"1971-05-01 00:00:00\",\n    \"1971-05-20 00:00:00\",\n    \"1971-05-31 00:00:00\",\n    \"1971-08-01 00:00:00\",\n    \"1971-12-25 00:00:00\",\n    \"1971-12-26 00:00:00\",\n    \"1972-01-01 00:00:00\",\n    \"1972-01-02 00:00:00\",\n    \"1972-03-31 00:00:00\",\n    \"1972-04-03 00:00:00\",\n    \"1972-05-01 00:00:00\",\n    \"1972-05-11 00:00:00\",\n    \"1972-05-22 00:00:00\",\n    \"1972-08-01 00:00:00\",\n    \"1972-12-25 00:00:00\",\n    \"1972-12-26 00:00:00\",\n    \"1973-01-01 00:00:00\",\n    \"1973-01-02 00:00:00\",\n    \"1973-04-20 00:00:00\",\n    \"1973-04-23 00:00:00\",\n    \"1973-05-01 00:00:00\",\n    \"1973-05-31 00:00:00\",\n    \"1973-06-11 00:00:00\",\n    \"1973-08-01 00:00:00\",\n    \"1973-12-25 00:00:00\",\n    \"1973-12-26 00:00:00\",\n    \"1974-01-01 00:00:00\",\n    \"1974-01-02 00:00:00\",\n    \"1974-04-12 00:00:00\",\n    \"1974-04-15 00:00:00\",\n    \"1974-05-01 00:00:00\",\n    \"1974-05-23 00:00:00\",\n    \"1974-06-03 00:00:00\",\n    \"1974-08-01 00:00:00\",\n    \"1974-12-25 00:00:00\",\n    \"1974-12-26 00:00:00\",\n    \"1975-01-01 00:00:00\",\n    \"1975-01-02 00:00:00\",\n    \"1975-03-28 00:00:00\",\n    \"1975-03-31 00:00:00\",\n    \"1975-05-01 00:00:00\",\n    \"1975-05-08 00:00:00\",\n    \"1975-05-19 00:00:00\",\n    \"1975-08-01 00:00:00\",\n    \"1975-12-25 00:00:00\",\n    \"1975-12-26 00:00:00\",\n    \"1976-01-01 00:00:00\",\n    \"1976-01-02 00:00:00\",\n    \"1976-04-16 00:00:00\",\n    \"1976-04-19 00:00:00\",\n    \"1976-05-01 00:00:00\",\n    \"1976-05-27 00:00:00\",\n    \"1976-06-07 00:00:00\",\n    \"1976-08-01 00:00:00\",\n    \"1976-12-25 00:00:00\",\n    \"1976-12-26 00:00:00\",\n    \"1977-01-01 00:00:00\",\n    \"1977-01-02 00:00:00\",\n    \"1977-04-08 00:00:00\",\n    \"1977-04-11 00:00:00\",\n    \"1977-05-01 00:00:00\",\n    \"1977-05-19 00:00:00\",\n    \"1977-05-30 00:00:00\",\n    \"1977-08-01 00:00:00\",\n    \"1977-12-25 00:00:00\",\n    \"1977-12-26 00:00:00\",\n    \"1978-01-01 00:00:00\",\n    \"1978-01-02 00:00:00\",\n    \"1978-03-24 00:00:00\",\n    \"1978-03-27 00:00:00\",\n    \"1978-05-01 00:00:00\",\n    \"1978-05-04 00:00:00\",\n    \"1978-05-15 00:00:00\",\n    \"1978-08-01 00:00:00\",\n    \"1978-12-25 00:00:00\",\n    \"1978-12-26 00:00:00\",\n    \"1979-01-01 00:00:00\",\n    \"1979-01-02 00:00:00\",\n    \"1979-04-13 00:00:00\",\n    \"1979-04-16 00:00:00\",\n    \"1979-05-01 00:00:00\",\n    \"1979-05-24 00:00:00\",\n    \"1979-06-04 00:00:00\",\n    \"1979-08-01 00:00:00\",\n    \"1979-12-25 00:00:00\",\n    \"1979-12-26 00:00:00\",\n    \"1980-01-01 00:00:00\",\n    \"1980-01-02 00:00:00\",\n    \"1980-04-04 00:00:00\",\n    \"1980-04-07 00:00:00\",\n    \"1980-05-01 00:00:00\",\n    \"1980-05-15 00:00:00\",\n    \"1980-05-26 00:00:00\",\n    \"1980-08-01 00:00:00\",\n    \"1980-12-25 00:00:00\",\n    \"1980-12-26 00:00:00\",\n    \"1981-01-01 00:00:00\",\n    \"1981-01-02 00:00:00\",\n    \"1981-04-17 00:00:00\",\n    \"1981-04-20 00:00:00\",\n    \"1981-05-01 00:00:00\",\n    \"1981-05-28 00:00:00\",\n    \"1981-06-08 00:00:00\",\n    \"1981-08-01 00:00:00\",\n    \"1981-12-25 00:00:00\",\n    \"1981-12-26 00:00:00\",\n    \"1982-01-01 00:00:00\",\n    \"1982-01-02 00:00:00\",\n    \"1982-04-09 00:00:00\",\n    \"1982-04-12 00:00:00\",\n    \"1982-05-01 00:00:00\",\n    \"1982-05-20 00:00:00\",\n    \"1982-05-31 00:00:00\",\n    \"1982-08-01 00:00:00\",\n    \"1982-12-25 00:00:00\",\n    \"1982-12-26 00:00:00\",\n    \"1983-01-01 00:00:00\",\n    \"1983-01-02 00:00:00\",\n    \"1983-04-01 00:00:00\",\n    \"1983-04-04 00:00:00\",\n    \"1983-05-01 00:00:00\",\n    \"1983-05-12 00:00:00\",\n    \"1983-05-23 00:00:00\",\n    \"1983-08-01 00:00:00\",\n    \"1983-12-25 00:00:00\",\n    \"1983-12-26 00:00:00\",\n    \"1984-01-01 00:00:00\",\n    \"1984-01-02 00:00:00\",\n    \"1984-04-20 00:00:00\",\n    \"1984-04-23 00:00:00\",\n    \"1984-05-01 00:00:00\",\n    \"1984-05-31 00:00:00\",\n    \"1984-06-11 00:00:00\",\n    \"1984-08-01 00:00:00\",\n    \"1984-12-25 00:00:00\",\n    \"1984-12-26 00:00:00\",\n    \"1985-01-01 00:00:00\",\n    \"1985-01-02 00:00:00\",\n    \"1985-04-05 00:00:00\",\n    \"1985-04-08 00:00:00\",\n    \"1985-05-01 00:00:00\",\n    \"1985-05-16 00:00:00\",\n    \"1985-05-27 00:00:00\",\n    \"1985-08-01 00:00:00\",\n    \"1985-12-25 00:00:00\",\n    \"1985-12-26 00:00:00\",\n    \"1986-01-01 00:00:00\",\n    \"1986-01-02 00:00:00\",\n    \"1986-03-28 00:00:00\",\n    \"1986-03-31 00:00:00\",\n    \"1986-05-01 00:00:00\",\n    \"1986-05-08 00:00:00\",\n    \"1986-05-19 00:00:00\",\n    \"1986-08-01 00:00:00\",\n    \"1986-12-25 00:00:00\",\n    \"1986-12-26 00:00:00\",\n    \"1987-01-01 00:00:00\",\n    \"1987-01-02 00:00:00\",\n    \"1987-04-17 00:00:00\",\n    \"1987-04-20 00:00:00\",\n    \"1987-05-01 00:00:00\",\n    \"1987-05-28 00:00:00\",\n    \"1987-06-08 00:00:00\",\n    \"1987-08-01 00:00:00\",\n    \"1987-12-25 00:00:00\",\n    \"1987-12-26 00:00:00\",\n    \"1988-01-01 00:00:00\",\n    \"1988-01-02 00:00:00\",\n    \"1988-04-01 00:00:00\",\n    \"1988-04-04 00:00:00\",\n    \"1988-05-01 00:00:00\",\n    \"1988-05-12 00:00:00\",\n    \"1988-05-23 00:00:00\",\n    \"1988-08-01 00:00:00\",\n    \"1988-12-25 00:00:00\",\n    \"1988-12-26 00:00:00\",\n    \"1989-01-01 00:00:00\",\n    \"1989-01-02 00:00:00\",\n    \"1989-03-24 00:00:00\",\n    \"1989-03-27 00:00:00\",\n    \"1989-05-01 00:00:00\",\n    \"1989-05-04 00:00:00\",\n    \"1989-05-15 00:00:00\",\n    \"1989-08-01 00:00:00\",\n    \"1989-12-25 00:00:00\",\n    \"1989-12-26 00:00:00\",\n    \"1990-01-01 00:00:00\",\n    \"1990-01-02 00:00:00\",\n    \"1990-04-13 00:00:00\",\n    \"1990-04-16 00:00:00\",\n    \"1990-05-01 00:00:00\",\n    \"1990-05-24 00:00:00\",\n    \"1990-06-04 00:00:00\",\n    \"1990-08-01 00:00:00\",\n    \"1990-12-25 00:00:00\",\n    \"1990-12-26 00:00:00\",\n    \"1991-01-01 00:00:00\",\n    \"1991-01-02 00:00:00\",\n    \"1991-03-29 00:00:00\",\n    \"1991-04-01 00:00:00\",\n    \"1991-05-01 00:00:00\",\n    \"1991-05-09 00:00:00\",\n    \"1991-05-20 00:00:00\",\n    \"1991-08-01 00:00:00\",\n    \"1991-12-25 00:00:00\",\n    \"1991-12-26 00:00:00\",\n    \"1992-01-01 00:00:00\",\n    \"1992-01-02 00:00:00\",\n    \"1992-04-17 00:00:00\",\n    \"1992-04-20 00:00:00\",\n    \"1992-05-01 00:00:00\",\n    \"1992-05-28 00:00:00\",\n    \"1992-06-08 00:00:00\",\n    \"1992-08-01 00:00:00\",\n    \"1992-12-25 00:00:00\",\n    \"1992-12-26 00:00:00\",\n    \"1993-01-01 00:00:00\",\n    \"1993-01-02 00:00:00\",\n    \"1993-04-09 00:00:00\",\n    \"1993-04-12 00:00:00\",\n    \"1993-05-01 00:00:00\",\n    \"1993-05-20 00:00:00\",\n    \"1993-05-31 00:00:00\",\n    \"1993-08-01 00:00:00\",\n    \"1993-12-25 00:00:00\",\n    \"1993-12-26 00:00:00\",\n    \"1994-01-01 00:00:00\",\n    \"1994-01-02 00:00:00\",\n    \"1994-04-01 00:00:00\",\n    \"1994-04-04 00:00:00\",\n    \"1994-05-01 00:00:00\",\n    \"1994-05-12 00:00:00\",\n    \"1994-05-23 00:00:00\",\n    \"1994-08-01 00:00:00\",\n    \"1994-12-25 00:00:00\",\n    \"1994-12-26 00:00:00\",\n    \"1995-01-01 00:00:00\",\n    \"1995-01-02 00:00:00\",\n    \"1995-04-14 00:00:00\",\n    \"1995-04-17 00:00:00\",\n    \"1995-05-01 00:00:00\",\n    \"1995-05-25 00:00:00\",\n    \"1995-06-05 00:00:00\",\n    \"1995-08-01 00:00:00\",\n    \"1995-12-25 00:00:00\",\n    \"1995-12-26 00:00:00\",\n    \"1996-01-01 00:00:00\",\n    \"1996-01-02 00:00:00\",\n    \"1996-04-05 00:00:00\",\n    \"1996-04-08 00:00:00\",\n    \"1996-05-01 00:00:00\",\n    \"1996-05-16 00:00:00\",\n    \"1996-05-27 00:00:00\",\n    \"1996-08-01 00:00:00\",\n    \"1996-12-25 00:00:00\",\n    \"1996-12-26 00:00:00\",\n    \"1997-01-01 00:00:00\",\n    \"1997-01-02 00:00:00\",\n    \"1997-03-28 00:00:00\",\n    \"1997-03-31 00:00:00\",\n    \"1997-05-01 00:00:00\",\n    \"1997-05-08 00:00:00\",\n    \"1997-05-19 00:00:00\",\n    \"1997-08-01 00:00:00\",\n    \"1997-12-25 00:00:00\",\n    \"1997-12-26 00:00:00\",\n    \"1998-01-01 00:00:00\",\n    \"1998-01-02 00:00:00\",\n    \"1998-04-10 00:00:00\",\n    \"1998-04-13 00:00:00\",\n    \"1998-05-01 00:00:00\",\n    \"1998-05-21 00:00:00\",\n    \"1998-06-01 00:00:00\",\n    \"1998-08-01 00:00:00\",\n    \"1998-12-25 00:00:00\",\n    \"1998-12-26 00:00:00\",\n    \"1999-01-01 00:00:00\",\n    \"1999-01-02 00:00:00\",\n    \"1999-04-02 00:00:00\",\n    \"1999-04-05 00:00:00\",\n    \"1999-05-01 00:00:00\",\n    \"1999-05-13 00:00:00\",\n    \"1999-05-24 00:00:00\",\n    \"1999-08-01 00:00:00\",\n    \"1999-12-25 00:00:00\",\n    \"1999-12-26 00:00:00\",\n    \"2000-01-01 00:00:00\",\n    \"2000-01-02 00:00:00\",\n    \"2000-04-21 00:00:00\",\n    \"2000-04-24 00:00:00\",\n    \"2000-05-01 00:00:00\",\n    \"2000-06-01 00:00:00\",\n    \"2000-06-12 00:00:00\",\n    \"2000-08-01 00:00:00\",\n    \"2000-12-25 00:00:00\",\n    \"2000-12-26 00:00:00\",\n    \"2001-01-01 00:00:00\",\n    \"2001-01-02 00:00:00\",\n    \"2001-04-13 00:00:00\",\n    \"2001-04-16 00:00:00\",\n    \"2001-05-01 00:00:00\",\n    \"2001-05-24 00:00:00\",\n    \"2001-06-04 00:00:00\",\n    \"2001-08-01 00:00:00\",\n    \"2001-12-25 00:00:00\",\n    \"2001-12-26 00:00:00\",\n    \"2002-01-01 00:00:00\",\n    \"2002-01-02 00:00:00\",\n    \"2002-03-29 00:00:00\",\n    \"2002-04-01 00:00:00\",\n    \"2002-05-01 00:00:00\",\n    \"2002-05-09 00:00:00\",\n    \"2002-05-20 00:00:00\",\n    \"2002-08-01 00:00:00\",\n    \"2002-12-25 00:00:00\",\n    \"2002-12-26 00:00:00\",\n    \"2003-01-01 00:00:00\",\n    \"2003-01-02 00:00:00\",\n    \"2003-04-18 00:00:00\",\n    \"2003-04-21 00:00:00\",\n    \"2003-05-01 00:00:00\",\n    \"2003-05-29 00:00:00\",\n    \"2003-06-09 00:00:00\",\n    \"2003-08-01 00:00:00\",\n    \"2003-12-25 00:00:00\",\n    \"2003-12-26 00:00:00\",\n    \"2004-01-01 00:00:00\",\n    \"2004-01-02 00:00:00\",\n    \"2004-04-09 00:00:00\",\n    \"2004-04-12 00:00:00\",\n    \"2004-05-01 00:00:00\",\n    \"2004-05-20 00:00:00\",\n    \"2004-05-31 00:00:00\",\n    \"2004-08-01 00:00:00\",\n    \"2004-12-25 00:00:00\",\n    \"2004-12-26 00:00:00\",\n    \"2005-01-01 00:00:00\",\n    \"2005-01-02 00:00:00\",\n    \"2005-03-25 00:00:00\",\n    \"2005-03-28 00:00:00\",\n    \"2005-05-01 00:00:00\",\n    \"2005-05-05 00:00:00\",\n    \"2005-05-16 00:00:00\",\n    \"2005-08-01 00:00:00\",\n    \"2005-12-25 00:00:00\",\n    \"2005-12-26 00:00:00\",\n    \"2006-01-01 00:00:00\",\n    \"2006-01-02 00:00:00\",\n    \"2006-04-14 00:00:00\",\n    \"2006-04-17 00:00:00\",\n    \"2006-05-01 00:00:00\",\n    \"2006-05-25 00:00:00\",\n    \"2006-06-05 00:00:00\",\n    \"2006-08-01 00:00:00\",\n    \"2006-12-25 00:00:00\",\n    \"2006-12-26 00:00:00\",\n    \"2007-01-01 00:00:00\",\n    \"2007-01-02 00:00:00\",\n    \"2007-04-06 00:00:00\",\n    \"2007-04-09 00:00:00\",\n    \"2007-05-01 00:00:00\",\n    \"2007-05-17 00:00:00\",\n    \"2007-05-28 00:00:00\",\n    \"2007-08-01 00:00:00\",\n    \"2007-12-25 00:00:00\",\n    \"2007-12-26 00:00:00\",\n    \"2008-01-01 00:00:00\",\n    \"2008-01-02 00:00:00\",\n    \"2008-03-21 00:00:00\",\n    \"2008-03-24 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-05-01 00:00:00\",\n    \"2008-05-12 00:00:00\",\n    \"2008-08-01 00:00:00\",\n    \"2008-12-25 00:00:00\",\n    \"2008-12-26 00:00:00\",\n    \"2009-01-01 00:00:00\",\n    \"2009-01-02 00:00:00\",\n    \"2009-04-10 00:00:00\",\n    \"2009-04-13 00:00:00\",\n    \"2009-05-01 00:00:00\",\n    \"2009-05-21 00:00:00\",\n    \"2009-06-01 00:00:00\",\n    \"2009-08-01 00:00:00\",\n    \"2009-12-25 00:00:00\",\n    \"2009-12-26 00:00:00\",\n    \"2010-01-01 00:00:00\",\n    \"2010-01-02 00:00:00\",\n    \"2010-04-02 00:00:00\",\n    \"2010-04-05 00:00:00\",\n    \"2010-05-01 00:00:00\",\n    \"2010-05-13 00:00:00\",\n    \"2010-05-24 00:00:00\",\n    \"2010-08-01 00:00:00\",\n    \"2010-12-25 00:00:00\",\n    \"2010-12-26 00:00:00\",\n    \"2011-01-01 00:00:00\",\n    \"2011-01-02 00:00:00\",\n    \"2011-04-22 00:00:00\",\n    \"2011-04-25 00:00:00\",\n    \"2011-05-01 00:00:00\",\n    \"2011-06-02 00:00:00\",\n    \"2011-06-13 00:00:00\",\n    \"2011-08-01 00:00:00\",\n    \"2011-12-25 00:00:00\",\n    \"2011-12-26 00:00:00\",\n    \"2012-01-01 00:00:00\",\n    \"2012-01-02 00:00:00\",\n    \"2012-04-06 00:00:00\",\n    \"2012-04-09 00:00:00\",\n    \"2012-05-01 00:00:00\",\n    \"2012-05-17 00:00:00\",\n    \"2012-05-28 00:00:00\",\n    \"2012-08-01 00:00:00\",\n    \"2012-12-25 00:00:00\",\n    \"2012-12-26 00:00:00\",\n    \"2013-01-01 00:00:00\",\n    \"2013-01-02 00:00:00\",\n    \"2013-03-29 00:00:00\",\n    \"2013-04-01 00:00:00\",\n    \"2013-05-01 00:00:00\",\n    \"2013-05-09 00:00:00\",\n    \"2013-05-20 00:00:00\",\n    \"2013-08-01 00:00:00\",\n    \"2013-12-25 00:00:00\",\n    \"2013-12-26 00:00:00\",\n    \"2014-01-01 00:00:00\",\n    \"2014-01-02 00:00:00\",\n    \"2014-04-18 00:00:00\",\n    \"2014-04-21 00:00:00\",\n    \"2014-05-01 00:00:00\",\n    \"2014-05-29 00:00:00\",\n    \"2014-06-09 00:00:00\",\n    \"2014-08-01 00:00:00\",\n    \"2014-12-25 00:00:00\",\n    \"2014-12-26 00:00:00\",\n    \"2015-01-01 00:00:00\",\n    \"2015-01-02 00:00:00\",\n    \"2015-04-03 00:00:00\",\n    \"2015-04-06 00:00:00\",\n    \"2015-05-01 00:00:00\",\n    \"2015-05-14 00:00:00\",\n    \"2015-05-25 00:00:00\",\n    \"2015-08-01 00:00:00\",\n    \"2015-12-25 00:00:00\",\n    \"2015-12-26 00:00:00\",\n    \"2016-01-01 00:00:00\",\n    \"2016-01-02 00:00:00\",\n    \"2016-03-25 00:00:00\",\n    \"2016-03-28 00:00:00\",\n    \"2016-05-01 00:00:00\",\n    \"2016-05-05 00:00:00\",\n    \"2016-05-16 00:00:00\",\n    \"2016-08-01 00:00:00\",\n    \"2016-12-25 00:00:00\",\n    \"2016-12-26 00:00:00\",\n    \"2017-01-01 00:00:00\",\n    \"2017-01-02 00:00:00\",\n    \"2017-04-14 00:00:00\",\n    \"2017-04-17 00:00:00\",\n    \"2017-05-01 00:00:00\",\n    \"2017-05-25 00:00:00\",\n    \"2017-06-05 00:00:00\",\n    \"2017-08-01 00:00:00\",\n    \"2017-12-25 00:00:00\",\n    \"2017-12-26 00:00:00\",\n    \"2018-01-01 00:00:00\",\n    \"2018-01-02 00:00:00\",\n    \"2018-03-30 00:00:00\",\n    \"2018-04-02 00:00:00\",\n    \"2018-05-01 00:00:00\",\n    \"2018-05-10 00:00:00\",\n    \"2018-05-21 00:00:00\",\n    \"2018-08-01 00:00:00\",\n    \"2018-12-25 00:00:00\",\n    \"2018-12-26 00:00:00\",\n    \"2019-01-01 00:00:00\",\n    \"2019-01-02 00:00:00\",\n    \"2019-04-19 00:00:00\",\n    \"2019-04-22 00:00:00\",\n    \"2019-05-01 00:00:00\",\n    \"2019-05-30 00:00:00\",\n    \"2019-06-10 00:00:00\",\n    \"2019-08-01 00:00:00\",\n    \"2019-12-25 00:00:00\",\n    \"2019-12-26 00:00:00\",\n    \"2020-01-01 00:00:00\",\n    \"2020-01-02 00:00:00\",\n    \"2020-04-10 00:00:00\",\n    \"2020-04-13 00:00:00\",\n    \"2020-05-01 00:00:00\",\n    \"2020-05-21 00:00:00\",\n    \"2020-06-01 00:00:00\",\n    \"2020-08-01 00:00:00\",\n    \"2020-12-25 00:00:00\",\n    \"2020-12-26 00:00:00\",\n    \"2021-01-01 00:00:00\",\n    \"2021-01-02 00:00:00\",\n    \"2021-04-02 00:00:00\",\n    \"2021-04-05 00:00:00\",\n    \"2021-05-01 00:00:00\",\n    \"2021-05-13 00:00:00\",\n    \"2021-05-24 00:00:00\",\n    \"2021-08-01 00:00:00\",\n    \"2021-12-25 00:00:00\",\n    \"2021-12-26 00:00:00\",\n    \"2022-01-01 00:00:00\",\n    \"2022-01-02 00:00:00\",\n    \"2022-04-15 00:00:00\",\n    \"2022-04-18 00:00:00\",\n    \"2022-05-01 00:00:00\",\n    \"2022-05-26 00:00:00\",\n    \"2022-06-06 00:00:00\",\n    \"2022-08-01 00:00:00\",\n    \"2022-12-25 00:00:00\",\n    \"2022-12-26 00:00:00\",\n    \"2023-01-01 00:00:00\",\n    \"2023-01-02 00:00:00\",\n    \"2023-04-07 00:00:00\",\n    \"2023-04-10 00:00:00\",\n    \"2023-05-01 00:00:00\",\n    \"2023-05-18 00:00:00\",\n    \"2023-05-29 00:00:00\",\n    \"2023-08-01 00:00:00\",\n    \"2023-12-25 00:00:00\",\n    \"2023-12-26 00:00:00\",\n    \"2024-01-01 00:00:00\",\n    \"2024-01-02 00:00:00\",\n    \"2024-03-29 00:00:00\",\n    \"2024-04-01 00:00:00\",\n    \"2024-05-01 00:00:00\",\n    \"2024-05-09 00:00:00\",\n    \"2024-05-20 00:00:00\",\n    \"2024-08-01 00:00:00\",\n    \"2024-12-25 00:00:00\",\n    \"2024-12-26 00:00:00\",\n    \"2025-01-01 00:00:00\",\n    \"2025-01-02 00:00:00\",\n    \"2025-04-18 00:00:00\",\n    \"2025-04-21 00:00:00\",\n    \"2025-05-01 00:00:00\",\n    \"2025-05-29 00:00:00\",\n    \"2025-06-09 00:00:00\",\n    \"2025-08-01 00:00:00\",\n    \"2025-12-25 00:00:00\",\n    \"2025-12-26 00:00:00\",\n    \"2026-01-01 00:00:00\",\n    \"2026-01-02 00:00:00\",\n    \"2026-04-03 00:00:00\",\n    \"2026-04-06 00:00:00\",\n    \"2026-05-01 00:00:00\",\n    \"2026-05-14 00:00:00\",\n    \"2026-05-25 00:00:00\",\n    \"2026-08-01 00:00:00\",\n    \"2026-12-25 00:00:00\",\n    \"2026-12-26 00:00:00\",\n    \"2027-01-01 00:00:00\",\n    \"2027-01-02 00:00:00\",\n    \"2027-03-26 00:00:00\",\n    \"2027-03-29 00:00:00\",\n    \"2027-05-01 00:00:00\",\n    \"2027-05-06 00:00:00\",\n    \"2027-05-17 00:00:00\",\n    \"2027-08-01 00:00:00\",\n    \"2027-12-25 00:00:00\",\n    \"2027-12-26 00:00:00\",\n    \"2028-01-01 00:00:00\",\n    \"2028-01-02 00:00:00\",\n    \"2028-04-14 00:00:00\",\n    \"2028-04-17 00:00:00\",\n    \"2028-05-01 00:00:00\",\n    \"2028-05-25 00:00:00\",\n    \"2028-06-05 00:00:00\",\n    \"2028-08-01 00:00:00\",\n    \"2028-12-25 00:00:00\",\n    \"2028-12-26 00:00:00\",\n    \"2029-01-01 00:00:00\",\n    \"2029-01-02 00:00:00\",\n    \"2029-03-30 00:00:00\",\n    \"2029-04-02 00:00:00\",\n    \"2029-05-01 00:00:00\",\n    \"2029-05-10 00:00:00\",\n    \"2029-05-21 00:00:00\",\n    \"2029-08-01 00:00:00\",\n    \"2029-12-25 00:00:00\",\n    \"2029-12-26 00:00:00\",\n    \"2030-01-01 00:00:00\",\n    \"2030-01-02 00:00:00\",\n    \"2030-04-19 00:00:00\",\n    \"2030-04-22 00:00:00\",\n    \"2030-05-01 00:00:00\",\n    \"2030-05-30 00:00:00\",\n    \"2030-06-10 00:00:00\",\n    \"2030-08-01 00:00:00\",\n    \"2030-12-25 00:00:00\",\n    \"2030-12-26 00:00:00\",\n    \"2031-01-01 00:00:00\",\n    \"2031-01-02 00:00:00\",\n    \"2031-04-11 00:00:00\",\n    \"2031-04-14 00:00:00\",\n    \"2031-05-01 00:00:00\",\n    \"2031-05-22 00:00:00\",\n    \"2031-06-02 00:00:00\",\n    \"2031-08-01 00:00:00\",\n    \"2031-12-25 00:00:00\",\n    \"2031-12-26 00:00:00\",\n    \"2032-01-01 00:00:00\",\n    \"2032-01-02 00:00:00\",\n    \"2032-03-26 00:00:00\",\n    \"2032-03-29 00:00:00\",\n    \"2032-05-01 00:00:00\",\n    \"2032-05-06 00:00:00\",\n    \"2032-05-17 00:00:00\",\n    \"2032-08-01 00:00:00\",\n    \"2032-12-25 00:00:00\",\n    \"2032-12-26 00:00:00\",\n    \"2033-01-01 00:00:00\",\n    \"2033-01-02 00:00:00\",\n    \"2033-04-15 00:00:00\",\n    \"2033-04-18 00:00:00\",\n    \"2033-05-01 00:00:00\",\n    \"2033-05-26 00:00:00\",\n    \"2033-06-06 00:00:00\",\n    \"2033-08-01 00:00:00\",\n    \"2033-12-25 00:00:00\",\n    \"2033-12-26 00:00:00\",\n    \"2034-01-01 00:00:00\",\n    \"2034-01-02 00:00:00\",\n    \"2034-04-07 00:00:00\",\n    \"2034-04-10 00:00:00\",\n    \"2034-05-01 00:00:00\",\n    \"2034-05-18 00:00:00\",\n    \"2034-05-29 00:00:00\",\n    \"2034-08-01 00:00:00\",\n    \"2034-12-25 00:00:00\",\n    \"2034-12-26 00:00:00\",\n    \"2035-01-01 00:00:00\",\n    \"2035-01-02 00:00:00\",\n    \"2035-03-23 00:00:00\",\n    \"2035-03-26 00:00:00\",\n    \"2035-05-01 00:00:00\",\n    \"2035-05-03 00:00:00\",\n    \"2035-05-14 00:00:00\",\n    \"2035-08-01 00:00:00\",\n    \"2035-12-25 00:00:00\",\n    \"2035-12-26 00:00:00\",\n    \"2036-01-01 00:00:00\",\n    \"2036-01-02 00:00:00\",\n    \"2036-04-11 00:00:00\",\n    \"2036-04-14 00:00:00\",\n    \"2036-05-01 00:00:00\",\n    \"2036-05-22 00:00:00\",\n    \"2036-06-02 00:00:00\",\n    \"2036-08-01 00:00:00\",\n    \"2036-12-25 00:00:00\",\n    \"2036-12-26 00:00:00\",\n    \"2037-01-01 00:00:00\",\n    \"2037-01-02 00:00:00\",\n    \"2037-04-03 00:00:00\",\n    \"2037-04-06 00:00:00\",\n    \"2037-05-01 00:00:00\",\n    \"2037-05-14 00:00:00\",\n    \"2037-05-25 00:00:00\",\n    \"2037-08-01 00:00:00\",\n    \"2037-12-25 00:00:00\",\n    \"2037-12-26 00:00:00\",\n    \"2038-01-01 00:00:00\",\n    \"2038-01-02 00:00:00\",\n    \"2038-04-23 00:00:00\",\n    \"2038-04-26 00:00:00\",\n    \"2038-05-01 00:00:00\",\n    \"2038-06-03 00:00:00\",\n    \"2038-06-14 00:00:00\",\n    \"2038-08-01 00:00:00\",\n    \"2038-12-25 00:00:00\",\n    \"2038-12-26 00:00:00\",\n    \"2039-01-01 00:00:00\",\n    \"2039-01-02 00:00:00\",\n    \"2039-04-08 00:00:00\",\n    \"2039-04-11 00:00:00\",\n    \"2039-05-01 00:00:00\",\n    \"2039-05-19 00:00:00\",\n    \"2039-05-30 00:00:00\",\n    \"2039-08-01 00:00:00\",\n    \"2039-12-25 00:00:00\",\n    \"2039-12-26 00:00:00\",\n    \"2040-01-01 00:00:00\",\n    \"2040-01-02 00:00:00\",\n    \"2040-03-30 00:00:00\",\n    \"2040-04-02 00:00:00\",\n    \"2040-05-01 00:00:00\",\n    \"2040-05-10 00:00:00\",\n    \"2040-05-21 00:00:00\",\n    \"2040-08-01 00:00:00\",\n    \"2040-12-25 00:00:00\",\n    \"2040-12-26 00:00:00\",\n    \"2041-01-01 00:00:00\",\n    \"2041-01-02 00:00:00\",\n    \"2041-04-19 00:00:00\",\n    \"2041-04-22 00:00:00\",\n    \"2041-05-01 00:00:00\",\n    \"2041-05-30 00:00:00\",\n    \"2041-06-10 00:00:00\",\n    \"2041-08-01 00:00:00\",\n    \"2041-12-25 00:00:00\",\n    \"2041-12-26 00:00:00\",\n    \"2042-01-01 00:00:00\",\n    \"2042-01-02 00:00:00\",\n    \"2042-04-04 00:00:00\",\n    \"2042-04-07 00:00:00\",\n    \"2042-05-01 00:00:00\",\n    \"2042-05-15 00:00:00\",\n    \"2042-05-26 00:00:00\",\n    \"2042-08-01 00:00:00\",\n    \"2042-12-25 00:00:00\",\n    \"2042-12-26 00:00:00\",\n    \"2043-01-01 00:00:00\",\n    \"2043-01-02 00:00:00\",\n    \"2043-03-27 00:00:00\",\n    \"2043-03-30 00:00:00\",\n    \"2043-05-01 00:00:00\",\n    \"2043-05-07 00:00:00\",\n    \"2043-05-18 00:00:00\",\n    \"2043-08-01 00:00:00\",\n    \"2043-12-25 00:00:00\",\n    \"2043-12-26 00:00:00\",\n    \"2044-01-01 00:00:00\",\n    \"2044-01-02 00:00:00\",\n    \"2044-04-15 00:00:00\",\n    \"2044-04-18 00:00:00\",\n    \"2044-05-01 00:00:00\",\n    \"2044-05-26 00:00:00\",\n    \"2044-06-06 00:00:00\",\n    \"2044-08-01 00:00:00\",\n    \"2044-12-25 00:00:00\",\n    \"2044-12-26 00:00:00\",\n    \"2045-01-01 00:00:00\",\n    \"2045-01-02 00:00:00\",\n    \"2045-04-07 00:00:00\",\n    \"2045-04-10 00:00:00\",\n    \"2045-05-01 00:00:00\",\n    \"2045-05-18 00:00:00\",\n    \"2045-05-29 00:00:00\",\n    \"2045-08-01 00:00:00\",\n    \"2045-12-25 00:00:00\",\n    \"2045-12-26 00:00:00\",\n    \"2046-01-01 00:00:00\",\n    \"2046-01-02 00:00:00\",\n    \"2046-03-23 00:00:00\",\n    \"2046-03-26 00:00:00\",\n    \"2046-05-01 00:00:00\",\n    \"2046-05-03 00:00:00\",\n    \"2046-05-14 00:00:00\",\n    \"2046-08-01 00:00:00\",\n    \"2046-12-25 00:00:00\",\n    \"2046-12-26 00:00:00\",\n    \"2047-01-01 00:00:00\",\n    \"2047-01-02 00:00:00\",\n    \"2047-04-12 00:00:00\",\n    \"2047-04-15 00:00:00\",\n    \"2047-05-01 00:00:00\",\n    \"2047-05-23 00:00:00\",\n    \"2047-06-03 00:00:00\",\n    \"2047-08-01 00:00:00\",\n    \"2047-12-25 00:00:00\",\n    \"2047-12-26 00:00:00\",\n    \"2048-01-01 00:00:00\",\n    \"2048-01-02 00:00:00\",\n    \"2048-04-03 00:00:00\",\n    \"2048-04-06 00:00:00\",\n    \"2048-05-01 00:00:00\",\n    \"2048-05-14 00:00:00\",\n    \"2048-05-25 00:00:00\",\n    \"2048-08-01 00:00:00\",\n    \"2048-12-25 00:00:00\",\n    \"2048-12-26 00:00:00\",\n    \"2049-01-01 00:00:00\",\n    \"2049-01-02 00:00:00\",\n    \"2049-04-16 00:00:00\",\n    \"2049-04-19 00:00:00\",\n    \"2049-05-01 00:00:00\",\n    \"2049-05-27 00:00:00\",\n    \"2049-06-07 00:00:00\",\n    \"2049-08-01 00:00:00\",\n    \"2049-12-25 00:00:00\",\n    \"2049-12-26 00:00:00\",\n    \"2050-01-01 00:00:00\",\n    \"2050-01-02 00:00:00\",\n    \"2050-04-08 00:00:00\",\n    \"2050-04-11 00:00:00\",\n    \"2050-05-01 00:00:00\",\n    \"2050-05-19 00:00:00\",\n    \"2050-05-30 00:00:00\",\n    \"2050-08-01 00:00:00\",\n    \"2050-12-25 00:00:00\",\n    \"2050-12-26 00:00:00\",\n    \"2051-01-01 00:00:00\",\n    \"2051-01-02 00:00:00\",\n    \"2051-03-31 00:00:00\",\n    \"2051-04-03 00:00:00\",\n    \"2051-05-01 00:00:00\",\n    \"2051-05-11 00:00:00\",\n    \"2051-05-22 00:00:00\",\n    \"2051-08-01 00:00:00\",\n    \"2051-12-25 00:00:00\",\n    \"2051-12-26 00:00:00\",\n    \"2052-01-01 00:00:00\",\n    \"2052-01-02 00:00:00\",\n    \"2052-04-19 00:00:00\",\n    \"2052-04-22 00:00:00\",\n    \"2052-05-01 00:00:00\",\n    \"2052-05-30 00:00:00\",\n    \"2052-06-10 00:00:00\",\n    \"2052-08-01 00:00:00\",\n    \"2052-12-25 00:00:00\",\n    \"2052-12-26 00:00:00\",\n    \"2053-01-01 00:00:00\",\n    \"2053-01-02 00:00:00\",\n    \"2053-04-04 00:00:00\",\n    \"2053-04-07 00:00:00\",\n    \"2053-05-01 00:00:00\",\n    \"2053-05-15 00:00:00\",\n    \"2053-05-26 00:00:00\",\n    \"2053-08-01 00:00:00\",\n    \"2053-12-25 00:00:00\",\n    \"2053-12-26 00:00:00\",\n    \"2054-01-01 00:00:00\",\n    \"2054-01-02 00:00:00\",\n    \"2054-03-27 00:00:00\",\n    \"2054-03-30 00:00:00\",\n    \"2054-05-01 00:00:00\",\n    \"2054-05-07 00:00:00\",\n    \"2054-05-18 00:00:00\",\n    \"2054-08-01 00:00:00\",\n    \"2054-12-25 00:00:00\",\n    \"2054-12-26 00:00:00\",\n    \"2055-01-01 00:00:00\",\n    \"2055-01-02 00:00:00\",\n    \"2055-04-16 00:00:00\",\n    \"2055-04-19 00:00:00\",\n    \"2055-05-01 00:00:00\",\n    \"2055-05-27 00:00:00\",\n    \"2055-06-07 00:00:00\",\n    \"2055-08-01 00:00:00\",\n    \"2055-12-25 00:00:00\",\n    \"2055-12-26 00:00:00\",\n    \"2056-01-01 00:00:00\",\n    \"2056-01-02 00:00:00\",\n    \"2056-03-31 00:00:00\",\n    \"2056-04-03 00:00:00\",\n    \"2056-05-01 00:00:00\",\n    \"2056-05-11 00:00:00\",\n    \"2056-05-22 00:00:00\",\n    \"2056-08-01 00:00:00\",\n    \"2056-12-25 00:00:00\",\n    \"2056-12-26 00:00:00\",\n    \"2057-01-01 00:00:00\",\n    \"2057-01-02 00:00:00\",\n    \"2057-04-20 00:00:00\",\n    \"2057-04-23 00:00:00\",\n    \"2057-05-01 00:00:00\",\n    \"2057-05-31 00:00:00\",\n    \"2057-06-11 00:00:00\",\n    \"2057-08-01 00:00:00\",\n    \"2057-12-25 00:00:00\",\n    \"2057-12-26 00:00:00\",\n    \"2058-01-01 00:00:00\",\n    \"2058-01-02 00:00:00\",\n    \"2058-04-12 00:00:00\",\n    \"2058-04-15 00:00:00\",\n    \"2058-05-01 00:00:00\",\n    \"2058-05-23 00:00:00\",\n    \"2058-06-03 00:00:00\",\n    \"2058-08-01 00:00:00\",\n    \"2058-12-25 00:00:00\",\n    \"2058-12-26 00:00:00\",\n    \"2059-01-01 00:00:00\",\n    \"2059-01-02 00:00:00\",\n    \"2059-03-28 00:00:00\",\n    \"2059-03-31 00:00:00\",\n    \"2059-05-01 00:00:00\",\n    \"2059-05-08 00:00:00\",\n    \"2059-05-19 00:00:00\",\n    \"2059-08-01 00:00:00\",\n    \"2059-12-25 00:00:00\",\n    \"2059-12-26 00:00:00\",\n    \"2060-01-01 00:00:00\",\n    \"2060-01-02 00:00:00\",\n    \"2060-04-16 00:00:00\",\n    \"2060-04-19 00:00:00\",\n    \"2060-05-01 00:00:00\",\n    \"2060-05-27 00:00:00\",\n    \"2060-06-07 00:00:00\",\n    \"2060-08-01 00:00:00\",\n    \"2060-12-25 00:00:00\",\n    \"2060-12-26 00:00:00\",\n    \"2061-01-01 00:00:00\",\n    \"2061-01-02 00:00:00\",\n    \"2061-04-08 00:00:00\",\n    \"2061-04-11 00:00:00\",\n    \"2061-05-01 00:00:00\",\n    \"2061-05-19 00:00:00\",\n    \"2061-05-30 00:00:00\",\n    \"2061-08-01 00:00:00\",\n    \"2061-12-25 00:00:00\",\n    \"2061-12-26 00:00:00\",\n    \"2062-01-01 00:00:00\",\n    \"2062-01-02 00:00:00\",\n    \"2062-03-24 00:00:00\",\n    \"2062-03-27 00:00:00\",\n    \"2062-05-01 00:00:00\",\n    \"2062-05-04 00:00:00\",\n    \"2062-05-15 00:00:00\",\n    \"2062-08-01 00:00:00\",\n    \"2062-12-25 00:00:00\",\n    \"2062-12-26 00:00:00\",\n    \"2063-01-01 00:00:00\",\n    \"2063-01-02 00:00:00\",\n    \"2063-04-13 00:00:00\",\n    \"2063-04-16 00:00:00\",\n    \"2063-05-01 00:00:00\",\n    \"2063-05-24 00:00:00\",\n    \"2063-06-04 00:00:00\",\n    \"2063-08-01 00:00:00\",\n    \"2063-12-25 00:00:00\",\n    \"2063-12-26 00:00:00\",\n    \"2064-01-01 00:00:00\",\n    \"2064-01-02 00:00:00\",\n    \"2064-04-04 00:00:00\",\n    \"2064-04-07 00:00:00\",\n    \"2064-05-01 00:00:00\",\n    \"2064-05-15 00:00:00\",\n    \"2064-05-26 00:00:00\",\n    \"2064-08-01 00:00:00\",\n    \"2064-12-25 00:00:00\",\n    \"2064-12-26 00:00:00\",\n    \"2065-01-01 00:00:00\",\n    \"2065-01-02 00:00:00\",\n    \"2065-03-27 00:00:00\",\n    \"2065-03-30 00:00:00\",\n    \"2065-05-01 00:00:00\",\n    \"2065-05-07 00:00:00\",\n    \"2065-05-18 00:00:00\",\n    \"2065-08-01 00:00:00\",\n    \"2065-12-25 00:00:00\",\n    \"2065-12-26 00:00:00\",\n    \"2066-01-01 00:00:00\",\n    \"2066-01-02 00:00:00\",\n    \"2066-04-09 00:00:00\",\n    \"2066-04-12 00:00:00\",\n    \"2066-05-01 00:00:00\",\n    \"2066-05-20 00:00:00\",\n    \"2066-05-31 00:00:00\",\n    \"2066-08-01 00:00:00\",\n    \"2066-12-25 00:00:00\",\n    \"2066-12-26 00:00:00\",\n    \"2067-01-01 00:00:00\",\n    \"2067-01-02 00:00:00\",\n    \"2067-04-01 00:00:00\",\n    \"2067-04-04 00:00:00\",\n    \"2067-05-01 00:00:00\",\n    \"2067-05-12 00:00:00\",\n    \"2067-05-23 00:00:00\",\n    \"2067-08-01 00:00:00\",\n    \"2067-12-25 00:00:00\",\n    \"2067-12-26 00:00:00\",\n    \"2068-01-01 00:00:00\",\n    \"2068-01-02 00:00:00\",\n    \"2068-04-20 00:00:00\",\n    \"2068-04-23 00:00:00\",\n    \"2068-05-01 00:00:00\",\n    \"2068-05-31 00:00:00\",\n    \"2068-06-11 00:00:00\",\n    \"2068-08-01 00:00:00\",\n    \"2068-12-25 00:00:00\",\n    \"2068-12-26 00:00:00\",\n    \"2069-01-01 00:00:00\",\n    \"2069-01-02 00:00:00\",\n    \"2069-04-12 00:00:00\",\n    \"2069-04-15 00:00:00\",\n    \"2069-05-01 00:00:00\",\n    \"2069-05-23 00:00:00\",\n    \"2069-06-03 00:00:00\",\n    \"2069-08-01 00:00:00\",\n    \"2069-12-25 00:00:00\",\n    \"2069-12-26 00:00:00\",\n    \"2070-01-01 00:00:00\",\n    \"2070-01-02 00:00:00\",\n    \"2070-03-28 00:00:00\",\n    \"2070-03-31 00:00:00\",\n    \"2070-05-01 00:00:00\",\n    \"2070-05-08 00:00:00\",\n    \"2070-05-19 00:00:00\",\n    \"2070-08-01 00:00:00\",\n    \"2070-12-25 00:00:00\",\n    \"2070-12-26 00:00:00\",\n    \"2071-01-01 00:00:00\",\n    \"2071-01-02 00:00:00\",\n    \"2071-04-17 00:00:00\",\n    \"2071-04-20 00:00:00\",\n    \"2071-05-01 00:00:00\",\n    \"2071-05-28 00:00:00\",\n    \"2071-06-08 00:00:00\",\n    \"2071-08-01 00:00:00\",\n    \"2071-12-25 00:00:00\",\n    \"2071-12-26 00:00:00\",\n    \"2072-01-01 00:00:00\",\n    \"2072-01-02 00:00:00\",\n    \"2072-04-08 00:00:00\",\n    \"2072-04-11 00:00:00\",\n    \"2072-05-01 00:00:00\",\n    \"2072-05-19 00:00:00\",\n    \"2072-05-30 00:00:00\",\n    \"2072-08-01 00:00:00\",\n    \"2072-12-25 00:00:00\",\n    \"2072-12-26 00:00:00\",\n    \"2073-01-01 00:00:00\",\n    \"2073-01-02 00:00:00\",\n    \"2073-03-24 00:00:00\",\n    \"2073-03-27 00:00:00\",\n    \"2073-05-01 00:00:00\",\n    \"2073-05-04 00:00:00\",\n    \"2073-05-15 00:00:00\",\n    \"2073-08-01 00:00:00\",\n    \"2073-12-25 00:00:00\",\n    \"2073-12-26 00:00:00\",\n    \"2074-01-01 00:00:00\",\n    \"2074-01-02 00:00:00\",\n    \"2074-04-13 00:00:00\",\n    \"2074-04-16 00:00:00\",\n    \"2074-05-01 00:00:00\",\n    \"2074-05-24 00:00:00\",\n    \"2074-06-04 00:00:00\",\n    \"2074-08-01 00:00:00\",\n    \"2074-12-25 00:00:00\",\n    \"2074-12-26 00:00:00\",\n    \"2075-01-01 00:00:00\",\n    \"2075-01-02 00:00:00\",\n    \"2075-04-05 00:00:00\",\n    \"2075-04-08 00:00:00\",\n    \"2075-05-01 00:00:00\",\n    \"2075-05-16 00:00:00\",\n    \"2075-05-27 00:00:00\",\n    \"2075-08-01 00:00:00\",\n    \"2075-12-25 00:00:00\",\n    \"2075-12-26 00:00:00\",\n    \"2076-01-01 00:00:00\",\n    \"2076-01-02 00:00:00\",\n    \"2076-04-17 00:00:00\",\n    \"2076-04-20 00:00:00\",\n    \"2076-05-01 00:00:00\",\n    \"2076-05-28 00:00:00\",\n    \"2076-06-08 00:00:00\",\n    \"2076-08-01 00:00:00\",\n    \"2076-12-25 00:00:00\",\n    \"2076-12-26 00:00:00\",\n    \"2077-01-01 00:00:00\",\n    \"2077-01-02 00:00:00\",\n    \"2077-04-09 00:00:00\",\n    \"2077-04-12 00:00:00\",\n    \"2077-05-01 00:00:00\",\n    \"2077-05-20 00:00:00\",\n    \"2077-05-31 00:00:00\",\n    \"2077-08-01 00:00:00\",\n    \"2077-12-25 00:00:00\",\n    \"2077-12-26 00:00:00\",\n    \"2078-01-01 00:00:00\",\n    \"2078-01-02 00:00:00\",\n    \"2078-04-01 00:00:00\",\n    \"2078-04-04 00:00:00\",\n    \"2078-05-01 00:00:00\",\n    \"2078-05-12 00:00:00\",\n    \"2078-05-23 00:00:00\",\n    \"2078-08-01 00:00:00\",\n    \"2078-12-25 00:00:00\",\n    \"2078-12-26 00:00:00\",\n    \"2079-01-01 00:00:00\",\n    \"2079-01-02 00:00:00\",\n    \"2079-04-21 00:00:00\",\n    \"2079-04-24 00:00:00\",\n    \"2079-05-01 00:00:00\",\n    \"2079-06-01 00:00:00\",\n    \"2079-06-12 00:00:00\",\n    \"2079-08-01 00:00:00\",\n    \"2079-12-25 00:00:00\",\n    \"2079-12-26 00:00:00\",\n    \"2080-01-01 00:00:00\",\n    \"2080-01-02 00:00:00\",\n    \"2080-04-05 00:00:00\",\n    \"2080-04-08 00:00:00\",\n    \"2080-05-01 00:00:00\",\n    \"2080-05-16 00:00:00\",\n    \"2080-05-27 00:00:00\",\n    \"2080-08-01 00:00:00\",\n    \"2080-12-25 00:00:00\",\n    \"2080-12-26 00:00:00\",\n    \"2081-01-01 00:00:00\",\n    \"2081-01-02 00:00:00\",\n    \"2081-03-28 00:00:00\",\n    \"2081-03-31 00:00:00\",\n    \"2081-05-01 00:00:00\",\n    \"2081-05-08 00:00:00\",\n    \"2081-05-19 00:00:00\",\n    \"2081-08-01 00:00:00\",\n    \"2081-12-25 00:00:00\",\n    \"2081-12-26 00:00:00\",\n    \"2082-01-01 00:00:00\",\n    \"2082-01-02 00:00:00\",\n    \"2082-04-17 00:00:00\",\n    \"2082-04-20 00:00:00\",\n    \"2082-05-01 00:00:00\",\n    \"2082-05-28 00:00:00\",\n    \"2082-06-08 00:00:00\",\n    \"2082-08-01 00:00:00\",\n    \"2082-12-25 00:00:00\",\n    \"2082-12-26 00:00:00\",\n    \"2083-01-01 00:00:00\",\n    \"2083-01-02 00:00:00\",\n    \"2083-04-02 00:00:00\",\n    \"2083-04-05 00:00:00\",\n    \"2083-05-01 00:00:00\",\n    \"2083-05-13 00:00:00\",\n    \"2083-05-24 00:00:00\",\n    \"2083-08-01 00:00:00\",\n    \"2083-12-25 00:00:00\",\n    \"2083-12-26 00:00:00\",\n    \"2084-01-01 00:00:00\",\n    \"2084-01-02 00:00:00\",\n    \"2084-03-24 00:00:00\",\n    \"2084-03-27 00:00:00\",\n    \"2084-05-01 00:00:00\",\n    \"2084-05-04 00:00:00\",\n    \"2084-05-15 00:00:00\",\n    \"2084-08-01 00:00:00\",\n    \"2084-12-25 00:00:00\",\n    \"2084-12-26 00:00:00\",\n    \"2085-01-01 00:00:00\",\n    \"2085-01-02 00:00:00\",\n    \"2085-04-13 00:00:00\",\n    \"2085-04-16 00:00:00\",\n    \"2085-05-01 00:00:00\",\n    \"2085-05-24 00:00:00\",\n    \"2085-06-04 00:00:00\",\n    \"2085-08-01 00:00:00\",\n    \"2085-12-25 00:00:00\",\n    \"2085-12-26 00:00:00\",\n    \"2086-01-01 00:00:00\",\n    \"2086-01-02 00:00:00\",\n    \"2086-03-29 00:00:00\",\n    \"2086-04-01 00:00:00\",\n    \"2086-05-01 00:00:00\",\n    \"2086-05-09 00:00:00\",\n    \"2086-05-20 00:00:00\",\n    \"2086-08-01 00:00:00\",\n    \"2086-12-25 00:00:00\",\n    \"2086-12-26 00:00:00\",\n    \"2087-01-01 00:00:00\",\n    \"2087-01-02 00:00:00\",\n    \"2087-04-18 00:00:00\",\n    \"2087-04-21 00:00:00\",\n    \"2087-05-01 00:00:00\",\n    \"2087-05-29 00:00:00\",\n    \"2087-06-09 00:00:00\",\n    \"2087-08-01 00:00:00\",\n    \"2087-12-25 00:00:00\",\n    \"2087-12-26 00:00:00\",\n    \"2088-01-01 00:00:00\",\n    \"2088-01-02 00:00:00\",\n    \"2088-04-09 00:00:00\",\n    \"2088-04-12 00:00:00\",\n    \"2088-05-01 00:00:00\",\n    \"2088-05-20 00:00:00\",\n    \"2088-05-31 00:00:00\",\n    \"2088-08-01 00:00:00\",\n    \"2088-12-25 00:00:00\",\n    \"2088-12-26 00:00:00\",\n    \"2089-01-01 00:00:00\",\n    \"2089-01-02 00:00:00\",\n    \"2089-04-01 00:00:00\",\n    \"2089-04-04 00:00:00\",\n    \"2089-05-01 00:00:00\",\n    \"2089-05-12 00:00:00\",\n    \"2089-05-23 00:00:00\",\n    \"2089-08-01 00:00:00\",\n    \"2089-12-25 00:00:00\",\n    \"2089-12-26 00:00:00\",\n    \"2090-01-01 00:00:00\",\n    \"2090-01-02 00:00:00\",\n    \"2090-04-14 00:00:00\",\n    \"2090-04-17 00:00:00\",\n    \"2090-05-01 00:00:00\",\n    \"2090-05-25 00:00:00\",\n    \"2090-06-05 00:00:00\",\n    \"2090-08-01 00:00:00\",\n    \"2090-12-25 00:00:00\",\n    \"2090-12-26 00:00:00\",\n    \"2091-01-01 00:00:00\",\n    \"2091-01-02 00:00:00\",\n    \"2091-04-06 00:00:00\",\n    \"2091-04-09 00:00:00\",\n    \"2091-05-01 00:00:00\",\n    \"2091-05-17 00:00:00\",\n    \"2091-05-28 00:00:00\",\n    \"2091-08-01 00:00:00\",\n    \"2091-12-25 00:00:00\",\n    \"2091-12-26 00:00:00\",\n    \"2092-01-01 00:00:00\",\n    \"2092-01-02 00:00:00\",\n    \"2092-03-28 00:00:00\",\n    \"2092-03-31 00:00:00\",\n    \"2092-05-01 00:00:00\",\n    \"2092-05-08 00:00:00\",\n    \"2092-05-19 00:00:00\",\n    \"2092-08-01 00:00:00\",\n    \"2092-12-25 00:00:00\",\n    \"2092-12-26 00:00:00\",\n    \"2093-01-01 00:00:00\",\n    \"2093-01-02 00:00:00\",\n    \"2093-04-10 00:00:00\",\n    \"2093-04-13 00:00:00\",\n    \"2093-05-01 00:00:00\",\n    \"2093-05-21 00:00:00\",\n    \"2093-06-01 00:00:00\",\n    \"2093-08-01 00:00:00\",\n    \"2093-12-25 00:00:00\",\n    \"2093-12-26 00:00:00\",\n    \"2094-01-01 00:00:00\",\n    \"2094-01-02 00:00:00\",\n    \"2094-04-02 00:00:00\",\n    \"2094-04-05 00:00:00\",\n    \"2094-05-01 00:00:00\",\n    \"2094-05-13 00:00:00\",\n    \"2094-05-24 00:00:00\",\n    \"2094-08-01 00:00:00\",\n    \"2094-12-25 00:00:00\",\n    \"2094-12-26 00:00:00\",\n    \"2095-01-01 00:00:00\",\n    \"2095-01-02 00:00:00\",\n    \"2095-04-22 00:00:00\",\n    \"2095-04-25 00:00:00\",\n    \"2095-05-01 00:00:00\",\n    \"2095-06-02 00:00:00\",\n    \"2095-06-13 00:00:00\",\n    \"2095-08-01 00:00:00\",\n    \"2095-12-25 00:00:00\",\n    \"2095-12-26 00:00:00\",\n    \"2096-01-01 00:00:00\",\n    \"2096-01-02 00:00:00\",\n    \"2096-04-13 00:00:00\",\n    \"2096-04-16 00:00:00\",\n    \"2096-05-01 00:00:00\",\n    \"2096-05-24 00:00:00\",\n    \"2096-06-04 00:00:00\",\n    \"2096-08-01 00:00:00\",\n    \"2096-12-25 00:00:00\",\n    \"2096-12-26 00:00:00\",\n    \"2097-01-01 00:00:00\",\n    \"2097-01-02 00:00:00\",\n    \"2097-03-29 00:00:00\",\n    \"2097-04-01 00:00:00\",\n    \"2097-05-01 00:00:00\",\n    \"2097-05-09 00:00:00\",\n    \"2097-05-20 00:00:00\",\n    \"2097-08-01 00:00:00\",\n    \"2097-12-25 00:00:00\",\n    \"2097-12-26 00:00:00\",\n    \"2098-01-01 00:00:00\",\n    \"2098-01-02 00:00:00\",\n    \"2098-04-18 00:00:00\",\n    \"2098-04-21 00:00:00\",\n    \"2098-05-01 00:00:00\",\n    \"2098-05-29 00:00:00\",\n    \"2098-06-09 00:00:00\",\n    \"2098-08-01 00:00:00\",\n    \"2098-12-25 00:00:00\",\n    \"2098-12-26 00:00:00\",\n    \"2099-01-01 00:00:00\",\n    \"2099-01-02 00:00:00\",\n    \"2099-04-10 00:00:00\",\n    \"2099-04-13 00:00:00\",\n    \"2099-05-01 00:00:00\",\n    \"2099-05-21 00:00:00\",\n    \"2099-06-01 00:00:00\",\n    \"2099-08-01 00:00:00\",\n    \"2099-12-25 00:00:00\",\n    \"2099-12-26 00:00:00\",\n    \"2100-01-01 00:00:00\",\n    \"2100-01-02 00:00:00\",\n    \"2100-03-26 00:00:00\",\n    \"2100-03-29 00:00:00\",\n    \"2100-05-01 00:00:00\",\n    \"2100-05-06 00:00:00\",\n    \"2100-05-17 00:00:00\",\n    \"2100-08-01 00:00:00\",\n    \"2100-12-25 00:00:00\",\n    \"2100-12-26 00:00:00\",\n    \"2101-01-01 00:00:00\",\n    \"2101-01-02 00:00:00\",\n    \"2101-04-15 00:00:00\",\n    \"2101-04-18 00:00:00\",\n    \"2101-05-01 00:00:00\",\n    \"2101-05-26 00:00:00\",\n    \"2101-06-06 00:00:00\",\n    \"2101-08-01 00:00:00\",\n    \"2101-12-25 00:00:00\",\n    \"2101-12-26 00:00:00\",\n    \"2102-01-01 00:00:00\",\n    \"2102-01-02 00:00:00\",\n    \"2102-04-07 00:00:00\",\n    \"2102-04-10 00:00:00\",\n    \"2102-05-01 00:00:00\",\n    \"2102-05-18 00:00:00\",\n    \"2102-05-29 00:00:00\",\n    \"2102-08-01 00:00:00\",\n    \"2102-12-25 00:00:00\",\n    \"2102-12-26 00:00:00\",\n    \"2103-01-01 00:00:00\",\n    \"2103-01-02 00:00:00\",\n    \"2103-03-23 00:00:00\",\n    \"2103-03-26 00:00:00\",\n    \"2103-05-01 00:00:00\",\n    \"2103-05-03 00:00:00\",\n    \"2103-05-14 00:00:00\",\n    \"2103-08-01 00:00:00\",\n    \"2103-12-25 00:00:00\",\n    \"2103-12-26 00:00:00\",\n    \"2104-01-01 00:00:00\",\n    \"2104-01-02 00:00:00\",\n    \"2104-04-11 00:00:00\",\n    \"2104-04-14 00:00:00\",\n    \"2104-05-01 00:00:00\",\n    \"2104-05-22 00:00:00\",\n    \"2104-06-02 00:00:00\",\n    \"2104-08-01 00:00:00\",\n    \"2104-12-25 00:00:00\",\n    \"2104-12-26 00:00:00\",\n    \"2105-01-01 00:00:00\",\n    \"2105-01-02 00:00:00\",\n    \"2105-04-03 00:00:00\",\n    \"2105-04-06 00:00:00\",\n    \"2105-05-01 00:00:00\",\n    \"2105-05-14 00:00:00\",\n    \"2105-05-25 00:00:00\",\n    \"2105-08-01 00:00:00\",\n    \"2105-12-25 00:00:00\",\n    \"2105-12-26 00:00:00\",\n    \"2106-01-01 00:00:00\",\n    \"2106-01-02 00:00:00\",\n    \"2106-04-16 00:00:00\",\n    \"2106-04-19 00:00:00\",\n    \"2106-05-01 00:00:00\",\n    \"2106-05-27 00:00:00\",\n    \"2106-06-07 00:00:00\",\n    \"2106-08-01 00:00:00\",\n    \"2106-12-25 00:00:00\",\n    \"2106-12-26 00:00:00\",\n    \"2107-01-01 00:00:00\",\n    \"2107-01-02 00:00:00\",\n    \"2107-04-08 00:00:00\",\n    \"2107-04-11 00:00:00\",\n    \"2107-05-01 00:00:00\",\n    \"2107-05-19 00:00:00\",\n    \"2107-05-30 00:00:00\",\n    \"2107-08-01 00:00:00\",\n    \"2107-12-25 00:00:00\",\n    \"2107-12-26 00:00:00\",\n    \"2108-01-01 00:00:00\",\n    \"2108-01-02 00:00:00\",\n    \"2108-03-30 00:00:00\",\n    \"2108-04-02 00:00:00\",\n    \"2108-05-01 00:00:00\",\n    \"2108-05-10 00:00:00\",\n    \"2108-05-21 00:00:00\",\n    \"2108-08-01 00:00:00\",\n    \"2108-12-25 00:00:00\",\n    \"2108-12-26 00:00:00\",\n    \"2109-01-01 00:00:00\",\n    \"2109-01-02 00:00:00\",\n    \"2109-04-19 00:00:00\",\n    \"2109-04-22 00:00:00\",\n    \"2109-05-01 00:00:00\",\n    \"2109-05-30 00:00:00\",\n    \"2109-06-10 00:00:00\",\n    \"2109-08-01 00:00:00\",\n    \"2109-12-25 00:00:00\",\n    \"2109-12-26 00:00:00\",\n    \"2110-01-01 00:00:00\",\n    \"2110-01-02 00:00:00\",\n    \"2110-04-04 00:00:00\",\n    \"2110-04-07 00:00:00\",\n    \"2110-05-01 00:00:00\",\n    \"2110-05-15 00:00:00\",\n    \"2110-05-26 00:00:00\",\n    \"2110-08-01 00:00:00\",\n    \"2110-12-25 00:00:00\",\n    \"2110-12-26 00:00:00\",\n    \"2111-01-01 00:00:00\",\n    \"2111-01-02 00:00:00\",\n    \"2111-03-27 00:00:00\",\n    \"2111-03-30 00:00:00\",\n    \"2111-05-01 00:00:00\",\n    \"2111-05-07 00:00:00\",\n    \"2111-05-18 00:00:00\",\n    \"2111-08-01 00:00:00\",\n    \"2111-12-25 00:00:00\",\n    \"2111-12-26 00:00:00\",\n    \"2112-01-01 00:00:00\",\n    \"2112-01-02 00:00:00\",\n    \"2112-04-15 00:00:00\",\n    \"2112-04-18 00:00:00\",\n    \"2112-05-01 00:00:00\",\n    \"2112-05-26 00:00:00\",\n    \"2112-06-06 00:00:00\",\n    \"2112-08-01 00:00:00\",\n    \"2112-12-25 00:00:00\",\n    \"2112-12-26 00:00:00\",\n    \"2113-01-01 00:00:00\",\n    \"2113-01-02 00:00:00\",\n    \"2113-03-31 00:00:00\",\n    \"2113-04-03 00:00:00\",\n    \"2113-05-01 00:00:00\",\n    \"2113-05-11 00:00:00\",\n    \"2113-05-22 00:00:00\",\n    \"2113-08-01 00:00:00\",\n    \"2113-12-25 00:00:00\",\n    \"2113-12-26 00:00:00\",\n    \"2114-01-01 00:00:00\",\n    \"2114-01-02 00:00:00\",\n    \"2114-04-20 00:00:00\",\n    \"2114-04-23 00:00:00\",\n    \"2114-05-01 00:00:00\",\n    \"2114-05-31 00:00:00\",\n    \"2114-06-11 00:00:00\",\n    \"2114-08-01 00:00:00\",\n    \"2114-12-25 00:00:00\",\n    \"2114-12-26 00:00:00\",\n    \"2115-01-01 00:00:00\",\n    \"2115-01-02 00:00:00\",\n    \"2115-04-12 00:00:00\",\n    \"2115-04-15 00:00:00\",\n    \"2115-05-01 00:00:00\",\n    \"2115-05-23 00:00:00\",\n    \"2115-06-03 00:00:00\",\n    \"2115-08-01 00:00:00\",\n    \"2115-12-25 00:00:00\",\n    \"2115-12-26 00:00:00\",\n    \"2116-01-01 00:00:00\",\n    \"2116-01-02 00:00:00\",\n    \"2116-03-27 00:00:00\",\n    \"2116-03-30 00:00:00\",\n    \"2116-05-01 00:00:00\",\n    \"2116-05-07 00:00:00\",\n    \"2116-05-18 00:00:00\",\n    \"2116-08-01 00:00:00\",\n    \"2116-12-25 00:00:00\",\n    \"2116-12-26 00:00:00\",\n    \"2117-01-01 00:00:00\",\n    \"2117-01-02 00:00:00\",\n    \"2117-04-16 00:00:00\",\n    \"2117-04-19 00:00:00\",\n    \"2117-05-01 00:00:00\",\n    \"2117-05-27 00:00:00\",\n    \"2117-06-07 00:00:00\",\n    \"2117-08-01 00:00:00\",\n    \"2117-12-25 00:00:00\",\n    \"2117-12-26 00:00:00\",\n    \"2118-01-01 00:00:00\",\n    \"2118-01-02 00:00:00\",\n    \"2118-04-08 00:00:00\",\n    \"2118-04-11 00:00:00\",\n    \"2118-05-01 00:00:00\",\n    \"2118-05-19 00:00:00\",\n    \"2118-05-30 00:00:00\",\n    \"2118-08-01 00:00:00\",\n    \"2118-12-25 00:00:00\",\n    \"2118-12-26 00:00:00\",\n    \"2119-01-01 00:00:00\",\n    \"2119-01-02 00:00:00\",\n    \"2119-03-24 00:00:00\",\n    \"2119-03-27 00:00:00\",\n    \"2119-05-01 00:00:00\",\n    \"2119-05-04 00:00:00\",\n    \"2119-05-15 00:00:00\",\n    \"2119-08-01 00:00:00\",\n    \"2119-12-25 00:00:00\",\n    \"2119-12-26 00:00:00\",\n    \"2120-01-01 00:00:00\",\n    \"2120-01-02 00:00:00\",\n    \"2120-04-12 00:00:00\",\n    \"2120-04-15 00:00:00\",\n    \"2120-05-01 00:00:00\",\n    \"2120-05-23 00:00:00\",\n    \"2120-06-03 00:00:00\",\n    \"2120-08-01 00:00:00\",\n    \"2120-12-25 00:00:00\",\n    \"2120-12-26 00:00:00\",\n    \"2121-01-01 00:00:00\",\n    \"2121-01-02 00:00:00\",\n    \"2121-04-04 00:00:00\",\n    \"2121-04-07 00:00:00\",\n    \"2121-05-01 00:00:00\",\n    \"2121-05-15 00:00:00\",\n    \"2121-05-26 00:00:00\",\n    \"2121-08-01 00:00:00\",\n    \"2121-12-25 00:00:00\",\n    \"2121-12-26 00:00:00\",\n    \"2122-01-01 00:00:00\",\n    \"2122-01-02 00:00:00\",\n    \"2122-03-27 00:00:00\",\n    \"2122-03-30 00:00:00\",\n    \"2122-05-01 00:00:00\",\n    \"2122-05-07 00:00:00\",\n    \"2122-05-18 00:00:00\",\n    \"2122-08-01 00:00:00\",\n    \"2122-12-25 00:00:00\",\n    \"2122-12-26 00:00:00\",\n    \"2123-01-01 00:00:00\",\n    \"2123-01-02 00:00:00\",\n    \"2123-04-09 00:00:00\",\n    \"2123-04-12 00:00:00\",\n    \"2123-05-01 00:00:00\",\n    \"2123-05-20 00:00:00\",\n    \"2123-05-31 00:00:00\",\n    \"2123-08-01 00:00:00\",\n    \"2123-12-25 00:00:00\",\n    \"2123-12-26 00:00:00\",\n    \"2124-01-01 00:00:00\",\n    \"2124-01-02 00:00:00\",\n    \"2124-03-31 00:00:00\",\n    \"2124-04-03 00:00:00\",\n    \"2124-05-01 00:00:00\",\n    \"2124-05-11 00:00:00\",\n    \"2124-05-22 00:00:00\",\n    \"2124-08-01 00:00:00\",\n    \"2124-12-25 00:00:00\",\n    \"2124-12-26 00:00:00\",\n    \"2125-01-01 00:00:00\",\n    \"2125-01-02 00:00:00\",\n    \"2125-04-20 00:00:00\",\n    \"2125-04-23 00:00:00\",\n    \"2125-05-01 00:00:00\",\n    \"2125-05-31 00:00:00\",\n    \"2125-06-11 00:00:00\",\n    \"2125-08-01 00:00:00\",\n    \"2125-12-25 00:00:00\",\n    \"2125-12-26 00:00:00\",\n    \"2126-01-01 00:00:00\",\n    \"2126-01-02 00:00:00\",\n    \"2126-04-12 00:00:00\",\n    \"2126-04-15 00:00:00\",\n    \"2126-05-01 00:00:00\",\n    \"2126-05-23 00:00:00\",\n    \"2126-06-03 00:00:00\",\n    \"2126-08-01 00:00:00\",\n    \"2126-12-25 00:00:00\",\n    \"2126-12-26 00:00:00\",\n    \"2127-01-01 00:00:00\",\n    \"2127-01-02 00:00:00\",\n    \"2127-03-28 00:00:00\",\n    \"2127-03-31 00:00:00\",\n    \"2127-05-01 00:00:00\",\n    \"2127-05-08 00:00:00\",\n    \"2127-05-19 00:00:00\",\n    \"2127-08-01 00:00:00\",\n    \"2127-12-25 00:00:00\",\n    \"2127-12-26 00:00:00\",\n    \"2128-01-01 00:00:00\",\n    \"2128-01-02 00:00:00\",\n    \"2128-04-16 00:00:00\",\n    \"2128-04-19 00:00:00\",\n    \"2128-05-01 00:00:00\",\n    \"2128-05-27 00:00:00\",\n    \"2128-06-07 00:00:00\",\n    \"2128-08-01 00:00:00\",\n    \"2128-12-25 00:00:00\",\n    \"2128-12-26 00:00:00\",\n    \"2129-01-01 00:00:00\",\n    \"2129-01-02 00:00:00\",\n    \"2129-04-08 00:00:00\",\n    \"2129-04-11 00:00:00\",\n    \"2129-05-01 00:00:00\",\n    \"2129-05-19 00:00:00\",\n    \"2129-05-30 00:00:00\",\n    \"2129-08-01 00:00:00\",\n    \"2129-12-25 00:00:00\",\n    \"2129-12-26 00:00:00\",\n    \"2130-01-01 00:00:00\",\n    \"2130-01-02 00:00:00\",\n    \"2130-03-24 00:00:00\",\n    \"2130-03-27 00:00:00\",\n    \"2130-05-01 00:00:00\",\n    \"2130-05-04 00:00:00\",\n    \"2130-05-15 00:00:00\",\n    \"2130-08-01 00:00:00\",\n    \"2130-12-25 00:00:00\",\n    \"2130-12-26 00:00:00\",\n    \"2131-01-01 00:00:00\",\n    \"2131-01-02 00:00:00\",\n    \"2131-04-13 00:00:00\",\n    \"2131-04-16 00:00:00\",\n    \"2131-05-01 00:00:00\",\n    \"2131-05-24 00:00:00\",\n    \"2131-06-04 00:00:00\",\n    \"2131-08-01 00:00:00\",\n    \"2131-12-25 00:00:00\",\n    \"2131-12-26 00:00:00\",\n    \"2132-01-01 00:00:00\",\n    \"2132-01-02 00:00:00\",\n    \"2132-04-04 00:00:00\",\n    \"2132-04-07 00:00:00\",\n    \"2132-05-01 00:00:00\",\n    \"2132-05-15 00:00:00\",\n    \"2132-05-26 00:00:00\",\n    \"2132-08-01 00:00:00\",\n    \"2132-12-25 00:00:00\",\n    \"2132-12-26 00:00:00\",\n    \"2133-01-01 00:00:00\",\n    \"2133-01-02 00:00:00\",\n    \"2133-04-17 00:00:00\",\n    \"2133-04-20 00:00:00\",\n    \"2133-05-01 00:00:00\",\n    \"2133-05-28 00:00:00\",\n    \"2133-06-08 00:00:00\",\n    \"2133-08-01 00:00:00\",\n    \"2133-12-25 00:00:00\",\n    \"2133-12-26 00:00:00\",\n    \"2134-01-01 00:00:00\",\n    \"2134-01-02 00:00:00\",\n    \"2134-04-09 00:00:00\",\n    \"2134-04-12 00:00:00\",\n    \"2134-05-01 00:00:00\",\n    \"2134-05-20 00:00:00\",\n    \"2134-05-31 00:00:00\",\n    \"2134-08-01 00:00:00\",\n    \"2134-12-25 00:00:00\",\n    \"2134-12-26 00:00:00\",\n    \"2135-01-01 00:00:00\",\n    \"2135-01-02 00:00:00\",\n    \"2135-04-01 00:00:00\",\n    \"2135-04-04 00:00:00\",\n    \"2135-05-01 00:00:00\",\n    \"2135-05-12 00:00:00\",\n    \"2135-05-23 00:00:00\",\n    \"2135-08-01 00:00:00\",\n    \"2135-12-25 00:00:00\",\n    \"2135-12-26 00:00:00\",\n    \"2136-01-01 00:00:00\",\n    \"2136-01-02 00:00:00\",\n    \"2136-04-20 00:00:00\",\n    \"2136-04-23 00:00:00\",\n    \"2136-05-01 00:00:00\",\n    \"2136-05-31 00:00:00\",\n    \"2136-06-11 00:00:00\",\n    \"2136-08-01 00:00:00\",\n    \"2136-12-25 00:00:00\",\n    \"2136-12-26 00:00:00\",\n    \"2137-01-01 00:00:00\",\n    \"2137-01-02 00:00:00\",\n    \"2137-04-05 00:00:00\",\n    \"2137-04-08 00:00:00\",\n    \"2137-05-01 00:00:00\",\n    \"2137-05-16 00:00:00\",\n    \"2137-05-27 00:00:00\",\n    \"2137-08-01 00:00:00\",\n    \"2137-12-25 00:00:00\",\n    \"2137-12-26 00:00:00\",\n    \"2138-01-01 00:00:00\",\n    \"2138-01-02 00:00:00\",\n    \"2138-03-28 00:00:00\",\n    \"2138-03-31 00:00:00\",\n    \"2138-05-01 00:00:00\",\n    \"2138-05-08 00:00:00\",\n    \"2138-05-19 00:00:00\",\n    \"2138-08-01 00:00:00\",\n    \"2138-12-25 00:00:00\",\n    \"2138-12-26 00:00:00\",\n    \"2139-01-01 00:00:00\",\n    \"2139-01-02 00:00:00\",\n    \"2139-04-17 00:00:00\",\n    \"2139-04-20 00:00:00\",\n    \"2139-05-01 00:00:00\",\n    \"2139-05-28 00:00:00\",\n    \"2139-06-08 00:00:00\",\n    \"2139-08-01 00:00:00\",\n    \"2139-12-25 00:00:00\",\n    \"2139-12-26 00:00:00\",\n    \"2140-01-01 00:00:00\",\n    \"2140-01-02 00:00:00\",\n    \"2140-04-01 00:00:00\",\n    \"2140-04-04 00:00:00\",\n    \"2140-05-01 00:00:00\",\n    \"2140-05-12 00:00:00\",\n    \"2140-05-23 00:00:00\",\n    \"2140-08-01 00:00:00\",\n    \"2140-12-25 00:00:00\",\n    \"2140-12-26 00:00:00\",\n    \"2141-01-01 00:00:00\",\n    \"2141-01-02 00:00:00\",\n    \"2141-03-24 00:00:00\",\n    \"2141-03-27 00:00:00\",\n    \"2141-05-01 00:00:00\",\n    \"2141-05-04 00:00:00\",\n    \"2141-05-15 00:00:00\",\n    \"2141-08-01 00:00:00\",\n    \"2141-12-25 00:00:00\",\n    \"2141-12-26 00:00:00\",\n    \"2142-01-01 00:00:00\",\n    \"2142-01-02 00:00:00\",\n    \"2142-04-13 00:00:00\",\n    \"2142-04-16 00:00:00\",\n    \"2142-05-01 00:00:00\",\n    \"2142-05-24 00:00:00\",\n    \"2142-06-04 00:00:00\",\n    \"2142-08-01 00:00:00\",\n    \"2142-12-25 00:00:00\",\n    \"2142-12-26 00:00:00\",\n    \"2143-01-01 00:00:00\",\n    \"2143-01-02 00:00:00\",\n    \"2143-03-29 00:00:00\",\n    \"2143-04-01 00:00:00\",\n    \"2143-05-01 00:00:00\",\n    \"2143-05-09 00:00:00\",\n    \"2143-05-20 00:00:00\",\n    \"2143-08-01 00:00:00\",\n    \"2143-12-25 00:00:00\",\n    \"2143-12-26 00:00:00\",\n    \"2144-01-01 00:00:00\",\n    \"2144-01-02 00:00:00\",\n    \"2144-04-17 00:00:00\",\n    \"2144-04-20 00:00:00\",\n    \"2144-05-01 00:00:00\",\n    \"2144-05-28 00:00:00\",\n    \"2144-06-08 00:00:00\",\n    \"2144-08-01 00:00:00\",\n    \"2144-12-25 00:00:00\",\n    \"2144-12-26 00:00:00\",\n    \"2145-01-01 00:00:00\",\n    \"2145-01-02 00:00:00\",\n    \"2145-04-09 00:00:00\",\n    \"2145-04-12 00:00:00\",\n    \"2145-05-01 00:00:00\",\n    \"2145-05-20 00:00:00\",\n    \"2145-05-31 00:00:00\",\n    \"2145-08-01 00:00:00\",\n    \"2145-12-25 00:00:00\",\n    \"2145-12-26 00:00:00\",\n    \"2146-01-01 00:00:00\",\n    \"2146-01-02 00:00:00\",\n    \"2146-04-01 00:00:00\",\n    \"2146-04-04 00:00:00\",\n    \"2146-05-01 00:00:00\",\n    \"2146-05-12 00:00:00\",\n    \"2146-05-23 00:00:00\",\n    \"2146-08-01 00:00:00\",\n    \"2146-12-25 00:00:00\",\n    \"2146-12-26 00:00:00\",\n    \"2147-01-01 00:00:00\",\n    \"2147-01-02 00:00:00\",\n    \"2147-04-14 00:00:00\",\n    \"2147-04-17 00:00:00\",\n    \"2147-05-01 00:00:00\",\n    \"2147-05-25 00:00:00\",\n    \"2147-06-05 00:00:00\",\n    \"2147-08-01 00:00:00\",\n    \"2147-12-25 00:00:00\",\n    \"2147-12-26 00:00:00\",\n    \"2148-01-01 00:00:00\",\n    \"2148-01-02 00:00:00\",\n    \"2148-04-05 00:00:00\",\n    \"2148-04-08 00:00:00\",\n    \"2148-05-01 00:00:00\",\n    \"2148-05-16 00:00:00\",\n    \"2148-05-27 00:00:00\",\n    \"2148-08-01 00:00:00\",\n    \"2148-12-25 00:00:00\",\n    \"2148-12-26 00:00:00\",\n    \"2149-01-01 00:00:00\",\n    \"2149-01-02 00:00:00\",\n    \"2149-03-28 00:00:00\",\n    \"2149-03-31 00:00:00\",\n    \"2149-05-01 00:00:00\",\n    \"2149-05-08 00:00:00\",\n    \"2149-05-19 00:00:00\",\n    \"2149-08-01 00:00:00\",\n    \"2149-12-25 00:00:00\",\n    \"2149-12-26 00:00:00\",\n    \"2150-01-01 00:00:00\",\n    \"2150-01-02 00:00:00\",\n    \"2150-04-10 00:00:00\",\n    \"2150-04-13 00:00:00\",\n    \"2150-05-01 00:00:00\",\n    \"2150-05-21 00:00:00\",\n    \"2150-06-01 00:00:00\",\n    \"2150-08-01 00:00:00\",\n    \"2150-12-25 00:00:00\",\n    \"2150-12-26 00:00:00\",\n    \"2151-01-01 00:00:00\",\n    \"2151-01-02 00:00:00\",\n    \"2151-04-02 00:00:00\",\n    \"2151-04-05 00:00:00\",\n    \"2151-05-01 00:00:00\",\n    \"2151-05-13 00:00:00\",\n    \"2151-05-24 00:00:00\",\n    \"2151-08-01 00:00:00\",\n    \"2151-12-25 00:00:00\",\n    \"2151-12-26 00:00:00\",\n    \"2152-01-01 00:00:00\",\n    \"2152-01-02 00:00:00\",\n    \"2152-04-21 00:00:00\",\n    \"2152-04-24 00:00:00\",\n    \"2152-05-01 00:00:00\",\n    \"2152-06-01 00:00:00\",\n    \"2152-06-12 00:00:00\",\n    \"2152-08-01 00:00:00\",\n    \"2152-12-25 00:00:00\",\n    \"2152-12-26 00:00:00\",\n    \"2153-01-01 00:00:00\",\n    \"2153-01-02 00:00:00\",\n    \"2153-04-13 00:00:00\",\n    \"2153-04-16 00:00:00\",\n    \"2153-05-01 00:00:00\",\n    \"2153-05-24 00:00:00\",\n    \"2153-06-04 00:00:00\",\n    \"2153-08-01 00:00:00\",\n    \"2153-12-25 00:00:00\",\n    \"2153-12-26 00:00:00\",\n    \"2154-01-01 00:00:00\",\n    \"2154-01-02 00:00:00\",\n    \"2154-03-29 00:00:00\",\n    \"2154-04-01 00:00:00\",\n    \"2154-05-01 00:00:00\",\n    \"2154-05-09 00:00:00\",\n    \"2154-05-20 00:00:00\",\n    \"2154-08-01 00:00:00\",\n    \"2154-12-25 00:00:00\",\n    \"2154-12-26 00:00:00\",\n    \"2155-01-01 00:00:00\",\n    \"2155-01-02 00:00:00\",\n    \"2155-04-18 00:00:00\",\n    \"2155-04-21 00:00:00\",\n    \"2155-05-01 00:00:00\",\n    \"2155-05-29 00:00:00\",\n    \"2155-06-09 00:00:00\",\n    \"2155-08-01 00:00:00\",\n    \"2155-12-25 00:00:00\",\n    \"2155-12-26 00:00:00\",\n    \"2156-01-01 00:00:00\",\n    \"2156-01-02 00:00:00\",\n    \"2156-04-09 00:00:00\",\n    \"2156-04-12 00:00:00\",\n    \"2156-05-01 00:00:00\",\n    \"2156-05-20 00:00:00\",\n    \"2156-05-31 00:00:00\",\n    \"2156-08-01 00:00:00\",\n    \"2156-12-25 00:00:00\",\n    \"2156-12-26 00:00:00\",\n    \"2157-01-01 00:00:00\",\n    \"2157-01-02 00:00:00\",\n    \"2157-03-25 00:00:00\",\n    \"2157-03-28 00:00:00\",\n    \"2157-05-01 00:00:00\",\n    \"2157-05-05 00:00:00\",\n    \"2157-05-16 00:00:00\",\n    \"2157-08-01 00:00:00\",\n    \"2157-12-25 00:00:00\",\n    \"2157-12-26 00:00:00\",\n    \"2158-01-01 00:00:00\",\n    \"2158-01-02 00:00:00\",\n    \"2158-04-14 00:00:00\",\n    \"2158-04-17 00:00:00\",\n    \"2158-05-01 00:00:00\",\n    \"2158-05-25 00:00:00\",\n    \"2158-06-05 00:00:00\",\n    \"2158-08-01 00:00:00\",\n    \"2158-12-25 00:00:00\",\n    \"2158-12-26 00:00:00\",\n    \"2159-01-01 00:00:00\",\n    \"2159-01-02 00:00:00\",\n    \"2159-04-06 00:00:00\",\n    \"2159-04-09 00:00:00\",\n    \"2159-05-01 00:00:00\",\n    \"2159-05-17 00:00:00\",\n    \"2159-05-28 00:00:00\",\n    \"2159-08-01 00:00:00\",\n    \"2159-12-25 00:00:00\",\n    \"2159-12-26 00:00:00\",\n    \"2160-01-01 00:00:00\",\n    \"2160-01-02 00:00:00\",\n    \"2160-03-21 00:00:00\",\n    \"2160-03-24 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-05-01 00:00:00\",\n    \"2160-05-12 00:00:00\",\n    \"2160-08-01 00:00:00\",\n    \"2160-12-25 00:00:00\",\n    \"2160-12-26 00:00:00\",\n    \"2161-01-01 00:00:00\",\n    \"2161-01-02 00:00:00\",\n    \"2161-04-10 00:00:00\",\n    \"2161-04-13 00:00:00\",\n    \"2161-05-01 00:00:00\",\n    \"2161-05-21 00:00:00\",\n    \"2161-06-01 00:00:00\",\n    \"2161-08-01 00:00:00\",\n    \"2161-12-25 00:00:00\",\n    \"2161-12-26 00:00:00\",\n    \"2162-01-01 00:00:00\",\n    \"2162-01-02 00:00:00\",\n    \"2162-04-02 00:00:00\",\n    \"2162-04-05 00:00:00\",\n    \"2162-05-01 00:00:00\",\n    \"2162-05-13 00:00:00\",\n    \"2162-05-24 00:00:00\",\n    \"2162-08-01 00:00:00\",\n    \"2162-12-25 00:00:00\",\n    \"2162-12-26 00:00:00\",\n    \"2163-01-01 00:00:00\",\n    \"2163-01-02 00:00:00\",\n    \"2163-04-22 00:00:00\",\n    \"2163-04-25 00:00:00\",\n    \"2163-05-01 00:00:00\",\n    \"2163-06-02 00:00:00\",\n    \"2163-06-13 00:00:00\",\n    \"2163-08-01 00:00:00\",\n    \"2163-12-25 00:00:00\",\n    \"2163-12-26 00:00:00\",\n    \"2164-01-01 00:00:00\",\n    \"2164-01-02 00:00:00\",\n    \"2164-04-06 00:00:00\",\n    \"2164-04-09 00:00:00\",\n    \"2164-05-01 00:00:00\",\n    \"2164-05-17 00:00:00\",\n    \"2164-05-28 00:00:00\",\n    \"2164-08-01 00:00:00\",\n    \"2164-12-25 00:00:00\",\n    \"2164-12-26 00:00:00\",\n    \"2165-01-01 00:00:00\",\n    \"2165-01-02 00:00:00\",\n    \"2165-03-29 00:00:00\",\n    \"2165-04-01 00:00:00\",\n    \"2165-05-01 00:00:00\",\n    \"2165-05-09 00:00:00\",\n    \"2165-05-20 00:00:00\",\n    \"2165-08-01 00:00:00\",\n    \"2165-12-25 00:00:00\",\n    \"2165-12-26 00:00:00\",\n    \"2166-01-01 00:00:00\",\n    \"2166-01-02 00:00:00\",\n    \"2166-04-18 00:00:00\",\n    \"2166-04-21 00:00:00\",\n    \"2166-05-01 00:00:00\",\n    \"2166-05-29 00:00:00\",\n    \"2166-06-09 00:00:00\",\n    \"2166-08-01 00:00:00\",\n    \"2166-12-25 00:00:00\",\n    \"2166-12-26 00:00:00\",\n    \"2167-01-01 00:00:00\",\n    \"2167-01-02 00:00:00\",\n    \"2167-04-03 00:00:00\",\n    \"2167-04-06 00:00:00\",\n    \"2167-05-01 00:00:00\",\n    \"2167-05-14 00:00:00\",\n    \"2167-05-25 00:00:00\",\n    \"2167-08-01 00:00:00\",\n    \"2167-12-25 00:00:00\",\n    \"2167-12-26 00:00:00\",\n    \"2168-01-01 00:00:00\",\n    \"2168-01-02 00:00:00\",\n    \"2168-03-25 00:00:00\",\n    \"2168-03-28 00:00:00\",\n    \"2168-05-01 00:00:00\",\n    \"2168-05-05 00:00:00\",\n    \"2168-05-16 00:00:00\",\n    \"2168-08-01 00:00:00\",\n    \"2168-12-25 00:00:00\",\n    \"2168-12-26 00:00:00\",\n    \"2169-01-01 00:00:00\",\n    \"2169-01-02 00:00:00\",\n    \"2169-04-14 00:00:00\",\n    \"2169-04-17 00:00:00\",\n    \"2169-05-01 00:00:00\",\n    \"2169-05-25 00:00:00\",\n    \"2169-06-05 00:00:00\",\n    \"2169-08-01 00:00:00\",\n    \"2169-12-25 00:00:00\",\n    \"2169-12-26 00:00:00\",\n    \"2170-01-01 00:00:00\",\n    \"2170-01-02 00:00:00\",\n    \"2170-03-30 00:00:00\",\n    \"2170-04-02 00:00:00\",\n    \"2170-05-01 00:00:00\",\n    \"2170-05-10 00:00:00\",\n    \"2170-05-21 00:00:00\",\n    \"2170-08-01 00:00:00\",\n    \"2170-12-25 00:00:00\",\n    \"2170-12-26 00:00:00\",\n    \"2171-01-01 00:00:00\",\n    \"2171-01-02 00:00:00\",\n    \"2171-04-19 00:00:00\",\n    \"2171-04-22 00:00:00\",\n    \"2171-05-01 00:00:00\",\n    \"2171-05-30 00:00:00\",\n    \"2171-06-10 00:00:00\",\n    \"2171-08-01 00:00:00\",\n    \"2171-12-25 00:00:00\",\n    \"2171-12-26 00:00:00\",\n    \"2172-01-01 00:00:00\",\n    \"2172-01-02 00:00:00\",\n    \"2172-04-10 00:00:00\",\n    \"2172-04-13 00:00:00\",\n    \"2172-05-01 00:00:00\",\n    \"2172-05-21 00:00:00\",\n    \"2172-06-01 00:00:00\",\n    \"2172-08-01 00:00:00\",\n    \"2172-12-25 00:00:00\",\n    \"2172-12-26 00:00:00\",\n    \"2173-01-01 00:00:00\",\n    \"2173-01-02 00:00:00\",\n    \"2173-04-02 00:00:00\",\n    \"2173-04-05 00:00:00\",\n    \"2173-05-01 00:00:00\",\n    \"2173-05-13 00:00:00\",\n    \"2173-05-24 00:00:00\",\n    \"2173-08-01 00:00:00\",\n    \"2173-12-25 00:00:00\",\n    \"2173-12-26 00:00:00\",\n    \"2174-01-01 00:00:00\",\n    \"2174-01-02 00:00:00\",\n    \"2174-04-15 00:00:00\",\n    \"2174-04-18 00:00:00\",\n    \"2174-05-01 00:00:00\",\n    \"2174-05-26 00:00:00\",\n    \"2174-06-06 00:00:00\",\n    \"2174-08-01 00:00:00\",\n    \"2174-12-25 00:00:00\",\n    \"2174-12-26 00:00:00\",\n    \"2175-01-01 00:00:00\",\n    \"2175-01-02 00:00:00\",\n    \"2175-04-07 00:00:00\",\n    \"2175-04-10 00:00:00\",\n    \"2175-05-01 00:00:00\",\n    \"2175-05-18 00:00:00\",\n    \"2175-05-29 00:00:00\",\n    \"2175-08-01 00:00:00\",\n    \"2175-12-25 00:00:00\",\n    \"2175-12-26 00:00:00\",\n    \"2176-01-01 00:00:00\",\n    \"2176-01-02 00:00:00\",\n    \"2176-03-29 00:00:00\",\n    \"2176-04-01 00:00:00\",\n    \"2176-05-01 00:00:00\",\n    \"2176-05-09 00:00:00\",\n    \"2176-05-20 00:00:00\",\n    \"2176-08-01 00:00:00\",\n    \"2176-12-25 00:00:00\",\n    \"2176-12-26 00:00:00\",\n    \"2177-01-01 00:00:00\",\n    \"2177-01-02 00:00:00\",\n    \"2177-04-18 00:00:00\",\n    \"2177-04-21 00:00:00\",\n    \"2177-05-01 00:00:00\",\n    \"2177-05-29 00:00:00\",\n    \"2177-06-09 00:00:00\",\n    \"2177-08-01 00:00:00\",\n    \"2177-12-25 00:00:00\",\n    \"2177-12-26 00:00:00\",\n    \"2178-01-01 00:00:00\",\n    \"2178-01-02 00:00:00\",\n    \"2178-04-03 00:00:00\",\n    \"2178-04-06 00:00:00\",\n    \"2178-05-01 00:00:00\",\n    \"2178-05-14 00:00:00\",\n    \"2178-05-25 00:00:00\",\n    \"2178-08-01 00:00:00\",\n    \"2178-12-25 00:00:00\",\n    \"2178-12-26 00:00:00\",\n    \"2179-01-01 00:00:00\",\n    \"2179-01-02 00:00:00\",\n    \"2179-03-26 00:00:00\",\n    \"2179-03-29 00:00:00\",\n    \"2179-05-01 00:00:00\",\n    \"2179-05-06 00:00:00\",\n    \"2179-05-17 00:00:00\",\n    \"2179-08-01 00:00:00\",\n    \"2179-12-25 00:00:00\",\n    \"2179-12-26 00:00:00\",\n    \"2180-01-01 00:00:00\",\n    \"2180-01-02 00:00:00\",\n    \"2180-04-14 00:00:00\",\n    \"2180-04-17 00:00:00\",\n    \"2180-05-01 00:00:00\",\n    \"2180-05-25 00:00:00\",\n    \"2180-06-05 00:00:00\",\n    \"2180-08-01 00:00:00\",\n    \"2180-12-25 00:00:00\",\n    \"2180-12-26 00:00:00\",\n    \"2181-01-01 00:00:00\",\n    \"2181-01-02 00:00:00\",\n    \"2181-03-30 00:00:00\",\n    \"2181-04-02 00:00:00\",\n    \"2181-05-01 00:00:00\",\n    \"2181-05-10 00:00:00\",\n    \"2181-05-21 00:00:00\",\n    \"2181-08-01 00:00:00\",\n    \"2181-12-25 00:00:00\",\n    \"2181-12-26 00:00:00\",\n    \"2182-01-01 00:00:00\",\n    \"2182-01-02 00:00:00\",\n    \"2182-04-19 00:00:00\",\n    \"2182-04-22 00:00:00\",\n    \"2182-05-01 00:00:00\",\n    \"2182-05-30 00:00:00\",\n    \"2182-06-10 00:00:00\",\n    \"2182-08-01 00:00:00\",\n    \"2182-12-25 00:00:00\",\n    \"2182-12-26 00:00:00\",\n    \"2183-01-01 00:00:00\",\n    \"2183-01-02 00:00:00\",\n    \"2183-04-11 00:00:00\",\n    \"2183-04-14 00:00:00\",\n    \"2183-05-01 00:00:00\",\n    \"2183-05-22 00:00:00\",\n    \"2183-06-02 00:00:00\",\n    \"2183-08-01 00:00:00\",\n    \"2183-12-25 00:00:00\",\n    \"2183-12-26 00:00:00\",\n    \"2184-01-01 00:00:00\",\n    \"2184-01-02 00:00:00\",\n    \"2184-03-26 00:00:00\",\n    \"2184-03-29 00:00:00\",\n    \"2184-05-01 00:00:00\",\n    \"2184-05-06 00:00:00\",\n    \"2184-05-17 00:00:00\",\n    \"2184-08-01 00:00:00\",\n    \"2184-12-25 00:00:00\",\n    \"2184-12-26 00:00:00\",\n    \"2185-01-01 00:00:00\",\n    \"2185-01-02 00:00:00\",\n    \"2185-04-15 00:00:00\",\n    \"2185-04-18 00:00:00\",\n    \"2185-05-01 00:00:00\",\n    \"2185-05-26 00:00:00\",\n    \"2185-06-06 00:00:00\",\n    \"2185-08-01 00:00:00\",\n    \"2185-12-25 00:00:00\",\n    \"2185-12-26 00:00:00\",\n    \"2186-01-01 00:00:00\",\n    \"2186-01-02 00:00:00\",\n    \"2186-04-07 00:00:00\",\n    \"2186-04-10 00:00:00\",\n    \"2186-05-01 00:00:00\",\n    \"2186-05-18 00:00:00\",\n    \"2186-05-29 00:00:00\",\n    \"2186-08-01 00:00:00\",\n    \"2186-12-25 00:00:00\",\n    \"2186-12-26 00:00:00\",\n    \"2187-01-01 00:00:00\",\n    \"2187-01-02 00:00:00\",\n    \"2187-03-23 00:00:00\",\n    \"2187-03-26 00:00:00\",\n    \"2187-05-01 00:00:00\",\n    \"2187-05-03 00:00:00\",\n    \"2187-05-14 00:00:00\",\n    \"2187-08-01 00:00:00\",\n    \"2187-12-25 00:00:00\",\n    \"2187-12-26 00:00:00\",\n    \"2188-01-01 00:00:00\",\n    \"2188-01-02 00:00:00\",\n    \"2188-04-11 00:00:00\",\n    \"2188-04-14 00:00:00\",\n    \"2188-05-01 00:00:00\",\n    \"2188-05-22 00:00:00\",\n    \"2188-06-02 00:00:00\",\n    \"2188-08-01 00:00:00\",\n    \"2188-12-25 00:00:00\",\n    \"2188-12-26 00:00:00\",\n    \"2189-01-01 00:00:00\",\n    \"2189-01-02 00:00:00\",\n    \"2189-04-03 00:00:00\",\n    \"2189-04-06 00:00:00\",\n    \"2189-05-01 00:00:00\",\n    \"2189-05-14 00:00:00\",\n    \"2189-05-25 00:00:00\",\n    \"2189-08-01 00:00:00\",\n    \"2189-12-25 00:00:00\",\n    \"2189-12-26 00:00:00\",\n    \"2190-01-01 00:00:00\",\n    \"2190-01-02 00:00:00\",\n    \"2190-04-23 00:00:00\",\n    \"2190-04-26 00:00:00\",\n    \"2190-05-01 00:00:00\",\n    \"2190-06-03 00:00:00\",\n    \"2190-06-14 00:00:00\",\n    \"2190-08-01 00:00:00\",\n    \"2190-12-25 00:00:00\",\n    \"2190-12-26 00:00:00\",\n    \"2191-01-01 00:00:00\",\n    \"2191-01-02 00:00:00\",\n    \"2191-04-08 00:00:00\",\n    \"2191-04-11 00:00:00\",\n    \"2191-05-01 00:00:00\",\n    \"2191-05-19 00:00:00\",\n    \"2191-05-30 00:00:00\",\n    \"2191-08-01 00:00:00\",\n    \"2191-12-25 00:00:00\",\n    \"2191-12-26 00:00:00\",\n    \"2192-01-01 00:00:00\",\n    \"2192-01-02 00:00:00\",\n    \"2192-03-30 00:00:00\",\n    \"2192-04-02 00:00:00\",\n    \"2192-05-01 00:00:00\",\n    \"2192-05-10 00:00:00\",\n    \"2192-05-21 00:00:00\",\n    \"2192-08-01 00:00:00\",\n    \"2192-12-25 00:00:00\",\n    \"2192-12-26 00:00:00\",\n    \"2193-01-01 00:00:00\",\n    \"2193-01-02 00:00:00\",\n    \"2193-04-19 00:00:00\",\n    \"2193-04-22 00:00:00\",\n    \"2193-05-01 00:00:00\",\n    \"2193-05-30 00:00:00\",\n    \"2193-06-10 00:00:00\",\n    \"2193-08-01 00:00:00\",\n    \"2193-12-25 00:00:00\",\n    \"2193-12-26 00:00:00\",\n    \"2194-01-01 00:00:00\",\n    \"2194-01-02 00:00:00\",\n    \"2194-04-04 00:00:00\",\n    \"2194-04-07 00:00:00\",\n    \"2194-05-01 00:00:00\",\n    \"2194-05-15 00:00:00\",\n    \"2194-05-26 00:00:00\",\n    \"2194-08-01 00:00:00\",\n    \"2194-12-25 00:00:00\",\n    \"2194-12-26 00:00:00\",\n    \"2195-01-01 00:00:00\",\n    \"2195-01-02 00:00:00\",\n    \"2195-03-27 00:00:00\",\n    \"2195-03-30 00:00:00\",\n    \"2195-05-01 00:00:00\",\n    \"2195-05-07 00:00:00\",\n    \"2195-05-18 00:00:00\",\n    \"2195-08-01 00:00:00\",\n    \"2195-12-25 00:00:00\",\n    \"2195-12-26 00:00:00\",\n    \"2196-01-01 00:00:00\",\n    \"2196-01-02 00:00:00\",\n    \"2196-04-15 00:00:00\",\n    \"2196-04-18 00:00:00\",\n    \"2196-05-01 00:00:00\",\n    \"2196-05-26 00:00:00\",\n    \"2196-06-06 00:00:00\",\n    \"2196-08-01 00:00:00\",\n    \"2196-12-25 00:00:00\",\n    \"2196-12-26 00:00:00\",\n    \"2197-01-01 00:00:00\",\n    \"2197-01-02 00:00:00\",\n    \"2197-04-07 00:00:00\",\n    \"2197-04-10 00:00:00\",\n    \"2197-05-01 00:00:00\",\n    \"2197-05-18 00:00:00\",\n    \"2197-05-29 00:00:00\",\n    \"2197-08-01 00:00:00\",\n    \"2197-12-25 00:00:00\",\n    \"2197-12-26 00:00:00\",\n    \"2198-01-01 00:00:00\",\n    \"2198-01-02 00:00:00\",\n    \"2198-03-23 00:00:00\",\n    \"2198-03-26 00:00:00\",\n    \"2198-05-01 00:00:00\",\n    \"2198-05-03 00:00:00\",\n    \"2198-05-14 00:00:00\",\n    \"2198-08-01 00:00:00\",\n    \"2198-12-25 00:00:00\",\n    \"2198-12-26 00:00:00\",\n    \"2199-01-01 00:00:00\",\n    \"2199-01-02 00:00:00\",\n    \"2199-04-12 00:00:00\",\n    \"2199-04-15 00:00:00\",\n    \"2199-05-01 00:00:00\",\n    \"2199-05-23 00:00:00\",\n    \"2199-06-03 00:00:00\",\n    \"2199-08-01 00:00:00\",\n    \"2199-12-25 00:00:00\",\n    \"2199-12-26 00:00:00\",\n    \"2200-01-01 00:00:00\",\n    \"2200-01-02 00:00:00\",\n    \"2200-04-04 00:00:00\",\n    \"2200-04-07 00:00:00\",\n    \"2200-05-01 00:00:00\",\n    \"2200-05-15 00:00:00\",\n    \"2200-05-26 00:00:00\",\n    \"2200-08-01 00:00:00\",\n    \"2200-12-25 00:00:00\",\n    \"2200-12-26 00:00:00\",\n];\n"
  },
  {
    "path": "rust/scheduling/calendars/named/zur_script.py",
    "content": "# SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n#\n# Copyright (c) 2026 Siffrorna Technology Limited\n#\n# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n# Source-available, not open source.\n#\n# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n# and/or contact info (at) rateslib (dot) com\n####################################################################################################\n\nimport pandas as pd\nfrom pandas.tseries.holiday import (\n    AbstractHolidayCalendar,\n    Holiday,\n)\nfrom pandas.tseries.offsets import CustomBusinessDay, Day, Easter\n\nRULES = [\n    Holiday(\"New Year's Day\", month=1, day=1),\n    Holiday(\"Berchtoldstag\", month=1, day=2),\n    Holiday(\"Good Friday\", month=1, day=1, offset=[Easter(), Day(-2)]),\n    Holiday(\"Easter Monday\", month=1, day=1, offset=[Easter(), Day(1)]),\n    Holiday(\"EU Labour Day\", month=5, day=1),\n    Holiday(\"Ascention Day\", month=1, day=1, offset=[Easter(), Day(39)]),\n    Holiday(\"Whit Monday\", month=1, day=1, offset=[Easter(), Day(50)]),\n    Holiday(\"Swiss National Day\", month=8, day=1),\n    Holiday(\"Christmas Day\", month=12, day=25),\n    Holiday(\"Boxing Day\", month=12, day=26),\n]\n\nCALENDAR = CustomBusinessDay(\n    calendar=AbstractHolidayCalendar(rules=RULES),\n    weekmask=\"Mon Tue Wed Thu Fri\",\n)\n\n### RUN THE SCRIPT TO EXPORT HOLIDAY LIST\nts = pd.to_datetime(CALENDAR.holidays)\nstrings = ['\"' + _.strftime(\"%Y-%m-%d %H:%M:%S\") + '\"' for _ in ts]\nline = \",\\n\".join(strings)\nprint(line)\n"
  },
  {
    "path": "rust/scheduling/calendars/named_cal.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse chrono::prelude::*;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\nuse std::sync::Arc;\n\nuse crate::scheduling::{Cal, CalendarAdjustment, DateRoll, UnionCal};\n\n#[derive(Clone, Debug)]\npub(crate) enum CalWrapper {\n    Cal(Cal),\n    UnionCal(UnionCal),\n}\n\nimpl DateRoll for CalWrapper {\n    fn is_weekday(&self, date: &NaiveDateTime) -> bool {\n        match self {\n            CalWrapper::Cal(c) => c.is_weekday(date),\n            CalWrapper::UnionCal(c) => c.is_weekday(date),\n        }\n    }\n\n    fn is_holiday(&self, date: &NaiveDateTime) -> bool {\n        match self {\n            CalWrapper::Cal(c) => c.is_holiday(date),\n            CalWrapper::UnionCal(c) => c.is_holiday(date),\n        }\n    }\n\n    fn is_settlement(&self, date: &NaiveDateTime) -> bool {\n        match self {\n            CalWrapper::Cal(c) => c.is_settlement(date),\n            CalWrapper::UnionCal(c) => c.is_settlement(date),\n        }\n    }\n}\n\nimpl CalendarAdjustment for CalWrapper {}\n\n/// A wrapper for a UnionCal struct specified by a string representation.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[serde(from = \"NamedCalDataModel\")]\npub struct NamedCal {\n    pub name: String,\n    #[serde(skip)]\n    pub(crate) inner: Arc<CalWrapper>,\n}\n\n#[derive(Deserialize)]\nstruct NamedCalDataModel {\n    name: String,\n}\n\nimpl std::convert::From<NamedCalDataModel> for NamedCal {\n    fn from(model: NamedCalDataModel) -> Self {\n        Self::try_new(&model.name).expect(\"NamedCal data model contains bad data.\")\n    }\n}\n\nimpl NamedCal {\n    /// Create a new [`NamedCal`].\n    ///\n    /// # Notes\n    /// `name` must be a string that contains pre-defined calendars separated by commas, additionally\n    /// separating business day calendars with associated settlement calendars by a pipe operator.\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{NamedCal, ndt, DateRoll};\n    /// let named_cal = NamedCal::try_new(\"ldn,tgt|fed\");\n    /// # let named_cal = named_cal.unwrap();\n    /// assert!(named_cal.is_bus_day(&ndt(2026, 2, 12)));\n    /// ```\n    pub fn try_new(name: &str) -> Result<Self, PyErr> {\n        let name_ = name.to_lowercase();\n        let parts: Vec<&str> = name_.split(\"|\").collect();\n        if parts.len() > 2 {\n            Err(PyValueError::new_err(\n                \"Cannot use more than one pipe ('|') operator in `name`.\",\n            ))\n        } else if parts.len() == 1 {\n            let cals: Vec<Cal> = parse_cals(parts[0])?;\n            Ok(Self {\n                name: name_,\n                inner: Arc::new(CalWrapper::UnionCal(UnionCal {\n                    calendars: cals,\n                    settlement_calendars: None,\n                })),\n            })\n        } else {\n            let cals: Vec<Cal> = parse_cals(parts[0])?;\n            let settle_cals: Vec<Cal> = parse_cals(parts[1])?;\n            Ok(Self {\n                name: name_,\n                inner: Arc::new(CalWrapper::UnionCal(UnionCal {\n                    calendars: cals,\n                    settlement_calendars: Some(settle_cals),\n                })),\n            })\n        }\n    }\n}\n\nimpl DateRoll for NamedCal {\n    fn is_weekday(&self, date: &NaiveDateTime) -> bool {\n        self.inner.is_weekday(date)\n    }\n\n    fn is_holiday(&self, date: &NaiveDateTime) -> bool {\n        self.inner.is_holiday(date)\n    }\n\n    fn is_settlement(&self, date: &NaiveDateTime) -> bool {\n        self.inner.is_settlement(date)\n    }\n}\n\nimpl CalendarAdjustment for NamedCal {}\n\nfn parse_cals(name: &str) -> Result<Vec<Cal>, PyErr> {\n    let mut cals: Vec<Cal> = Vec::new();\n    for cal in name.split(\",\") {\n        cals.push(Cal::try_from_name(cal)?)\n    }\n    Ok(cals)\n}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::ndt;\n\n    #[test]\n    fn test_named_cal() {\n        let ncal = NamedCal::try_new(\"tgt,nyc\").unwrap();\n\n        assert!(ncal.is_non_bus_day(&ndt(1970, 2, 16))); // NYC holiday\n        assert!(ncal.is_non_bus_day(&ndt(1970, 5, 1))); // TGT holiday\n        assert!(ncal.is_bus_day(&ndt(1970, 2, 17)));\n    }\n\n    #[test]\n    fn test_named_cal_pipe() {\n        let ncal = NamedCal::try_new(\"tgt,nyc|ldn\").unwrap();\n\n        assert!(ncal.is_non_bus_day(&ndt(1970, 2, 16))); // NYC holiday\n        assert!(ncal.is_non_bus_day(&ndt(1970, 5, 1))); // TGT holiday\n        assert!(ncal.is_bus_day(&ndt(1970, 2, 17)));\n\n        assert!(!ncal.is_settlement(&ndt(1970, 5, 4))); // LDN holiday\n        assert!(ncal.is_settlement(&ndt(1970, 5, 1))); // not LDN holiday\n    }\n\n    #[test]\n    fn test_named_cal_error() {\n        let ncal = NamedCal::try_new(\"tgt,nyc|ldn|\");\n        assert!(ncal.is_err());\n\n        let ncal = NamedCal::try_new(\"\");\n        assert!(ncal.is_err());\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/calendars/union_cal.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse chrono::prelude::*;\nuse pyo3::exceptions::PyKeyError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\n\nuse crate::scheduling::{Cal, CalWrapper, CalendarAdjustment, CalendarManager, DateRoll};\n\n/// A business day calendar which is the potential union of multiple calendars.\n///\n/// # Notes\n/// The following set definitions are observed by this object:\n/// - A **business day** is such if it is a *business day* in each one of the `calendars`.\n/// - A **settleable day** is such if it is a *business day* in each one of the\n///   `settlement_calendars`, otherwise every calendar day is a *settleable day*.\n/// - A **settleable business day** is both a *business day* and a *settleable day*.\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Default, Debug, Serialize, Deserialize)]\npub struct UnionCal {\n    /// A vector of [Cal] used to determine **business** days.\n    pub calendars: Vec<Cal>,\n    /// A vector of [Cal] used to determine **settleable** days.\n    pub settlement_calendars: Option<Vec<Cal>>,\n}\n\nimpl UnionCal {\n    /// Create a new [`UnionCal`].\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{Cal, UnionCal, ndt, DateRoll};\n    /// let stk = Cal::new(vec![ndt(2025, 6, 20)], vec![5,6]);\n    /// let fed = Cal::new(vec![ndt(2025, 6, 19)], vec![5,6]);\n    /// let stk_pipe_fed = UnionCal::new(vec![stk], Some(vec![fed]));\n    /// assert_eq!(true, stk_pipe_fed.is_bus_day(&ndt(2025, 6, 19)));\n    /// assert_eq!(false, stk_pipe_fed.is_settlement(&ndt(2025, 6, 19)));\n    /// ```\n    pub fn new(calendars: Vec<Cal>, settlement_calendars: Option<Vec<Cal>>) -> Self {\n        UnionCal {\n            calendars,\n            settlement_calendars,\n        }\n    }\n\n    /// Return a [`UnionCal`] specified by a pre-defined named identifier.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use rateslib::scheduling::UnionCal;\n    /// let ldn_tgt_cal = UnionCal::try_from_name(\"ldn,tgt\").unwrap();\n    /// ```\n    pub fn try_from_name(name: &str) -> Result<UnionCal, PyErr> {\n        let cm = CalendarManager::new();\n        let named_cal = cm.get_with_insert(name)?;\n        match (*named_cal.inner).clone() {\n            CalWrapper::Cal(_) => Err(PyKeyError::new_err(\n                \"`name` was key for a Cal not a UnionCal.\",\n            )),\n            CalWrapper::UnionCal(cal) => Ok(cal),\n        }\n    }\n}\n\nimpl DateRoll for UnionCal {\n    fn is_weekday(&self, date: &NaiveDateTime) -> bool {\n        self.calendars.iter().all(|cal| cal.is_weekday(date))\n    }\n\n    fn is_holiday(&self, date: &NaiveDateTime) -> bool {\n        self.calendars.iter().any(|cal| cal.is_holiday(date))\n    }\n\n    fn is_settlement(&self, date: &NaiveDateTime) -> bool {\n        self.settlement_calendars\n            .as_ref()\n            .map_or(true, |v| !v.iter().any(|cal| cal.is_non_bus_day(date)))\n    }\n}\n\nimpl CalendarAdjustment for UnionCal {}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::ndt;\n\n    fn fixture_hol_cal() -> Cal {\n        let hols = vec![ndt(2015, 9, 5), ndt(2015, 9, 7)]; // Saturday and Monday\n        Cal::new(hols, vec![5, 6])\n    }\n\n    fn fixture_hol_cal2() -> Cal {\n        let hols = vec![\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap(),\n            NaiveDateTime::parse_from_str(\"2015-09-09 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap(),\n        ];\n        Cal::new(hols, vec![5, 6])\n    }\n\n    #[test]\n    fn test_union_cal() {\n        let cal1 = fixture_hol_cal();\n        let cal2 = fixture_hol_cal2();\n        let ucal = UnionCal::new(vec![cal1, cal2], None);\n\n        let sat =\n            NaiveDateTime::parse_from_str(\"2015-09-05 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let next = ucal.roll_forward_bus_day(&sat);\n        assert_eq!(\n            next,\n            NaiveDateTime::parse_from_str(\"2015-09-10 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n    }\n\n    #[test]\n    fn test_union_cal_with_settle() {\n        let hols = vec![\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap(),\n            NaiveDateTime::parse_from_str(\"2015-09-09 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap(),\n        ];\n        let scal = Cal::new(hols, vec![5, 6]);\n        let cal = Cal::new(vec![], vec![5, 6]);\n        let ucal = UnionCal::new(vec![cal], vec![scal].into());\n\n        let mon =\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap();\n        let next = ucal.roll_forward_bus_day(&mon);\n        assert_eq!(\n            next,\n            NaiveDateTime::parse_from_str(\"2015-09-08 00:00:00\", \"%Y-%m-%d %H:%M:%S\").unwrap()\n        );\n    }\n\n    #[test]\n    fn test_cross_equality() {\n        let cal = fixture_hol_cal();\n        let ucal = UnionCal::new(vec![cal.clone()], None);\n        assert_eq!(cal, ucal);\n        assert_eq!(ucal, cal);\n\n        let ucals = UnionCal::new(vec![cal.clone()], vec![cal.clone()].into());\n        assert_ne!(cal, ucals);\n        assert_ne!(ucals, cal);\n\n        let cal2 = fixture_hol_cal2();\n        assert_ne!(cal2, ucal);\n        assert_ne!(ucal, cal2);\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/convention.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse chrono::prelude::*;\nuse chrono::Months;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse serde::{Deserialize, Serialize};\nuse std::cmp::PartialEq;\n\nuse crate::scheduling::{\n    ndt, Adjuster, Adjustment, Calendar, DateRoll, Frequency, Imm, RollDay, Scheduling,\n};\n\n/// Specifier for day count conventions\n#[pyclass(module = \"rateslib.rs\", eq, eq_int, hash, frozen, from_py_object)]\n#[derive(Debug, Hash, Copy, Clone, Serialize, Deserialize, PartialEq)]\npub enum Convention {\n    /// Actual days in period divided by 365.\n    Act365F = 0,\n    /// Actual days in period divided by 360.\n    Act360 = 1,\n    /// 30 days in month and 360 days in year with month end modification rules.\n    ///\n    /// - Start day is *min(30, start day)*.\n    /// - End day is *min(30, end day)* if start day is 30.\n    Thirty360 = 2,\n    /// 30 days in month and 360 days in year with month end modification rules.\n    ///\n    /// - Start day is *min(30, start day)* or 30 if start day is EoM February and roll is EoM.\n    /// - End day is *min(30, end day)* if start day is 30, or 30 if start and end are EoM February\n    ///   and roll is EoM.\n    ///\n    /// For [dcf][Convention::dcf]: requires ``frequency`` with a [RollDay] for February EoM adjustment.\n    ThirtyU360 = 3,\n    /// 30 days in month and 360 days in year with month end modification rules.\n    ///\n    /// - Start day is *min(30, start day)*.\n    /// - End day is *min(30, end day)*.\n    ThirtyE360 = 4,\n    /// 30 days in month and 360 days in year with month end modification rules.\n    ///\n    /// - Start day is *min(30, start day)* or 30 if start day is February EoM.\n    /// - End day is *min(30, end day)* or 30 if end day is February EoM and not *Leg* termination.\n    ///\n    /// For [dcf][Convention::dcf]: requires ``termination`` for February EoM adjustments.\n    ThirtyE360ISDA = 5,\n    /// Number of whole years plus fractional end period according to 'Act365F'.\n    YearsAct365F = 6,\n    /// Number of whole years plus fractional end period according to 'Act360'.\n    YearsAct360 = 7,\n    /// Number of whole years plus fractional counting months difference divided by 12.\n    YearsMonths = 8,\n    /// Return 1.0 for any period.\n    One = 9,\n    /// Actual days divided by actual days with leap year modification rules.\n    ActActISDA = 10,\n    /// Day count based on [Frequency] definition.\n    ///\n    /// For [dcf][Convention::dcf]: requires ``frequency`` and ``stub`` in all cases.\n    /// If a stub period further requires ``termination``, ``calendar`` and ``adjuster`` to\n    /// accurately evaluate the fractional part of a period.\n    ActActICMA = 11,\n    /// Number of business days in period divided by 252.\n    ///\n    /// For [dcf][Convention::dcf]: a ``calendar`` is required. If not given, a [Calendar] will\n    /// attempt to be sourced from the ``frequency`` if given a *BusDays* variant.\n    Bus252 = 12,\n    /// ActActICMA falling back to Act365F in stub periods.\n    ///\n    /// For [dcf][Convention::dcf]: requires the same arguments as ``ActActICMA`` variant.\n    ActActICMAStubAct365F = 13,\n    /// Actual days in period divided by 365.25.\n    Act365_25 = 14,\n    /// Actual days in period divided by 364.\n    Act364 = 15,\n}\n\nimpl Convention {\n    pub fn dcf(\n        &self,\n        start: &NaiveDateTime,\n        end: &NaiveDateTime,\n        termination: Option<&NaiveDateTime>,\n        frequency: Option<&Frequency>,\n        stub: Option<bool>,\n        calendar: Option<&Calendar>,\n        adjuster: Option<&Adjuster>,\n    ) -> Result<f64, PyErr> {\n        match self {\n            Convention::Act360 => Ok(dcf_act_numeric(360.0, start, end)),\n            Convention::Act365F => Ok(dcf_act_numeric(365.0, start, end)),\n            Convention::Act365_25 => Ok(dcf_act_numeric(365.25, start, end)),\n            Convention::Act364 => Ok(dcf_act_numeric(364.0, start, end)),\n            Convention::YearsAct365F => Ok(dcf_years_and_act_numeric(365.0, start, end)),\n            Convention::YearsAct360 => Ok(dcf_years_and_act_numeric(360.0, start, end)),\n            Convention::YearsMonths => Ok(dcf_years_and_months(start, end)),\n            Convention::Thirty360 => Ok(dcf_30360(start, end)),\n            Convention::ThirtyU360 => dcf_30u360(start, end, frequency),\n            Convention::ThirtyE360 => Ok(dcf_30e360(start, end)),\n            Convention::ThirtyE360ISDA => dcf_30e360_isda(start, end, termination),\n            Convention::One => Ok(1.0),\n            Convention::ActActISDA => Ok(dcf_act_isda(start, end)),\n            Convention::ActActICMA => {\n                if frequency.is_none() {\n                    Err(PyValueError::new_err(\n                        \"`frequency` must be supplied for 'ActActICMA' type convention.\",\n                    ))\n                } else if stub.is_none() {\n                    Err(PyValueError::new_err(\n                        \"`stub` must be supplied for 'ActActICMA' type convention.\",\n                    ))\n                } else {\n                    dcf_act_icma(\n                        start,\n                        end,\n                        termination,\n                        frequency.unwrap(),\n                        stub.unwrap(),\n                        calendar,\n                        adjuster,\n                    )\n                }\n            }\n            Convention::Bus252 => {\n                let calendar_: &Calendar;\n                if calendar.is_none() {\n                    match frequency {\n                        Some(Frequency::BusDays {\n                            number: _,\n                            calendar: c,\n                        }) => calendar_ = c,\n                        _ => {\n                            return Err(PyValueError::new_err(\n                                \"`calendar` must be supplied for 'Bus252' type convention.\",\n                            ));\n                        }\n                    }\n                } else {\n                    calendar_ = calendar.unwrap();\n                }\n                Ok(dcf_bus252(start, end, calendar_))\n            }\n            Convention::ActActICMAStubAct365F => {\n                if frequency.is_none() {\n                    Err(PyValueError::new_err(\n                        \"`frequency` must be supplied for 'ActActICMA' type convention.\",\n                    ))\n                } else if stub.is_none() {\n                    Err(PyValueError::new_err(\n                        \"`stub` must be supplied for 'ActActICMA' type convention.\",\n                    ))\n                } else {\n                    dcf_act_icma_stub_365f(\n                        start,\n                        end,\n                        termination,\n                        frequency.unwrap(),\n                        stub.unwrap(),\n                        calendar,\n                        adjuster,\n                    )\n                }\n            }\n        }\n    }\n}\n\nfn dcf_act_numeric(denominator: f64, start: &NaiveDateTime, end: &NaiveDateTime) -> f64 {\n    (*end - *start).num_days() as f64 / denominator\n}\n\nfn dcf_years_and_act_numeric(denominator: f64, start: &NaiveDateTime, end: &NaiveDateTime) -> f64 {\n    if *end <= (*start + Months::new(12)) {\n        dcf_act_numeric(denominator, start, end)\n    } else {\n        let intermediate = RollDay::Day(start.day())\n            .try_from_ym(end.year(), start.month())\n            .expect(\"Dates are out of bounds\");\n        if intermediate <= *end {\n            let years: f64 = (end.year() - start.year()) as f64;\n            years + dcf_act_numeric(denominator, &intermediate, end)\n        } else {\n            let years: f64 = (end.year() - start.year()) as f64 - 1.0;\n            years + dcf_act_numeric(denominator, &(intermediate - Months::new(12)), end)\n        }\n    }\n}\n\nfn dcf_years_and_months(start: &NaiveDateTime, end: &NaiveDateTime) -> f64 {\n    let start_ = ndt(start.year(), start.month(), 1);\n    let end_ = ndt(end.year(), end.month(), 1);\n    let mut count_date = ndt(end.year(), start.month(), 1);\n    if count_date > end_ {\n        count_date = count_date - Months::new(12)\n    };\n    let years = count_date.year() - start_.year();\n    let mut counter = 0;\n    while count_date < end_ {\n        count_date = count_date + Months::new(1);\n        counter += 1;\n    }\n    years as f64 + counter as f64 / 12.0\n}\n\n/// Normal 30360 without any adjustments\nfn dcf_30360_unadjusted(ys: i32, ms: u32, ds: u32, ye: i32, me: u32, de: u32) -> f64 {\n    (ye - ys) as f64 + (me as f64 - ms as f64) / 12.0 + (de as f64 - ds as f64) / 360.0\n}\n\n/// Return DCF under 30360 convention.\n///\n/// - start.day is adjusted to min(30, start.day)\n/// - end.day is adjusted to min(30, end.day) only if start.day is 30.\n/// - calculation proceeds as normal\nfn dcf_30360(start: &NaiveDateTime, end: &NaiveDateTime) -> f64 {\n    let ds = u32::min(30_u32, start.day());\n    let de = if ds == 30 {\n        u32::min(30_u32, end.day())\n    } else {\n        end.day()\n    };\n    dcf_30360_unadjusted(start.year(), start.month(), ds, end.year(), end.month(), de)\n}\n\n/// Return DCF under 30e360 convention.\n///\n/// - start.day is adjusted to min(30, start.day)\n/// - end.day is adjusted to min(30, end.day)\n/// - calculation proceeds as normal\nfn dcf_30e360(start: &NaiveDateTime, end: &NaiveDateTime) -> f64 {\n    let ds = u32::min(30_u32, start.day());\n    let de = u32::min(30_u32, end.day());\n    dcf_30360_unadjusted(start.year(), start.month(), ds, end.year(), end.month(), de)\n}\n\n/// Return DCF under 30u360 convention.\n///\n/// - start.day is 30 if roll is EoM and start is last day in February.\n/// - end.day is 30 if roll is EoM and start and end are both last days of February.\n/// - start.day is 30 if start.day is 31.\n/// - end.day is 30 if end.day is 31 and start.day is 30.\n///\n/// # Notes\n/// `frequency` is only evaluated to determine a [RollDay] if start is end of February.\nfn dcf_30u360(\n    start: &NaiveDateTime,\n    end: &NaiveDateTime,\n    frequency: Option<&Frequency>,\n) -> Result<f64, PyErr> {\n    let mut ds = start.day();\n    let mut de = end.day();\n\n    // handle February EoM rolls adjustment\n    if Imm::Eom.validate(start) && start.month() == 2 {\n        let roll: RollDay = match frequency {\n            Some(Frequency::Months {\n                number: _,\n                roll: Some(r),\n            }) => *r,\n            _ => {\n                return Err(PyValueError::new_err(\n                    \"`frequency` must be provided or has no `roll`. A roll-day must be supplied for '30u360' convention to detect February EoM rolls.\\n`start` is detected as end of February, otherwise use '30360' which will leave this date unadjusted.\",\n                ));\n            }\n        };\n        if roll == RollDay::Day(31) {\n            ds = 30;\n            if Imm::Eom.validate(end) && end.month() == 2 {\n                de = 30;\n            }\n        }\n    }\n\n    // perform regular 30360 adjustments\n    ds = u32::min(30_u32, ds);\n    if de == 31 && ds == 30 {\n        de = 30;\n    }\n    Ok(dcf_30360_unadjusted(\n        start.year(),\n        start.month(),\n        ds,\n        end.year(),\n        end.month(),\n        de,\n    ))\n}\n\n/// Return DCF under 30e360ISDA convention.\n///\n/// - start.day is 30 if start.day is 31 or start.day is end of February.\n/// - end.day is 30 if end.day is 31 or end.day is end of February and not the termination date.\nfn dcf_30e360_isda(\n    start: &NaiveDateTime,\n    end: &NaiveDateTime,\n    termination: Option<&NaiveDateTime>,\n) -> Result<f64, PyErr> {\n    let mut ds = u32::min(30_u32, start.day());\n\n    //handle February EoM adjustments\n    if Imm::Eom.validate(start) && start.month() == 2 {\n        ds = 30;\n    }\n    let mut de = u32::min(30_u32, end.day());\n    if Imm::Eom.validate(end) && end.month() == 2 {\n        if termination.is_none() {\n            return Err(PyValueError::new_err(\n                \"`termination` must be provided for '30e360ISDA' convention to detect end of February.\\n`end` is detected as end of February, otherwise use '30e360' which will leave this date unadjusted.\",\n            ));\n        } else if *end != *(termination.unwrap()) {\n            de = 30;\n        }\n    }\n\n    Ok(dcf_30360_unadjusted(\n        start.year(),\n        start.month(),\n        ds,\n        end.year(),\n        end.month(),\n        de,\n    ))\n}\n\nfn dcf_act_isda(start: &NaiveDateTime, end: &NaiveDateTime) -> f64 {\n    if start == end {\n        return 0.0;\n    };\n\n    let is_start_leap = NaiveDate::from_ymd_opt(start.year(), 2, 29).is_some();\n    let is_end_leap = NaiveDate::from_ymd_opt(end.year(), 2, 29).is_some();\n\n    let year_1_diff = if is_start_leap { 366.0 } else { 365.0 };\n    let year_2_diff = if is_end_leap { 366.0 } else { 365.0 };\n\n    let mut total_sum: f64 = (end.year() - start.year()) as f64 - 1.0;\n    total_sum += (ndt(start.year() + 1, 1, 1) - *start).num_days() as f64 / year_1_diff;\n    total_sum += (*end - ndt(end.year(), 1, 1)).num_days() as f64 / year_2_diff;\n    total_sum\n}\n\nfn dcf_act_icma(\n    start: &NaiveDateTime,\n    end: &NaiveDateTime,\n    termination: Option<&NaiveDateTime>,\n    frequency: &Frequency,\n    stub: bool,\n    calendar: Option<&Calendar>,\n    adjuster: Option<&Adjuster>,\n) -> Result<f64, PyErr> {\n    let freq = actacticma_frequency_conversion(frequency);\n    let ppa = freq.periods_per_annum();\n\n    if !stub {\n        Ok(1.0 / ppa)\n    } else {\n        if termination.is_none() || adjuster.is_none() || calendar.is_none() {\n            return Err(PyValueError::new_err(\n                \"Stub periods under ActActICMA require `termination`, `adjuster` and `calendar` arguments to determine appropriate fractions.\"\n            ));\n        }\n        let is_back_stub = end == termination.unwrap();\n        let mut fraction = -1.0;\n        if is_back_stub {\n            let mut qe0 = *start;\n            let mut qe1 = *start;\n            while *end > qe1 {\n                fraction += 1.0;\n                qe0 = qe1;\n                qe1 = (*(adjuster.unwrap())).adjust(&freq.next(&qe0), calendar.unwrap());\n            }\n            fraction =\n                fraction + ((*end - qe0).num_days() as f64) / ((qe1 - qe0).num_days() as f64);\n            Ok(fraction / ppa)\n        } else {\n            let mut qs0 = *end;\n            let mut qs1 = *end;\n            while *start < qs1 {\n                fraction += 1.0;\n                qs0 = qs1;\n                qs1 = (*(adjuster.unwrap())).adjust(&freq.previous(&qs0), calendar.unwrap());\n            }\n            fraction =\n                fraction + ((qs0 - *start).num_days() as f64) / ((qs0 - qs1).num_days() as f64);\n            Ok(fraction / ppa)\n        }\n    }\n}\n\nfn dcf_act_icma_stub_365f(\n    start: &NaiveDateTime,\n    end: &NaiveDateTime,\n    termination: Option<&NaiveDateTime>,\n    frequency: &Frequency,\n    stub: bool,\n    calendar: Option<&Calendar>,\n    adjuster: Option<&Adjuster>,\n) -> Result<f64, PyErr> {\n    let freq = actacticma_frequency_conversion(frequency);\n    let ppa = freq.periods_per_annum();\n\n    if !stub {\n        Ok(1.0 / ppa)\n    } else {\n        if termination.is_none() || adjuster.is_none() || calendar.is_none() {\n            return Err(PyValueError::new_err(\n                \"Stub periods under ActActICMA require `termination`, `adjuster` and `calendar` arguments to determine appropriate fractions.\"\n            ));\n        }\n        let is_back_stub = end == termination.unwrap();\n        let mut fraction = -1.0;\n        if is_back_stub {\n            let mut qe0 = *start;\n            let mut qe1 = *start;\n            while *end > qe1 {\n                fraction += 1.0;\n                qe0 = qe1;\n                qe1 = (*(adjuster.unwrap())).adjust(&freq.next(&qe0), calendar.unwrap());\n            }\n            fraction = fraction + ppa * (*end - qe0).num_days() as f64 / 365.0;\n            Ok(fraction / ppa)\n        } else {\n            let mut qs0 = *end;\n            let mut qs1 = *end;\n            while *start < qs1 {\n                fraction += 1.0;\n                qs0 = qs1;\n                qs1 = (*(adjuster.unwrap())).adjust(&freq.previous(&qs0), calendar.unwrap());\n            }\n            fraction = fraction + ppa * (qs0 - *start).num_days() as f64 / 365.0;\n            Ok(fraction / ppa)\n        }\n    }\n}\n\nfn actacticma_frequency_conversion(frequency: &Frequency) -> Frequency {\n    match frequency {\n        Frequency::Zero {} => Frequency::Months {\n            number: 12,\n            roll: None,\n        },\n        _ => frequency.clone(),\n    }\n}\n\nfn dcf_bus252(start: &NaiveDateTime, end: &NaiveDateTime, calendar: &Calendar) -> f64 {\n    if end < start {\n        panic!(\"Given end is greater than start\");\n    } else if start == end {\n        return 0.0;\n    }\n    let start_bd = Adjuster::Following {}.adjust(start, calendar);\n    let end_bd = Adjuster::Previous {}.adjust(end, calendar);\n    let subtract = if end_bd == *end { -1.0 } else { 0.0 };\n    if start_bd == end_bd {\n        if start_bd > *start && end_bd < *end {\n            //then logically there is one b.d. between the non-business start and non-business end\n            1.0 / 252.0\n        } else if end_bd < *end {\n            // then the business start is permitted to the calculation until the non-business end\n            1.0 / 252.0\n        } else {\n            // start_bd > start\n            // then the business end is not permitted to have occurred and non-business start\n            // does not count\n            0.0\n        }\n    } else if start_bd > end_bd {\n        // there are no business days in between start and end\n        0.0\n    } else {\n        (calendar.bus_date_range(&start_bd, &end_bd).unwrap().len() as f64 + subtract) / 252.0\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::{ndt, Cal};\n\n    #[test]\n    fn test_act_numeric() {\n        let result = dcf_act_numeric(10.0, &ndt(2000, 1, 1), &ndt(2000, 1, 21));\n        assert_eq!(result, 2.0)\n    }\n\n    #[test]\n    fn test_act_plus() {\n        let options: Vec<(NaiveDateTime, NaiveDateTime, f64)> = vec![\n            (ndt(2000, 1, 1), ndt(2002, 1, 21), 2.0 + 20.0 / 365.0),\n            (ndt(2000, 12, 31), ndt(2002, 1, 1), 1.0 + 1.0 / 365.0),\n            (ndt(2000, 12, 31), ndt(2002, 12, 31), 2.0),\n            (ndt(2024, 2, 29), ndt(2025, 2, 28), 1.0),\n            (ndt(2000, 12, 15), ndt(2003, 1, 15), 2.0 + 31.0 / 365.0),\n        ];\n        for option in options {\n            let result = dcf_years_and_act_numeric(365.0, &option.0, &option.1);\n            assert_eq!(result, option.2)\n        }\n    }\n\n    #[test]\n    fn test_30360() {\n        let options: Vec<(NaiveDateTime, NaiveDateTime, f64)> = vec![\n            (ndt(2000, 1, 1), ndt(2000, 1, 21), 20.0 / 360.0),\n            (\n                ndt(2000, 1, 1),\n                ndt(2001, 3, 21),\n                1.0 + 2.0 / 12.0 + 20.0 / 360.0,\n            ),\n        ];\n        for option in options {\n            let result = dcf_30360(&option.0, &option.1);\n            assert_eq!(result, option.2)\n        }\n    }\n\n    #[test]\n    fn test_30u360() {\n        let options: Vec<(NaiveDateTime, NaiveDateTime, Frequency, f64)> = vec![\n            (\n                ndt(2000, 1, 1),\n                ndt(2000, 1, 21),\n                Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(1)),\n                },\n                20.0 / 360.0,\n            ),\n            (\n                ndt(2000, 1, 1),\n                ndt(2001, 3, 21),\n                Frequency::CalDays { number: 20 },\n                1.0 + 2.0 / 12.0 + 20.0 / 360.0,\n            ),\n            (\n                ndt(2024, 2, 29),\n                ndt(2025, 2, 28),\n                Frequency::Months {\n                    number: 12,\n                    roll: Some(RollDay::Day(29)),\n                },\n                1.0 - 1.0 / 360.0,\n            ),\n            (\n                ndt(2024, 2, 29),\n                ndt(2025, 2, 28),\n                Frequency::Months {\n                    number: 12,\n                    roll: Some(RollDay::Day(31)),\n                },\n                1.0,\n            ),\n        ];\n        for option in options {\n            let result = dcf_30u360(&option.0, &option.1, Some(&option.2)).unwrap();\n            assert_eq!(result, option.3);\n        }\n    }\n\n    #[test]\n    fn test_years_and_months() {\n        let options: Vec<(NaiveDateTime, NaiveDateTime, f64)> = vec![\n            (ndt(2000, 1, 1), ndt(2000, 1, 21), 0.0),\n            (ndt(2000, 1, 1), ndt(2001, 3, 21), 1.0 + 2.0 / 12.0),\n            (ndt(2024, 2, 29), ndt(2025, 2, 28), 1.0),\n            (ndt(2024, 2, 29), ndt(2025, 2, 28), 1.0),\n            (ndt(2000, 12, 29), ndt(2025, 1, 12), 24.0 + 1.0 / 12.0),\n        ];\n        for option in options {\n            let result = dcf_years_and_months(&option.0, &option.1);\n            assert_eq!(result, option.2)\n        }\n    }\n\n    #[test]\n    fn test_actacticma() {\n        let options: Vec<(NaiveDateTime, NaiveDateTime, Frequency, f64)> = vec![\n            (\n                ndt(1999, 2, 1),\n                ndt(1999, 7, 1),\n                Frequency::Months {\n                    number: 12,\n                    roll: None,\n                },\n                150.0 / 365.0,\n            ),\n            (\n                ndt(2002, 8, 15),\n                ndt(2003, 7, 15),\n                Frequency::Months {\n                    number: 6,\n                    roll: None,\n                },\n                0.5 + 153.0 / 368.0,\n            ),\n        ];\n        for option in options {\n            let result = dcf_act_icma(\n                &option.0,\n                &option.1,\n                Some(&ndt(2099, 1, 1)),\n                &option.2,\n                true,\n                Some(&Cal::new(vec![], vec![]).into()),\n                Some(&Adjuster::Actual {}),\n            )\n            .unwrap();\n            assert_eq!(result, option.3)\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/frequency/frequency.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::scheduling::{ndt, Adjuster, Cal, Calendar, DateRoll, RollDay};\nuse chrono::prelude::*;\nuse chrono::Months;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\n\n/// Specifier for generating unadjusted scheduling periods.\n#[pyclass(module = \"rateslib.rs\", eq, from_py_object)]\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\npub enum Frequency {\n    /// A set number of business days, defined by a [`Calendar`], which can only align with a\n    /// business day as defined by that [`Calendar`].\n    BusDays { number: i32, calendar: Calendar },\n    /// A set number of calendar days, which can align with any unadjusted date. To achieve a\n    /// `Weeks` variant use an appropriate `number` of days.\n    CalDays { number: i32 },\n    /// A set number of calendar months, with a potential [`RollDay`].\n    /// To achieve a `Years` variant use an appropriate `number` of months.\n    Months { number: i32, roll: Option<RollDay> },\n    /// Only ever defining one single period, and which can align with any unadjusted date.\n    Zero {},\n}\n\n/// Used to define periods of financial instrument schedules.\npub trait Scheduling {\n    /// Validate if an unadjusted date aligns with the scheduling object.\n    fn try_udate(&self, udate: &NaiveDateTime) -> Result<NaiveDateTime, PyErr>;\n\n    /// Calculate the next unadjusted scheduling period date from a given `date`.\n    ///\n    /// <div class=\"warning\">\n    ///\n    /// The input `date` is not checked to align with the scheduling object. This can lead to\n    /// to optically unexpected results (see examples). If a check on the date is required use the\n    /// [`try_unext`](Scheduling::try_unext) method instead.\n    ///\n    /// </div>\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{Frequency, Scheduling, ndt, RollDay};\n    /// let f = Frequency::Months{number:1, roll: Some(RollDay::Day(1))};\n    /// let result = f.next(&ndt(2000, 1, 31));\n    /// assert_eq!(ndt(2000, 2, 1), result);\n    /// assert!(f.try_unext(&ndt(2000, 1, 31)).is_err());\n    /// ```\n    fn next(&self, date: &NaiveDateTime) -> NaiveDateTime;\n\n    /// Calculate the previous unadjusted scheduling period date from a given `date`.\n    ///\n    /// <div class=\"warning\">\n    ///\n    /// The input `date` is not checked to align with the scheduling object. This can lead to\n    /// to optically unexpected results (see examples). If a check on the date is required use the\n    /// [`try_uprevious`](Scheduling::try_uprevious) method instead.\n    ///\n    /// </div>\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{Frequency, Scheduling, ndt, RollDay};\n    /// let f = Frequency::Months{number:1, roll: Some(RollDay::Day(31))};\n    /// let result = f.previous(&ndt(2000, 2, 1));\n    /// assert_eq!(ndt(2000, 1, 31), result);\n    /// assert!(f.try_uprevious(&ndt(2000, 2, 1)).is_err());\n    /// ```\n    fn previous(&self, date: &NaiveDateTime) -> NaiveDateTime;\n\n    /// Return a vector of unadjusted regular scheduling dates if it exists.\n    ///\n    /// # Notes\n    /// In many standard cases this will simply use the provided method\n    /// [`try_uregular_from_unext`](Scheduling::try_uregular_from_unext), but allows for custom\n    /// implementations when required.\n    fn try_uregular(\n        &self,\n        ueffective: &NaiveDateTime,\n        utermination: &NaiveDateTime,\n    ) -> Result<Vec<NaiveDateTime>, PyErr>;\n\n    /// Calculate the next unadjusted scheduling period date from an unadjusted base date.\n    ///     \n    /// # Notes\n    /// This method first checks that the `udate` is valid and returns an error if not.\n    fn try_unext(&self, udate: &NaiveDateTime) -> Result<NaiveDateTime, PyErr> {\n        let _ = self.try_udate(udate)?;\n        Ok(self.next(udate))\n    }\n\n    /// Calculate the previous unadjusted scheduling period date from an unadjusted base date.\n    ///\n    /// # Notes\n    /// This method first checks that the `udate` is valid and returns an error if not.\n    fn try_uprevious(&self, udate: &NaiveDateTime) -> Result<NaiveDateTime, PyErr> {\n        let _ = self.try_udate(udate)?;\n        Ok(self.previous(udate))\n    }\n\n    /// Return a vector of unadjusted regular scheduling dates if it exists.\n    ///\n    /// # Notes\n    /// This method begins with ``ueffective`` and repeatedly applies [`try_unext`](Scheduling::try_unext)\n    /// to derive all appropriate dates until ``utermination``.\n    fn try_uregular_from_unext(\n        &self,\n        ueffective: &NaiveDateTime,\n        utermination: &NaiveDateTime,\n    ) -> Result<Vec<NaiveDateTime>, PyErr> {\n        let mut v: Vec<NaiveDateTime> = vec![];\n        let mut date = *ueffective;\n        while date < *utermination {\n            v.push(date);\n            date = self.try_unext(&date)?;\n        }\n        if date == *utermination {\n            v.push(*utermination);\n            Ok(v)\n        } else {\n            Err(PyValueError::new_err(\n                \"Input dates to Frequency do not define a regular unadjusted schedule\",\n            ))\n        }\n    }\n\n    /// Check if two given unadjusted dates define a **regular period** under a scheduling object.\n    ///\n    /// # Notes\n    /// This method tests if [`try_uregular`](Scheduling::try_uregular) has exactly two dates.\n    fn is_regular_period(&self, ueffective: &NaiveDateTime, utermination: &NaiveDateTime) -> bool {\n        let s = self.try_uregular(ueffective, utermination);\n        match s {\n            Ok(v) => v.len() == 2,\n            Err(_) => false,\n        }\n    }\n\n    /// Check if two given unadjusted dates define a **short front stub period** under a scheduling object.\n    ///\n    /// # Notes\n    /// This method tests if [`try_uprevious`](Scheduling::try_uprevious) is before `ueffective`.\n    /// If dates are undeterminable this returns `false`.\n    fn is_short_front_stub(\n        &self,\n        ueffective: &NaiveDateTime,\n        utermination: &NaiveDateTime,\n    ) -> bool {\n        let quasi = self.try_uprevious(utermination);\n        match quasi {\n            Ok(date) => date < *ueffective,\n            Err(_) => false,\n        }\n    }\n\n    /// Check if two given unadjusted dates define a **long front stub period** under a scheduling object.\n    fn is_long_front_stub(&self, ueffective: &NaiveDateTime, utermination: &NaiveDateTime) -> bool {\n        let quasi = self.try_uprevious(utermination);\n        match quasi {\n            Ok(date) if *ueffective < date => {\n                let quasi_2 = self.try_uprevious(&date);\n                match quasi_2 {\n                    Ok(date) => date <= *ueffective, // for long stub equal to allowed\n                    Err(_) => false,\n                }\n            }\n            _ => false,\n        }\n    }\n\n    /// Check if two given unadjusted dates define a **short back stub period** under a scheduling object.\n    ///\n    /// # Notes\n    /// This method tests if [Scheduling::try_unext] is after `utermination`.\n    /// If dates are undeterminable this returns `false`.\n    fn is_short_back_stub(&self, ueffective: &NaiveDateTime, utermination: &NaiveDateTime) -> bool {\n        let quasi = self.try_unext(ueffective);\n        match quasi {\n            Ok(date) => *utermination < date,\n            Err(_) => false,\n        }\n    }\n\n    /// Check if two given unadjusted dates define a **long back stub period** under a scheduling object.\n    fn is_long_back_stub(&self, ueffective: &NaiveDateTime, utermination: &NaiveDateTime) -> bool {\n        let quasi = self.try_unext(ueffective);\n        match quasi {\n            Ok(date) if date < *utermination => {\n                let quasi_2 = self.try_unext(&date);\n                match quasi_2 {\n                    Ok(date) => *utermination <= date, // for long stub equal to allowed.\n                    Err(_) => false,\n                }\n            }\n            _ => false,\n        }\n    }\n\n    /// Check if two given unadjusted dates define any **front stub** under a scheduling object.\n    ///\n    /// # Notes\n    /// If dates are undeterminable this returns `false`.\n    fn is_front_stub(&self, ueffective: &NaiveDateTime, utermination: &NaiveDateTime) -> bool {\n        self.is_short_front_stub(ueffective, utermination)\n            || self.is_long_front_stub(ueffective, utermination)\n    }\n\n    /// Check if two given unadjusted dates define any **back stub** under a scheduling object.\n    ///\n    /// # Notes\n    /// If dates are undeterminable this returns `false`.\n    fn is_back_stub(&self, ueffective: &NaiveDateTime, utermination: &NaiveDateTime) -> bool {\n        self.is_short_back_stub(ueffective, utermination)\n            || self.is_long_back_stub(ueffective, utermination)\n    }\n\n    /// Infer an unadjusted front stub date from unadjusted irregular schedule dates.\n    ///\n    /// # Notes\n    /// If a regular schedule is defined then the result will hold `None` as no stub is required.\n    /// If a stub can be inferred then it will be returned as `Some(date)`.\n    /// An errors will be returned if the dates are too close together to infer stubs and do not\n    /// define a regular period.\n    fn try_infer_ufront_stub(\n        &self,\n        ueffective: &NaiveDateTime,\n        utermination: &NaiveDateTime,\n        short: bool,\n    ) -> Result<Option<NaiveDateTime>, PyErr> {\n        let mut date = *utermination;\n        while date > *ueffective {\n            date = self.try_uprevious(&date)?;\n        }\n        if date == *ueffective {\n            // defines a regular schedule and no stub is required.\n            Ok(None)\n        } else {\n            if short {\n                date = self.try_unext(&date)?;\n            } else {\n                date = self.try_unext(&date)?;\n                date = self.try_unext(&date)?;\n            }\n            if date >= *utermination {\n                // then the dates are too close together to define a stub\n                Ok(None)\n            } else {\n                // return the valid stub date\n                Ok(Some(date))\n            }\n        }\n    }\n\n    /// Infer an unadjusted back stub date from unadjusted irregular schedule dates.\n    ///\n    /// # Notes\n    /// If a regular schedule is defined then the result will hold `None` as no stub is required.\n    /// If a stub can be inferred then it will be returned as `Some(date)`.\n    /// An errors will be returned if the dates are too close together to infer stubs and do not\n    /// define a regular period.\n    fn try_infer_uback_stub(\n        &self,\n        ueffective: &NaiveDateTime,\n        utermination: &NaiveDateTime,\n        short: bool,\n    ) -> Result<Option<NaiveDateTime>, PyErr> {\n        let mut date = *ueffective;\n        while date < *utermination {\n            date = self.try_unext(&date)?;\n        }\n        if date == *utermination {\n            // regular schedule so no stub required\n            Ok(None)\n        } else {\n            if short {\n                date = self.try_uprevious(&date)?;\n            } else {\n                date = self.try_uprevious(&date)?;\n                date = self.try_uprevious(&date)?;\n            }\n            if date <= *ueffective {\n                // dates are too close together to define a stub.\n                Ok(None)\n            } else {\n                // return the valid stub\n                Ok(Some(date))\n            }\n        }\n    }\n\n    /// Get the approximate number of coupons per annum.\n    ///\n    /// This will average the number coupons paid in 50 year period.\n    fn periods_per_annum(&self) -> f64 {\n        periods_per_annum(self)\n    }\n}\n\nimpl Frequency {\n    /// Get a vector of possible, fully specified [`Frequency`] variants for a series of unadjusted dates.\n    ///\n    /// # Notes\n    /// This method exists primarily to resolve cases when the [`RollDay`] on a\n    /// [`Frequency::Months`](Frequency) variant is `None`, and there are multiple possibilities. In this case\n    /// the method [`RollDay::vec_from`] is called internally.\n    ///\n    /// If the [`Frequency`] variant does not align with any of the provided unadjusted dates this\n    /// will return an error.\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{Frequency, ndt, RollDay};\n    /// // The RollDay is unspecified here\n    /// let f = Frequency::Months{number: 3, roll: None};\n    /// let result = f.try_vec_from(&vec![ndt(2024, 2, 29)]);\n    /// assert_eq!(result.unwrap(), vec![\n    ///     Frequency::Months{number: 3, roll: Some(RollDay::Day(29))},\n    ///     Frequency::Months{number: 3, roll: Some(RollDay::Day(30))},\n    ///     Frequency::Months{number: 3, roll: Some(RollDay::Day(31))},\n    /// ]);\n    /// ```\n    pub fn try_vec_from(&self, udates: &Vec<NaiveDateTime>) -> Result<Vec<Frequency>, PyErr> {\n        match self {\n            Frequency::Months {\n                number: n,\n                roll: None,\n            } => {\n                // the RollDay is unspecified so get all possible RollDay variants\n                Ok(RollDay::vec_from(udates)\n                    .into_iter()\n                    .map(|r| Frequency::Months {\n                        number: *n,\n                        roll: Some(r),\n                    })\n                    .collect())\n            }\n            _ => {\n                // the Frequency is fully specified so return single element vector if\n                // at least 1 udate is valid\n                for udate in udates {\n                    if self.try_udate(udate).is_ok() {\n                        return Ok(vec![self.clone()]);\n                    }\n                }\n                Err(PyValueError::new_err(\n                    \"The Frequency does not align with any of the `udates`.\",\n                ))\n            }\n        }\n    }\n}\n\nimpl Scheduling for Frequency {\n    /// Validate if an unadjusted date aligns with the specified [Frequency] variant.\n    ///\n    /// # Notes\n    /// This method will return error in one of two cases:\n    /// - The `udate` does not align with the fully defined variant.\n    /// - The variant is not fully defined (e.g. a [`Months`](Frequency) variant is missing\n    ///   a [`RollDay`](RollDay)) and cannot make the determination.\n    ///\n    /// Therefore,\n    /// - For a [CalDays](Frequency) variant or [Zero](Frequency) variant, any ``udate`` is valid.\n    /// - For a [BusDays](Frequency) variant, ``udate`` must be a business day.\n    /// - For a [Months](Frequency) variant, ``udate`` must align with the [RollDay]. If no [RollDay] is\n    ///   specified an error will always be returned.\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{Frequency, RollDay, ndt, Scheduling};\n    /// let result = Frequency::Months{number: 1, roll: Some(RollDay::IMM{})}.try_udate(&ndt(2025, 7, 16));\n    /// assert!(result.is_ok());\n    ///\n    /// let result = Frequency::Months{number: 1, roll: None}.try_udate(&ndt(2025, 7, 16));\n    /// assert!(result.is_err());\n    /// ```\n    fn try_udate(&self, udate: &NaiveDateTime) -> Result<NaiveDateTime, PyErr> {\n        match self {\n            Frequency::BusDays {\n                number: _n,\n                calendar: c,\n            } => {\n                if c.is_bus_day(udate) {\n                    Ok(*udate)\n                } else {\n                    Err(PyValueError::new_err(\n                        \"`udate` is not a business day of the given calendar.\",\n                    ))\n                }\n            }\n            Frequency::CalDays { number: _n } => Ok(*udate),\n            Frequency::Months {\n                number: _n,\n                roll: r,\n            } => match r {\n                Some(r) => r.try_udate(udate),\n                None => Err(PyValueError::new_err(\n                    \"`udate` cannot be validated since RollDay is None.\",\n                )),\n            },\n            Frequency::Zero {} => Ok(*udate),\n        }\n    }\n\n    fn next(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        match self {\n            Frequency::BusDays {\n                number: n,\n                calendar: c,\n            } => c.lag_bus_days(date, *n, false),\n            Frequency::CalDays { number: n } => {\n                let cal = Cal::new(vec![], vec![]);\n                cal.add_cal_days(date, *n, &Adjuster::Actual {})\n            }\n            Frequency::Months { number: n, roll: r } => match r {\n                Some(r) => r.uadd(date, *n),\n                None => RollDay::Day(date.day()).uadd(date, *n),\n            },\n            Frequency::Zero {} => ndt(9999, 1, 1),\n        }\n    }\n\n    fn previous(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        match self {\n            Frequency::BusDays {\n                number: n,\n                calendar: c,\n            } => c.lag_bus_days(date, -(*n), false),\n            Frequency::CalDays { number: n } => {\n                let cal = Cal::new(vec![], vec![]);\n                cal.add_cal_days(date, -(*n), &Adjuster::Actual {})\n            }\n            Frequency::Months { number: n, roll: r } => match r {\n                Some(r) => r.uadd(date, -(*n)),\n                None => RollDay::Day(date.day()).uadd(date, -(*n)),\n            },\n            Frequency::Zero {} => ndt(1500, 1, 1),\n        }\n    }\n\n    fn try_uregular(\n        &self,\n        ueffective: &NaiveDateTime,\n        utermination: &NaiveDateTime,\n    ) -> Result<Vec<NaiveDateTime>, PyErr> {\n        match self {\n            Frequency::Zero {} => Ok(vec![*ueffective, *utermination]),\n            _ => self.try_uregular_from_unext(ueffective, utermination),\n        }\n    }\n\n    fn periods_per_annum(&self) -> f64 {\n        match self {\n            Frequency::Zero {} => 0.01_f64,\n            Frequency::Months { number: 1, roll: _ } => 12.0_f64,\n            Frequency::Months { number: 2, roll: _ } => 6.0_f64,\n            Frequency::Months { number: 3, roll: _ } => 4.0_f64,\n            Frequency::Months { number: 4, roll: _ } => 3.0_f64,\n            Frequency::Months { number: 6, roll: _ } => 2.0_f64,\n            Frequency::Months {\n                number: 12,\n                roll: _,\n            } => 1.0_f64,\n            _ => periods_per_annum(self),\n        }\n    }\n}\n\nfn periods_per_annum<T: Scheduling + ?Sized>(obj: &T) -> f64 {\n    let mut date = obj.next(&ndt(1999, 12, 31));\n    if date > ndt(2049, 12, 31) {\n        // then the next method has generated an unusually long period. return nominal value\n        return 0.01_f64;\n    }\n    let estimated_end = date + Months::new(600);\n    let mut counter = 0_f64;\n    let count: f64;\n    loop {\n        counter += 1.0;\n        let prev = date;\n        date = obj.next(&prev);\n        if date < prev {\n            // Scheduling object is reversed so make a correction.\n            date = obj.previous(&prev)\n        }\n        if date >= estimated_end {\n            if (estimated_end - prev) < (date - estimated_end) {\n                count = f64::max(1.0, counter - 1.0);\n            } else {\n                count = counter;\n            }\n            break;\n        }\n    }\n    count / 50.0\n}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::ndt;\n\n    #[test]\n    fn test_try_udate() {\n        let options: Vec<(Frequency, NaiveDateTime)> = vec![\n            (\n                Frequency::BusDays {\n                    number: 4,\n                    calendar: Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                },\n                ndt(2025, 7, 11),\n            ),\n            (Frequency::CalDays { number: 4 }, ndt(2025, 7, 11)),\n            (Frequency::Zero {}, ndt(2025, 7, 11)),\n            (\n                Frequency::Months {\n                    number: 4,\n                    roll: Some(RollDay::Day(11)),\n                },\n                ndt(2025, 7, 11),\n            ),\n        ];\n        for option in options {\n            let result = option.0.try_udate(&option.1).unwrap();\n            assert_eq!(result, option.1);\n        }\n    }\n\n    #[test]\n    fn test_try_udate_err() {\n        let options: Vec<(Frequency, NaiveDateTime)> = vec![\n            (\n                Frequency::BusDays {\n                    number: 4,\n                    calendar: Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                },\n                ndt(2025, 7, 12),\n            ),\n            (\n                Frequency::Months {\n                    number: 4,\n                    roll: None,\n                },\n                ndt(2025, 7, 12),\n            ),\n            (\n                Frequency::Months {\n                    number: 4,\n                    roll: Some(RollDay::IMM {}),\n                },\n                ndt(2025, 7, 1),\n            ),\n        ];\n        for option in options {\n            assert!(option.0.try_udate(&option.1).is_err());\n        }\n    }\n\n    #[test]\n    fn test_is_regular_period_ok() {\n        let options: Vec<(Frequency, NaiveDateTime, NaiveDateTime, bool)> = vec![\n            (\n                Frequency::CalDays { number: 5 },\n                ndt(2000, 1, 1),\n                ndt(2000, 1, 6),\n                true,\n            ),\n            (\n                Frequency::CalDays { number: 5 },\n                ndt(2000, 1, 1),\n                ndt(2000, 1, 5),\n                false,\n            ),\n            (\n                Frequency::Months {\n                    number: 5,\n                    roll: Some(RollDay::Day(1)),\n                },\n                ndt(2000, 1, 1),\n                ndt(2000, 6, 1),\n                true,\n            ),\n            (\n                Frequency::Months {\n                    number: 5,\n                    roll: Some(RollDay::Day(1)),\n                },\n                ndt(2000, 1, 1),\n                ndt(2000, 6, 5),\n                false,\n            ),\n        ];\n\n        for option in options {\n            let result = option.0.is_regular_period(&option.1, &option.2);\n            assert_eq!(result, option.3);\n        }\n    }\n\n    #[test]\n    fn test_is_short_front_stub() {\n        assert_eq!(\n            true,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_short_front_stub(&ndt(2000, 1, 1), &ndt(2000, 1, 20))\n        );\n        assert_eq!(\n            false,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(1))\n            }\n            .is_short_front_stub(&ndt(2000, 1, 1), &ndt(2000, 2, 1))\n        );\n        assert_eq!(\n            false,\n            Frequency::Months {\n                number: 1,\n                roll: None\n            }\n            .is_short_front_stub(&ndt(2000, 1, 1), &ndt(2000, 1, 15))\n        );\n    }\n\n    #[test]\n    fn test_is_long_front_stub() {\n        assert_eq!(\n            // is a valid long stub\n            true,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_long_front_stub(&ndt(2000, 1, 1), &ndt(2000, 2, 20))\n        );\n        assert_eq!(\n            // is a valid 2-regular period long stub\n            true,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_long_front_stub(&ndt(2000, 1, 20), &ndt(2000, 3, 20))\n        );\n        assert_eq!(\n            // is too short\n            false,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_long_front_stub(&ndt(2000, 1, 25), &ndt(2000, 2, 20))\n        );\n        assert_eq!(\n            // is too long\n            false,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_long_front_stub(&ndt(2000, 1, 15), &ndt(2000, 3, 20))\n        );\n    }\n\n    #[test]\n    fn test_is_long_back_stub() {\n        assert_eq!(\n            // is a valid long stub\n            true,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_long_back_stub(&ndt(2000, 1, 20), &ndt(2000, 2, 28))\n        );\n        assert_eq!(\n            // is a valid 2-regular period long stub\n            true,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_long_back_stub(&ndt(2000, 1, 20), &ndt(2000, 3, 20))\n        );\n        assert_eq!(\n            // is too short\n            false,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_long_back_stub(&ndt(2000, 1, 20), &ndt(2000, 2, 10))\n        );\n        assert_eq!(\n            // is too long\n            false,\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::Day(20))\n            }\n            .is_long_front_stub(&ndt(2000, 1, 20), &ndt(2000, 3, 30))\n        );\n    }\n\n    // #[test]\n    // fn test_try_scheduling() {\n    //     let options: Vec<(Frequency, NaiveDateTime, NaiveDateTime)> = vec![\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: None,\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 8, 30),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 2,\n    //                 roll: Some(RollDay::Day { day: 30 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 9, 30),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 3,\n    //                 roll: Some(RollDay::Day { day: 30 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 10, 30),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 4,\n    //                 roll: None,\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 11, 30),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 6,\n    //                 roll: Some(RollDay::Day { day: 30 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2023, 1, 30),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 12,\n    //                 roll: Some(RollDay::Day { day: 30 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2023, 7, 30),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: Some(RollDay::Day { day: 31 }),\n    //             },\n    //             ndt(2022, 6, 30),\n    //             ndt(2022, 7, 31),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: Some(RollDay::IMM {}),\n    //             },\n    //             ndt(2022, 6, 15),\n    //             ndt(2022, 7, 20),\n    //         ),\n    //         (\n    //             Frequency::CalDays { number: 5 },\n    //             ndt(2022, 6, 15),\n    //             ndt(2022, 6, 20),\n    //         ),\n    //         (\n    //             Frequency::CalDays { number: 14 },\n    //             ndt(2022, 6, 15),\n    //             ndt(2022, 6, 29),\n    //         ),\n    //         (\n    //             Frequency::BusDays {\n    //                 number: 5,\n    //                 calendar: Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n    //             },\n    //             ndt(2025, 6, 23),\n    //             ndt(2025, 6, 30),\n    //         ),\n    //         (Frequency::Zero {}, ndt(1500, 1, 1), ndt(9999, 1, 1)),\n    //     ];\n    //     for option in options.iter() {\n    //         assert_eq!(option.2, option.0.try_unext(&option.1).unwrap());\n    //         assert_eq!(option.1, option.0.try_uprevious(&option.2).unwrap());\n    //     }\n    // }\n    //\n    #[test]\n    fn test_get_uschedule_imm() {\n        // test the example given in Coding Interest Rates\n        let result = Frequency::Months {\n            number: 1,\n            roll: Some(RollDay::IMM {}),\n        }\n        .try_uregular(&ndt(2023, 3, 15), &ndt(2023, 9, 20))\n        .unwrap();\n        assert_eq!(\n            result,\n            vec![\n                ndt(2023, 3, 15),\n                ndt(2023, 4, 19),\n                ndt(2023, 5, 17),\n                ndt(2023, 6, 21),\n                ndt(2023, 7, 19),\n                ndt(2023, 8, 16),\n                ndt(2023, 9, 20)\n            ]\n        );\n    }\n    //\n    // #[test]\n    // fn test_get_uschedule() {\n    //     let result = Frequency::Months {\n    //         number: 3,\n    //         roll: Some(RollDay::Day { day: 1 }),\n    //     }\n    //     .try_uregular(&ndt(2000, 1, 1), &ndt(2001, 1, 1))\n    //     .unwrap();\n    //     assert_eq!(\n    //         result,\n    //         vec![\n    //             ndt(2000, 1, 1),\n    //             ndt(2000, 4, 1),\n    //             ndt(2000, 7, 1),\n    //             ndt(2000, 10, 1),\n    //             ndt(2001, 1, 1)\n    //         ]\n    //     );\n    // }\n\n    // #[test]\n    // fn test_infer_ufront() {\n    //     let options: Vec<(\n    //         Frequency,\n    //         NaiveDateTime,\n    //         NaiveDateTime,\n    //         bool,\n    //         Option<NaiveDateTime>,\n    //     )> = vec![\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: Some(RollDay::Day { day: 15 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 10, 15),\n    //             true,\n    //             Some(ndt(2022, 8, 15)),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: None,\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 10, 15),\n    //             false,\n    //             Some(ndt(2022, 9, 15)),\n    //         ),\n    //     ];\n    //\n    //     for option in options.iter() {\n    //         assert_eq!(\n    //             option.4,\n    //             option\n    //                 .0\n    //                 .try_infer_ufront_stub(&option.1, &option.2, option.3)\n    //                 .unwrap()\n    //         );\n    //     }\n    // }\n\n    // #[test]\n    // fn test_infer_ufront_err() {\n    //     let options: Vec<(Frequency, NaiveDateTime, NaiveDateTime, bool)> = vec![\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: Some(RollDay::Day { day: 15 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 8, 15),\n    //             true,\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: None,\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 9, 15),\n    //             false,\n    //         ),\n    //         (\n    //             Frequency::Zero {},\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 9, 15),\n    //             false,\n    //         ),\n    //     ];\n    //\n    //     for option in options.iter() {\n    //         let result = option\n    //             .0\n    //             .try_infer_ufront_stub(&option.1, &option.2, option.3)\n    //             .is_err();\n    //         assert_eq!(true, result);\n    //     }\n    // }\n\n    // #[test]\n    // fn test_infer_uback() {\n    //     let options: Vec<(\n    //         Frequency,\n    //         NaiveDateTime,\n    //         NaiveDateTime,\n    //         bool,\n    //         Option<NaiveDateTime>,\n    //     )> = vec![\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: Some(RollDay::Day { day: 30 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 10, 15),\n    //             true,\n    //             Some(ndt(2022, 9, 30)),\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: Some(RollDay::Day { day: 30 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 10, 15),\n    //             false,\n    //             Some(ndt(2022, 8, 30)),\n    //         ),\n    //     ];\n    //\n    //     for option in options.iter() {\n    //         assert_eq!(\n    //             option.4,\n    //             option\n    //                 .0\n    //                 .try_infer_uback_stub(&option.1, &option.2, option.3)\n    //                 .unwrap()\n    //         );\n    //     }\n    // }\n    //\n    // #[test]\n    // fn test_infer_uback_err() {\n    //     let options: Vec<(Frequency, NaiveDateTime, NaiveDateTime, bool)> = vec![\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: Some(RollDay::Day { day: 30 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 8, 15),\n    //             true,\n    //         ),\n    //         (\n    //             Frequency::Months {\n    //                 number: 1,\n    //                 roll: Some(RollDay::Day { day: 30 }),\n    //             },\n    //             ndt(2022, 7, 30),\n    //             ndt(2022, 9, 15),\n    //             false,\n    //         ),\n    //     ];\n    //\n    //     for option in options.iter() {\n    //         let result = option\n    //             .0\n    //             .try_infer_uback_stub(&option.1, &option.2, option.3)\n    //             .is_err();\n    //         assert_eq!(true, result);\n    //     }\n    // }\n    //\n    #[test]\n    fn test_try_vec_from() {\n        let options: Vec<(Frequency, Vec<NaiveDateTime>, Vec<Frequency>)> = vec![\n            (\n                Frequency::Months {\n                    number: 1,\n                    roll: None,\n                },\n                vec![ndt(2022, 7, 30)],\n                vec![Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(30)),\n                }],\n            ),\n            (\n                Frequency::Months {\n                    number: 1,\n                    roll: None,\n                },\n                vec![ndt(2022, 2, 28)],\n                vec![\n                    Frequency::Months {\n                        number: 1,\n                        roll: Some(RollDay::Day(28)),\n                    },\n                    Frequency::Months {\n                        number: 1,\n                        roll: Some(RollDay::Day(29)),\n                    },\n                    Frequency::Months {\n                        number: 1,\n                        roll: Some(RollDay::Day(30)),\n                    },\n                    Frequency::Months {\n                        number: 1,\n                        roll: Some(RollDay::Day(31)),\n                    },\n                ],\n            ),\n            (\n                Frequency::CalDays { number: 1 },\n                vec![ndt(2022, 2, 28)],\n                vec![Frequency::CalDays { number: 1 }],\n            ),\n        ];\n\n        for option in options.iter() {\n            let result = option.0.try_vec_from(&option.1).unwrap();\n            assert_eq!(option.2, result);\n        }\n    }\n\n    #[test]\n    fn test_try_vec_from_err() {\n        let options: Vec<(Frequency, Vec<NaiveDateTime>)> = vec![(\n            Frequency::Months {\n                number: 1,\n                roll: Some(RollDay::IMM {}),\n            },\n            vec![ndt(2022, 7, 30)],\n        )];\n\n        for option in options.iter() {\n            assert_eq!(true, option.0.try_vec_from(&option.1).is_err());\n        }\n    }\n\n    #[test]\n    fn test_coupons_per_annum() {\n        let options: Vec<(Frequency, f64)> = vec![\n            (Frequency::CalDays { number: 365 }, 1.0),\n            (Frequency::CalDays { number: 182 }, 2.0),\n            (Frequency::CalDays { number: 183 }, 2.0),\n            (Frequency::CalDays { number: 91 }, 4.02),\n            (Frequency::CalDays { number: 28 }, 13.04),\n            (Frequency::CalDays { number: 7 }, 52.18),\n            (\n                Frequency::BusDays {\n                    number: 5,\n                    calendar: Cal::new(vec![], vec![5, 6]).into(),\n                },\n                52.18,\n            ),\n            (\n                Frequency::BusDays {\n                    number: 63,\n                    calendar: Cal::new(vec![], vec![5, 6]).into(),\n                },\n                4.14,\n            ),\n            (\n                Frequency::BusDays {\n                    number: 62,\n                    calendar: Cal::new(vec![], vec![5, 6]).into(),\n                },\n                4.2,\n            ),\n            (\n                Frequency::Months {\n                    number: 1,\n                    roll: None,\n                },\n                12.0,\n            ),\n            (\n                Frequency::Months {\n                    number: 2,\n                    roll: None,\n                },\n                6.0,\n            ),\n            (\n                Frequency::Months {\n                    number: 3,\n                    roll: None,\n                },\n                4.0,\n            ),\n            (\n                Frequency::Months {\n                    number: 4,\n                    roll: None,\n                },\n                3.0,\n            ),\n            (\n                Frequency::Months {\n                    number: 6,\n                    roll: None,\n                },\n                2.0,\n            ),\n            (\n                Frequency::Months {\n                    number: 9,\n                    roll: None,\n                },\n                1.34,\n            ),\n            (\n                Frequency::Months {\n                    number: 12,\n                    roll: None,\n                },\n                1.0,\n            ),\n            (\n                Frequency::Months {\n                    number: 24,\n                    roll: None,\n                },\n                0.5,\n            ),\n            (\n                Frequency::Months {\n                    number: 3,\n                    roll: Some(RollDay::IMM()),\n                },\n                4.0,\n            ),\n            (Frequency::Zero {}, 0.01),\n        ];\n        for option in options {\n            let result = option.0.periods_per_annum();\n            assert_eq!(result, option.1);\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/frequency/imm.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n#![allow(non_camel_case_types)]\n\nuse chrono::prelude::*;\nuse chrono::Months;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse serde::{Deserialize, Serialize};\nuse std::cmp::{Eq, PartialEq};\n\nuse crate::scheduling::ndt;\n\n/// Specifier for IMM date definitions.\n#[pyclass(module = \"rateslib.rs\", eq, from_py_object)]\n#[derive(Debug, Copy, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum Imm {\n    /// 3rd Wednesday of March, June, September and December.\n    ///\n    /// Commonly used by STIR futures in northern hemisphere.\n    Wed3_HMUZ = 0,\n    /// 3rd Wednesday of any calendar month.\n    ///\n    /// Commonly used by STIR futures in northern hemisphere.\n    Wed3 = 1,\n    /// 20th day of March, June, September and December.\n    ///\n    /// Commonly used by CDS.\n    Day20_HMUZ = 2,\n    /// 20th day of March and September.\n    ///\n    /// Commonly used by CDS.\n    Day20_HU = 3,\n    /// 20th day of June and December.\n    ///\n    /// Commonly used by CDS.\n    Day20_MZ = 4,\n    /// 20th day of any calendar month.\n    Day20 = 5,\n    /// 2nd Friday of March, June, September and December.\n    ///\n    /// Commonly used by ASX 90 day AUD bank bill futures.\n    Fri2_HMUZ = 6,\n    /// 2nd Friday of any calendar month.\n    ///\n    /// Commonly used by ASX 90 day AUD bank bill futures.\n    Fri2 = 7,\n    /// 1st Wednesday after the 9th of the month in March, June, September and December.\n    ///\n    /// Commonly used by ASX 90 day NZD bank bill futures.\n    Wed1_Post9_HMUZ = 10,\n    /// 1st Wednesday after the 9th of any calendar month.\n    ///\n    /// Commonly used by ASX 90 day NZD bank bill futures.\n    Wed1_Post9 = 11,\n    /// End of any calendar month.\n    Eom = 8,\n    /// February Leap days.\n    Leap = 9,\n    /// Start of any calendar month.\n    Som = 12,\n}\n\nimpl Imm {\n    /// Check whether a given date aligns with the IMM date definition.\n    pub fn validate(&self, date: &NaiveDateTime) -> bool {\n        let result = self.from_ym_opt(date.year(), date.month());\n        match result {\n            Ok(val) => *date == val,\n            Err(_) => false,\n        }\n    }\n\n    /// Get an IMM date with the appropriate definition from a given month and year.\n    pub fn from_ym_opt(&self, year: i32, month: u32) -> Result<NaiveDateTime, PyErr> {\n        match self {\n            Imm::Wed3_HMUZ => {\n                if month == 3 || month == 6 || month == 9 || month == 12 {\n                    Imm::Wed3.from_ym_opt(year, month)\n                } else {\n                    Err(PyValueError::new_err(\"Must be month Mar, Jun, Sep or Dec.\"))\n                }\n            }\n            Imm::Fri2_HMUZ => {\n                if month == 3 || month == 6 || month == 9 || month == 12 {\n                    Imm::Fri2.from_ym_opt(year, month)\n                } else {\n                    Err(PyValueError::new_err(\"Must be month Mar, Jun, Sep or Dec.\"))\n                }\n            }\n            Imm::Wed1_Post9_HMUZ => {\n                if month == 3 || month == 6 || month == 9 || month == 12 {\n                    Imm::Wed1_Post9.from_ym_opt(year, month)\n                } else {\n                    Err(PyValueError::new_err(\"Must be month Mar, Jun, Sep or Dec.\"))\n                }\n            }\n            Imm::Wed3 => {\n                let w = ndt(year, month, 1).weekday() as u32;\n                let r = if w <= 2 { 17 - w } else { 24 - w };\n                Ok(ndt(year, month, r))\n            }\n            Imm::Fri2 => {\n                let w = ndt(year, month, 1).weekday() as u32;\n                let r = if w <= 4 { 12 - w } else { 19 - w };\n                Ok(ndt(year, month, r))\n            }\n            Imm::Wed1_Post9 => {\n                let w = ndt(year, month, 1).weekday() as u32;\n                let r = if w <= 0 { 10 - w } else { 17 - w };\n                Ok(ndt(year, month, r))\n            }\n            Imm::Day20_HMUZ => {\n                if month == 3 || month == 6 || month == 9 || month == 12 {\n                    Ok(ndt(year, month, 20))\n                } else {\n                    Err(PyValueError::new_err(\"Must be month Mar, Jun, Sep or Dec.\"))\n                }\n            }\n            Imm::Day20_HU => {\n                if month == 3 || month == 9 {\n                    Ok(ndt(year, month, 20))\n                } else {\n                    Err(PyValueError::new_err(\"Must be month Mar, or Sep.\"))\n                }\n            }\n            Imm::Day20_MZ => {\n                if month == 6 || month == 12 {\n                    Ok(ndt(year, month, 20))\n                } else {\n                    Err(PyValueError::new_err(\"Must be month Jun, or Dec.\"))\n                }\n            }\n            Imm::Day20 => Ok(ndt(year, month, 20)),\n            Imm::Eom => {\n                let mut day = 31;\n                let mut date = NaiveDate::from_ymd_opt(year, month, day);\n                while date == None {\n                    day = day - 1;\n                    date = NaiveDate::from_ymd_opt(year, month, day);\n                    if day == 0 {\n                        return Err(PyValueError::new_err(\"`year` or `month` out of range.\"));\n                    }\n                }\n                Ok(date.unwrap().and_hms_opt(0, 0, 0).unwrap())\n            }\n            Imm::Som => {\n                let date = NaiveDate::from_ymd_opt(year, month, 1);\n                match date {\n                    Some(d) => Ok(d.and_hms_opt(0, 0, 0).unwrap()),\n                    None => return Err(PyValueError::new_err(\"`year` or `month` out of range.\")),\n                }\n            }\n            Imm::Leap => {\n                if month != 2 {\n                    Err(PyValueError::new_err(\"Leap is only in `month`:2.\"))\n                } else {\n                    let d = NaiveDate::from_ymd_opt(year, 2, 29);\n                    match d {\n                        None => Err(PyValueError::new_err(\"No Leap in given `year`.\")),\n                        Some(val) => Ok(val.and_hms_opt(0, 0, 0).unwrap()),\n                    }\n                }\n            }\n        }\n    }\n\n    /// Get the IMM date that follows the given ``date``.\n    pub fn next(&self, date: &NaiveDateTime) -> NaiveDateTime {\n        let mut sample = *date;\n        let mut result = self.from_ym_opt(date.year(), date.month());\n        loop {\n            match result {\n                Ok(v) if v > *date => return v,\n                _ => {\n                    sample = sample + Months::new(1);\n                    result = self.from_ym_opt(sample.year(), sample.month());\n                }\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn imm_date_determination() {\n        let options: Vec<(Imm, NaiveDateTime, bool)> = vec![\n            (Imm::Wed3_HMUZ, ndt(2000, 3, 15), true),\n            (Imm::Wed3_HMUZ, ndt(2000, 3, 22), false),\n            (Imm::Wed3_HMUZ, ndt(2000, 3, 8), false),\n            (Imm::Wed3_HMUZ, ndt(2000, 2, 21), false),\n            (Imm::Wed3, ndt(2024, 2, 21), true),\n            (Imm::Wed3, ndt(2000, 3, 15), true),\n            (Imm::Wed3, ndt(2025, 3, 19), true),\n            (Imm::Wed3, ndt(2025, 3, 18), false),\n            (Imm::Day20_HMUZ, ndt(2000, 2, 21), false),\n            (Imm::Day20_HMUZ, ndt(2000, 2, 20), false),\n            (Imm::Day20_HMUZ, ndt(2000, 3, 20), true),\n            (Imm::Day20_HU, ndt(2000, 3, 20), true),\n            (Imm::Day20_HU, ndt(2000, 6, 20), false),\n            (Imm::Day20_MZ, ndt(2000, 3, 20), false),\n            (Imm::Day20_MZ, ndt(2000, 6, 20), true),\n            (Imm::Fri2, ndt(2024, 2, 9), true),\n            (Imm::Fri2, ndt(2024, 12, 13), true),\n            (Imm::Wed1_Post9, ndt(2025, 9, 10), true),\n            (Imm::Wed1_Post9, ndt(2026, 9, 16), true),\n            (Imm::Som, ndt(2025, 9, 1), true),\n            (Imm::Som, ndt(2026, 9, 16), false),\n        ];\n        for option in options {\n            assert_eq!(option.2, option.0.validate(&option.1));\n        }\n    }\n\n    #[test]\n    fn next_check() {\n        let options: Vec<(Imm, NaiveDateTime, NaiveDateTime)> = vec![\n            (Imm::Wed3_HMUZ, ndt(2024, 3, 20), ndt(2024, 6, 19)),\n            (Imm::Wed3_HMUZ, ndt(2024, 3, 19), ndt(2024, 3, 20)),\n            (Imm::Wed3, ndt(2024, 3, 21), ndt(2024, 4, 17)),\n            (Imm::Day20_HU, ndt(2024, 3, 21), ndt(2024, 9, 20)),\n            (Imm::Leap, ndt(2022, 1, 1), ndt(2024, 2, 29)),\n            (Imm::Som, ndt(2022, 1, 1), ndt(2022, 2, 1)),\n        ];\n        for option in options {\n            assert_eq!(option.2, option.0.next(&option.1));\n        }\n    }\n\n    #[test]\n    fn test_is_eom() {\n        assert_eq!(true, Imm::Eom.validate(&ndt(2025, 3, 31)));\n        assert_eq!(false, Imm::Eom.validate(&ndt(2025, 3, 30)));\n    }\n\n    #[test]\n    fn test_get_from() {\n        assert_eq!(ndt(2022, 2, 28), Imm::Eom.from_ym_opt(2022, 2).unwrap());\n        assert_eq!(ndt(2024, 2, 29), Imm::Eom.from_ym_opt(2024, 2).unwrap());\n        assert_eq!(ndt(2022, 4, 30), Imm::Eom.from_ym_opt(2022, 4).unwrap());\n        assert_eq!(ndt(2022, 3, 31), Imm::Eom.from_ym_opt(2022, 3).unwrap());\n        assert_eq!(ndt(2024, 2, 29), Imm::Leap.from_ym_opt(2024, 2).unwrap());\n        assert_eq!(ndt(2024, 2, 1), Imm::Som.from_ym_opt(2024, 2).unwrap());\n        assert!(Imm::Leap.from_ym_opt(2022, 2).is_err());\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/frequency/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nmod frequency;\nmod imm;\nmod rollday;\n\npub use crate::scheduling::frequency::{\n    frequency::{Frequency, Scheduling},\n    imm::Imm,\n    rollday::RollDay,\n};\n\npub(crate) use crate::scheduling::frequency::rollday::get_unadjusteds;\n"
  },
  {
    "path": "rust/scheduling/frequency/rollday.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse chrono::prelude::*;\nuse indexmap::IndexSet;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse serde::{Deserialize, Serialize};\nuse std::cmp::{Eq, PartialEq};\n\nuse crate::scheduling::{Adjuster, Adjustment, Calendar, Imm};\n\n/// A roll-day used with a [`Frequency::Months`](crate::scheduling::Frequency) variant.\n#[pyclass(module = \"rateslib.rs\", eq, from_py_object)]\n#[derive(Debug, Copy, Hash, Clone, PartialEq, Eq, Deserialize, Serialize)]\npub enum RollDay {\n    /// A day of the month in [1, 31].\n    Day(u32),\n    /// The third Wednesday of any month (equivalent to [Imm::Wed3](crate::scheduling::Imm))\n    IMM(),\n}\n\nimpl RollDay {\n    /// Get all possible [`RollDay`] variants implied from one or more unadjusted dates.\n    ///\n    /// # Notes\n    /// Each date is analysed in turn. The order of [`RollDay`] construction for each date is:\n    ///\n    /// - Get the integer roll-day of the date.\n    /// - Get additional end-of-month related integer roll-days for short calendar months if necessary.\n    /// - Get non-numeric roll-days if date aligns with those, ordered by the underlying enum order.\n    ///\n    /// When multiple dates are checked the results for a subsequent date is added to the prior\n    /// results under the [`IndexSet.intersection`] ordering rules.\n    ///\n    /// Any date will always return at least one [RollDay] and the first one will always be\n    /// equivalent to an integer variant whose day equals the calendar day of the first date.\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{RollDay, ndt};\n    /// let result = RollDay::vec_from(&vec![ndt(2024, 2, 29), ndt(2024, 3, 20), ndt(2024, 3, 31)]);\n    /// assert_eq!(result, vec![\n    ///     RollDay::Day(29),\n    ///     RollDay::Day(30),\n    ///     RollDay::Day(31),\n    ///     RollDay::Day(20),\n    ///     RollDay::IMM(),\n    /// ]);\n    /// ```\n    pub fn vec_from(udates: &Vec<NaiveDateTime>) -> Vec<Self> {\n        let mut set: IndexSet<RollDay> = IndexSet::new();\n\n        for udate in udates {\n            // numeric first\n            let mut v: Vec<Self> = vec![RollDay::Day(udate.day())];\n            // EoM check\n            if Imm::Eom.validate(udate) {\n                let mut day = udate.day() + 1;\n                while day < 32 {\n                    v.push(RollDay::Day(day));\n                    day = day + 1;\n                }\n            }\n            // IMM check\n            if Imm::Wed3.validate(udate) {\n                v.push(RollDay::IMM())\n            }\n            // Intersect existing results\n            set.append(&mut IndexSet::<RollDay>::from_iter(v));\n        }\n        set.into_iter().collect()\n    }\n\n    /// Validate whether an unadjusted date is an allowed value under the [`RollDay`] definition.\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{RollDay, ndt};\n    /// let date = RollDay::Day(31).try_udate(&ndt(2024, 2, 29));\n    /// assert!(date.is_ok());\n    ///\n    /// let date = RollDay::IMM().try_udate(&ndt(2024, 1, 1));\n    /// assert!(date.is_err());\n    /// ```\n    pub fn try_udate(&self, udate: &NaiveDateTime) -> Result<NaiveDateTime, PyErr> {\n        let msg = \"`udate` does not align with given `RollDay`.\".to_string();\n        match self {\n            RollDay::Day(31) => {\n                if Imm::Eom.validate(udate) {\n                    Ok(*udate)\n                } else {\n                    Err(PyValueError::new_err(msg))\n                }\n            }\n            RollDay::Day(30) => {\n                if (Imm::Eom.validate(udate) && udate.day() < 30) || udate.day() == 30 {\n                    Ok(*udate)\n                } else {\n                    Err(PyValueError::new_err(msg))\n                }\n            }\n            RollDay::Day(29) => {\n                if (Imm::Eom.validate(udate) && udate.day() < 29) || udate.day() == 29 {\n                    Ok(*udate)\n                } else {\n                    Err(PyValueError::new_err(msg))\n                }\n            }\n            RollDay::IMM() => {\n                if Imm::Wed3.validate(udate) {\n                    Ok(*udate)\n                } else {\n                    Err(PyValueError::new_err(msg))\n                }\n            }\n            RollDay::Day(value) => {\n                if udate.day() == *value {\n                    Ok(*udate)\n                } else {\n                    Err(PyValueError::new_err(msg))\n                }\n            }\n        }\n    }\n\n    /// Add a given number of months to an unadjusted date under the [RollDay] definition.\n    ///\n    /// # Notes\n    /// This method will also check the given `udate` using [RollDay::try_udate].\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{RollDay, ndt};\n    /// let date = RollDay::IMM().try_uadd(&ndt(2024, 3, 20), 3);\n    /// assert_eq!(ndt(2024, 6, 19), date.unwrap());\n    ///\n    /// let date = RollDay::Day(31).try_uadd(&ndt(2024, 3, 15), 3);\n    /// assert!(date.is_err());\n    /// ```\n    pub fn try_uadd(&self, udate: &NaiveDateTime, months: i32) -> Result<NaiveDateTime, PyErr> {\n        let _ = self.try_udate(udate)?;\n        Ok(self.uadd(udate, months))\n    }\n\n    /// Add a given number of months to an unadjusted date under the [RollDay] definition.\n    ///\n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{RollDay, ndt};\n    /// let date = RollDay::Day(31).uadd(&ndt(2024, 3, 15), 3);\n    /// assert_eq!(date, ndt(2024, 6, 30));\n    /// ```\n    pub fn uadd(&self, udate: &NaiveDateTime, months: i32) -> NaiveDateTime {\n        // convert months to a set of years and remainder months\n        let mut yr_roll = (months.abs() / 12) * months.signum();\n        let rem_months = months - yr_roll * 12;\n\n        // determine the new month\n        let mut new_month = i32::try_from(udate.month()).unwrap() + rem_months;\n        if new_month <= 0 {\n            yr_roll -= 1;\n            new_month = new_month.rem_euclid(12);\n        } else if new_month >= 13 {\n            yr_roll += 1;\n            new_month = new_month.rem_euclid(12);\n        }\n        if new_month == 0 {\n            new_month = 12;\n        }\n\n        // perform the date roll\n        self.try_from_ym(udate.year() + yr_roll, new_month.try_into().unwrap())\n            .unwrap()\n    }\n\n    /// Return a specific date given the `month`, `year` that aligns with the [RollDay].\n    ///     \n    /// # Examples\n    /// ```rust\n    /// # use rateslib::scheduling::{RollDay, ndt};\n    /// let date = RollDay::Day(31).try_from_ym(2024, 2);\n    /// # let date = date.unwrap();\n    /// assert_eq!(date, ndt(2024, 2, 29));\n    /// ```\n    pub fn try_from_ym(&self, year: i32, month: u32) -> Result<NaiveDateTime, PyErr> {\n        match self {\n            RollDay::Day(value) => Ok(get_roll_by_day(year, month, *value)),\n            RollDay::IMM {} => Imm::Wed3.from_ym_opt(year, month),\n        }\n    }\n}\n\n/// Get unadjusted date alternatives for an associated adjusted date.\n///\n/// Note this only handles simple date rolling operations, and does not generalise to any\n/// possible adjuster.\npub(crate) fn get_unadjusteds(\n    date: &NaiveDateTime,\n    adjuster: &Adjuster,\n    calendar: &Calendar,\n) -> Vec<NaiveDateTime> {\n    let mut udates: Vec<NaiveDateTime> = vec![];\n\n    // always return at least `date`\n    udates.push(*date);\n\n    // get the vector of reversals and filter out date\n    let reversals: Vec<NaiveDateTime> = adjuster\n        .reverse(date, calendar)\n        .into_iter()\n        .filter(|v| v != date)\n        .collect();\n    udates.extend(reversals);\n    udates\n}\n\n/// Return a specific roll date given the `month`, `year` and `roll`.\nfn get_roll_by_day(year: i32, month: u32, day: u32) -> NaiveDateTime {\n    let d = NaiveDate::from_ymd_opt(year, month, day);\n    match d {\n        Some(date) => NaiveDateTime::new(date, NaiveTime::from_hms_opt(0, 0, 0).unwrap()),\n        None => {\n            if day > 28 {\n                get_roll_by_day(year, month, day - 1)\n            } else {\n                panic!(\"Unexpected error in `get_roll_by_day`\")\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::{ndt, Cal};\n\n    fn fixture_bus_cal() -> Calendar {\n        Cal::try_from_name(\"bus\").unwrap().into()\n    }\n\n    #[test]\n    fn test_rollday_equality() {\n        let rd1 = RollDay::IMM();\n        let rd2 = RollDay::IMM();\n        assert_eq!(rd1, rd2);\n\n        let rd1 = RollDay::IMM();\n        let rd2 = RollDay::Day(21);\n        assert_ne!(rd1, rd2);\n\n        let rd1 = RollDay::Day(20);\n        let rd2 = RollDay::Day(20);\n        assert_eq!(rd1, rd2);\n\n        let rd1 = RollDay::Day(21);\n        let rd2 = RollDay::Day(9);\n        assert_ne!(rd1, rd2);\n    }\n\n    #[test]\n    fn test_rollday_try_udate() {\n        let options: Vec<(RollDay, NaiveDateTime)> = vec![\n            (RollDay::Day(15), ndt(2000, 3, 15)),\n            (RollDay::Day(31), ndt(2000, 3, 31)),\n            (RollDay::Day(31), ndt(2022, 2, 28)),\n            (RollDay::Day(30), ndt(2024, 2, 29)),\n            (RollDay::Day(31), ndt(2024, 2, 29)),\n        ];\n        for option in options {\n            assert_eq!(false, option.0.try_udate(&option.1).is_err());\n        }\n    }\n\n    #[test]\n    fn test_get_unadjusteds() {\n        let options: Vec<(NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (ndt(2000, 2, 29), vec![ndt(2000, 2, 29)]),\n            (\n                ndt(2025, 11, 28),\n                vec![ndt(2025, 11, 28), ndt(2025, 11, 29), ndt(2025, 11, 30)],\n            ),\n            (\n                ndt(2025, 2, 3),\n                vec![ndt(2025, 2, 3), ndt(2025, 2, 2), ndt(2025, 2, 1)],\n            ),\n        ];\n\n        for option in options {\n            let result = get_unadjusteds(\n                &option.0,\n                &Adjuster::ModifiedFollowing {},\n                &fixture_bus_cal(),\n            );\n\n            assert_eq!(result, option.1);\n        }\n    }\n\n    #[test]\n    fn test_vec_from() {\n        let options: Vec<(Vec<NaiveDateTime>, Vec<RollDay>)> = vec![\n            (\n                vec![ndt(2000, 2, 29)],\n                vec![RollDay::Day(29), RollDay::Day(30), RollDay::Day(31)],\n            ),\n            (vec![ndt(2025, 11, 28)], vec![RollDay::Day(28)]),\n            (\n                vec![ndt(2025, 3, 19)],\n                vec![RollDay::Day(19), RollDay::IMM {}],\n            ),\n            (vec![ndt(2025, 9, 15)], vec![RollDay::Day(15)]),\n        ];\n\n        for option in options {\n            let result = RollDay::vec_from(&option.0);\n            assert_eq!(result, option.1);\n        }\n    }\n\n    #[test]\n    fn test_vec_from_multiple() {\n        let options: Vec<(Vec<NaiveDateTime>, Vec<RollDay>)> = vec![\n            (\n                vec![ndt(2000, 2, 29)],\n                vec![RollDay::Day(29), RollDay::Day(30), RollDay::Day(31)],\n            ),\n            (\n                vec![ndt(2025, 11, 28), ndt(2025, 11, 29), ndt(2025, 11, 30)],\n                vec![\n                    RollDay::Day(28),\n                    RollDay::Day(29),\n                    RollDay::Day(30),\n                    RollDay::Day(31),\n                ],\n            ),\n            (\n                vec![ndt(2025, 3, 19)],\n                vec![RollDay::Day(19), RollDay::IMM()],\n            ),\n            (\n                vec![ndt(2025, 9, 15), ndt(2025, 9, 14), ndt(2025, 9, 13)],\n                vec![RollDay::Day(15), RollDay::Day(14), RollDay::Day(13)],\n            ),\n        ];\n\n        for option in options {\n            let result = RollDay::vec_from(&option.0);\n            assert_eq!(result, option.1);\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Create a business day [`Calendar`], instrument [`Schedule`] and perform financial date manipulation.\n//!\n//! The purpose of this module is to provide objects which are capable of replicating all of the\n//! complexities of financial instrument specification, including examples such as;\n//! - FX spot determination including all of the various currency pair rules.\n//! - Business day calendar combination for multi-currency derivatives.\n//! - Standard schedule generation including all of the accrual and payment [`Adjuster`] rules, like\n//!   *modified following*, CDS's unadjusted last period etc.\n//! - Inference for stub dates and monthly [`RollDay`] when utilising a UI which extends to users\n//!   being allowed to supply unknown or ambiguous parameters.\n//!\n//! # Calendars and Date Adjustment\n//!\n//! ## Calendars\n//!\n//! *Rateslib* provides three calendar types: [`Cal`], [`UnionCal`] and [`NamedCal`] and the container\n//! enum [`Calendar`]. These are based on simple holiday and weekend specification and union rules\n//! for combinations. Some common calendars are implemented directly by name, and can be combined\n//! with string parsing syntax.\n//!\n//! All calendars implement the [`DateRoll`] trait which provide simple date adjustment, which\n//! *rateslib* calls **rolling**. This involves moving forward or backward from non-business days\n//! (or non-settleable days) to specific **business days** or **settleable business days**.\n//!\n//! ### Example\n//! This example creates a business day calendar defining Saturday and Sunday weekends and a\n//! specific holiday (the Early May UK Bank Holiday). It uses a date rolling method to\n//! manipulate Saturday 29th April 2017 under the *'following'* and *'modified following'* rules.\n//! ```rust\n//! # use rateslib::scheduling::{Cal, ndt, DateRoll};\n//! let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);\n//! assert_eq!(ndt(2017, 5, 2), cal.roll_forward_bus_day(&ndt(2017, 4, 29)));\n//! assert_eq!(ndt(2017, 4, 28), cal.roll_mod_forward_bus_day(&ndt(2017, 4, 29)));\n//! ```\n//!\n//! ## Date Adjustment\n//!\n//! Date adjustment allows for a more complicated set of rules than simple date rolling.\n//! The [`Adjuster`] is an enum which defines the implementation of all of these rules and may\n//! be extended in the future if more rules are required for more complex instruments. It\n//! implements the [`Adjustment`] trait requiring some object capable of performing [`DateRoll`] to\n//! define the operations.\n//!\n//! All [`Calendar`] types implement the [`CalendarAdjustment`] trait which permits date\n//! adjustment when an [`Adjuster`] is cross-provided.\n//!\n//! ### Example\n//! This example performs the complex rule of adjusting a given date forward by 5 calendar days\n//! and then rolling that result forward to the next settleable business day.\n//! ```rust\n//! # use rateslib::scheduling::{Cal, ndt, Adjuster, CalendarAdjustment};\n//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);\n//! let adjuster = Adjuster::CalDaysLagSettle(5);\n//! assert_eq!(ndt(2017, 5, 2), cal.adjust(&ndt(2017, 4, 27), &adjuster));\n//! assert_eq!(ndt(2017, 5, 2), cal.adjust(&ndt(2017, 4, 24), &adjuster));\n//! ```\n//!\n//! # Schedules\n//!\n//! A [`Schedule`] is an ordered and patterned array of periods and dates.\n//!\n//! All [`Schedule`] objects in *rateslib* are centered about the definition of their [`Frequency`],\n//! which is an enum describing a regular period of time. Certain [`Frequency`] variants have\n//! additional information to fully parametrise them. For example a [`Frequency::BusDays`](Frequency) variant\n//! requires a [`Calendar`] to define its valid days, and a [`Frequency::Months`](Frequency) variant requires\n//! a [`RollDay`] to define the day in the month that separates its periods.\n//!\n//! The [`Frequency`] implements the [`Scheduling`] trait which allows periods and stubs to be\n//! defined, alluding to the documented definition of **regular** and **irregular** schedules as\n//! well as permitting the pattern of periods that can form a valid [`Schedule`].\n//!\n//! ### Example\n//! This example creates a new [`Schedule`] by inferring that it can be constructed as a **regular schedule**\n//! (one without stubs) if the [`RollDay`] is asserted to be the [`RollDay::IMM`](RollDay) variant.\n//! Without an *IMM* roll-day this schedule would be irregular with a short front stub.\n//! ```rust\n//! # use rateslib::scheduling::{Cal, ndt, Adjuster, Frequency, Schedule, RollDay, StubInference, Calendar};\n//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);\n//! let schedule = Schedule::try_new_inferred(\n//!    ndt(2024, 3, 20),                        // effective\n//!    ndt(2025, 9, 17),                        // termination\n//!    Frequency::Months{number:3, roll: None}, // frequency\n//!    None,                                    // front_stub\n//!    None,                                    // back_stub\n//!    Calendar::Cal(cal),                      // calendar\n//!    Adjuster::ModifiedFollowing{},           // accrual_adjuster\n//!    Adjuster::BusDaysLagSettle(2),           // payment_adjuster\n//!    Adjuster::Actual{},                      // payment_adjuster2\n//!    None,                                    // payment_adjuster3\n//!    false,                                   // eom\n//!    StubInference::ShortFront,               // stub_inference\n//! );\n//! # let schedule = schedule.unwrap();\n//! assert_eq!(schedule.frequency, Frequency::Months{number:3, roll: Some(RollDay::IMM())});\n//! assert!(schedule.is_regular());\n//! ```\n//! The next example creates a new [`Schedule`] by inferring that its `termination` is an adjusted\n//! end-of-month date, and therefore its [`RollDay`] is asserted to be the [`RollDay::Day(31)`](RollDay)\n//! variant, and its `utermination` is therefore 30th November and it infers a `ufront_stub` correctly\n//! as 31st May 2025.\n//! ```rust\n//! # use rateslib::scheduling::{Cal, ndt, Adjuster, Frequency, Schedule, RollDay, StubInference, Calendar};\n//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);\n//! let schedule = Schedule::try_new_inferred(\n//!    ndt(2025, 4, 15),                        // effective\n//!    ndt(2025, 11, 28),                       // termination\n//!    Frequency::Months{number:3, roll: None}, // frequency\n//!    None,                                    // front_stub\n//!    None,                                    // back_stub\n//!    Calendar::Cal(cal),                      // calendar\n//!    Adjuster::ModifiedFollowing{},           // accrual_adjuster\n//!    Adjuster::BusDaysLagSettle(2),           // payment_adjuster\n//!    Adjuster::Actual{},                      // payment_adjuster2\n//!    None,                                    // payment_adjuster3\n//!    true,                                    // eom\n//!    StubInference::ShortFront,               // stub_inference\n//! );\n//! # let schedule = schedule.unwrap();\n//! assert_eq!(schedule.frequency, Frequency::Months{number:3, roll: Some(RollDay::Day(31))});\n//! assert_eq!(schedule.utermination, ndt(2025, 11, 30));\n//! assert_eq!(schedule.ufront_stub, Some(ndt(2025, 5, 31)));\n//! ```\n\nmod calendars;\nmod convention;\nmod frequency;\nmod schedule;\n\nmod serde;\n\npub(crate) mod py;\n\npub(crate) use crate::scheduling::{\n    calendars::CalWrapper, frequency::get_unadjusteds, py::PyAdjuster,\n};\npub use crate::scheduling::{\n    calendars::{\n        ndt, Adjuster, Adjustment, Cal, Calendar, CalendarAdjustment, CalendarManager, DateRoll,\n        NamedCal, UnionCal,\n    },\n    convention::Convention,\n    frequency::{Frequency, Imm, RollDay, Scheduling},\n    schedule::{Schedule, StubInference},\n};\n"
  },
  {
    "path": "rust/scheduling/py/adjuster.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper module to export to Python using pyo3 bindings.\n\nuse crate::json::{DeserializedObj, JSON};\nuse crate::scheduling::{Adjuster, Adjustment, Calendar};\nuse chrono::NaiveDateTime;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::types::PyTuple;\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\n\n/// Python wrapper for Adjuster to facilitate complex enum pickling.\n#[pyclass(module = \"rateslib.rs\", name = \"Adjuster\", eq, from_py_object)]\n#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]\npub(crate) enum PyAdjuster {\n    #[pyo3(constructor = (_u8=0))]\n    Actual { _u8: u8 },\n    #[pyo3(constructor = (_u8=1))]\n    Following { _u8: u8 },\n    #[pyo3(constructor = (_u8=2))]\n    ModifiedFollowing { _u8: u8 },\n    #[pyo3(constructor = (_u8=3))]\n    Previous { _u8: u8 },\n    #[pyo3(constructor = (_u8=4))]\n    ModifiedPrevious { _u8: u8 },\n    #[pyo3(constructor = (_u8=5))]\n    FollowingSettle { _u8: u8 },\n    #[pyo3(constructor = (_u8=6))]\n    ModifiedFollowingSettle { _u8: u8 },\n    #[pyo3(constructor = (_u8=7))]\n    PreviousSettle { _u8: u8 },\n    #[pyo3(constructor = (_u8=8))]\n    ModifiedPreviousSettle { _u8: u8 },\n    #[pyo3(constructor = (number, _u8=9))]\n    BusDaysLagSettle { number: i32, _u8: u8 },\n    #[pyo3(constructor = (number, _u8=10))]\n    CalDaysLagSettle { number: i32, _u8: u8 },\n    #[pyo3(constructor = (_u8=11))]\n    FollowingExLast { _u8: u8 },\n    #[pyo3(constructor = (_u8=12))]\n    FollowingExLastSettle { _u8: u8 },\n    #[pyo3(constructor = (number, _u8=13))]\n    BusDaysLagSettleInAdvance { number: i32, _u8: u8 },\n}\n\n/// Used for providing pickle support for PyAdjuster\nenum PyAdjusterNewArgs {\n    NoArgs(u8),\n    I32(i32, u8),\n}\n\nimpl<'py> IntoPyObject<'py> for PyAdjusterNewArgs {\n    type Target = PyTuple;\n    type Output = Bound<'py, Self::Target>;\n    type Error = std::convert::Infallible;\n\n    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {\n        match self {\n            PyAdjusterNewArgs::NoArgs(x) => Ok((x,).into_pyobject(py).unwrap()),\n            PyAdjusterNewArgs::I32(x, y) => Ok((x, y).into_pyobject(py).unwrap()),\n        }\n    }\n}\n\nimpl<'py> FromPyObject<'py, 'py> for PyAdjusterNewArgs {\n    type Error = PyErr;\n\n    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {\n        let ext: PyResult<(u8,)> = obj.extract();\n        if ext.is_ok() {\n            let (x,) = ext.unwrap();\n            return Ok(PyAdjusterNewArgs::NoArgs(x));\n        }\n        let ext: PyResult<(i32, u8)> = obj.extract();\n        if ext.is_ok() {\n            let (x, y) = ext.unwrap();\n            return Ok(PyAdjusterNewArgs::I32(x, y));\n        }\n        Err(PyValueError::new_err(\"Undefined behaviour\"))\n    }\n}\n\nimpl From<Adjuster> for PyAdjuster {\n    fn from(value: Adjuster) -> Self {\n        match value {\n            Adjuster::Actual {} => PyAdjuster::Actual { _u8: 0 },\n            Adjuster::Following {} => PyAdjuster::Following { _u8: 1 },\n            Adjuster::ModifiedFollowing {} => PyAdjuster::ModifiedFollowing { _u8: 2 },\n            Adjuster::Previous {} => PyAdjuster::Previous { _u8: 3 },\n            Adjuster::ModifiedPrevious {} => PyAdjuster::ModifiedPrevious { _u8: 4 },\n            Adjuster::FollowingSettle {} => PyAdjuster::FollowingSettle { _u8: 5 },\n            Adjuster::ModifiedFollowingSettle {} => PyAdjuster::ModifiedFollowingSettle { _u8: 6 },\n            Adjuster::PreviousSettle {} => PyAdjuster::PreviousSettle { _u8: 7 },\n            Adjuster::ModifiedPreviousSettle {} => PyAdjuster::ModifiedPreviousSettle { _u8: 8 },\n            Adjuster::BusDaysLagSettle(n) => PyAdjuster::BusDaysLagSettle { number: n, _u8: 9 },\n            Adjuster::CalDaysLagSettle(n) => PyAdjuster::CalDaysLagSettle { number: n, _u8: 10 },\n            Adjuster::FollowingExLast {} => PyAdjuster::FollowingExLast { _u8: 11 },\n            Adjuster::FollowingExLastSettle {} => PyAdjuster::FollowingExLastSettle { _u8: 12 },\n            Adjuster::BusDaysLagSettleInAdvance(n) => {\n                PyAdjuster::BusDaysLagSettleInAdvance { number: n, _u8: 13 }\n            }\n        }\n    }\n}\n\nimpl From<PyAdjuster> for Adjuster {\n    fn from(value: PyAdjuster) -> Self {\n        match value {\n            PyAdjuster::Actual { _u8: _ } => Adjuster::Actual {},\n            PyAdjuster::Following { _u8: _ } => Adjuster::Following {},\n            PyAdjuster::ModifiedFollowing { _u8: _ } => Adjuster::ModifiedFollowing {},\n            PyAdjuster::Previous { _u8: _ } => Adjuster::Previous {},\n            PyAdjuster::ModifiedPrevious { _u8: _ } => Adjuster::ModifiedPrevious {},\n            PyAdjuster::FollowingSettle { _u8: _ } => Adjuster::FollowingSettle {},\n            PyAdjuster::ModifiedFollowingSettle { _u8: _ } => Adjuster::ModifiedFollowingSettle {},\n            PyAdjuster::PreviousSettle { _u8: _ } => Adjuster::PreviousSettle {},\n            PyAdjuster::ModifiedPreviousSettle { _u8: _ } => Adjuster::ModifiedPreviousSettle {},\n            PyAdjuster::BusDaysLagSettle { number: n, _u8: _ } => Adjuster::BusDaysLagSettle(n),\n            PyAdjuster::CalDaysLagSettle { number: n, _u8: _ } => Adjuster::CalDaysLagSettle(n),\n            PyAdjuster::FollowingExLast { _u8: _ } => Adjuster::FollowingExLast {},\n            PyAdjuster::FollowingExLastSettle { _u8: _ } => Adjuster::FollowingExLastSettle {},\n            PyAdjuster::BusDaysLagSettleInAdvance { number: n, _u8: _ } => {\n                Adjuster::BusDaysLagSettleInAdvance(n)\n            }\n        }\n    }\n}\n\n#[pymethods]\nimpl PyAdjuster {\n    /// Return a `date` under a date adjustment rule.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     Date to adjust.\n    /// calendar: Cal, UnionCal or NamedCal\n    ///     The calendar to assist with date adjustment.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"adjust\")]\n    fn adjust_py(&self, date: NaiveDateTime, calendar: Calendar) -> NaiveDateTime {\n        let adjuster: Adjuster = (*self).into();\n        adjuster.adjust(&date, &calendar)\n    }\n\n    /// Return a list of `dates` which result in ``date`` when the adjustment is applied.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     Date to reverse to detect possible unadjusted dates.\n    /// calendar: Cal, UnionCal or NamedCal\n    ///     The calendar to assist with date adjustment.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"reverse\")]\n    fn reverse_py(&self, date: NaiveDateTime, calendar: Calendar) -> Vec<NaiveDateTime> {\n        let adjuster: Adjuster = (*self).into();\n        adjuster.reverse(&date, &calendar)\n    }\n\n    /// Return a vector of `dates` adjusted under a date adjustment rule.\n    ///\n    /// Parameters\n    /// ----------\n    /// dates: list[datetime]\n    ///     Dates to adjust.\n    /// calendar: Cal, UnionCal or NamedCal\n    ///     The calendar to assist with date adjustment.\n    ///\n    /// Returns\n    /// -------\n    /// list[datetime]\n    #[pyo3(name = \"adjusts\")]\n    fn adjusts_py(&self, dates: Vec<NaiveDateTime>, calendar: Calendar) -> Vec<NaiveDateTime> {\n        let adjuster: Adjuster = (*self).into();\n        adjuster.adjusts(&dates, &calendar)\n    }\n\n    fn __str__(&self) -> String {\n        match self {\n            PyAdjuster::Actual { _u8: _ } => \"NONE\".to_string(),\n            PyAdjuster::Following { _u8: _ } => \"F\".to_string(),\n            PyAdjuster::Previous { _u8: _ } => \"P\".to_string(),\n            PyAdjuster::ModifiedFollowing { _u8: _ } => \"MF\".to_string(),\n            PyAdjuster::ModifiedPrevious { _u8: _ } => \"MP\".to_string(),\n            PyAdjuster::FollowingSettle { _u8: _ } => \"FSETTLE\".to_string(),\n            PyAdjuster::PreviousSettle { _u8: _ } => \"PSETTLE\".to_string(),\n            PyAdjuster::ModifiedFollowingSettle { _u8: _ } => \"MFSETTLE\".to_string(),\n            PyAdjuster::ModifiedPreviousSettle { _u8: _ } => \"FSETTLE\".to_string(),\n            PyAdjuster::BusDaysLagSettle { number: n, _u8: _ } => format!(\"{n}B\"),\n            PyAdjuster::CalDaysLagSettle { number: n, _u8: _ } => format!(\"{n}D\"),\n            PyAdjuster::FollowingExLast { _u8: _ } => format!(\"FEX\"),\n            PyAdjuster::FollowingExLastSettle { _u8: _ } => format!(\"FEXSETTLE\"),\n            PyAdjuster::BusDaysLagSettleInAdvance { number: n, _u8: _ } => format!(\"IA{n}B\"),\n        }\n    }\n\n    fn __getnewargs__(&self) -> PyAdjusterNewArgs {\n        match self {\n            PyAdjuster::Actual { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::Following { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::Previous { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::ModifiedFollowing { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::ModifiedPrevious { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::FollowingSettle { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::PreviousSettle { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::ModifiedFollowingSettle { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::ModifiedPreviousSettle { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::BusDaysLagSettle { number: n, _u8: u } => PyAdjusterNewArgs::I32(*n, *u),\n            PyAdjuster::CalDaysLagSettle { number: n, _u8: u } => PyAdjusterNewArgs::I32(*n, *u),\n            PyAdjuster::FollowingExLast { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::FollowingExLastSettle { _u8: u } => PyAdjusterNewArgs::NoArgs(*u),\n            PyAdjuster::BusDaysLagSettleInAdvance { number: n, _u8: u } => {\n                PyAdjusterNewArgs::I32(*n, *u)\n            }\n        }\n    }\n\n    #[new]\n    fn new_py(args: PyAdjusterNewArgs) -> PyAdjuster {\n        match args {\n            PyAdjusterNewArgs::NoArgs(0) => PyAdjuster::Actual { _u8: 0 },\n            PyAdjusterNewArgs::NoArgs(1) => PyAdjuster::Following { _u8: 1 },\n            PyAdjusterNewArgs::NoArgs(2) => PyAdjuster::Previous { _u8: 2 },\n            PyAdjusterNewArgs::NoArgs(3) => PyAdjuster::ModifiedFollowing { _u8: 3 },\n            PyAdjusterNewArgs::NoArgs(4) => PyAdjuster::ModifiedPrevious { _u8: 4 },\n            PyAdjusterNewArgs::NoArgs(5) => PyAdjuster::FollowingSettle { _u8: 5 },\n            PyAdjusterNewArgs::NoArgs(6) => PyAdjuster::PreviousSettle { _u8: 6 },\n            PyAdjusterNewArgs::NoArgs(7) => PyAdjuster::ModifiedFollowingSettle { _u8: 7 },\n            PyAdjusterNewArgs::NoArgs(8) => PyAdjuster::ModifiedPreviousSettle { _u8: 8 },\n            PyAdjusterNewArgs::I32(n, 9) => PyAdjuster::BusDaysLagSettle { number: n, _u8: 9 },\n            PyAdjusterNewArgs::I32(n, 10) => PyAdjuster::CalDaysLagSettle { number: n, _u8: 10 },\n            PyAdjusterNewArgs::NoArgs(11) => PyAdjuster::FollowingExLast { _u8: 11 },\n            PyAdjusterNewArgs::NoArgs(12) => PyAdjuster::FollowingExLastSettle { _u8: 12 },\n            PyAdjusterNewArgs::I32(n, 13) => {\n                PyAdjuster::BusDaysLagSettleInAdvance { number: n, _u8: 13 }\n            }\n            _ => panic!(\"Undefined behaviour.\"),\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        let adjuster: Adjuster = (*self).into();\n        format!(\"<rl.Adjuster.{:?} at {:p}>\", adjuster, self)\n    }\n\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::PyAdjuster(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `Adjuster` to JSON.\",\n            )),\n        }\n    }\n}\n\n/// This function appears to be unused.\npub(crate) fn get_roll_adjuster_from_str(input: (&str, bool)) -> Result<Adjuster, PyErr> {\n    let hmap: HashMap<(&str, bool), Adjuster> = HashMap::from([\n        ((\"act\", true), Adjuster::Actual {}),\n        ((\"actual\", true), Adjuster::Actual {}),\n        ((\"f\", true), Adjuster::FollowingSettle {}),\n        ((\"mf\", true), Adjuster::ModifiedFollowingSettle {}),\n        ((\"p\", true), Adjuster::PreviousSettle {}),\n        ((\"mp\", true), Adjuster::ModifiedPreviousSettle {}),\n        ((\"act\", false), Adjuster::Actual {}),\n        ((\"actual\", false), Adjuster::Actual {}),\n        ((\"f\", false), Adjuster::Following {}),\n        ((\"mf\", false), Adjuster::ModifiedFollowing {}),\n        ((\"p\", false), Adjuster::Previous {}),\n        ((\"mp\", false), Adjuster::ModifiedPrevious {}),\n        ((\"fex\", false), Adjuster::FollowingExLast {}),\n        ((\"fex\", true), Adjuster::FollowingExLastSettle {}),\n    ]);\n    match hmap.get(&input) {\n        None => Err(PyValueError::new_err(format!(\n            \"'{}', with '{}' settlement, is not found in the list of allowed roll adjusters.\",\n            input.0, input.1\n        ))),\n        Some(value) => Ok(*value),\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/py/calendar.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper module to export to Python using pyo3 bindings.\n\nuse crate::json::json_py::DeserializedObj;\nuse crate::json::JSON;\nuse crate::scheduling::py::adjuster::get_roll_adjuster_from_str;\nuse crate::scheduling::{\n    Adjuster, Adjustment, Cal, CalWrapper, Calendar, CalendarAdjustment, CalendarManager, DateRoll,\n    NamedCal, PyAdjuster, RollDay, UnionCal,\n};\nuse chrono::NaiveDateTime;\nuse indexmap::set::IndexSet;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::types::PyType;\nuse std::collections::HashSet;\nuse std::sync::Arc;\n\n#[pymethods]\nimpl CalendarManager {\n    /// Create a new calendar manager object.\n    ///\n    /// .. warning::\n    ///\n    ///    Use the ``calendars`` object specifically. It is not be necessary to create your\n    ///    own calendar manager, which refers to the same underlying data on the heap.\n    ///\n    #[new]\n    fn new_py() -> Self {\n        CalendarManager::new()\n    }\n\n    /// Add a :class:`~rateslib.scheduling.Cal` to the calendar manager.\n    ///\n    /// Parameters\n    /// -----------\n    /// name: str\n    ///     The name of the calendar to add, cannot use a comma (',') or pipe ('|') character.\n    /// calendar: Cal\n    ///     The :class:`~rateslib.scheduling.Cal` object to add to the manager.\n    ///\n    /// Returns\n    /// --------\n    /// None\n    #[pyo3(name = \"add\")]\n    fn add_py(&self, name: &str, calendar: Cal) -> PyResult<()> {\n        self.add(name, calendar)\n    }\n\n    /// Pop a :class:`~rateslib.scheduling.Cal` or :class:`~rateslib.scheduling.UnionCal`\n    /// from the calendar manager.\n    ///\n    /// Parameters\n    /// -----------\n    /// name: str\n    ///     The name of the calendar to remove, which already exists in the manager.\n    ///\n    /// Returns\n    /// --------\n    /// Cal, UnionCal\n    #[pyo3(name = \"pop\")]\n    pub fn pop_py(&self, name: &str) -> Result<Calendar, PyErr> {\n        self.pop(name)\n    }\n\n    /// Get a :class:`~rateslib.scheduling.NamedCal` from the calendar manager.\n    ///\n    /// Parameters\n    /// -----------\n    /// name: str\n    ///     The name of the calendar to lookup.\n    ///\n    /// Returns\n    /// --------\n    /// NamedCal\n    #[pyo3(name = \"get\")]\n    pub fn get_py(&self, name: &str) -> Result<NamedCal, PyErr> {\n        self.get_with_insert(name)\n    }\n\n    fn __contains__(&self, item: &str) -> bool {\n        self.contains_key(item)\n    }\n\n    /// Get a list of calendar names in the map.\n    ///\n    /// Returns\n    /// --------\n    /// list of str\n    #[pyo3(name = \"keys\")]\n    fn keys_py(&self) -> Vec<String> {\n        self.keys()\n    }\n}\n\n#[pymethods]\nimpl Cal {\n    /// Create a new *Cal* object.\n    ///\n    /// Parameters\n    /// ----------\n    /// holidays: list[datetime]\n    ///     List of datetimes as the specific holiday days.\n    /// week_mask: list[int],\n    ///     List of integers defining the weekends, [5, 6] for Saturday and Sunday.\n    #[new]\n    fn new_py(holidays: Vec<NaiveDateTime>, week_mask: Vec<u8>) -> PyResult<Self> {\n        Ok(Cal::new(holidays, week_mask))\n    }\n\n    /// Create a new *Cal* object from simple string name.\n    /// Parameters\n    /// ----------\n    /// name: str\n    ///     The 3-digit name of the calendar to load. Must be pre-defined in the Rust core code.\n    ///\n    /// Returns\n    /// -------\n    /// Cal\n    #[classmethod]\n    #[pyo3(name = \"from_name\")]\n    fn from_name_py(_cls: &Bound<'_, PyType>, name: String) -> PyResult<Self> {\n        Cal::try_from_name(&name)\n    }\n\n    /// A list of specifically provided non-business days.\n    #[getter]\n    fn holidays(&self) -> PyResult<Vec<NaiveDateTime>> {\n        Ok(self.holidays.clone().into_iter().collect())\n    }\n\n    /// A list of days in the week defined as weekends.\n    #[getter]\n    fn week_mask(&self) -> PyResult<HashSet<u8>> {\n        Ok(HashSet::from_iter(\n            self.week_mask\n                .clone()\n                .into_iter()\n                .map(|x| x.num_days_from_monday() as u8),\n        ))\n    }\n\n    // #[getter]\n    // fn rules(&self) -> PyResult<String> {\n    //     Ok(self.meta.join(\",\\n\"))\n    // }\n\n    /// Return whether the `date` is a business day.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     Date to test\n    ///\n    /// Returns\n    /// -------\n    /// bool\n    #[pyo3(name = \"is_bus_day\")]\n    fn is_bus_day_py(&self, date: NaiveDateTime) -> bool {\n        self.is_bus_day(&date)\n    }\n\n    /// Return whether the `date` is **not** a business day.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     Date to test\n    ///\n    /// Returns\n    /// -------\n    /// bool\n    #[pyo3(name = \"is_non_bus_day\")]\n    fn is_non_bus_day_py(&self, date: NaiveDateTime) -> bool {\n        self.is_non_bus_day(&date)\n    }\n\n    /// Return whether the `date` is a business day of an associated settlement calendar.\n    ///\n    /// .. note::\n    ///\n    ///    *Cal* objects will always return *True*, since they do not contain any\n    ///    associated settlement calendars. This method is provided only for API consistency.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     Date to test\n    ///\n    /// Returns\n    /// -------\n    /// bool\n    #[pyo3(name = \"is_settlement\")]\n    fn is_settlement_py(&self, date: NaiveDateTime) -> bool {\n        self.is_settlement(&date)\n    }\n\n    /// Return a date separated by calendar days from input date, and rolled with a modifier.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     The original business date. Raise if a non-business date is given.\n    /// days: int\n    ///     The number of calendar days to add.\n    /// adjuster: Adjuster\n    ///     The date adjustment rule to use on the unadjusted result.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"add_cal_days\")]\n    fn add_cal_days_py(\n        &self,\n        date: NaiveDateTime,\n        days: i32,\n        adjuster: PyAdjuster,\n    ) -> PyResult<NaiveDateTime> {\n        Ok(self.add_cal_days(&date, days, &adjuster.into()))\n    }\n\n    /// Return a business date separated by `days` from an input business `date`.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     The original business date. *Raises* if a non-business date is given.\n    /// days: int\n    ///     Number of business days to add.\n    /// settlement: bool\n    ///     Enforce an associated settlement calendar, if *True* and if one exists.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    ///\n    /// Notes\n    /// -----\n    /// If adding negative number of business days a failing\n    /// settlement will be rolled **backwards**, whilst adding a\n    /// positive number of days will roll a failing settlement day **forwards**,\n    /// if ``settlement`` is *True*.\n    ///\n    /// .. seealso::\n    ///\n    ///    :meth:`~rateslib.scheduling.Cal.lag_bus_days`: Add business days to inputs which are potentially\n    ///    non-business dates.\n    #[pyo3(name = \"add_bus_days\")]\n    fn add_bus_days_py(\n        &self,\n        date: NaiveDateTime,\n        days: i32,\n        settlement: bool,\n    ) -> PyResult<NaiveDateTime> {\n        self.add_bus_days(&date, days, settlement)\n    }\n\n    /// Return a date separated by months from an input date, and rolled with a modifier.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     The original date to adjust.\n    /// months: int\n    ///     The number of months to add.\n    /// adjuster: Adjuster\n    ///     The date adjustment rule to apply to the unadjusted result.\n    /// roll: RollDay, optional\n    ///     The day of the month to adjust to. If not given adopts the calendar day of ``date``.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"add_months\")]\n    fn add_months_py(\n        &self,\n        date: NaiveDateTime,\n        months: i32,\n        adjuster: PyAdjuster,\n        roll: Option<RollDay>,\n    ) -> NaiveDateTime {\n        let roll_ = match roll {\n            Some(val) => val,\n            None => RollDay::vec_from(&vec![date])[0],\n        };\n        let adjuster: Adjuster = adjuster.into();\n        adjuster.adjust(&roll_.uadd(&date, months), self)\n    }\n\n    /// Roll a date under a simplified adjustment rule.\n    ///\n    /// Parameters\n    /// -----------\n    /// date: datetime\n    ///     The date to adjust.\n    /// modifier: str in {\"F\", \"P\", \"MF\", \"MP\", \"Act\"}\n    ///     The simplified date adjustment rule to apply\n    /// settlement: bool\n    ///     Whether to adhere to an additional settlement calendar.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"roll\")]\n    fn roll_py(\n        &self,\n        date: NaiveDateTime,\n        modifier: &str,\n        settlement: bool,\n    ) -> PyResult<NaiveDateTime> {\n        let adjuster = get_roll_adjuster_from_str((&modifier.to_lowercase(), settlement))?;\n        Ok(self.adjust(&date, &adjuster))\n    }\n\n    /// Adjust a date under a date adjustment rule.\n    ///\n    /// Parameters\n    /// -----------\n    /// date: datetime\n    ///     The date to adjust.\n    /// adjuster: Adjuster\n    ///     The date adjustment rule to apply.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"adjust\")]\n    fn adjust_py(&self, date: NaiveDateTime, adjuster: PyAdjuster) -> PyResult<NaiveDateTime> {\n        Ok(self.adjust(&date, &adjuster.into()))\n    }\n\n    /// Adjust a list of dates under a date adjustment rule.\n    ///\n    /// Parameters\n    /// -----------\n    /// dates: list[datetime]\n    ///     The dates to adjust.\n    /// adjuster: Adjuster\n    ///     The date adjustment rule to apply.\n    ///\n    /// Returns\n    /// -------\n    /// list[datetime]\n    #[pyo3(name = \"adjusts\")]\n    fn adjusts_py(\n        &self,\n        dates: Vec<NaiveDateTime>,\n        adjuster: PyAdjuster,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        Ok(self.adjusts(&dates, &adjuster.into()))\n    }\n\n    /// Adjust a date by a number of business days, under lag rules.\n    ///\n    /// Parameters\n    /// -----------\n    /// date: datetime\n    ///     The date to adjust.\n    /// days: int\n    ///     Number of business days to add.\n    /// settlement: bool\n    ///     Whether to enforce settlement against an associated settlement calendar.\n    ///\n    /// Returns\n    /// --------\n    /// datetime\n    ///\n    /// Notes\n    /// -----\n    /// ``lag_bus_days`` and ``add_bus_days`` will return the same value if the input date is a business\n    /// date. If not a business date, ``add_bus_days`` will raise, while ``lag_bus_days`` will follow\n    /// lag rules. ``lag_bus_days`` should be used when the input date cannot be guaranteed to be a\n    /// business date.\n    ///\n    /// **Lag rules** define the addition of business days to a date that is a non-business date:\n    ///\n    /// - Adding zero days will roll the date **forwards** to the next available business day.\n    /// - Adding one day will roll the date **forwards** to the next available business day.\n    /// - Subtracting one day will roll the date **backwards** to the previous available business day.\n    ///\n    /// Adding (or subtracting) further business days adopts the\n    /// :meth:`~rateslib.scheduling.Cal.add_bus_days` approach with a valid result.\n    #[pyo3(name = \"lag_bus_days\")]\n    fn lag_bus_days_py(&self, date: NaiveDateTime, days: i32, settlement: bool) -> NaiveDateTime {\n        self.lag_bus_days(&date, days, settlement)\n    }\n\n    /// Return a list of business dates in a range.\n    ///\n    /// Parameters\n    /// ----------\n    /// start: datetime\n    ///     The start date of the range, inclusive.\n    /// end: datetime\n    ///     The end date of the range, inclusive.\n    ///\n    /// Returns\n    /// -------\n    /// list[datetime]\n    #[pyo3(name = \"bus_date_range\")]\n    fn bus_date_range_py(\n        &self,\n        start: NaiveDateTime,\n        end: NaiveDateTime,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        self.bus_date_range(&start, &end)\n    }\n\n    /// Return a list of calendar dates within a range.\n    ///\n    /// Parameters\n    /// -----------\n    /// start: datetime\n    ///     The start date of the range, inclusive.\n    /// end: datetime\n    ///     The end date of the range, inclusive,\n    ///\n    /// Returns\n    /// --------\n    /// list[datetime]\n    #[pyo3(name = \"cal_date_range\")]\n    fn cal_date_range_py(\n        &self,\n        start: NaiveDateTime,\n        end: NaiveDateTime,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        self.cal_date_range(&start, &end)\n    }\n\n    /// Return a string representation of a calendar under a legend.\n    ///\n    /// Parameters\n    /// -----------\n    /// year: int\n    ///     The year of the calendar to display.\n    /// month: int, optional\n    ///     The optional month of the calendar to display.\n    ///\n    /// Returns\n    /// --------\n    /// str\n    #[pyo3(name = \"print\", signature = (year, month = None))]\n    fn print_month_py(&self, year: i32, month: Option<u8>) -> PyResult<String> {\n        match month {\n            Some(m) => Ok(self.print_month(year, m)),\n            None => Ok(self.print_year(year)),\n        }\n    }\n\n    /// Return a string representation of a calendar compared to another.\n    ///\n    /// Parameters\n    /// -----------\n    /// comparator: Cal, UnionCal, NamedCal\n    ///     The secondary calendar to compare dates against.\n    /// year: int\n    ///     The year of the calendar to display.\n    ///\n    /// Returns\n    /// --------\n    /// str\n    ///\n    /// Examples\n    /// ---------\n    /// The following example highlights the differences between the FED and NYC calendars in 2026.\n    ///\n    /// .. ipython:: python\n    ///    :suppress:\n    ///\n    ///    from rateslib import get_calendar\n    ///\n    /// .. ipython:: python\n    ///\n    ///    print(get_calendar(\"nyc\").print_compare(get_calendar(\"fed\"), 2026))\n    ///\n    #[pyo3(name = \"print_compare\")]\n    fn print_compare_py(&self, comparator: Calendar, year: i32) -> PyResult<String> {\n        Ok(self.print_compare(&comparator, year))\n    }\n\n    // Pickling\n    fn __getnewargs__(&self) -> PyResult<(Vec<NaiveDateTime>, Vec<u8>)> {\n        Ok((\n            self.clone().holidays.into_iter().collect(),\n            self.clone()\n                .week_mask\n                .into_iter()\n                .map(|x| x.num_days_from_monday() as u8)\n                .collect(),\n        ))\n    }\n\n    // JSON\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::Cal(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\"Failed to serialize `Cal` to JSON.\")),\n        }\n    }\n\n    // Equality\n    fn __eq__(&self, other: Calendar) -> bool {\n        match other {\n            Calendar::UnionCal(c) => *self == c,\n            Calendar::Cal(c) => *self == c,\n            Calendar::NamedCal(c) => *self == c,\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"<rl.Cal at {:p}>\", self)\n    }\n}\n\n#[pymethods]\nimpl UnionCal {\n    #[new]\n    #[pyo3(signature = (calendars, settlement_calendars=None))]\n    fn new_py(calendars: Vec<Cal>, settlement_calendars: Option<Vec<Cal>>) -> PyResult<Self> {\n        Ok(UnionCal::new(calendars, settlement_calendars))\n    }\n\n    /// Create a new *UnionCal* object from simple string name.\n    /// Parameters\n    /// ----------\n    /// name: str\n    ///     The string identifier for the calendar to load.\n    ///\n    /// Returns\n    /// -------\n    /// UnionCal\n    #[classmethod]\n    #[pyo3(name = \"from_name\")]\n    fn from_name_py(_cls: &Bound<'_, PyType>, name: String) -> PyResult<Self> {\n        UnionCal::try_from_name(&name)\n    }\n\n    /// A list of specifically provided non-business days.\n    #[getter]\n    fn holidays(&self) -> PyResult<Vec<NaiveDateTime>> {\n        let mut set = self.calendars.iter().fold(IndexSet::new(), |acc, x| {\n            IndexSet::from_iter(acc.union(&x.holidays).cloned())\n        });\n        set.sort();\n        Ok(Vec::from_iter(set))\n    }\n\n    /// A list of days in the week defined as weekends.\n    #[getter]\n    fn week_mask(&self) -> PyResult<HashSet<u8>> {\n        let mut s: HashSet<u8> = HashSet::new();\n        for cal in &self.calendars {\n            let ns = cal.week_mask()?;\n            s.extend(&ns);\n        }\n        Ok(s)\n    }\n\n    /// A list of :class:`~rateslib.scheduling.Cal` objects defining **business days**.\n    #[getter]\n    fn calendars(&self) -> Vec<Cal> {\n        self.calendars.clone()\n    }\n\n    /// A list of :class:`~rateslib.scheduling.Cal` objects defining **settleable days**.\n    #[getter]\n    fn settlement_calendars(&self) -> Option<Vec<Cal>> {\n        self.settlement_calendars.clone()\n    }\n\n    /// Return whether the `date` is a business day.\n    ///\n    /// See :meth:`Cal.is_bus_day <rateslib.scheduling.Cal.is_bus_day>`.\n    #[pyo3(name = \"is_bus_day\")]\n    fn is_bus_day_py(&self, date: NaiveDateTime) -> bool {\n        self.is_bus_day(&date)\n    }\n\n    /// Return whether the `date` is **not** a business day.\n    ///\n    /// See :meth:`Cal.is_non_bus_day <rateslib.scheduling.Cal.is_non_bus_day>`.\n    #[pyo3(name = \"is_non_bus_day\")]\n    fn is_non_bus_day_py(&self, date: NaiveDateTime) -> bool {\n        self.is_non_bus_day(&date)\n    }\n\n    /// Return whether the `date` is a business day in an associated settlement calendar.\n    ///\n    /// If no such associated settlement calendar exists this will return *True*.\n    ///\n    /// See :meth:`Cal.is_settlement <rateslib.scheduling.Cal.is_settlement>`.\n    #[pyo3(name = \"is_settlement\")]\n    fn is_settlement_py(&self, date: NaiveDateTime) -> bool {\n        self.is_settlement(&date)\n    }\n\n    /// Return a date separated by calendar days from input date, and rolled with a modifier.\n    ///\n    /// See :meth:`Cal.add_cal_days <rateslib.scheduling.Cal.add_cal_days>`.\n    #[pyo3(name = \"add_cal_days\")]\n    fn add_cal_days_py(\n        &self,\n        date: NaiveDateTime,\n        days: i32,\n        adjuster: PyAdjuster,\n    ) -> PyResult<NaiveDateTime> {\n        Ok(self.add_cal_days(&date, days, &adjuster.into()))\n    }\n\n    /// Return a business date separated by `days` from an input business `date`.\n    ///\n    /// See :meth:`Cal.add_bus_days <rateslib.scheduling.Cal.add_bus_days>`.\n    #[pyo3(name = \"add_bus_days\")]\n    fn add_bus_days_py(\n        &self,\n        date: NaiveDateTime,\n        days: i32,\n        settlement: bool,\n    ) -> PyResult<NaiveDateTime> {\n        self.add_bus_days(&date, days, settlement)\n    }\n\n    /// Return a date separated by months from an input date, and rolled with a modifier.\n    ///\n    /// See :meth:`Cal.add_months <rateslib.scheduling.Cal.add_months>`.\n    #[pyo3(name = \"add_months\")]\n    fn add_months_py(\n        &self,\n        date: NaiveDateTime,\n        months: i32,\n        adjuster: PyAdjuster,\n        roll: Option<RollDay>,\n    ) -> NaiveDateTime {\n        let roll_ = match roll {\n            Some(val) => val,\n            None => RollDay::vec_from(&vec![date])[0],\n        };\n        let adjuster: Adjuster = adjuster.into();\n        adjuster.adjust(&roll_.uadd(&date, months), self)\n    }\n\n    /// Adjust a non-business date to a business date under a specific modification rule.\n    ///\n    /// See :meth:`Cal.adjust <rateslib.scheduling.Cal.adjust>`.\n    #[pyo3(name = \"adjust\")]\n    fn adjust_py(&self, date: NaiveDateTime, adjuster: PyAdjuster) -> PyResult<NaiveDateTime> {\n        Ok(self.adjust(&date, &adjuster.into()))\n    }\n\n    /// Adjust a list of dates under a date adjustment rule.\n    ///\n    /// See :meth:`Cal.adjusts <rateslib.scheduling.Cal.adjusts>`.\n    #[pyo3(name = \"adjusts\")]\n    fn adjusts_py(\n        &self,\n        dates: Vec<NaiveDateTime>,\n        adjuster: PyAdjuster,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        Ok(self.adjusts(&dates, &adjuster.into()))\n    }\n\n    /// Roll a date under a simplified adjustment rule.\n    ///\n    /// See :meth:`Cal.roll <rateslib.scheduling.Cal.roll>`.\n    #[pyo3(name = \"roll\")]\n    fn roll_py(\n        &self,\n        date: NaiveDateTime,\n        modifier: &str,\n        settlement: bool,\n    ) -> PyResult<NaiveDateTime> {\n        let adjuster = get_roll_adjuster_from_str((&modifier.to_lowercase(), settlement))?;\n        Ok(self.adjust(&date, &adjuster))\n    }\n\n    /// Adjust a date by a number of business days, under lag rules.\n    ///\n    /// See :meth:`Cal.lag_bus_days <rateslib.scheduling.Cal.lag_bus_days>`.\n    #[pyo3(name = \"lag_bus_days\")]\n    fn lag_bus_days_py(&self, date: NaiveDateTime, days: i32, settlement: bool) -> NaiveDateTime {\n        self.lag_bus_days(&date, days, settlement)\n    }\n\n    /// Return a list of business dates in a range.\n    ///\n    /// See :meth:`Cal.bus_date_range <rateslib.scheduling.Cal.bus_date_range>`.\n    #[pyo3(name = \"bus_date_range\")]\n    fn bus_date_range_py(\n        &self,\n        start: NaiveDateTime,\n        end: NaiveDateTime,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        self.bus_date_range(&start, &end)\n    }\n\n    /// Return a list of calendar dates in a range.\n    ///\n    /// See :meth:`Cal.cal_date_range <rateslib.scheduling.Cal.cal_date_range>`.\n    #[pyo3(name = \"cal_date_range\")]\n    fn cal_date_range_py(\n        &self,\n        start: NaiveDateTime,\n        end: NaiveDateTime,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        self.cal_date_range(&start, &end)\n    }\n\n    /// Return a string representation of a calendar under a legend.\n    ///\n    /// Parameters\n    /// -----------\n    /// year: int\n    ///     The year of the calendar to display.\n    /// month: int, optional\n    ///     The optional month of the calendar to display.\n    ///\n    /// Returns\n    /// --------\n    /// str\n    #[pyo3(name = \"print\", signature = (year, month = None))]\n    fn print_month_py(&self, year: i32, month: Option<u8>) -> PyResult<String> {\n        match month {\n            Some(m) => Ok(self.print_month(year, m)),\n            None => Ok(self.print_year(year)),\n        }\n    }\n\n    /// Return a string representation of a calendar compared to another.\n    ///\n    /// Parameters\n    /// -----------\n    /// comparator: Cal, UnionCal, NamedCal\n    ///     The secondary calendar to compare dates against.\n    /// year: int\n    ///     The year of the calendar to display.\n    ///\n    /// Returns\n    /// --------\n    /// str\n    ///\n    /// Examples\n    /// ---------\n    /// The following example highlights the differences between the FED and NYC calendars in 2026.\n    ///\n    /// .. ipython:: python\n    ///    :suppress:\n    ///\n    ///    from rateslib import get_calendar\n    ///\n    /// .. ipython:: python\n    ///\n    ///    print(get_calendar(\"nyc\").print_compare(get_calendar(\"fed\"), 2026))\n    ///\n    #[pyo3(name = \"print_compare\")]\n    fn print_compare_py(&self, comparator: Calendar, year: i32) -> PyResult<String> {\n        Ok(self.print_compare(&comparator, year))\n    }\n\n    // Pickling\n    fn __getnewargs__(&self) -> PyResult<(Vec<Cal>, Option<Vec<Cal>>)> {\n        Ok((self.calendars.clone(), self.settlement_calendars.clone()))\n    }\n\n    // JSON\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::UnionCal(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `UnionCal` to JSON.\",\n            )),\n        }\n    }\n\n    // Equality\n    fn __eq__(&self, other: Calendar) -> bool {\n        match other {\n            Calendar::UnionCal(c) => *self == c,\n            Calendar::Cal(c) => *self == c,\n            Calendar::NamedCal(c) => *self == c,\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"<rl.UnionCal at {:p}>\", self)\n    }\n}\n\n#[pymethods]\nimpl NamedCal {\n    #[new]\n    fn new_py(name: String) -> PyResult<Self> {\n        NamedCal::try_new(&name)\n    }\n\n    /// A list of specifically provided non-business days.\n    #[getter]\n    fn holidays(&self) -> PyResult<Vec<NaiveDateTime>> {\n        match &*self.inner {\n            CalWrapper::Cal(c) => c.holidays(),\n            CalWrapper::UnionCal(c) => c.holidays(),\n        }\n    }\n\n    /// A list of days in the week defined as weekends.\n    #[getter]\n    fn week_mask(&self) -> PyResult<HashSet<u8>> {\n        match &*self.inner {\n            CalWrapper::Cal(c) => c.week_mask(),\n            CalWrapper::UnionCal(c) => c.week_mask(),\n        }\n    }\n\n    /// The string identifier for this constructed calendar.\n    #[getter]\n    fn name(&self) -> String {\n        self.name.clone()\n    }\n\n    /// The wrapped :class:`~rateslib.scheduling.UnionCal` or :class:`~rateslib.scheduling.Cal` object.\n    #[getter]\n    fn inner(&self) -> Calendar {\n        match (*self.inner).clone() {\n            CalWrapper::Cal(c) => Calendar::Cal(c),\n            CalWrapper::UnionCal(c) => Calendar::UnionCal(c),\n        }\n    }\n\n    /// Check whether the memory allocation of the calendar object matches that of another.\n    ///\n    /// Parameters\n    /// -----------\n    /// other: NamedCal\n    ///     The other :class:`~rateslib.scheduling.NamedCal` to test memory allocation against.\n    ///\n    /// Returns\n    /// --------\n    /// bool\n    fn inner_ptr_eq(&self, other: NamedCal) -> bool {\n        Arc::ptr_eq(&self.inner, &other.inner)\n    }\n\n    /// Return whether the `date` is a business day.\n    ///\n    /// See :meth:`Cal.is_bus_day <rateslib.scheduling.Cal.is_bus_day>`.\n    #[pyo3(name = \"is_bus_day\")]\n    fn is_bus_day_py(&self, date: NaiveDateTime) -> bool {\n        self.is_bus_day(&date)\n    }\n\n    /// Return whether the `date` is **not** a business day.\n    ///\n    /// See :meth:`Cal.is_non_bus_day <rateslib.scheduling.Cal.is_non_bus_day>`.\n    #[pyo3(name = \"is_non_bus_day\")]\n    fn is_non_bus_day_py(&self, date: NaiveDateTime) -> bool {\n        self.is_non_bus_day(&date)\n    }\n\n    /// Return whether the `date` is a business day in an associated settlement calendar.\n    ///\n    /// If no such associated settlement calendar exists this will return *True*.\n    ///\n    /// See :meth:`Cal.is_settlement <rateslib.scheduling.Cal.is_settlement>`.\n    #[pyo3(name = \"is_settlement\")]\n    fn is_settlement_py(&self, date: NaiveDateTime) -> bool {\n        self.is_settlement(&date)\n    }\n\n    /// Return a date separated by calendar days from input date, and rolled with a modifier.\n    ///\n    /// See :meth:`Cal.add_cal_days <rateslib.scheduling.Cal.add_cal_days>`.\n    #[pyo3(name = \"add_cal_days\")]\n    fn add_cal_days_py(\n        &self,\n        date: NaiveDateTime,\n        days: i32,\n        adjuster: PyAdjuster,\n    ) -> PyResult<NaiveDateTime> {\n        Ok(self.add_cal_days(&date, days, &adjuster.into()))\n    }\n\n    /// Return a business date separated by `days` from an input business `date`.\n    ///\n    /// See :meth:`Cal.add_bus_days <rateslib.scheduling.Cal.add_bus_days>`.\n    #[pyo3(name = \"add_bus_days\")]\n    fn add_bus_days_py(\n        &self,\n        date: NaiveDateTime,\n        days: i32,\n        settlement: bool,\n    ) -> PyResult<NaiveDateTime> {\n        self.add_bus_days(&date, days, settlement)\n    }\n\n    /// Return a date separated by months from an input date, and rolled with a modifier.\n    ///\n    /// See :meth:`Cal.add_months <rateslib.scheduling.Cal.add_months>`.\n    #[pyo3(name = \"add_months\")]\n    fn add_months_py(\n        &self,\n        date: NaiveDateTime,\n        months: i32,\n        adjuster: PyAdjuster,\n        roll: Option<RollDay>,\n    ) -> NaiveDateTime {\n        let roll_ = match roll {\n            Some(val) => val,\n            None => RollDay::vec_from(&vec![date])[0],\n        };\n        let adjuster: Adjuster = adjuster.into();\n        adjuster.adjust(&roll_.uadd(&date, months), self)\n    }\n\n    /// Adjust a non-business date to a business date under a specific modification rule.\n    ///\n    /// See :meth:`Cal.adjust <rateslib.scheduling.Cal.adjust>`.\n    #[pyo3(name = \"adjust\")]\n    fn adjust_py(&self, date: NaiveDateTime, adjuster: PyAdjuster) -> PyResult<NaiveDateTime> {\n        Ok(self.adjust(&date, &adjuster.into()))\n    }\n\n    /// Adjust a list of dates under a date adjustment rule.\n    ///\n    /// See :meth:`Cal.adjusts <rateslib.scheduling.Cal.adjusts>`.\n    #[pyo3(name = \"adjusts\")]\n    fn adjusts_py(\n        &self,\n        dates: Vec<NaiveDateTime>,\n        adjuster: PyAdjuster,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        Ok(self.adjusts(&dates, &adjuster.into()))\n    }\n\n    /// Roll a date under a simplified adjustment rule.\n    ///\n    /// See :meth:`Cal.roll <rateslib.scheduling.Cal.roll>`.\n    #[pyo3(name = \"roll\")]\n    fn roll_py(\n        &self,\n        date: NaiveDateTime,\n        modifier: &str,\n        settlement: bool,\n    ) -> PyResult<NaiveDateTime> {\n        let adjuster = get_roll_adjuster_from_str((&modifier.to_lowercase(), settlement))?;\n        Ok(self.adjust(&date, &adjuster))\n    }\n\n    /// Adjust a date by a number of business days, under lag rules.\n    ///\n    /// See :meth:`Cal.lag_bus_days <rateslib.scheduling.Cal.lag_bus_days>`.\n    #[pyo3(name = \"lag_bus_days\")]\n    fn lag_bus_days_py(&self, date: NaiveDateTime, days: i32, settlement: bool) -> NaiveDateTime {\n        self.lag_bus_days(&date, days, settlement)\n    }\n\n    /// Return a list of business dates in a range.\n    ///\n    /// See :meth:`Cal.bus_date_range <rateslib.scheduling.Cal.bus_date_range>`.\n    #[pyo3(name = \"bus_date_range\")]\n    fn bus_date_range_py(\n        &self,\n        start: NaiveDateTime,\n        end: NaiveDateTime,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        self.bus_date_range(&start, &end)\n    }\n\n    /// Return a list of calendar dates in a range.\n    ///\n    /// See :meth:`Cal.cal_date_range <rateslib.scheduling.Cal.cal_date_range>`.\n    #[pyo3(name = \"cal_date_range\")]\n    fn cal_date_range_py(\n        &self,\n        start: NaiveDateTime,\n        end: NaiveDateTime,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        self.cal_date_range(&start, &end)\n    }\n\n    /// Return a string representation of a calendar under a legend.\n    ///\n    /// Parameters\n    /// -----------\n    /// year: int\n    ///     The year of the calendar to display.\n    /// month: int, optional\n    ///     The optional month of the calendar to display.\n    ///\n    /// Returns\n    /// --------\n    /// str\n    #[pyo3(name = \"print\", signature = (year, month = None))]\n    fn print_month_py(&self, year: i32, month: Option<u8>) -> PyResult<String> {\n        match month {\n            Some(m) => Ok(self.print_month(year, m)),\n            None => Ok(self.print_year(year)),\n        }\n    }\n\n    /// Return a string representation of a calendar compared to another.\n    ///\n    /// Parameters\n    /// -----------\n    /// comparator: Cal, UnionCal, NamedCal\n    ///     The secondary calendar to compare dates against.\n    /// year: int\n    ///     The year of the calendar to display.\n    ///\n    /// Returns\n    /// --------\n    /// str\n    ///\n    /// Examples\n    /// ---------\n    /// The following example highlights the differences between the FED and NYC calendars in 2026.\n    ///\n    /// .. ipython:: python\n    ///    :suppress:\n    ///\n    ///    from rateslib import get_calendar\n    ///\n    /// .. ipython:: python\n    ///\n    ///    print(get_calendar(\"nyc\").print_compare(get_calendar(\"fed\"), 2026))\n    ///\n    #[pyo3(name = \"print_compare\")]\n    fn print_compare_py(&self, comparator: Calendar, year: i32) -> PyResult<String> {\n        Ok(self.print_compare(&comparator, year))\n    }\n\n    // Pickling\n    fn __getnewargs__(&self) -> PyResult<(String,)> {\n        Ok((self.name.clone(),))\n    }\n\n    // JSON\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::NamedCal(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `NamedCal` to JSON.\",\n            )),\n        }\n    }\n\n    // Equality\n    fn __eq__(&self, other: Calendar) -> bool {\n        match other {\n            Calendar::UnionCal(c) => *self == c,\n            Calendar::Cal(c) => *self == c,\n            Calendar::NamedCal(c) => *self == c,\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"<rl.NamedCal:'{}' at {:p}>\", self.name, self)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::ndt;\n\n    #[test]\n    fn test_add_37_months() {\n        let cal = Cal::try_from_name(\"all\").unwrap();\n\n        let dates = vec![\n            (ndt(2000, 1, 1), ndt(2003, 2, 1)),\n            (ndt(2000, 2, 1), ndt(2003, 3, 1)),\n            (ndt(2000, 3, 1), ndt(2003, 4, 1)),\n            (ndt(2000, 4, 1), ndt(2003, 5, 1)),\n            (ndt(2000, 5, 1), ndt(2003, 6, 1)),\n            (ndt(2000, 6, 1), ndt(2003, 7, 1)),\n            (ndt(2000, 7, 1), ndt(2003, 8, 1)),\n            (ndt(2000, 8, 1), ndt(2003, 9, 1)),\n            (ndt(2000, 9, 1), ndt(2003, 10, 1)),\n            (ndt(2000, 10, 1), ndt(2003, 11, 1)),\n            (ndt(2000, 11, 1), ndt(2003, 12, 1)),\n            (ndt(2000, 12, 1), ndt(2004, 1, 1)),\n        ];\n        for i in 0..12 {\n            assert_eq!(\n                cal.add_months_py(\n                    dates[i].0,\n                    37,\n                    Adjuster::FollowingSettle {}.into(),\n                    Some(RollDay::Day(1)),\n                ),\n                dates[i].1\n            )\n        }\n    }\n\n    #[test]\n    fn test_sub_37_months() {\n        let cal = Cal::try_from_name(\"all\").unwrap();\n\n        let dates = vec![\n            (ndt(2000, 1, 1), ndt(1996, 12, 1)),\n            (ndt(2000, 2, 1), ndt(1997, 1, 1)),\n            (ndt(2000, 3, 1), ndt(1997, 2, 1)),\n            (ndt(2000, 4, 1), ndt(1997, 3, 1)),\n            (ndt(2000, 5, 1), ndt(1997, 4, 1)),\n            (ndt(2000, 6, 1), ndt(1997, 5, 1)),\n            (ndt(2000, 7, 1), ndt(1997, 6, 1)),\n            (ndt(2000, 8, 1), ndt(1997, 7, 1)),\n            (ndt(2000, 9, 1), ndt(1997, 8, 1)),\n            (ndt(2000, 10, 1), ndt(1997, 9, 1)),\n            (ndt(2000, 11, 1), ndt(1997, 10, 1)),\n            (ndt(2000, 12, 1), ndt(1997, 11, 1)),\n        ];\n        for i in 0..12 {\n            assert_eq!(\n                cal.add_months_py(\n                    dates[i].0,\n                    -37,\n                    Adjuster::FollowingSettle {}.into(),\n                    Some(RollDay::Day(1)),\n                ),\n                dates[i].1\n            )\n        }\n    }\n\n    #[test]\n    fn test_add_months_py_roll() {\n        let cal = Cal::try_from_name(\"all\").unwrap();\n        let roll = vec![\n            (RollDay::Day(7), ndt(1998, 3, 7), ndt(1996, 12, 7)),\n            (RollDay::Day(21), ndt(1998, 3, 21), ndt(1996, 12, 21)),\n            (RollDay::Day(31), ndt(1998, 3, 31), ndt(1996, 12, 31)),\n            (RollDay::Day(1), ndt(1998, 3, 1), ndt(1996, 12, 1)),\n            (RollDay::IMM(), ndt(1998, 3, 18), ndt(1996, 12, 18)),\n        ];\n        for i in 0..5 {\n            assert_eq!(\n                cal.add_months_py(\n                    roll[i].1,\n                    -15,\n                    Adjuster::FollowingSettle {}.into(),\n                    Some(roll[i].0)\n                ),\n                roll[i].2\n            );\n        }\n    }\n\n    #[test]\n    fn test_add_months_roll_invalid_days() {\n        let cal = Cal::try_from_name(\"all\").unwrap();\n        let roll = vec![\n            (RollDay::Day(21), ndt(1996, 12, 21)),\n            (RollDay::Day(31), ndt(1996, 12, 31)),\n            (RollDay::Day(1), ndt(1996, 12, 1)),\n            (RollDay::IMM(), ndt(1996, 12, 18)),\n        ];\n        for i in 0..4 {\n            assert_eq!(\n                roll[i].1,\n                cal.add_months_py(\n                    ndt(1998, 3, 7),\n                    -15,\n                    Adjuster::FollowingSettle {}.into(),\n                    Some(roll[i].0),\n                ),\n            );\n        }\n    }\n\n    #[test]\n    fn test_add_months_modifier() {\n        let cal = Cal::try_from_name(\"bus\").unwrap();\n        let modi = vec![\n            (Adjuster::Actual {}, ndt(2023, 9, 30)),          // Saturday\n            (Adjuster::FollowingSettle {}, ndt(2023, 10, 2)), // Monday\n            (Adjuster::ModifiedFollowingSettle {}, ndt(2023, 9, 29)), // Friday\n            (Adjuster::PreviousSettle {}, ndt(2023, 9, 29)),  // Friday\n            (Adjuster::ModifiedPreviousSettle {}, ndt(2023, 9, 29)), // Friday\n        ];\n        for i in 0..4 {\n            assert_eq!(\n                cal.add_months_py(\n                    ndt(2023, 8, 31),\n                    1,\n                    modi[i].0.into(),\n                    Some(RollDay::Day(31))\n                ),\n                modi[i].1\n            );\n        }\n    }\n\n    #[test]\n    fn test_add_months_modifier_p() {\n        let cal = Cal::try_from_name(\"bus\").unwrap();\n        let modi = vec![\n            (Adjuster::Actual {}, ndt(2023, 7, 1)),          // Saturday\n            (Adjuster::FollowingSettle {}, ndt(2023, 7, 3)), // Monday\n            (Adjuster::ModifiedFollowingSettle {}, ndt(2023, 7, 3)), // Monday\n            (Adjuster::PreviousSettle {}, ndt(2023, 6, 30)), // Friday\n            (Adjuster::ModifiedPreviousSettle {}, ndt(2023, 7, 3)), // Monday\n        ];\n        for i in 0..4 {\n            assert_eq!(\n                cal.add_months_py(ndt(2023, 8, 1), -1, modi[i].0.into(), Some(RollDay::Day(1))),\n                modi[i].1\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/py/convention.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::json::{DeserializedObj, JSON};\nuse crate::scheduling::{Adjuster, Calendar, Convention, Frequency, PyAdjuster};\nuse chrono::prelude::*;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\n\n#[pymethods]\nimpl Convention {\n    // Pickling\n    #[new]\n    fn new_py(variant: u8) -> PyResult<Convention> {\n        match variant {\n            0_u8 => Ok(Convention::Act365F),\n            1_u8 => Ok(Convention::Act360),\n            2_u8 => Ok(Convention::Thirty360),\n            3_u8 => Ok(Convention::ThirtyU360),\n            4_u8 => Ok(Convention::ThirtyE360),\n            5_u8 => Ok(Convention::ThirtyE360ISDA),\n            6_u8 => Ok(Convention::YearsAct365F),\n            7_u8 => Ok(Convention::YearsAct360),\n            8_u8 => Ok(Convention::YearsMonths),\n            9_u8 => Ok(Convention::One),\n            10_u8 => Ok(Convention::ActActISDA),\n            11_u8 => Ok(Convention::ActActICMA),\n            12_u8 => Ok(Convention::Bus252),\n            13_u8 => Ok(Convention::ActActICMAStubAct365F),\n            14_u8 => Ok(Convention::Act365_25),\n            15_u8 => Ok(Convention::Act364),\n            _ => Err(PyValueError::new_err(\n                \"unreachable code on Convention pickle.\",\n            )),\n        }\n    }\n\n    /// Calculate the day count fraction of a period.\n    ///\n    /// Parameters\n    /// ----------\n    /// start : datetime\n    ///     The adjusted start date of the calculation period.\n    /// end : datetime\n    ///     The adjusted end date of the calculation period.\n    /// termination : datetime, optional\n    ///     The adjusted termination date of the leg. Required only for some ``convention``.\n    /// frequency : Frequency, str, optional\n    ///     The frequency of the period. Required only for some ``convention``.\n    /// stub : bool, optional\n    ///    Indicates whether the period is a stub or not. Required only for some ``convention``.\n    /// roll : str, int, optional\n    ///     Used only if ``frequency`` is given in string form. Required only for some ``convention``.\n    /// calendar: str, Calendar, optional\n    ///     Used only of ``frequency`` is given in string form. Required only for some ``convention``.\n    /// adjuster: Adjuster, str, optional\n    ///     The :class:`~rateslib.scheduling.Adjuster` used to convert unadjusted dates to\n    ///     adjusted accrual dates on the period. Required only for some ``convention``.\n    ///\n    /// Returns\n    /// --------\n    /// float\n    ///\n    /// Notes\n    /// -----\n    /// Further details on the required arguments can be found under ``Convention`` at the\n    /// lower level Rust docs, see :rust:`rateslib-rs: Scheduling <scheduling>`.\n    #[pyo3(name = \"dcf\", signature=(start, end, termination=None, frequency=None, stub=None, calendar=None, adjuster=None   ))]\n    fn dcf_py(\n        &self,\n        start: NaiveDateTime,\n        end: NaiveDateTime,\n        termination: Option<NaiveDateTime>,\n        frequency: Option<Frequency>,\n        stub: Option<bool>,\n        calendar: Option<Calendar>,\n        adjuster: Option<PyAdjuster>,\n    ) -> PyResult<f64> {\n        let adjuster_opt: Option<Adjuster> = match adjuster {\n            Some(val) => Some(val.into()),\n            None => None,\n        };\n        self.dcf(\n            &start,\n            &end,\n            termination.as_ref(),\n            frequency.as_ref(),\n            stub,\n            calendar.as_ref(),\n            adjuster_opt.as_ref(),\n        )\n    }\n    fn __getnewargs__<'py>(&self) -> PyResult<(usize,)> {\n        Ok((*self as usize,))\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"<rl.Convention.{:?} at {:p}>\", self, self)\n    }\n\n    fn __str__(&self) -> String {\n        match self {\n            Convention::Act360 => \"Act360\".to_string(),\n            Convention::Act365F => \"Act365F\".to_string(),\n            Convention::YearsAct365F => \"YearsAct365F\".to_string(),\n            Convention::YearsAct360 => \"YearsAct360\".to_string(),\n            Convention::YearsMonths => \"YearsMonths\".to_string(),\n            Convention::Thirty360 => \"30360\".to_string(),\n            Convention::ThirtyU360 => \"30u360\".to_string(),\n            Convention::ThirtyE360 => \"30e360\".to_string(),\n            Convention::ThirtyE360ISDA => \"30e360ISDA\".to_string(),\n            Convention::One => \"One\".to_string(),\n            Convention::ActActISDA => \"ActActISDA\".to_string(),\n            Convention::ActActICMA => \"ActActICMA\".to_string(),\n            Convention::Bus252 => \"Bus252\".to_string(),\n            Convention::ActActICMAStubAct365F => \"ActActICMAStubAct365F\".to_string(),\n            Convention::Act365_25 => \"Act365_25\".to_string(),\n            Convention::Act364 => \"Act364\".to_string(),\n        }\n    }\n\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::Convention(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `Convention` to JSON.\",\n            )),\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/py/frequency.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::json::{DeserializedObj, JSON};\nuse crate::scheduling::calendars::Calendar;\nuse crate::scheduling::frequency::{Frequency, RollDay, Scheduling};\n\nuse chrono::prelude::*;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::types::PyTuple;\n\nenum FrequencyNewArgs {\n    CalDays(i32),\n    BusDays(i32, Calendar),\n    Months(i32, Option<RollDay>),\n    Zero(),\n}\n\nimpl<'py> IntoPyObject<'py> for FrequencyNewArgs {\n    type Target = PyTuple;\n    type Output = Bound<'py, Self::Target>;\n    type Error = std::convert::Infallible;\n\n    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {\n        match self {\n            FrequencyNewArgs::CalDays(x) => Ok((x,).into_pyobject(py).unwrap()),\n            FrequencyNewArgs::BusDays(x, y) => Ok((x, y).into_pyobject(py).unwrap()),\n            FrequencyNewArgs::Months(x, y) => Ok((x, y).into_pyobject(py).unwrap()),\n            FrequencyNewArgs::Zero() => Ok(PyTuple::empty(py)),\n        }\n    }\n}\n\nimpl<'py> FromPyObject<'py, 'py> for FrequencyNewArgs {\n    type Error = PyErr;\n\n    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {\n        let ext: PyResult<(i32,)> = obj.extract();\n        if ext.is_ok() {\n            let (x,) = ext.unwrap();\n            return Ok(Self::CalDays(x));\n        }\n        let ext: PyResult<(i32, Calendar)> = obj.extract();\n        if ext.is_ok() {\n            let (x, y) = ext.unwrap();\n            return Ok(Self::BusDays(x, y));\n        }\n        let ext: PyResult<(i32, Option<RollDay>)> = obj.extract();\n        if ext.is_ok() {\n            let (x, y) = ext.unwrap();\n            Ok(Self::Months(x, y))\n        } else {\n            // must be empty tuple args\n            Ok(Self::Zero())\n        }\n    }\n}\n\n#[pymethods]\nimpl Frequency {\n    /// Return the next unadjusted date under the schedule frequency.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     Any unchecked date, which may or may not align with the `Frequency`.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"next\")]\n    fn next_py(&self, date: NaiveDateTime) -> NaiveDateTime {\n        self.next(&date)\n    }\n\n    /// Return an average number of coupons per annum measured over 50 years.\n    ///\n    /// Returns\n    /// -------\n    /// float\n    #[pyo3(name = \"periods_per_annum\")]\n    fn periods_per_annum_py(&self) -> f64 {\n        self.periods_per_annum()\n    }\n\n    /// Return the next unadjusted date under the schedule frequency.\n    ///\n    /// Parameters\n    /// ----------\n    /// udate: datetime\n    ///     The unadjusted start date of the frequency period. If this is not a valid unadjusted\n    ///     date aligned with the Frequency then it will raise.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"unext\")]\n    fn unext_py(&self, udate: NaiveDateTime) -> PyResult<NaiveDateTime> {\n        self.try_unext(&udate)\n    }\n\n    /// Return the previous unadjusted date under the schedule frequency.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     Any unchecked date, which may or may not align with the `Frequency`.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"previous\")]\n    fn previous_py(&self, date: NaiveDateTime) -> NaiveDateTime {\n        self.previous(&date)\n    }\n\n    /// Return the previous unadjusted date under the schedule frequency.\n    ///\n    /// Parameters\n    /// ----------\n    /// udate: datetime\n    ///     The unadjusted end date of the frequency period. If this is not a valid unadjusted\n    ///     date aligned with the Frequency then it will raise.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"uprevious\")]\n    fn uprevious_py(&self, udate: NaiveDateTime) -> PyResult<NaiveDateTime> {\n        self.try_uprevious(&udate)\n    }\n\n    /// Return a list of unadjusted regular schedule dates.\n    ///\n    /// Parameters\n    /// ----------\n    /// ueffective: datetime\n    ///     The unadjusted effective date of the schedule. If this is not a valid unadjusted\n    ///     date aligned with the Frequency then it will raise.\n    /// utermination: datetime\n    ///     The unadjusted termination date of the frequency period. If this is not a valid\n    ///     unadjusted date aligned with the Frequency then it will raise.\n    ///\n    /// Returns\n    /// -------\n    /// list[datetime]\n    #[pyo3(name = \"uregular\")]\n    fn uregular_py(\n        &self,\n        ueffective: NaiveDateTime,\n        utermination: NaiveDateTime,\n    ) -> PyResult<Vec<NaiveDateTime>> {\n        self.try_uregular(&ueffective, &utermination)\n    }\n\n    /// Check whether two unadjusted dates define a regular unadjusted schedule.\n    ///\n    /// Parameters\n    /// ----------\n    /// ueffective: datetime\n    ///     The unadjusted effective date of the schedule. If this is not a valid unadjusted\n    ///     date aligned with the Frequency then it will raise.\n    /// utermination: datetime\n    ///     The unadjusted termination date of the frequency period. If this is not a valid\n    ///     unadjusted date aligned with the Frequency then it will raise.\n    ///\n    /// Returns\n    /// -------\n    /// bool\n    #[pyo3(name = \"is_uregular\")]\n    fn is_uregular_py(&self, ueffective: NaiveDateTime, utermination: NaiveDateTime) -> bool {\n        match self.try_uregular(&ueffective, &utermination) {\n            Err(_) => false,\n            Ok(_) => true,\n        }\n    }\n\n    /// Infer an unadjusted stub date from given schedule endpoints.\n    ///\n    /// Parameters\n    /// ----------\n    /// ueffective: datetime\n    ///     The unadjusted effective date of the schedule.\n    /// utermination: datetime\n    ///     The unadjusted termination date of the frequency period. If this is not a valid\n    ///     unadjusted date aligned with the Frequency then it will raise.\n    /// short: bool\n    ///     Whether to infer a short or a long stub.\n    /// front: bool\n    ///     Whether to infer a front or a back stub.\n    ///\n    /// Returns\n    /// -------\n    /// datetime or None\n    ///\n    /// Notes\n    /// -----\n    /// This function will return `None` if the dates define a regular schedule and no stub is\n    /// required.\n    #[pyo3(name = \"infer_ustub\")]\n    fn infer_ustub_py(\n        &self,\n        ueffective: NaiveDateTime,\n        utermination: NaiveDateTime,\n        short: bool,\n        front: bool,\n    ) -> PyResult<Option<NaiveDateTime>> {\n        if front {\n            self.try_infer_ufront_stub(&ueffective, &utermination, short)\n        } else {\n            self.try_infer_uback_stub(&ueffective, &utermination, short)\n        }\n    }\n\n    /// Check whether unadjusted dates define a stub period.\n    ///\n    /// Parameters\n    /// ----------\n    /// ustart: datetime\n    ///     The unadjusted start date of the period.\n    /// uend: datetime\n    ///     The unadjusted end date of the period.\n    /// front: bool\n    ///     Test for either a front or a back stub.\n    ///\n    /// Returns\n    /// -------\n    /// bool\n    #[pyo3(name = \"is_stub\")]\n    fn is_stub_py(&self, ustart: NaiveDateTime, uend: NaiveDateTime, front: bool) -> bool {\n        if front {\n            self.is_front_stub(&ustart, &uend)\n        } else {\n            self.is_back_stub(&ustart, &uend)\n        }\n    }\n\n    /// Return a string representation of the Frequency.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"string\")]\n    fn string_py(&self) -> PyResult<String> {\n        match self {\n            Frequency::Zero {} => Ok(\"Z\".to_string()),\n            Frequency::CalDays { number: n } => Ok(format!(\"{n}D\")),\n            Frequency::BusDays {\n                number: n,\n                calendar: _,\n            } => Ok(format!(\"{n}B\")),\n            Frequency::Months { number: 1, roll: _ } => Ok(format!(\"M\")),\n            Frequency::Months { number: 2, roll: _ } => Ok(format!(\"B\")),\n            Frequency::Months { number: 3, roll: _ } => Ok(format!(\"Q\")),\n            Frequency::Months { number: 4, roll: _ } => Ok(format!(\"T\")),\n            Frequency::Months { number: 6, roll: _ } => Ok(format!(\"S\")),\n            Frequency::Months {\n                number: 12,\n                roll: _,\n            } => Ok(format!(\"A\")),\n            _ => Err(PyValueError::new_err(\n                \"No recognisable string representation for Frequency.\",\n            )),\n        }\n    }\n\n    fn __str__(&self) -> String {\n        match self {\n            Frequency::Zero {} => \"Z\".to_string(),\n            Frequency::CalDays { number: n } => format!(\"{n}D\"),\n            Frequency::BusDays {\n                number: n,\n                calendar: _,\n            } => format!(\"{n}B\"),\n            Frequency::Months { number: n, roll: r } => {\n                let x = match r {\n                    Some(v) => v.__str__(),\n                    None => \"none\".to_string(),\n                };\n                format!(\"{n}M (roll: {x})\")\n            }\n        }\n    }\n\n    fn __getnewargs__(&self) -> FrequencyNewArgs {\n        match self {\n            Frequency::BusDays {\n                number: n,\n                calendar: c,\n            } => FrequencyNewArgs::BusDays(*n, c.clone()),\n            Frequency::CalDays { number: n } => FrequencyNewArgs::CalDays(*n),\n            Frequency::Months { number: n, roll: r } => FrequencyNewArgs::Months(*n, *r),\n            Frequency::Zero {} => FrequencyNewArgs::Zero(),\n        }\n    }\n\n    #[new]\n    fn new_py(args: FrequencyNewArgs) -> Frequency {\n        match args {\n            FrequencyNewArgs::BusDays(n, c) => Frequency::BusDays {\n                number: n,\n                calendar: c,\n            },\n            FrequencyNewArgs::CalDays(n) => Frequency::CalDays { number: n },\n            FrequencyNewArgs::Months(n, r) => Frequency::Months { number: n, roll: r },\n            FrequencyNewArgs::Zero() => Frequency::Zero {},\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        match self {\n            Frequency::Zero {} => format!(\"<rl.Frequency.Zero at {:p}>\", self),\n            Frequency::CalDays { number: n } => {\n                format!(\"<rl.Frequency.CalDays({}) at {:p}>\", n, self)\n            }\n            Frequency::BusDays {\n                number: n,\n                calendar: _,\n            } => format!(\"<rl.Frequency.BusDays({}, ...) at {:p}>\", n, self),\n            Frequency::Months { number: n, roll: r } => match r {\n                Some(val) => format!(\"<rl.Frequency.Months({}, {:?}) at {:p}>\", n, val, self),\n                None => format!(\"<rl.Frequency.Months({}, None) at {:p}>\", n, self),\n            },\n        }\n    }\n\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::Frequency(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `Frequency` to JSON.\",\n            )),\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/py/imm.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::json::{DeserializedObj, JSON};\nuse crate::scheduling::frequency::Imm;\n\nuse chrono::prelude::*;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\n\n#[pymethods]\nimpl Imm {\n    /// Return the next IMM date after ``date`` under the given definition.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     The input date.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"next\")]\n    fn next_py(&self, date: NaiveDateTime) -> NaiveDateTime {\n        self.next(&date)\n    }\n\n    /// Check whether a date is an IMM date under the given definition.\n    ///\n    /// Parameters\n    /// ----------\n    /// date: datetime\n    ///     The input date.\n    ///\n    /// Returns\n    /// -------\n    /// bool\n    #[pyo3(name = \"validate\")]\n    fn validate_py(&self, date: NaiveDateTime) -> bool {\n        self.validate(&date)\n    }\n\n    /// Return an IMM date from a given year and month under the given definition.\n    ///\n    /// Parameters\n    /// ----------\n    /// year: int\n    ///     The year.\n    /// month: int\n    ///     The month.\n    ///\n    /// Returns\n    /// -------\n    /// datetime\n    #[pyo3(name = \"get\")]\n    fn from_ym_opt_py(&self, year: i32, month: u32) -> PyResult<NaiveDateTime> {\n        self.from_ym_opt(year, month)\n    }\n\n    // JSON\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::Imm(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\"Failed to serialize `Imm` to JSON.\")),\n        }\n    }\n\n    // Pickling\n    #[new]\n    fn new_py(item: usize) -> Imm {\n        match item {\n            _ if item == Imm::Wed3 as usize => Imm::Wed3,\n            _ if item == Imm::Wed3_HMUZ as usize => Imm::Wed3_HMUZ,\n            _ if item == Imm::Fri2 as usize => Imm::Fri2,\n            _ if item == Imm::Fri2_HMUZ as usize => Imm::Fri2_HMUZ,\n            _ if item == Imm::Day20 as usize => Imm::Day20,\n            _ if item == Imm::Day20_HU as usize => Imm::Day20_HU,\n            _ if item == Imm::Day20_MZ as usize => Imm::Day20_MZ,\n            _ if item == Imm::Day20_HMUZ as usize => Imm::Day20_HMUZ,\n            _ if item == Imm::Wed1_Post9 as usize => Imm::Wed1_Post9,\n            _ if item == Imm::Wed1_Post9_HMUZ as usize => Imm::Wed1_Post9_HMUZ,\n            _ if item == Imm::Eom as usize => Imm::Eom,\n            _ if item == Imm::Leap as usize => Imm::Leap,\n            _ if item == Imm::Som as usize => Imm::Som,\n            _ => panic!(\"Reportable issue: must map this enum variant for serialization.\"),\n        }\n    }\n    fn __getnewargs__<'py>(&self) -> PyResult<(usize,)> {\n        Ok((*self as usize,))\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"<rl.Imm.{:?} at {:p}>\", self, self)\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/py/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\npub(crate) mod adjuster;\npub(crate) use adjuster::PyAdjuster;\npub(crate) mod calendar;\npub(crate) mod convention;\npub(crate) mod frequency;\npub(crate) mod imm;\npub(crate) mod rollday;\npub(crate) mod schedule;\n"
  },
  {
    "path": "rust/scheduling/py/rollday.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::json::{DeserializedObj, JSON};\nuse crate::scheduling::RollDay;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::types::PyTuple;\n\nenum RollDayNewArgs {\n    U32(u32),\n    NoArgs(),\n}\n\nimpl<'py> IntoPyObject<'py> for RollDayNewArgs {\n    type Target = PyTuple;\n    type Output = Bound<'py, Self::Target>;\n    type Error = std::convert::Infallible;\n\n    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {\n        match self {\n            RollDayNewArgs::U32(a) => Ok((a,).into_pyobject(py).unwrap()),\n            RollDayNewArgs::NoArgs() => Ok(PyTuple::empty(py)),\n        }\n    }\n}\n\nimpl<'py> FromPyObject<'py, 'py> for RollDayNewArgs {\n    type Error = PyErr;\n\n    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {\n        let ext: PyResult<(u32,)> = obj.extract();\n        match ext {\n            Ok(v) => Ok(RollDayNewArgs::U32(v.0)),\n            Err(_) => Ok(RollDayNewArgs::NoArgs()),\n        }\n    }\n}\n\n#[pymethods]\nimpl RollDay {\n    pub(crate) fn __str__(&self) -> String {\n        match self {\n            RollDay::Day(n) => format!(\"{n}\"),\n            RollDay::IMM() => \"IMM\".to_string(),\n        }\n    }\n\n    fn __getnewargs__(&self) -> RollDayNewArgs {\n        match self {\n            RollDay::Day(n) => RollDayNewArgs::U32(*n),\n            RollDay::IMM() => RollDayNewArgs::NoArgs(),\n        }\n    }\n\n    #[new]\n    fn new_py(args: RollDayNewArgs) -> RollDay {\n        match args {\n            RollDayNewArgs::U32(n) => RollDay::Day(n),\n            RollDayNewArgs::NoArgs() => RollDay::IMM(),\n        }\n    }\n\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::RollDay(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `RollDay` to JSON.\",\n            )),\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"<rl.RollDay.{:?} at {:p}>\", self, self)\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/py/schedule.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::json::{DeserializedObj, JSON};\nuse crate::scheduling::{Calendar, Frequency, PyAdjuster, Schedule, StubInference};\n\nuse chrono::prelude::*;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\n\n#[pymethods]\nimpl StubInference {\n    // Pickling\n    #[new]\n    fn new_py(item: usize) -> StubInference {\n        match item {\n            _ if item == StubInference::ShortFront as usize => StubInference::ShortFront,\n            _ if item == StubInference::ShortBack as usize => StubInference::ShortBack,\n            _ if item == StubInference::LongFront as usize => StubInference::LongFront,\n            _ if item == StubInference::LongBack as usize => StubInference::LongBack,\n            _ if item == StubInference::NeitherSide as usize => StubInference::NeitherSide,\n            _ => panic!(\"Reportable issue: must map this enum variant for serialization.\"),\n        }\n    }\n    fn __getnewargs__<'py>(&self) -> PyResult<(usize,)> {\n        Ok((*self as usize,))\n    }\n    fn __repr__(&self) -> String {\n        format!(\"<rl.StubInference.{:?} at {:p}>\", self, self)\n    }\n    // JSON\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::StubInference(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `StubInference` to JSON.\",\n            )),\n        }\n    }\n}\n\n#[pymethods]\nimpl Schedule {\n    #[new]\n    #[pyo3(signature = (effective, termination, frequency, calendar, accrual_adjuster, payment_adjuster, payment_adjuster2, eom, stub_inference, front_stub=None, back_stub=None, payment_adjuster3=None))]\n    fn new_py(\n        effective: NaiveDateTime,\n        termination: NaiveDateTime,\n        frequency: Frequency,\n        calendar: Calendar,\n        accrual_adjuster: PyAdjuster,\n        payment_adjuster: PyAdjuster,\n        payment_adjuster2: PyAdjuster,\n        eom: bool,\n        stub_inference: StubInference,\n        front_stub: Option<NaiveDateTime>,\n        back_stub: Option<NaiveDateTime>,\n        payment_adjuster3: Option<PyAdjuster>,\n    ) -> PyResult<Self> {\n        Schedule::try_new_inferred(\n            effective,\n            termination,\n            frequency,\n            front_stub,\n            back_stub,\n            calendar,\n            accrual_adjuster.into(),\n            payment_adjuster.into(),\n            payment_adjuster2.into(),\n            payment_adjuster3.map(Into::into),\n            eom,\n            stub_inference,\n        )\n    }\n\n    #[pyo3(name = \"is_regular\")]\n    fn is_regular_py(&self) -> bool {\n        self.is_regular()\n    }\n\n    #[getter]\n    #[pyo3(name = \"ueffective\")]\n    fn ueffective_py(&self) -> NaiveDateTime {\n        self.ueffective\n    }\n\n    #[getter]\n    #[pyo3(name = \"utermination\")]\n    fn utermination_py(&self) -> NaiveDateTime {\n        self.utermination\n    }\n\n    #[getter]\n    #[pyo3(name = \"frequency\")]\n    fn frequency_py(&self) -> Frequency {\n        self.frequency.clone()\n    }\n\n    #[getter]\n    #[pyo3(name = \"accrual_adjuster\")]\n    fn accrual_adjuster_py(&self) -> PyAdjuster {\n        self.accrual_adjuster.into()\n    }\n\n    #[getter]\n    #[pyo3(name = \"calendar\")]\n    fn calendar_py(&self) -> Calendar {\n        self.calendar.clone()\n    }\n\n    #[getter]\n    #[pyo3(name = \"payment_adjuster\")]\n    fn payment_adjuster_py(&self) -> PyAdjuster {\n        self.payment_adjuster.into()\n    }\n\n    #[getter]\n    #[pyo3(name = \"payment_adjuster2\")]\n    fn payment_adjuster2_py(&self) -> PyAdjuster {\n        self.payment_adjuster2.into()\n    }\n\n    #[getter]\n    #[pyo3(name = \"payment_adjuster3\")]\n    fn payment_adjuster3_py(&self) -> Option<PyAdjuster> {\n        self.payment_adjuster3.map(Into::into)\n    }\n\n    #[getter]\n    #[pyo3(name = \"ufront_stub\")]\n    fn ufront_stub_py(&self) -> Option<NaiveDateTime> {\n        self.ufront_stub\n    }\n\n    #[getter]\n    #[pyo3(name = \"uback_stub\")]\n    fn uback_stub_py(&self) -> Option<NaiveDateTime> {\n        self.uback_stub\n    }\n\n    #[getter]\n    #[pyo3(name = \"uschedule\")]\n    fn uschedule_py(&self) -> Vec<NaiveDateTime> {\n        self.uschedule.clone()\n    }\n\n    #[getter]\n    #[pyo3(name = \"aschedule\")]\n    fn aschedule_py(&self) -> Vec<NaiveDateTime> {\n        self.aschedule.clone()\n    }\n\n    #[getter]\n    #[pyo3(name = \"pschedule\")]\n    fn pschedule_py(&self) -> Vec<NaiveDateTime> {\n        self.pschedule.clone()\n    }\n\n    #[getter]\n    #[pyo3(name = \"pschedule2\")]\n    fn pschedule2_py(&self) -> Vec<NaiveDateTime> {\n        self.pschedule2.clone()\n    }\n\n    #[getter]\n    #[pyo3(name = \"pschedule3\")]\n    fn pschedule3_py(&self) -> Vec<NaiveDateTime> {\n        self.pschedule3.clone()\n    }\n\n    // Pickling\n    fn __getnewargs__(\n        &self,\n    ) -> PyResult<(\n        NaiveDateTime,\n        NaiveDateTime,\n        Frequency,\n        Calendar,\n        PyAdjuster,\n        PyAdjuster,\n        PyAdjuster,\n        bool,\n        StubInference,\n        Option<NaiveDateTime>,\n        Option<NaiveDateTime>,\n        Option<PyAdjuster>,\n    )> {\n        Ok((\n            self.ueffective,\n            self.utermination,\n            self.frequency.clone(),\n            self.calendar.clone(),\n            self.accrual_adjuster.into(),\n            self.payment_adjuster.into(),\n            self.payment_adjuster2.into(),\n            false,\n            StubInference::NeitherSide,\n            self.ufront_stub,\n            self.uback_stub,\n            self.payment_adjuster3.map(Into::into),\n        ))\n    }\n\n    /// Return a JSON representation of the object.\n    ///\n    /// Returns\n    /// -------\n    /// str\n    #[pyo3(name = \"to_json\")]\n    fn to_json_py(&self) -> PyResult<String> {\n        match DeserializedObj::Schedule(self.clone()).to_json() {\n            Ok(v) => Ok(v),\n            Err(_) => Err(PyValueError::new_err(\n                \"Failed to serialize `Schedule` to JSON.\",\n            )),\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"<rl.Schedule at {:p}>\", self)\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/schedule.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::scheduling::{\n    get_unadjusteds, Adjuster, Adjustment, Calendar, Frequency, RollDay, Scheduling,\n};\nuse chrono::prelude::*;\nuse itertools::iproduct;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\n\n/// Specifier used by [`Schedule::try_new_inferred`] to instruct its inference logic.\n#[pyclass(module = \"rateslib.rs\", eq, eq_int, from_py_object)]\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum StubInference {\n    /// Short front stub inference.\n    ShortFront = 0,\n    /// Long front stub inference.\n    LongFront = 1,\n    /// Short back stub inference.\n    ShortBack = 2,\n    /// Long back stub inference.\n    LongBack = 3,\n    /// Explicitly avoid any stub inference.\n    NeitherSide = 4,\n}\n\n/// A generic financial schedule with regular contiguous periods and, possibly, stubs.\n///\n/// # Notes\n/// - A **regular** schedule has a [`Frequency`] that perfectly divides its ``ueffective`` and\n///   ``utermination`` dates, and has no stub dates.\n/// - An **irregular** schedule has a ``ufront_stub`` and/or ``uback_stub`` dates defining periods\n///   at the boundary of the schedule which are not a standard length of time defined by the\n///   [`Frequency`]. However, a regular schedule must exist between those interior dates.\n#[pyclass(module = \"rateslib.rs\", eq, from_py_object)]\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\n#[serde(from = \"ScheduleDataModel\")]\npub struct Schedule {\n    /// The unadjusted start date of the schedule.\n    pub ueffective: NaiveDateTime,\n    /// The unadjusted end date of the schedule.\n    pub utermination: NaiveDateTime,\n    /// The scheduling [`Frequency`] for regular periods.\n    pub frequency: Frequency,\n    /// The optional, unadjusted front stub date.\n    pub ufront_stub: Option<NaiveDateTime>,\n    /// The optional, unadjusted back stub date.\n    pub uback_stub: Option<NaiveDateTime>,\n    /// The [`Calendar`] for accrual and payment date adjustment.\n    pub calendar: Calendar,\n    /// The [`Adjuster`] to adjust the unadjusted schedule dates to adjusted period accrual dates.\n    pub accrual_adjuster: Adjuster,\n    /// The [`Adjuster`] to adjust the accrual schedule dates to period payment dates.\n    pub payment_adjuster: Adjuster,\n    /// An additional [`Adjuster`] to adjust the accrual schedule dates to some other period payment or fixing dates.\n    ///\n    /// This is often used as a notional exchange lag, which for XCS, for example, differs to a regular coupon lag.\n    pub payment_adjuster2: Adjuster,\n    /// An additional [`Adjuster`] to adjust the accrual schedule dates to some other period payment or fixing dates.\n    ///\n    /// If *None* is set to match ``payment_adjuster``.\n    pub payment_adjuster3: Option<Adjuster>,\n    /// The vector of unadjusted period accrual dates.\n    #[serde(skip)]\n    pub uschedule: Vec<NaiveDateTime>,\n    /// The vector of adjusted period accrual dates.\n    #[serde(skip)]\n    pub aschedule: Vec<NaiveDateTime>,\n    /// The vector of payment dates associated with the adjusted accrual dates.\n    #[serde(skip)]\n    pub pschedule: Vec<NaiveDateTime>,\n    /// An additional vector of payment dates associated with the adjusted accrual dates.\n    #[serde(skip)]\n    pub pschedule2: Vec<NaiveDateTime>,\n    /// An additional vector of payment dates associated with the adjusted accrual dates.\n    #[serde(skip)]\n    pub pschedule3: Vec<NaiveDateTime>,\n}\n\n#[derive(Deserialize)]\nstruct ScheduleDataModel {\n    ueffective: NaiveDateTime,\n    utermination: NaiveDateTime,\n    frequency: Frequency,\n    ufront_stub: Option<NaiveDateTime>,\n    uback_stub: Option<NaiveDateTime>,\n    calendar: Calendar,\n    accrual_adjuster: Adjuster,\n    payment_adjuster: Adjuster,\n    payment_adjuster2: Adjuster,\n    payment_adjuster3: Option<Adjuster>,\n}\n\nimpl std::convert::From<ScheduleDataModel> for Schedule {\n    fn from(model: ScheduleDataModel) -> Self {\n        Self::try_new_defined(\n            model.ueffective,\n            model.utermination,\n            model.frequency,\n            model.ufront_stub,\n            model.uback_stub,\n            model.calendar,\n            model.accrual_adjuster,\n            model.payment_adjuster,\n            model.payment_adjuster2,\n            model.payment_adjuster3,\n        )\n        .expect(\"Data model for `Schedule` is corrupt or invalid.\")\n    }\n}\n\n/// Check that right is greater than left if both Some, and that they do not create a 'dead stub'.\nfn validate_individual_dates(\n    left: &Option<NaiveDateTime>,\n    right: &Option<NaiveDateTime>,\n    accrual_adjuster: &Adjuster,\n    calendar: &Calendar,\n) -> Result<(), PyErr> {\n    match (left, right) {\n        (Some(_left), Some(_right)) => {}\n        _ => return Ok(()),\n    }\n    if left >= right {\n        return Err(PyValueError::new_err(\n            \"Dates are invalid since they are repeated.\",\n        ));\n    }\n    if accrual_adjuster.adjust(&left.unwrap(), calendar)\n        >= accrual_adjuster.adjust(&right.unwrap(), calendar)\n    {\n        return Err(PyValueError::new_err(\n            \"Dates define dead stubs and are invalid\",\n        ));\n    }\n    Ok(())\n}\n\n/// Ensure dates are ordered and that they do not define 'dead stubs', which are created when\n/// two scheduling dates are adjusted under some [Adjuster] and result in the same date.\nfn validate_date_ordering(\n    ueffective: &NaiveDateTime,\n    ufront_stub: &Option<NaiveDateTime>,\n    uback_stub: &Option<NaiveDateTime>,\n    utermination: &NaiveDateTime,\n    accrual_adjuster: &Adjuster,\n    calendar: &Calendar,\n) -> Result<(), PyErr> {\n    let _ = validate_individual_dates(&Some(*ueffective), ufront_stub, accrual_adjuster, calendar)?;\n    let _ = validate_individual_dates(&Some(*ueffective), uback_stub, accrual_adjuster, calendar)?;\n    let _ = validate_individual_dates(\n        &Some(*ueffective),\n        &Some(*utermination),\n        accrual_adjuster,\n        calendar,\n    )?;\n    // front and back stub dates can be equal if the schedule is defined only by two stubs\n    // let _ = validate_individual_dates(ufront_stub, uback_stub, accrual_adjuster, calendar)?;\n    let _ = validate_individual_dates(\n        ufront_stub,\n        &Some(*utermination),\n        accrual_adjuster,\n        calendar,\n    )?;\n    let _ =\n        validate_individual_dates(uback_stub, &Some(*utermination), accrual_adjuster, calendar)?;\n    Ok(())\n}\n\n/// Ensure that two dates can define a proper stub period, either short or long, front or back.\nfn validate_is_stub(\n    left: &NaiveDateTime,\n    right: &NaiveDateTime,\n    frequency: &Frequency,\n    front: bool,\n) -> Result<(), PyErr> {\n    if front {\n        if frequency.is_front_stub(left, right) {\n            Ok(())\n        } else {\n            Err(PyValueError::new_err(\n                \"Dates intended to define a front stub do not permit a valid stub period.\",\n            ))\n        }\n    } else {\n        if frequency.is_back_stub(left, right) {\n            Ok(())\n        } else {\n            Err(PyValueError::new_err(\n                \"Dates intended to define a back stub do not permit a valid stub period.\",\n            ))\n        }\n    }\n}\n\nimpl Schedule {\n    /// Create a [`Schedule`] from well defined unadjusted dates and a [`Frequency`].\n    ///\n    /// # Notes\n    /// If provided arguments do not define a valid schedule pattern then an error is returned.\n    ///\n    /// # Examples\n    /// This is a valid schedule with a long back stub and regular monthly periods.\n    /// ```rust\n    /// # use rateslib::scheduling::{Schedule, ndt, Frequency, Adjuster, Calendar, Cal, RollDay};\n    /// let s = Schedule::try_new_defined(\n    ///     ndt(2024, 1, 3), ndt(2024, 4, 15),                  // ueffective, utermination\n    ///     Frequency::Months{number:1, roll: Some(RollDay::Day(3))}, // frequency\n    ///     None, Some(ndt(2024, 3, 3)),                        // ufront_stub, uback_stub\n    ///     Cal::new(vec![], vec![5,6]).into(),                 // calendar\n    ///     Adjuster::ModifiedFollowing{},                      // accrual_adjuster\n    ///     Adjuster::BusDaysLagSettle(3),                      // payment_adjuster\n    ///     Adjuster::Actual{},                                 // payment_adjuster2\n    ///     None,                                               // payment_adjuster3\n    /// );\n    /// # let s = s.unwrap();\n    /// assert_eq!(s.uschedule, vec![ndt(2024, 1, 3), ndt(2024, 2, 3), ndt(2024, 3, 3), ndt(2024, 4, 15)]);\n    /// assert_eq!(s.aschedule, vec![ndt(2024, 1, 3), ndt(2024, 2, 5), ndt(2024, 3, 4), ndt(2024, 4, 15)]);\n    /// assert_eq!(s.pschedule, vec![ndt(2024, 1, 8), ndt(2024, 2, 8), ndt(2024, 3, 7), ndt(2024, 4, 18)]);\n    /// ```\n    /// This is not a valid schedule since there are no defined stubs and the dates do not align\n    /// with the [RollDay].\n    /// ```rust\n    /// # use rateslib::scheduling::{Schedule, ndt, Frequency, Adjuster, Calendar, Cal, RollDay};\n    /// let s = Schedule::try_new_defined(\n    ///     ndt(2024, 1, 6), ndt(2024, 4, 6),                  // ueffective, utermination\n    ///     Frequency::Months{number:1, roll: Some(RollDay::Day(3))}, // frequency\n    ///     None, None,                                         // ufront_stub, uback_stub\n    ///     Cal::new(vec![], vec![5,6]).into(),                 // calendar\n    ///     Adjuster::ModifiedFollowing{},                      // accrual_adjuster\n    ///     Adjuster::BusDaysLagSettle(3),                      // payment_adjuster\n    ///     Adjuster::Actual{},                                 // payment_adjuster2\n    ///     None,                                               // payment_adjuster3\n    /// );\n    /// assert!(s.is_err());\n    /// ```\n    pub fn try_new_defined(\n        ueffective: NaiveDateTime,\n        utermination: NaiveDateTime,\n        frequency: Frequency,\n        ufront_stub: Option<NaiveDateTime>,\n        uback_stub: Option<NaiveDateTime>,\n        calendar: Calendar,\n        accrual_adjuster: Adjuster,\n        payment_adjuster: Adjuster,\n        payment_adjuster2: Adjuster,\n        payment_adjuster3: Option<Adjuster>,\n    ) -> Result<Self, PyErr> {\n        // validate date ordering\n        let _ = validate_date_ordering(\n            &ueffective,\n            &ufront_stub,\n            &uback_stub,\n            &utermination,\n            &accrual_adjuster,\n            &calendar,\n        )?;\n\n        let uschedule: Vec<NaiveDateTime>;\n\n        match (ufront_stub, uback_stub) {\n            (None, None) => {\n                // then schedule is defined only by ueffective and utermination\n                let uregular = frequency.try_uregular(&ueffective, &utermination);\n                if uregular.is_ok() {\n                    // case 1) schedule must be a regular schedule\n                    uschedule = uregular.unwrap();\n                } else if frequency.is_front_stub(&ueffective, &utermination)\n                    || frequency.is_back_stub(&ueffective, &utermination)\n                {\n                    //case 2) schedule must be a single period stub\n                    uschedule = vec![ueffective, utermination];\n                } else {\n                    return Err(PyValueError::new_err(\"`ueffective`, `utermination` and `frequency` do not define a regular schedule or a single period stub.\"));\n                }\n            }\n            (Some(regular_start), None) => {\n                // case 3) with a front stub\n                let uregular = frequency.try_uregular(&regular_start, &utermination)?;\n                let _ = validate_is_stub(&ueffective, &regular_start, &frequency, true)?;\n                uschedule = composite_uschedule(\n                    &ueffective,\n                    &utermination,\n                    &ufront_stub,\n                    &uback_stub,\n                    &uregular,\n                );\n            }\n            (None, Some(regular_end)) => {\n                // case 3) with a back stub\n                let uregular = frequency.try_uregular(&ueffective, &regular_end)?;\n                let _ = validate_is_stub(&regular_end, &utermination, &frequency, false)?;\n                uschedule = composite_uschedule(\n                    &ueffective,\n                    &utermination,\n                    &ufront_stub,\n                    &uback_stub,\n                    &uregular,\n                );\n            }\n            (Some(regular_start), Some(regular_end)) => {\n                let _ = validate_is_stub(&ueffective, &regular_start, &frequency, true)?;\n                let _ = validate_is_stub(&regular_end, &utermination, &frequency, false)?;\n                if regular_start == regular_end {\n                    // is only possible when stubs are both given and are equal, due to date validation\n                    // case 4) schedule must be two stubs\n                    uschedule = vec![ueffective, regular_start, utermination];\n                } else {\n                    // case 5) some regular component with stubs at both ends\n                    let uregular = frequency.try_uregular(&regular_start, &regular_end)?;\n                    uschedule = composite_uschedule(\n                        &ueffective,\n                        &utermination,\n                        &ufront_stub,\n                        &uback_stub,\n                        &uregular,\n                    );\n                }\n            }\n        }\n\n        let aschedule: Vec<NaiveDateTime> = accrual_adjuster.adjusts(&uschedule, &calendar);\n        let pschedule = payment_adjuster.adjusts(&aschedule, &calendar);\n        let pschedule2 = payment_adjuster2.adjusts(&aschedule, &calendar);\n        let pschedule3 = match payment_adjuster3 {\n            None => pschedule.clone(),\n            Some(adjuster) => adjuster.adjusts(&aschedule, &calendar),\n        };\n\n        Ok(Self {\n            ueffective,\n            utermination,\n            frequency,\n            ufront_stub,\n            uback_stub,\n            calendar: calendar.clone(),\n            accrual_adjuster,\n            payment_adjuster,\n            payment_adjuster2,\n            payment_adjuster3,\n            uschedule,\n            aschedule,\n            pschedule,\n            pschedule2,\n            pschedule3,\n        })\n    }\n\n    /// Create a [`Schedule`] from unadjusted dates with specified [`StubInference`].\n    ///\n    /// # Notes\n    /// This method introduces the ``stub_inference`` argument.\n    /// If it is given as `None` then this method will revert to [Schedule::try_new_uschedule].\n    /// If ``stub_inference`` is given but it conflicts with an explicit ``stub`` date given then\n    /// an error will be returned.\n    /// If ``stub_inference`` is given but a ``stub`` date is not required then a valid [Schedule]\n    /// is returned without an inferred stub.\n    fn try_new_infer_stub(\n        ueffective: NaiveDateTime,\n        utermination: NaiveDateTime,\n        frequency: Frequency,\n        ufront_stub: Option<NaiveDateTime>,\n        uback_stub: Option<NaiveDateTime>,\n        calendar: Calendar,\n        accrual_adjuster: Adjuster,\n        payment_adjuster: Adjuster,\n        payment_adjuster2: Adjuster,\n        payment_adjuster3: Option<Adjuster>,\n        stub_inference: StubInference,\n    ) -> Result<Self, PyErr> {\n        // evaluate if schedule is valid as defined without stub inference\n        let temp_schedule = Schedule::try_new_defined(\n            ueffective,\n            utermination,\n            frequency.clone(),\n            ufront_stub,\n            uback_stub,\n            calendar.clone(),\n            accrual_adjuster,\n            payment_adjuster,\n            payment_adjuster2,\n            payment_adjuster3,\n        );\n        // validate inference is not blocked by user defined values.\n        let _ = validate_stub_dates_and_inference(&ufront_stub, &uback_stub, &stub_inference)?;\n\n        let stubs: (Option<NaiveDateTime>, Option<NaiveDateTime>);\n\n        let (interior_start, interior_end) =\n            match_interior_dates(&ueffective, &ufront_stub, &uback_stub, &utermination);\n        stubs = match stub_inference {\n            // for no inference simply return the initially generated schedule result.\n            StubInference::NeitherSide => return temp_schedule,\n            // when testing stub inference, a single period stub has a uschedule length of 2 and\n            // the stub can be evaluated in either direction: front or back.\n            StubInference::ShortFront => {\n                if temp_schedule.is_ok() {\n                    let test_schedule = temp_schedule.unwrap();\n                    if frequency.is_short_front_stub(\n                        &test_schedule.uschedule[0],\n                        &test_schedule.uschedule[1],\n                    ) || (test_schedule.uschedule.len() == 2\n                        && frequency.is_short_back_stub(\n                            &test_schedule.uschedule[0],\n                            &test_schedule.uschedule[1],\n                        ))\n                    {\n                        return Ok(test_schedule);\n                    } // already has a short front stub\n                }\n                let mut ufront_stub =\n                    frequency.try_infer_ufront_stub(&interior_start, &interior_end, true)?;\n                // validate as dead stub\n                let valid_stub = validate_individual_dates(\n                    &Some(ueffective),\n                    &ufront_stub,\n                    &accrual_adjuster,\n                    &calendar,\n                );\n                if valid_stub.is_err() {\n                    // an error indicates a dead stub so convert to Long\n                    ufront_stub =\n                        frequency.try_infer_ufront_stub(&interior_start, &interior_end, false)?;\n                }\n                (ufront_stub, uback_stub)\n            }\n            StubInference::LongFront => {\n                if temp_schedule.is_ok() {\n                    let test_schedule = temp_schedule.unwrap();\n                    if frequency.is_long_front_stub(\n                        &test_schedule.uschedule[0],\n                        &test_schedule.uschedule[1],\n                    ) || (test_schedule.uschedule.len() == 2\n                        && frequency\n                            .is_back_stub(&test_schedule.uschedule[0], &test_schedule.uschedule[1]))\n                    {\n                        return Ok(test_schedule);\n                    } // already has a long front stub\n                }\n                (\n                    frequency.try_infer_ufront_stub(&interior_start, &interior_end, false)?,\n                    uback_stub,\n                )\n            }\n            StubInference::ShortBack => {\n                if temp_schedule.is_ok() {\n                    let test_schedule = temp_schedule.unwrap();\n                    let n = test_schedule.uschedule.len();\n                    if frequency.is_short_back_stub(\n                        &test_schedule.uschedule[n - 2],\n                        &test_schedule.uschedule[n - 1],\n                    ) || (n == 2\n                        && frequency.is_short_front_stub(\n                            &test_schedule.uschedule[n - 2],\n                            &test_schedule.uschedule[n - 1],\n                        ))\n                    {\n                        return Ok(test_schedule);\n                    } // already has a short back stub\n                }\n                let mut uback_stub =\n                    frequency.try_infer_uback_stub(&interior_start, &interior_end, true)?;\n                // validate as dead stub\n                let valid_stub = validate_individual_dates(\n                    &uback_stub,\n                    &Some(utermination),\n                    &accrual_adjuster,\n                    &calendar,\n                );\n                if valid_stub.is_err() {\n                    // error indicates a dead stub so convert to Long\n                    uback_stub =\n                        frequency.try_infer_uback_stub(&interior_start, &interior_end, false)?;\n                }\n                (ufront_stub, uback_stub)\n            }\n            StubInference::LongBack => {\n                if temp_schedule.is_ok() {\n                    let test_schedule = temp_schedule.unwrap();\n                    let n = test_schedule.uschedule.len();\n                    if frequency.is_short_back_stub(\n                        &test_schedule.uschedule[n - 2],\n                        &test_schedule.uschedule[n - 1],\n                    ) || (n == 2\n                        && frequency.is_front_stub(\n                            &test_schedule.uschedule[n - 2],\n                            &test_schedule.uschedule[n - 1],\n                        ))\n                    {\n                        return Ok(test_schedule);\n                    } // already has a long back stub\n                }\n                (\n                    ufront_stub,\n                    frequency.try_infer_uback_stub(&interior_start, &interior_end, false)?,\n                )\n            }\n        };\n        Self::try_new_defined(\n            ueffective,\n            utermination,\n            frequency,\n            stubs.0,\n            stubs.1,\n            calendar,\n            accrual_adjuster,\n            payment_adjuster,\n            payment_adjuster2,\n            payment_adjuster3,\n        )\n    }\n\n    /// Create a [`Schedule`] from unadjusted dates.\n    ///\n    /// # Notes\n    ///\n    /// An unadjusted regular schedule, that aligns with [Frequency], must be defined between\n    /// the relevant dates. If not an error is returned.\n    ///\n    /// This method uses [Scheduling::try_uregular](crate::scheduling::Scheduling::try_uregular)\n    /// to ascertain if the provided dates define a regular schedule or not.\n    fn try_new_uschedule_infer_frequency(\n        ueffective: NaiveDateTime,\n        utermination: NaiveDateTime,\n        frequency: Frequency,\n        ufront_stub: Option<NaiveDateTime>,\n        uback_stub: Option<NaiveDateTime>,\n        calendar: Calendar,\n        accrual_adjuster: Adjuster,\n        payment_adjuster: Adjuster,\n        payment_adjuster2: Adjuster,\n        payment_adjuster3: Option<Adjuster>,\n        eom: bool,\n        stub_inference: StubInference,\n    ) -> Result<Self, PyErr> {\n        // evaluate the Options and get the start and end of regular schedule component\n        let (regular_start, regular_end) =\n            match_interior_dates(&ueffective, &ufront_stub, &uback_stub, &utermination);\n\n        // get all possible Frequency variants. this will often only be 1 element\n        let frequencies = frequency.try_vec_from(&vec![regular_start, regular_end])?;\n\n        // find all possible schedules that are valid for frequencies\n        let uschedules: Vec<Schedule> = frequencies\n            .into_iter()\n            .filter_map(|f| {\n                Schedule::try_new_infer_stub(\n                    ueffective,\n                    utermination,\n                    f,\n                    ufront_stub,\n                    uback_stub,\n                    calendar.clone(),\n                    accrual_adjuster,\n                    payment_adjuster,\n                    payment_adjuster2,\n                    payment_adjuster3,\n                    stub_inference,\n                )\n                .ok()\n            })\n            .collect();\n\n        // error if no valid schedules were found\n        if uschedules.len() == 0 {\n            return Err(PyValueError::new_err(\n                \"No valid Schedules could be created with given `udates` combinations and `frequency`.\",\n            ));\n        }\n\n        // filter regular schedules\n        let regulars: Vec<Schedule> = uschedules\n            .iter()\n            .cloned()\n            .filter(|schedule| schedule.is_regular())\n            .collect();\n        if regulars.len() != 0 {\n            Ok(filter_schedules_by_eom(regulars, eom))\n        } else {\n            Ok(filter_schedules_by_eom(uschedules, eom))\n        }\n    }\n\n    /// Create a [`Schedule`] using inference if some of the parameters are not well defined.\n    ///\n    /// # Notes\n    /// If all parameters are well defined and dates are definitively known in their unadjusted\n    /// forms then the [`try_new_defined`](Schedule::try_new_defined) method\n    /// should be used instead.\n    ///\n    /// This method provides the additional features below:\n    /// - **Unadjusted date inference**: if *adjusted* dates are given then a neighbourhood of\n    ///   dates will be sub-sampled to determine\n    ///   any possibilities for *unadjusted* dates defined by the `accrual_adjuster` and `calendar`.\n    ///   Only the dates at either side of the regular schedule component are explored. Stub date\n    ///   boundaries are used as provided.\n    /// - **Frequency inference**: any [`Frequency`](crate::scheduling::Frequency) that contains\n    ///   optional elements, e.g. no [`RollDay`],\n    ///   will be explored for all possible alternatives that results in the most likely schedule,\n    ///   guided by the `eom` parameter.\n    /// - **Stub date inference**: one-sided stub date inference can be attempted guided by\n    ///   the `stub_inference` parameter.\n    pub fn try_new_inferred(\n        effective: NaiveDateTime,\n        termination: NaiveDateTime,\n        frequency: Frequency,\n        front_stub: Option<NaiveDateTime>,\n        back_stub: Option<NaiveDateTime>,\n        calendar: Calendar,\n        accrual_adjuster: Adjuster,\n        payment_adjuster: Adjuster,\n        payment_adjuster2: Adjuster,\n        payment_adjuster3: Option<Adjuster>,\n        eom: bool,\n        stub_inference: StubInference,\n    ) -> Result<Schedule, PyErr> {\n        // perform a preliminary check to determine if a given stub date actually falls under some\n        // regular schedule. This is common when a list of bonds have 'first coupon' dates that\n        // may or may not be official stub dates.\n        if front_stub.is_none() && back_stub.is_none() {\n            // then do nothing in this pre-check\n        } else {\n            let dates: (Vec<NaiveDateTime>, Vec<NaiveDateTime>) = (\n                get_unadjusteds(&effective, &accrual_adjuster, &calendar),\n                get_unadjusteds(&termination, &accrual_adjuster, &calendar),\n            );\n            let combinations = iproduct!(dates.0, dates.1);\n            let schedules: Vec<Schedule> = combinations\n                .into_iter()\n                .filter_map(|(e, t)| {\n                    Schedule::try_new_uschedule_infer_frequency(\n                        e,\n                        t,\n                        frequency.clone(),\n                        None,\n                        None,\n                        calendar.clone(),\n                        accrual_adjuster,\n                        payment_adjuster,\n                        payment_adjuster2,\n                        payment_adjuster3,\n                        eom,\n                        stub_inference,\n                    )\n                    .ok()\n                })\n                .filter(|schedule| schedule.is_regular())\n                .filter(|s| {\n                    front_stub.is_none()\n                        || (front_stub.is_some()\n                            && (front_stub.unwrap() == s.aschedule[1]\n                                || front_stub.unwrap() == s.uschedule[1]))\n                })\n                .filter(|s| {\n                    back_stub.is_none()\n                        || (back_stub.is_some()\n                            && (back_stub.unwrap() == s.aschedule[s.aschedule.len() - 2]\n                                || back_stub.unwrap() == s.uschedule[s.uschedule.len() - 2]))\n                })\n                .collect();\n            if schedules.len() == 0 {\n                // do nothing because the pre-check has failed: moved to usual construction\n            } else {\n                // filter regular schedules\n                return Ok(filter_schedules_by_eom(schedules, eom));\n            }\n        }\n\n        // find all unadjusted combinations. only adjust the boundaries of the regular component.\n        let dates: (\n            Vec<NaiveDateTime>,\n            Vec<Option<NaiveDateTime>>,\n            Vec<Option<NaiveDateTime>>,\n            Vec<NaiveDateTime>,\n        ) = match (front_stub, back_stub) {\n            (None, None) => (\n                get_unadjusteds(&effective, &accrual_adjuster, &calendar),\n                vec![None],\n                vec![None],\n                get_unadjusteds(&termination, &accrual_adjuster, &calendar),\n            ),\n            (Some(d), None) => (\n                vec![effective],\n                get_unadjusteds(&d, &accrual_adjuster, &calendar)\n                    .into_iter()\n                    .map(Some)\n                    .collect(),\n                vec![None],\n                get_unadjusteds(&termination, &accrual_adjuster, &calendar),\n            ),\n            (None, Some(d)) => (\n                get_unadjusteds(&effective, &accrual_adjuster, &calendar),\n                vec![None],\n                get_unadjusteds(&d, &accrual_adjuster, &calendar)\n                    .into_iter()\n                    .map(Some)\n                    .collect(),\n                vec![termination],\n            ),\n            (Some(d), Some(d2)) => (\n                vec![effective],\n                get_unadjusteds(&d, &accrual_adjuster, &calendar)\n                    .into_iter()\n                    .map(Some)\n                    .collect(),\n                get_unadjusteds(&d2, &accrual_adjuster, &calendar)\n                    .into_iter()\n                    .map(Some)\n                    .collect(),\n                vec![termination],\n            ),\n        };\n\n        let combinations = iproduct!(dates.0, dates.1, dates.2, dates.3);\n        let schedules: Vec<Schedule> = combinations\n            .into_iter()\n            .filter_map(|(e, fs, bs, t)| {\n                Schedule::try_new_uschedule_infer_frequency(\n                    e,\n                    t,\n                    frequency.clone(),\n                    fs,\n                    bs,\n                    calendar.clone(),\n                    accrual_adjuster,\n                    payment_adjuster,\n                    payment_adjuster2,\n                    payment_adjuster3,\n                    eom,\n                    stub_inference,\n                )\n                .ok()\n            })\n            .collect();\n\n        if schedules.len() == 0 {\n            Err(PyValueError::new_err(\n                \"A Schedule could not be generated from the parameter combinations.\",\n            ))\n        } else {\n            // filter regular schedules\n            let regulars: Vec<Schedule> = schedules\n                .iter()\n                .cloned()\n                .filter(|schedule| schedule.is_regular())\n                .collect();\n            if regulars.len() != 0 {\n                Ok(filter_schedules_by_eom(regulars, eom))\n            } else {\n                Ok(filter_schedules_by_eom(schedules, eom))\n            }\n        }\n    }\n\n    /// Check if a [`Schedule`] contains only regular periods, and no stub periods.\n    pub fn is_regular(&self) -> bool {\n        let ucheck = self\n            .frequency\n            .try_uregular(&self.ueffective, &self.utermination);\n        if ucheck.is_ok() {\n            ucheck.unwrap() == self.uschedule\n        } else {\n            false\n        }\n    }\n}\n\nfn match_interior_dates(\n    ueffective: &NaiveDateTime,\n    ufront_stub: &Option<NaiveDateTime>,\n    uback_stub: &Option<NaiveDateTime>,\n    utermination: &NaiveDateTime,\n) -> (NaiveDateTime, NaiveDateTime) {\n    match (ufront_stub, uback_stub) {\n        (None, None) => (*ueffective, *utermination),\n        (Some(v), None) => (*v, *utermination),\n        (None, Some(v)) => (*ueffective, *v),\n        (Some(v), Some(w)) => (*v, *w),\n    }\n}\n\n/// Validate provided stubs do not conflict with the required [StubInference]\nfn validate_stub_dates_and_inference(\n    ufront_stub: &Option<NaiveDateTime>,\n    uback_stub: &Option<NaiveDateTime>,\n    stub_inference: &StubInference,\n) -> Result<(), PyErr> {\n    match (ufront_stub, uback_stub, stub_inference) {\n        (Some(_v), Some(_w), si) if !matches!(si, StubInference::NeitherSide) => Err(\n            PyValueError::new_err(\"Cannot infer any stubs if both are explicitly given.\"),\n        ),\n        (Some(_v), None, si)\n            if matches!(si, StubInference::ShortFront | StubInference::LongFront) =>\n        {\n            Err(PyValueError::new_err(\n                \"Cannot infer front stub if it is explicitly given.\",\n            ))\n        }\n        (None, Some(_w), si)\n            if matches!(si, StubInference::ShortBack | StubInference::LongBack) =>\n        {\n            Err(PyValueError::new_err(\n                \"Cannot infer back stub if it is explicitly given.\",\n            ))\n        }\n        _ => Ok(()),\n    }\n}\n\n/// Get unadjusted schedule dates assuming all inputs are correct and pre-validated.\nfn composite_uschedule(\n    ueffective: &NaiveDateTime,\n    utermination: &NaiveDateTime,\n    ufront_stub: &Option<NaiveDateTime>,\n    uback_stub: &Option<NaiveDateTime>,\n    regular_uschedule: &Vec<NaiveDateTime>,\n) -> Vec<NaiveDateTime> {\n    let mut uschedule: Vec<NaiveDateTime> = vec![];\n    match (*ufront_stub, *uback_stub) {\n        (None, None) => {\n            uschedule.extend(regular_uschedule);\n        }\n        (Some(_v), None) => {\n            uschedule.push(*ueffective);\n            uschedule.extend(regular_uschedule);\n        }\n        (None, Some(_v)) => {\n            uschedule.extend(regular_uschedule);\n            uschedule.push(*utermination);\n        }\n        (Some(_v), Some(_w)) => {\n            uschedule.push(*ueffective);\n            uschedule.extend(regular_uschedule);\n            uschedule.push(*utermination);\n        }\n    }\n    uschedule\n}\n\nfn filter_schedules_by_eom(uschedules: Vec<Schedule>, eom: bool) -> Schedule {\n    // filter the found schedules. if `eom` then prefer the first schedule with RollDay::Day(31)\n    // else prefer the first found schedule.\n    let original = uschedules[0].clone();\n\n    if !eom {\n        // just return the first schedule\n        original\n    } else {\n        // scan for an eom possibility\n        let possibles: Vec<Schedule> = uschedules\n            .into_iter()\n            .filter(|s| {\n                matches!(\n                    s.frequency,\n                    Frequency::Months {\n                        number: _,\n                        roll: Some(RollDay::Day(31))\n                    }\n                )\n            })\n            .collect();\n        if possibles.len() >= 1 {\n            possibles[0].clone()\n        } else {\n            original\n        }\n    }\n}\n\n// UNIT TESTS\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::{ndt, Cal, NamedCal, RollDay};\n\n    #[test]\n    fn test_new_uschedule_defined_cases_1_and_2() {\n        let options: Vec<(NaiveDateTime, NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (\n                ndt(2000, 1, 1), // regular schedule\n                ndt(2000, 3, 1),\n                vec![ndt(2000, 1, 1), ndt(2000, 2, 1), ndt(2000, 3, 1)],\n            ),\n            (\n                ndt(2000, 1, 1), // short single period sub\n                ndt(2000, 1, 20),\n                vec![ndt(2000, 1, 1), ndt(2000, 1, 20)],\n            ),\n            (\n                ndt(2000, 1, 1), // long single period stub\n                ndt(2000, 2, 15),\n                vec![ndt(2000, 1, 1), ndt(2000, 2, 15)],\n            ),\n        ];\n        for option in options {\n            let result = Schedule::try_new_defined(\n                option.0,\n                option.1,\n                Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(1)),\n                },\n                None,\n                None,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::Following {},\n                Adjuster::Following {},\n                None,\n            );\n            assert_eq!(result.unwrap().uschedule, option.2);\n        }\n    }\n\n    #[test]\n    fn test_new_uschedule_defined_cases_1_and_2_err() {\n        let options: Vec<(NaiveDateTime, NaiveDateTime, Frequency)> = vec![\n            (\n                ndt(2000, 1, 1), // regular schedule is not defined stub too long\n                ndt(2000, 3, 15),\n                Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(1)),\n                },\n            ),\n            (\n                ndt(2000, 1, 1), // undefined RollDay\n                ndt(2000, 3, 1),\n                Frequency::Months {\n                    number: 1,\n                    roll: None,\n                },\n            ),\n        ];\n        for option in options {\n            let result = Schedule::try_new_defined(\n                option.0,\n                option.1,\n                option.2,\n                None,\n                None,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::Following {},\n                Adjuster::Following {},\n                None,\n            );\n            assert!(result.is_err());\n        }\n    }\n\n    #[test]\n    fn test_new_uschedule_defined_cases_4() {\n        let options: Vec<(\n            NaiveDateTime,\n            NaiveDateTime,\n            NaiveDateTime,\n            Vec<NaiveDateTime>,\n        )> = vec![\n            (\n                ndt(2000, 1, 1), // Short then Short\n                ndt(2000, 1, 15),\n                ndt(2000, 2, 10),\n                vec![ndt(2000, 1, 1), ndt(2000, 1, 15), ndt(2000, 2, 10)],\n            ),\n            (\n                ndt(2000, 1, 1), // Short then Long\n                ndt(2000, 1, 15),\n                ndt(2000, 2, 25),\n                vec![ndt(2000, 1, 1), ndt(2000, 1, 15), ndt(2000, 2, 25)],\n            ),\n            (\n                ndt(2000, 1, 1), // Long then Short\n                ndt(2000, 2, 15),\n                ndt(2000, 2, 25),\n                vec![ndt(2000, 1, 1), ndt(2000, 2, 15), ndt(2000, 2, 25)],\n            ),\n            (\n                ndt(2000, 1, 1), // Long then Long\n                ndt(2000, 2, 15),\n                ndt(2000, 3, 20),\n                vec![ndt(2000, 1, 1), ndt(2000, 2, 15), ndt(2000, 3, 20)],\n            ),\n        ];\n        for option in options {\n            let result = Schedule::try_new_defined(\n                option.0,\n                option.2,\n                Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(15)),\n                }, // Zero also works, as does CalDays(30)\n                Some(option.1),\n                Some(option.1),\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::Following {},\n                Adjuster::Following {},\n                None,\n            );\n            assert_eq!(result.unwrap().uschedule, option.3);\n        }\n    }\n\n    #[test]\n    fn test_new_uschedule_defined_cases_3() {\n        let options: Vec<(\n            NaiveDateTime,\n            Option<NaiveDateTime>,\n            Option<NaiveDateTime>,\n            NaiveDateTime,\n            Vec<NaiveDateTime>,\n        )> = vec![\n            (\n                ndt(2000, 1, 1), // Short then Regular\n                Some(ndt(2000, 1, 15)),\n                None,\n                ndt(2000, 3, 15),\n                vec![\n                    ndt(2000, 1, 1),\n                    ndt(2000, 1, 15),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                ],\n            ),\n            (\n                ndt(2000, 1, 1), // Long then Regular\n                Some(ndt(2000, 2, 15)),\n                None,\n                ndt(2000, 4, 15),\n                vec![\n                    ndt(2000, 1, 1),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                    ndt(2000, 4, 15),\n                ],\n            ),\n            (\n                ndt(2000, 1, 15), // Regular then Short\n                None,\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 4, 10),\n                vec![\n                    ndt(2000, 1, 15),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                    ndt(2000, 4, 10),\n                ],\n            ),\n            (\n                ndt(2000, 1, 15), // Regular then Long\n                None,\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 4, 25),\n                vec![\n                    ndt(2000, 1, 15),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                    ndt(2000, 4, 25),\n                ],\n            ),\n            (\n                ndt(2000, 1, 15), // Regular then 2 -period Long\n                None,\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 5, 15),\n                vec![\n                    ndt(2000, 1, 15),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                    ndt(2000, 5, 15),\n                ],\n            ),\n        ];\n        for option in options {\n            let result = Schedule::try_new_defined(\n                option.0,\n                option.3,\n                Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(15)),\n                }, // Zero also works\n                option.1,\n                option.2,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::Following {},\n                Adjuster::Following {},\n                None,\n            );\n            assert_eq!(result.unwrap().uschedule, option.4);\n        }\n    }\n\n    #[test]\n    fn test_new_uschedule_defined_cases_3_err() {\n        let options: Vec<(\n            NaiveDateTime,\n            Option<NaiveDateTime>,\n            Option<NaiveDateTime>,\n            NaiveDateTime,\n        )> = vec![\n            (\n                ndt(2000, 1, 1), // Short then Regular misaligned\n                Some(ndt(2000, 1, 15)),\n                None,\n                ndt(2000, 3, 16),\n            ),\n            (\n                ndt(2000, 1, 1), // Front Stub is too long\n                Some(ndt(2000, 5, 15)),\n                None,\n                ndt(2000, 7, 15),\n            ),\n            (\n                ndt(2000, 1, 13), // Regular misaligned then Short\n                None,\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 4, 10),\n            ),\n            (\n                ndt(2000, 1, 15), // Back Stub is too long\n                None,\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 7, 25),\n            ),\n            (\n                ndt(2000, 1, 15), // Short stub cannot be a regular period\n                None,\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 4, 15),\n            ),\n        ];\n        for option in options {\n            let result = Schedule::try_new_defined(\n                option.0,\n                option.3,\n                Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(15)),\n                }, // Zero also works\n                option.1,\n                option.2,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::Following {},\n                Adjuster::Following {},\n                None,\n            );\n            assert!(result.is_err());\n        }\n    }\n\n    #[test]\n    fn test_new_uschedule_defined_cases_5() {\n        let options: Vec<(\n            NaiveDateTime,\n            Option<NaiveDateTime>,\n            Option<NaiveDateTime>,\n            NaiveDateTime,\n            Vec<NaiveDateTime>,\n        )> = vec![\n            (\n                ndt(2000, 1, 1), // Short Short\n                Some(ndt(2000, 1, 15)),\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 4, 10),\n                vec![\n                    ndt(2000, 1, 1),\n                    ndt(2000, 1, 15),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                    ndt(2000, 4, 10),\n                ],\n            ),\n            (\n                ndt(2000, 1, 1), // Short Long\n                Some(ndt(2000, 1, 15)),\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 4, 25),\n                vec![\n                    ndt(2000, 1, 1),\n                    ndt(2000, 1, 15),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                    ndt(2000, 4, 25),\n                ],\n            ),\n            (\n                ndt(2000, 1, 1), // Long Long\n                Some(ndt(2000, 2, 15)),\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 4, 25),\n                vec![\n                    ndt(2000, 1, 1),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                    ndt(2000, 4, 25),\n                ],\n            ),\n            (\n                ndt(2000, 1, 1), // Long Short\n                Some(ndt(2000, 2, 15)),\n                Some(ndt(2000, 3, 15)),\n                ndt(2000, 4, 10),\n                vec![\n                    ndt(2000, 1, 1),\n                    ndt(2000, 2, 15),\n                    ndt(2000, 3, 15),\n                    ndt(2000, 4, 10),\n                ],\n            ),\n        ];\n        for option in options {\n            let result = Schedule::try_new_defined(\n                option.0,\n                option.3,\n                Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(15)),\n                }, // Zero also works\n                option.1,\n                option.2,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::Following {},\n                Adjuster::Following {},\n                None,\n            );\n            assert_eq!(result.unwrap().uschedule, option.4);\n        }\n    }\n\n    #[test]\n    fn test_new_uschedule_defined_cases_5_err() {\n        let options: Vec<(\n            NaiveDateTime,\n            Option<NaiveDateTime>,\n            Option<NaiveDateTime>,\n            NaiveDateTime,\n        )> = vec![(\n            ndt(2000, 1, 1), // Regular is misaligned\n            Some(ndt(2000, 1, 15)),\n            Some(ndt(2000, 3, 16)),\n            ndt(2000, 4, 10),\n        )];\n        for option in options {\n            let result = Schedule::try_new_defined(\n                option.0,\n                option.3,\n                Frequency::Months {\n                    number: 1,\n                    roll: Some(RollDay::Day(15)),\n                }, // Zero also works\n                option.1,\n                option.2,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::Following {},\n                Adjuster::Following {},\n                None,\n            );\n            assert!(result.is_err());\n        }\n    }\n\n    #[test]\n    fn test_new_uschedule_defined_err() {\n        // test that None RollDay produces errors even for a well defined schedule\n        let result = Schedule::try_new_defined(\n            ndt(2000, 1, 1),\n            ndt(2001, 1, 1),\n            Frequency::Months {\n                number: 6,\n                roll: None,\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::Actual {},\n            Adjuster::Actual {},\n            Adjuster::Following {},\n            None,\n        );\n        assert!(result.is_err())\n    }\n\n    #[test]\n    fn test_try_new_uschedule_dead_stubs() {\n        let s = Schedule::try_new_defined(\n            ndt(2023, 1, 1),\n            ndt(2024, 1, 2),\n            Frequency::Months {\n                number: 6,\n                roll: Some(RollDay::Day(2)),\n            },\n            Some(ndt(2023, 1, 2)),\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n        );\n        assert!(s.is_err()); // 1st Jan is adjusted to 2nd Jan aligning with front stub\n\n        let s = Schedule::try_new_defined(\n            ndt(2022, 1, 1),\n            ndt(2023, 1, 2),\n            Frequency::Months {\n                number: 6,\n                roll: Some(RollDay::Day(1)),\n            },\n            None,\n            Some(ndt(2023, 1, 1)),\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n        );\n        assert!(s.is_err()); // 1st Jan is adjusted to 2nd Jan aligning with front stub\n    }\n\n    #[test]\n    fn test_try_new_uschedule_eom_parameter_selection() {\n        let s = Schedule::try_new_uschedule_infer_frequency(\n            ndt(2024, 2, 29),\n            ndt(2024, 11, 30),\n            Frequency::Months {\n                number: 3,\n                roll: None,\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n            true,\n            StubInference::NeitherSide,\n        )\n        .unwrap();\n        assert_eq!(\n            s.frequency,\n            Frequency::Months {\n                number: 3,\n                roll: Some(RollDay::Day(31))\n            }\n        );\n\n        let s = Schedule::try_new_uschedule_infer_frequency(\n            ndt(2024, 2, 29),\n            ndt(2024, 11, 30),\n            Frequency::Months {\n                number: 3,\n                roll: None,\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n            false,\n            StubInference::NeitherSide,\n        )\n        .unwrap();\n        assert_eq!(\n            s.frequency,\n            Frequency::Months {\n                number: 3,\n                roll: Some(RollDay::Day(30))\n            }\n        );\n\n        let s = Schedule::try_new_uschedule_infer_frequency(\n            ndt(2024, 2, 29),\n            ndt(2024, 11, 29),\n            Frequency::Months {\n                number: 3,\n                roll: None,\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n            true,\n            StubInference::NeitherSide,\n        )\n        .unwrap();\n        assert_eq!(\n            s.frequency,\n            Frequency::Months {\n                number: 3,\n                roll: Some(RollDay::Day(29))\n            }\n        );\n    }\n\n    #[test]\n    fn test_try_new_uschedule_inferred_fails() {\n        // fails because stub dates are given as well as an inference enum\n        assert_eq!(\n            true,\n            Schedule::try_new_infer_stub(\n                ndt(2000, 1, 1),\n                ndt(2000, 2, 1),\n                Frequency::CalDays { number: 100 },\n                Some(ndt(2000, 1, 10)),\n                Some(ndt(2000, 1, 16)),\n                Calendar::Cal(Cal::new(vec![], vec![])),\n                Adjuster::ModifiedFollowing {},\n                Adjuster::BusDaysLagSettle(1),\n                Adjuster::Following {},\n                None,\n                StubInference::ShortBack,\n            )\n            .is_err()\n        );\n\n        // fails because stub date is given as well as an inference enum\n        assert_eq!(\n            true,\n            Schedule::try_new_infer_stub(\n                ndt(2000, 1, 1),\n                ndt(2000, 2, 1),\n                Frequency::CalDays { number: 100 },\n                None,\n                Some(ndt(2000, 1, 16)),\n                Calendar::Cal(Cal::new(vec![], vec![])),\n                Adjuster::ModifiedFollowing {},\n                Adjuster::BusDaysLagSettle(1),\n                Adjuster::Following {},\n                None,\n                StubInference::ShortBack,\n            )\n            .is_err()\n        );\n\n        // fails because stub date is given as well as an inference enum\n        assert_eq!(\n            true,\n            Schedule::try_new_infer_stub(\n                ndt(2000, 1, 1),\n                ndt(2000, 2, 1),\n                Frequency::CalDays { number: 100 },\n                None,\n                Some(ndt(2000, 1, 16)),\n                Calendar::Cal(Cal::new(vec![], vec![])),\n                Adjuster::ModifiedFollowing {},\n                Adjuster::BusDaysLagSettle(1),\n                Adjuster::Following {},\n                None,\n                StubInference::LongBack,\n            )\n            .is_err()\n        );\n\n        // fails because stub date is given as well as an inference enum\n        assert_eq!(\n            true,\n            Schedule::try_new_infer_stub(\n                ndt(2000, 1, 1),\n                ndt(2000, 2, 1),\n                Frequency::CalDays { number: 100 },\n                Some(ndt(2000, 1, 16)),\n                None,\n                Calendar::Cal(Cal::new(vec![], vec![])),\n                Adjuster::ModifiedFollowing {},\n                Adjuster::BusDaysLagSettle(1),\n                Adjuster::Following {},\n                None,\n                StubInference::ShortFront,\n            )\n            .is_err()\n        );\n\n        // fails because stub date is given as well as an inference enum\n        assert_eq!(\n            true,\n            Schedule::try_new_infer_stub(\n                ndt(2000, 1, 1),\n                ndt(2000, 2, 1),\n                Frequency::CalDays { number: 100 },\n                Some(ndt(2000, 1, 16)),\n                None,\n                Calendar::Cal(Cal::new(vec![], vec![])),\n                Adjuster::ModifiedFollowing {},\n                Adjuster::BusDaysLagSettle(1),\n                Adjuster::Following {},\n                None,\n                StubInference::LongFront,\n            )\n            .is_err()\n        );\n    }\n\n    #[test]\n    fn test_try_new_schedule_short_period() {\n        // test infer stub works when no stub is required for single period stub case\n        let s = Schedule::try_new_uschedule_infer_frequency(\n            ndt(2022, 7, 1),\n            ndt(2022, 10, 1),\n            Frequency::Months {\n                number: 12,\n                roll: None,\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n            true,\n            StubInference::ShortFront,\n        )\n        .expect(\"short period\");\n        assert_eq!(s.uschedule, vec![ndt(2022, 7, 1), ndt(2022, 10, 1)]);\n    }\n\n    #[test]\n    fn test_try_new_schedule_infer_frequency_imm() {\n        // test IMM frequency is inferred\n        let s = Schedule::try_new_uschedule_infer_frequency(\n            ndt(2025, 3, 19),\n            ndt(2025, 9, 17),\n            Frequency::Months {\n                number: 3,\n                roll: None,\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n            true,\n            StubInference::NeitherSide,\n        )\n        .expect(\"short period\");\n        assert_eq!(\n            s.frequency,\n            Frequency::Months {\n                number: 3,\n                roll: Some(RollDay::IMM())\n            }\n        );\n    }\n\n    #[test]\n    fn test_is_regular() {\n        let s = Schedule::try_new_uschedule_infer_frequency(\n            ndt(2025, 3, 19),\n            ndt(2025, 9, 19),\n            Frequency::Months {\n                number: 3,\n                roll: Some(RollDay::Day(19)),\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n            true,\n            StubInference::NeitherSide,\n        )\n        .expect(\"regular\");\n        assert!(s.is_regular());\n\n        let s = Schedule::try_new_uschedule_infer_frequency(\n            ndt(2025, 3, 19),\n            ndt(2025, 9, 25),\n            Frequency::Months {\n                number: 3,\n                roll: Some(RollDay::Day(19)),\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(1),\n            Adjuster::Following {},\n            None,\n            true,\n            StubInference::ShortBack,\n        )\n        .expect(\"regular\");\n        assert!(!s.is_regular());\n    }\n\n    #[test]\n    fn test_front_stub_inference() {\n        let s = Schedule::try_new_inferred(\n            ndt(2022, 1, 1),\n            ndt(2022, 6, 1),\n            Frequency::Months {\n                number: 3,\n                roll: None,\n            },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(2),\n            Adjuster::Following {},\n            None,\n            false,\n            StubInference::ShortFront,\n        )\n        .expect(\"schedule is valid\");\n        assert_eq!(\n            s.uschedule,\n            vec![ndt(2022, 1, 1), ndt(2022, 3, 1), ndt(2022, 6, 1)]\n        );\n    }\n\n    #[test]\n    fn test_inference_allows_stubs_when_they_are_regular() {\n        let s = Schedule::try_new_inferred(\n            ndt(2025, 1, 15),\n            ndt(2025, 4, 15),\n            Frequency::Months {\n                number: 1,\n                roll: None,\n            },\n            None,\n            Some(ndt(2025, 3, 15)),\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(2),\n            Adjuster::Following {},\n            None,\n            false,\n            StubInference::ShortFront,\n        )\n        .expect(\"schedule is valid\");\n        assert_eq!(\n            s.uschedule,\n            vec![\n                ndt(2025, 1, 15),\n                ndt(2025, 2, 15),\n                ndt(2025, 3, 15),\n                ndt(2025, 4, 15)\n            ]\n        );\n    }\n\n    #[test]\n    fn test_cny7d_bug() {\n        // this bug appeared in the course of building CNY swaps.\n        let f = Frequency::CalDays {\n            // frequency\n            number: 7,\n        };\n        let s = Schedule::try_new_infer_stub(\n            ndt(2028, 11, 4),                                      // ueffective\n            ndt(2029, 2, 4),                                       // utermination\n            f,                                                     // frequency\n            None,                                                  // ufront_stub\n            None,                                                  // uback_stub\n            Calendar::NamedCal(NamedCal::try_new(\"bjs\").unwrap()), // calendar\n            Adjuster::Following {},                                // accrual_adjuster\n            Adjuster::BusDaysLagSettle(0),                         // payment_adjuster\n            Adjuster::Following {},                                // payment_adjuster2\n            None,                                                  // payment_adjuster3\n            StubInference::ShortBack,                              // stub_inference\n        );\n\n        assert!(s.is_ok());\n    }\n\n    // The follow tests explore dead stubs\n    #[test]\n    fn test_7day_defined_errors() {\n        // this is identified as a single period long stub because it is shorter than 2 periods.\n        let s = Schedule::try_new_defined(\n            ndt(2026, 1, 3),  // saturday\n            ndt(2026, 1, 11), // sunday\n            Frequency::CalDays { number: 7 },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::Following {},\n            Adjuster::BusDaysLagSettle(0),\n            Adjuster::Following {},\n            None,\n        )\n        .unwrap();\n        assert_eq!(s.uschedule, vec![ndt(2026, 1, 3), ndt(2026, 1, 11)]);\n        assert_eq!(s.aschedule, vec![ndt(2026, 1, 5), ndt(2026, 1, 12)]);\n\n        // this must fail because stubs are not specified and this is not a regular\n        // schedule and is longer than 2 periods.\n        let s = Schedule::try_new_defined(\n            ndt(2026, 1, 3),  // saturday\n            ndt(2026, 1, 18), // sunday\n            Frequency::CalDays { number: 7 },\n            None,\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::Following {},\n            Adjuster::BusDaysLagSettle(0),\n            Adjuster::Following {},\n            None,\n        );\n        assert!(s.is_err());\n    }\n\n    #[test]\n    fn test_7day_frequency_can_infer_short_stubs() {\n        // a 7D frequency can properly infer these unadjusted front stub dates, even though\n        // they will prove to be dead stub dates when compared with the wider schedules of\n        // later tests.\n        let result = Frequency::CalDays { number: 7 }.try_infer_uback_stub(\n            &ndt(2026, 1, 3),\n            &ndt(2026, 1, 11),\n            true,\n        );\n        assert!(result.is_ok());\n        assert_eq!(ndt(2026, 1, 10), result.unwrap().unwrap());\n\n        let result = Frequency::CalDays { number: 7 }.try_infer_ufront_stub(\n            &ndt(2026, 1, 3),\n            &ndt(2026, 1, 11),\n            true,\n        );\n        assert!(result.is_ok());\n        assert_eq!(ndt(2026, 1, 4), result.unwrap().unwrap());\n    }\n\n    #[test]\n    fn test_7day_dead_stubs_error() {\n        // these schedules are defined by dead stubs. They cannot produce valid schedules\n        // when entered as defined.\n        let s = Schedule::try_new_defined(\n            ndt(2026, 1, 3),  // saturday\n            ndt(2026, 1, 11), // sunday\n            Frequency::CalDays { number: 7 },\n            Some(ndt(2026, 1, 4)),\n            None,\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::Following {},\n            Adjuster::BusDaysLagSettle(0),\n            Adjuster::Following {},\n            None,\n        );\n        assert!(s.is_err());\n        let s = Schedule::try_new_defined(\n            ndt(2026, 1, 3),  // saturday\n            ndt(2026, 1, 11), // sunday\n            Frequency::CalDays { number: 7 },\n            None,\n            Some(ndt(2026, 1, 10)),\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::Following {},\n            Adjuster::BusDaysLagSettle(0),\n            Adjuster::Following {},\n            None,\n        );\n        assert!(s.is_err());\n    }\n\n    #[test]\n    fn test_7day_infer_long_inference() {\n        // all of these schedules should be able to derive valid results with inference.\n        // because the period is long (longer than holiday blocks) it should not produce a dead stub\n        // 1st Jan 2028 is a Saturday\n        let options: Vec<(StubInference, NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (\n                StubInference::LongFront,\n                ndt(2028, 1, 9),\n                vec![ndt(2028, 1, 1), ndt(2028, 1, 9)],\n            ),\n            (\n                StubInference::LongFront,\n                ndt(2028, 1, 16),\n                vec![ndt(2028, 1, 1), ndt(2028, 1, 9), ndt(2028, 1, 16)],\n            ),\n            (\n                StubInference::LongFront,\n                ndt(2028, 1, 23),\n                vec![\n                    ndt(2028, 1, 1),\n                    ndt(2028, 1, 9),\n                    ndt(2028, 1, 16),\n                    ndt(2028, 1, 23),\n                ],\n            ),\n            (\n                StubInference::LongBack,\n                ndt(2028, 1, 9),\n                vec![ndt(2028, 1, 1), ndt(2028, 1, 9)],\n            ),\n            (\n                StubInference::LongBack,\n                ndt(2028, 1, 16),\n                vec![ndt(2028, 1, 1), ndt(2028, 1, 8), ndt(2028, 1, 16)],\n            ),\n            (\n                StubInference::LongBack,\n                ndt(2028, 1, 23),\n                vec![\n                    ndt(2028, 1, 1),\n                    ndt(2028, 1, 8),\n                    ndt(2028, 1, 15),\n                    ndt(2028, 1, 23),\n                ],\n            ),\n        ];\n\n        for option in options {\n            let s = Schedule::try_new_infer_stub(\n                ndt(2028, 1, 1), // saturday\n                option.1,        // sunday\n                Frequency::CalDays { number: 7 },\n                None,\n                None,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::BusDaysLagSettle(0),\n                Adjuster::Following {},\n                None,\n                option.0,\n            )\n            .unwrap();\n            assert_eq!(s.uschedule, option.2);\n        }\n    }\n\n    #[test]\n    fn test_7day_infer_short_inference_converting_dead_stub_to_long() {\n        // all of these schedules should be able to derive valid results with inference.\n        // the short stubs initially derived are detected as dead and then converted to long\n        // 1st Jan 2028 is a Saturday\n        let options: Vec<(StubInference, NaiveDateTime, Vec<NaiveDateTime>)> = vec![\n            (\n                StubInference::ShortFront,\n                ndt(2028, 1, 9),\n                vec![ndt(2028, 1, 1), ndt(2028, 1, 9)],\n            ),\n            (\n                StubInference::ShortFront,\n                ndt(2028, 1, 16),\n                vec![ndt(2028, 1, 1), ndt(2028, 1, 9), ndt(2028, 1, 16)],\n            ),\n            (\n                StubInference::ShortFront,\n                ndt(2028, 1, 23),\n                vec![\n                    ndt(2028, 1, 1),\n                    ndt(2028, 1, 9),\n                    ndt(2028, 1, 16),\n                    ndt(2028, 1, 23),\n                ],\n            ),\n            (\n                StubInference::ShortBack,\n                ndt(2028, 1, 9),\n                vec![ndt(2028, 1, 1), ndt(2028, 1, 9)],\n            ),\n            (\n                StubInference::ShortBack,\n                ndt(2028, 1, 16),\n                vec![ndt(2028, 1, 1), ndt(2028, 1, 8), ndt(2028, 1, 16)],\n            ),\n            (\n                StubInference::ShortBack,\n                ndt(2028, 1, 23),\n                vec![\n                    ndt(2028, 1, 1),\n                    ndt(2028, 1, 8),\n                    ndt(2028, 1, 15),\n                    ndt(2028, 1, 23),\n                ],\n            ),\n        ];\n\n        for option in options {\n            let s = Schedule::try_new_infer_stub(\n                ndt(2028, 1, 1), // saturday\n                option.1,        // sunday\n                Frequency::CalDays { number: 7 },\n                None,\n                None,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::Following {},\n                Adjuster::BusDaysLagSettle(0),\n                Adjuster::Following {},\n                None,\n                option.0,\n            )\n            .unwrap();\n            assert_eq!(s.uschedule, option.2);\n        }\n    }\n\n    #[test]\n    fn test_bug_developing_neither_side_stub_inference() {\n        let s = Schedule::try_new_inferred(\n            ndt(2022, 1, 3),\n            ndt(2023, 1, 3),\n            Frequency::Months {\n                number: 3,\n                roll: None,\n            },\n            Some(ndt(2022, 2, 10)),\n            Some(ndt(2022, 8, 10)),\n            Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n            Adjuster::ModifiedFollowing {},\n            Adjuster::BusDaysLagSettle(2),\n            Adjuster::Following {},\n            None,\n            false,\n            StubInference::NeitherSide,\n        )\n        .unwrap();\n        assert_eq!(\n            s.uschedule,\n            vec![\n                ndt(2022, 1, 3),\n                ndt(2022, 2, 10),\n                ndt(2022, 5, 10),\n                ndt(2022, 8, 10),\n                ndt(2023, 1, 3),\n            ]\n        );\n    }\n\n    #[test]\n    fn test_return_single_period_even_with_stub_inference() {\n        let options = vec![\n            StubInference::ShortFront,\n            StubInference::LongFront,\n            StubInference::ShortBack,\n            StubInference::LongBack,\n            StubInference::NeitherSide,\n        ];\n        for option in options {\n            let s = Schedule::try_new_inferred(\n                ndt(2000, 1, 1),\n                ndt(2000, 2, 15),\n                Frequency::Months {\n                    number: 3,\n                    roll: Some(RollDay::Day(1)),\n                },\n                None,\n                None,\n                Calendar::Cal(Cal::new(vec![], vec![5, 6])),\n                Adjuster::ModifiedFollowing {},\n                Adjuster::BusDaysLagSettle(2),\n                Adjuster::Following {},\n                None,\n                false,\n                option,\n            )\n            .unwrap();\n            assert_eq!(s.uschedule, vec![ndt(2000, 1, 1), ndt(2000, 2, 15),]);\n        }\n    }\n}\n"
  },
  {
    "path": "rust/scheduling/serde.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::json::JSON;\nuse crate::scheduling::{Cal, Calendar, Convention, NamedCal, StubInference, UnionCal};\n\nimpl JSON for Cal {}\nimpl JSON for UnionCal {}\nimpl JSON for NamedCal {}\nimpl JSON for Calendar {}\nimpl JSON for StubInference {}\nimpl JSON for Convention {}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::scheduling::ndt;\n\n    #[test]\n    fn test_cal_json() {\n        let hols = vec![ndt(2015, 9, 8), ndt(2015, 9, 10)];\n        let hcal = Cal::new(hols, vec![5, 6]);\n        let js = hcal.to_json().unwrap();\n        let hcal2 = Cal::from_json(&js).unwrap();\n        assert_eq!(hcal, hcal2);\n    }\n\n    #[test]\n    fn test_union_cal_json() {\n        let hols = vec![ndt(2015, 9, 8), ndt(2015, 9, 10)];\n        let settle = vec![ndt(2015, 9, 11)];\n        let hcal = Cal::new(hols, vec![5, 6]);\n        let scal = Cal::new(settle, vec![5, 6]);\n        let ucal = UnionCal::new(vec![hcal], vec![scal].into());\n        let js = ucal.to_json().unwrap();\n        let ucal2 = UnionCal::from_json(&js).unwrap();\n        assert_eq!(ucal, ucal2);\n    }\n\n    #[test]\n    fn test_named_cal_json() {\n        let ncal = NamedCal::try_new(\"tgt,ldn|fed\").unwrap();\n        let js = ncal.to_json().unwrap();\n        let ncal2 = NamedCal::from_json(&js).unwrap();\n        assert_eq!(ncal, ncal2);\n    }\n\n    #[test]\n    fn test_cal_type_json() {\n        let cal = Calendar::NamedCal(NamedCal::try_new(\"tgt,ldn|fed\").unwrap());\n        let js = cal.to_json().unwrap();\n        let cal2 = Calendar::from_json(&js).unwrap();\n        assert_eq!(cal, cal2);\n    }\n\n    #[test]\n    fn test_stub_inf_json() {\n        let si = StubInference::LongBack;\n        let js = si.to_json().unwrap();\n        let si2 = StubInference::from_json(&js).unwrap();\n        assert_eq!(si, si2);\n    }\n}\n"
  },
  {
    "path": "rust/splines/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Toolset to create one dimensional spline curves.\n\nmod spline;\npub(crate) mod spline_py;\n\npub use crate::splines::spline::{\n    bspldnev_single_dual, bspldnev_single_dual2, bspldnev_single_f64, bsplev_single_dual,\n    bsplev_single_dual2, bsplev_single_f64, PPSpline, PPSplineDual, PPSplineDual2, PPSplineF64,\n};\n"
  },
  {
    "path": "rust/splines/spline.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::linalg::{dmul11_, fdmul11_, fdsolve, fouter11_};\nuse crate::dual::{Dual, Dual2, Gradient1, Gradient2, Number, NumberMapping};\nuse ndarray::{Array1, Array2};\nuse num_traits::{Signed, Zero};\nuse pyo3::exceptions::{PyTypeError, PyValueError};\nuse pyo3::{pyclass, PyErr};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    cmp::PartialEq,\n    iter::{zip, Sum},\n    ops::{Mul, Sub},\n};\n\n/// Evaluate the `x` value on the `i`'th B-spline with order `k` and knot sequence `t`.\n///\n/// Note `org_k` should be input as None, it is used internally for recursively calculating\n/// spline derivatives, where it is set to the original `k` value from the outer scope.\npub fn bsplev_single_f64(x: &f64, i: usize, k: &usize, t: &Vec<f64>, org_k: Option<usize>) -> f64 {\n    let org_k: usize = org_k.unwrap_or(*k);\n\n    // Short circuit (positivity and support property)\n    if *x < t[i] || *x > t[i + k] {\n        return 0.0_f64;\n    }\n\n    // Right side end point support\n    if *x == t[t.len() - 1] && i >= (t.len() - org_k - 1) {\n        return 1.0_f64;\n    }\n\n    // Recursion\n    if *k == 1_usize {\n        if t[i] <= *x && *x < t[i + 1] {\n            1.0_f64\n        } else {\n            0.0_f64\n        }\n    } else {\n        let mut left: f64 = 0.0_f64;\n        let mut right: f64 = 0.0_f64;\n        if t[i] != t[i + k - 1] {\n            left = (x - t[i]) / (t[i + k - 1] - t[i]) * bsplev_single_f64(x, i, &(k - 1), t, None);\n        }\n        if t[i + 1] != t[i + k] {\n            right = (t[i + k] - x) / (t[i + k] - t[i + 1])\n                * bsplev_single_f64(x, i + 1, &(k - 1), t, None);\n        }\n        left + right\n    }\n}\n\n/// Evaluate the `x` value on the `i`'th B-spline with order `k` and knot sequence `t`.\n///\n/// Note `org_k` should be input as None, it is used internally for recursively calculating\n/// spline derivatives, where it is set to the original `k` value from the outer scope.\npub fn bsplev_single_dual(\n    x: &Dual,\n    i: usize,\n    k: &usize,\n    t: &Vec<f64>,\n    org_k: Option<usize>,\n) -> Dual {\n    let b_f64 = bsplev_single_f64(&x.real(), i, k, t, org_k);\n    let dbdx_f64 = bspldnev_single_f64(&x.real(), i, k, t, 1, org_k);\n    Dual::clone_from(x, b_f64, dbdx_f64 * x.dual())\n}\n\n/// Evaluate the `x` value on the `i`'th B-spline with order `k` and knot sequence `t`.\n///\n/// Note `org_k` should be input as None, it is used internally for recursively calculating\n/// spline derivatives, where it is set to the original `k` value from the outer scope.\npub fn bsplev_single_dual2(\n    x: &Dual2,\n    i: usize,\n    k: &usize,\n    t: &Vec<f64>,\n    org_k: Option<usize>,\n) -> Dual2 {\n    let b_f64 = bsplev_single_f64(&x.real(), i, k, t, org_k);\n    let dbdx_f64 = bspldnev_single_f64(&x.real(), i, k, t, 1, org_k);\n    let d2bdx2_f64 = bspldnev_single_f64(&x.real(), i, k, t, 2, org_k);\n    let dual2 =\n        dbdx_f64 * x.dual2() + 0.5 * d2bdx2_f64 * fouter11_(&x.dual().view(), &x.dual().view());\n    Dual2::clone_from(x, b_f64, dbdx_f64 * x.dual(), dual2)\n}\n\n/// Evaluate the `m`'th order derivative of the `x` value on the `i`'th B-spline with\n/// order `k` and knot sequence `t`.\n///\n/// Note `org_k` should be input as None, it is used internally for recursively calculating\n/// spline derivatives, where it is set to the original `k` value from the outer scope.\npub fn bspldnev_single_f64(\n    x: &f64,\n    i: usize,\n    k: &usize,\n    t: &Vec<f64>,\n    m: usize,\n    org_k: Option<usize>,\n) -> f64 {\n    if m == 0 {\n        return bsplev_single_f64(x, i, k, t, None);\n    } else if *k == 1 || m >= *k {\n        return 0.0_f64;\n    }\n\n    let org_k: usize = org_k.unwrap_or(*k);\n    let mut r: f64 = 0.0;\n    let div1: f64 = t[i + k - 1] - t[i];\n    let div2: f64 = t[i + k] - t[i + 1];\n\n    if m == 1 {\n        if div1 != 0_f64 {\n            r += bsplev_single_f64(x, i, &(k - 1), t, Some(org_k)) / div1;\n        }\n        if div2 != 0_f64 {\n            r -= bsplev_single_f64(x, i + 1, &(k - 1), t, Some(org_k)) / div2;\n        }\n        r *= (k - 1) as f64;\n    } else {\n        if div1 != 0_f64 {\n            r += bspldnev_single_f64(x, i, &(k - 1), t, m - 1, Some(org_k)) / div1;\n        }\n        if div2 != 0_f64 {\n            r -= bspldnev_single_f64(x, i + 1, &(k - 1), t, m - 1, Some(org_k)) / div2;\n        }\n        r *= (k - 1) as f64\n    }\n    r\n}\n\n/// Evaluate the `m`'th order derivative of the `x` value on the `i`'th B-spline with\n/// order `k` and knot sequence `t`.\n///\n/// Note `org_k` should be input as None, it is used internally for recursively calculating\n/// spline derivatives, where it is set to the original `k` value from the outer scope.\npub fn bspldnev_single_dual(\n    x: &Dual,\n    i: usize,\n    k: &usize,\n    t: &Vec<f64>,\n    m: usize,\n    org_k: Option<usize>,\n) -> Dual {\n    let b_f64 = bspldnev_single_f64(&x.real(), i, k, t, m, org_k);\n    let dbdx_f64 = bspldnev_single_f64(&x.real(), i, k, t, m + 1, org_k);\n    Dual::clone_from(x, b_f64, dbdx_f64 * x.dual())\n}\n\n/// Evaluate the `m`'th order derivative of the `x` value on the `i`'th B-spline with\n/// order `k` and knot sequence `t`.\n///\n/// Note `org_k` should be input as None, it is used internally for recursively calculating\n/// spline derivatives, where it is set to the original `k` value from the outer scope.\npub fn bspldnev_single_dual2(\n    x: &Dual2,\n    i: usize,\n    k: &usize,\n    t: &Vec<f64>,\n    m: usize,\n    org_k: Option<usize>,\n) -> Dual2 {\n    let b_f64 = bspldnev_single_f64(&x.real(), i, k, t, m, org_k);\n    let dbdx_f64 = bspldnev_single_f64(&x.real(), i, k, t, m + 1, org_k);\n    let d2bdx2_f64 = bspldnev_single_f64(&x.real(), i, k, t, m + 2, org_k);\n    let dual2 =\n        dbdx_f64 * x.dual2() + 0.5 * d2bdx2_f64 * fouter11_(&x.dual().view(), &x.dual().view());\n    Dual2::clone_from(x, b_f64, dbdx_f64 * x.dual(), dual2)\n}\n\n/// A piecewise polynomial spline of given order and knot sequence.\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct PPSpline<T> {\n    k: usize,\n    t: Vec<f64>,\n    c: Option<Array1<T>>,\n    n: usize,\n}\n\nimpl<T> PPSpline<T> {\n    pub fn k(&self) -> &usize {\n        &self.k\n    }\n\n    pub fn t(&self) -> &Vec<f64> {\n        &self.t\n    }\n\n    pub fn n(&self) -> &usize {\n        &self.n\n    }\n\n    pub fn c(&self) -> &Option<Array1<T>> {\n        &self.c\n    }\n}\n\nimpl<T> PPSpline<T>\nwhere\n    T: PartialOrd + Signed + Clone + Sum + Zero,\n    for<'a> &'a T: Sub<&'a T, Output = T>,\n    for<'a> &'a f64: Mul<&'a T, Output = T>,\n{\n    /// Create a PPSpline from its order `k`, knot sequence `t` and optional spline coefficents `c`.\n    pub fn new(k: usize, t: Vec<f64>, c: Option<Vec<T>>) -> Self {\n        // t is given and is non-decreasing\n        assert!(t.len() > 1);\n        assert!(zip(&t[1..], &t[..(t.len() - 1)]).all(|(a, b)| a >= b));\n        let n = t.len() - k;\n        let c_ = c.map(Array1::from_vec);\n        PPSpline { k, t, n, c: c_ }\n    }\n\n    pub fn ppdnev_single(&self, x: &f64, m: usize) -> Result<T, PyErr> {\n        let b: Array1<f64> = Array1::from_vec(\n            (0..self.n)\n                .map(|i| bspldnev_single_f64(x, i, &self.k, &self.t, m, None))\n                .collect(),\n        );\n        match &self.c {\n            Some(c) => Ok(fdmul11_(&b.view(), &c.view())),\n            None => Err(PyValueError::new_err(\n                \"Must call `csolve` before evaluating PPSpline.\",\n            )),\n        }\n    }\n\n    pub fn csolve(\n        &mut self,\n        tau: &[f64],\n        y: &[T],\n        left_n: usize,\n        right_n: usize,\n        allow_lsq: bool,\n    ) -> Result<(), PyErr> {\n        if tau.len() != self.n && !(allow_lsq && tau.len() > self.n) {\n            return Err(PyValueError::new_err(\n                \"`csolve` cannot complete if length of `tau` < n or `allow_lsq` is false.\",\n            ));\n        }\n        if tau.len() != y.len() {\n            return Err(PyValueError::new_err(\n                \"`tau` and `y` must have the same length.\",\n            ));\n        }\n        let b: Array2<f64> = self.bsplmatrix(tau, left_n, right_n);\n        let ya: Array1<T> = Array1::from_vec(y.to_owned());\n        let c: Array1<T> = fdsolve(&b.view(), &ya.view(), allow_lsq);\n        self.c = Some(c);\n        Ok(())\n    }\n\n    // pub fn bsplev(&self, x: &Vec<f64>, i: &usize) -> Vec<f64> {\n    //     x.iter().map(|v| bsplev_single_f64(v, *i, self.k(), self.t(), None)).collect()\n    // }\n\n    pub fn bspldnev(&self, x: &[f64], i: &usize, m: &usize) -> Vec<f64> {\n        x.iter()\n            .map(|v| bspldnev_single_f64(v, *i, self.k(), self.t(), *m, None))\n            .collect()\n    }\n\n    pub fn bsplmatrix(&self, tau: &[f64], left_n: usize, right_n: usize) -> Array2<f64> {\n        let mut b = Array2::zeros((tau.len(), self.n));\n        for i in 0..self.n {\n            b[[0, i]] = bspldnev_single_f64(&tau[0], i, &self.k, &self.t, left_n, None);\n            b[[tau.len() - 1, i]] =\n                bspldnev_single_f64(&tau[tau.len() - 1], i, &self.k, &self.t, right_n, None);\n            for j in 1..(tau.len() - 1) {\n                b[[j, i]] = bsplev_single_f64(&tau[j], i, &self.k, &self.t, None)\n            }\n        }\n        b\n    }\n}\n\nimpl NumberMapping for PPSpline<f64> {\n    fn mapped_value(&self, x: &Number) -> Result<Number, PyErr> {\n        match x {\n            Number::F64(f) => Ok(Number::F64(self.ppdnev_single(f, 0_usize)?)),\n            Number::Dual(d) => Ok(Number::Dual(self.ppdnev_single_dual(d, 0_usize)?)),\n            Number::Dual2(d) => Ok(Number::Dual2(self.ppdnev_single_dual2(d, 0_usize)?)),\n        }\n    }\n}\n\nimpl PPSpline<f64> {\n    pub fn ppdnev_single_dual(&self, x: &Dual, m: usize) -> Result<Dual, PyErr> {\n        let b: Array1<Dual> = Array1::from_vec(\n            (0..self.n)\n                .map(|i| bspldnev_single_dual(x, i, &self.k, &self.t, m, None))\n                .collect(),\n        );\n        match &self.c {\n            Some(c) => Ok(fdmul11_(&c.view(), &b.view())),\n            None => Err(PyValueError::new_err(\n                \"Must call `csolve` before evaluating PPSpline.\",\n            )),\n        }\n    }\n\n    pub fn ppdnev_single_dual2(&self, x: &Dual2, m: usize) -> Result<Dual2, PyErr> {\n        let b: Array1<Dual2> = Array1::from_vec(\n            (0..self.n)\n                .map(|i| bspldnev_single_dual2(x, i, &self.k, &self.t, m, None))\n                .collect(),\n        );\n        match &self.c {\n            Some(c) => Ok(fdmul11_(&c.view(), &b.view())),\n            None => Err(PyValueError::new_err(\n                \"Must call `csolve` before evaluating PPSpline.\",\n            )),\n        }\n    }\n}\n\nimpl NumberMapping for PPSpline<Dual> {\n    fn mapped_value(&self, x: &Number) -> Result<Number, PyErr> {\n        match x {\n            Number::F64(f) => Ok(Number::Dual(self.ppdnev_single(f, 0_usize)?)),\n            Number::Dual(d) => Ok(Number::Dual(self.ppdnev_single_dual(d, 0_usize)?)),\n            Number::Dual2(d) => Ok(Number::Dual2(self.ppdnev_single_dual2(d, 0_usize)?)),\n        }\n    }\n}\n\nimpl PPSpline<Dual> {\n    pub fn ppdnev_single_dual2(&self, _x: &Dual2, _m: usize) -> Result<Dual2, PyErr> {\n        Err(PyTypeError::new_err(\n            \"Cannot index with type `Dual2` on PPSpline<Dual>`.\",\n        ))\n    }\n\n    pub fn ppdnev_single_dual(&self, x: &Dual, m: usize) -> Result<Dual, PyErr> {\n        let b: Array1<Dual> = Array1::from_vec(\n            (0..self.n)\n                .map(|i| bspldnev_single_dual(x, i, &self.k, &self.t, m, None))\n                .collect(),\n        );\n        match &self.c {\n            Some(c) => Ok(dmul11_(&c.view(), &b.view())),\n            None => Err(PyValueError::new_err(\n                \"Must call `csolve` before evaluating PPSpline.\",\n            )),\n        }\n    }\n}\n\nimpl NumberMapping for PPSpline<Dual2> {\n    fn mapped_value(&self, x: &Number) -> Result<Number, PyErr> {\n        match x {\n            Number::F64(f) => Ok(Number::Dual2(self.ppdnev_single(f, 0_usize)?)),\n            Number::Dual(d) => Ok(Number::Dual(self.ppdnev_single_dual(d, 0_usize)?)),\n            Number::Dual2(d) => Ok(Number::Dual2(self.ppdnev_single_dual2(d, 0_usize)?)),\n        }\n    }\n}\n\nimpl PPSpline<Dual2> {\n    pub fn ppdnev_single_dual(&self, _x: &Dual, _m: usize) -> Result<Dual, PyErr> {\n        Err(PyTypeError::new_err(\n            \"Cannot index with type `Dual` on PPSpline<Dual2>.\",\n        ))\n    }\n\n    pub fn ppdnev_single_dual2(&self, x: &Dual2, m: usize) -> Result<Dual2, PyErr> {\n        let b: Array1<Dual2> = Array1::from_vec(\n            (0..self.n)\n                .map(|i| bspldnev_single_dual2(x, i, &self.k, &self.t, m, None))\n                .collect(),\n        );\n        match &self.c {\n            Some(c) => Ok(dmul11_(&c.view(), &b.view())),\n            None => Err(PyValueError::new_err(\n                \"Must call `csolve` before evaluating PPSpline.\",\n            )),\n        }\n    }\n}\n\nimpl<T> PartialEq for PPSpline<T>\nwhere\n    T: PartialEq,\n{\n    /// Equality of `PPSpline` if\n\n    fn eq(&self, other: &Self) -> bool {\n        if self.k != other.k || self.n != other.n {\n            return false;\n        }\n        if !self.t.eq(&other.t) {\n            return false;\n        }\n        match (&self.c, &other.c) {\n            (Some(c1), Some(c2)) => c1.eq(&c2),\n            (Some(_c), None) => false,\n            (None, Some(_c)) => false,\n            (None, None) => true,\n        }\n    }\n}\n\n/// Definitive [f64] type variant of a [PPSpline].\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Deserialize, Serialize)]\npub struct PPSplineF64 {\n    pub(crate) inner: PPSpline<f64>,\n}\n\n/// Definitive [Dual] type variant of a [PPSpline].\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Deserialize, Serialize)]\npub struct PPSplineDual {\n    pub(crate) inner: PPSpline<Dual>,\n}\n\n/// Definitive [Dual2] type variant of a [PPSpline].\n#[pyclass(module = \"rateslib.rs\", from_py_object)]\n#[derive(Clone, Deserialize, Serialize)]\npub struct PPSplineDual2 {\n    pub(crate) inner: PPSpline<Dual2>,\n}\n\nimpl PartialEq for PPSplineF64 {\n    /// Equality of `PPSplineF64` if\n    fn eq(&self, other: &Self) -> bool {\n        self.inner.eq(&other.inner)\n    }\n}\n\nimpl PartialEq for PPSplineDual {\n    /// Equality of `PPSplineDual` if\n    fn eq(&self, other: &Self) -> bool {\n        self.inner.eq(&other.inner)\n    }\n}\n\nimpl PartialEq for PPSplineDual2 {\n    /// Equality of `PPSplineDual2` if\n    fn eq(&self, other: &Self) -> bool {\n        self.inner.eq(&other.inner)\n    }\n}\n\n// UNIT TESTS\n\n//\n\n//\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::dual::Dual;\n    use ndarray::{arr1, arr2};\n    use num_traits::One;\n\n    fn is_close(a: &f64, b: &f64, abs_tol: Option<f64>) -> bool {\n        // used rather than equality for float numbers\n        (a - b).abs() < abs_tol.unwrap_or(1e-8)\n    }\n\n    #[test]\n    fn bsplev_single_f64_() {\n        let x: f64 = 1.5_f64;\n        let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n        let k: usize = 4;\n        let result: Vec<f64> = (0..8)\n            .map(|i| bsplev_single_f64(&x, i as usize, &k, &t, None))\n            .collect();\n        let expected: Vec<f64> = Vec::from(&[0.125, 0.375, 0.375, 0.125, 0., 0., 0., 0.]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn bsplev_single_dual_() {\n        let x: Dual = Dual::new(1.5, vec![\"x\".to_string()]);\n        let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n        let k: usize = 4;\n        let result: Vec<Dual> = (0..8)\n            .map(|i| bsplev_single_dual(&x, i as usize, &k, &t, None))\n            .collect();\n        let expected: Vec<f64> = Vec::from(&[0.125, 0.375, 0.375, 0.125, 0., 0., 0., 0.]);\n        for i in 0..8 {\n            assert_eq!(result[i].real(), expected[i])\n        }\n        // These are values from the bspldnev_single evaluation test\n        let dual_expected: Vec<f64> = Vec::from(&[-0.75, -0.75, 0.75, 0.75, 0., 0., 0., 0.]);\n        for i in 0..8 {\n            assert_eq!(result[i].dual()[0], dual_expected[i])\n        }\n    }\n\n    #[test]\n    fn bsplev_single_f64_right() {\n        let x: f64 = 4.0_f64;\n        let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n        let k: usize = 4;\n        let result: Vec<f64> = (0..8)\n            .map(|i| bsplev_single_f64(&x, i as usize, &k, &t, None))\n            .collect();\n        let expected: Vec<f64> = Vec::from(&[0., 0., 0., 0., 0., 0., 0., 1.0]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn bspldnev_single_f64_() {\n        let x: f64 = 1.5_f64;\n        let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n        let k: usize = 4;\n        let result: Vec<f64> = (0..8)\n            .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 1_usize, None))\n            .collect();\n        let expected: Vec<f64> = Vec::from(&[-0.75, -0.75, 0.75, 0.75, 0., 0., 0., 0.]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn bspldnev_single_f64_right() {\n        let x: f64 = 4.0_f64;\n        let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n        let k: usize = 4;\n        let result: Vec<f64> = (0..8)\n            .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 1_usize, None))\n            .collect();\n        let expected: Vec<f64> = Vec::from(&[0., 0., 0., 0., 0., 0., -3., 3.]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn bspldnev_single_shortcut() {\n        let x: f64 = 1.5_f64;\n        let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n        let k: usize = 4;\n        let result: Vec<f64> = (0..8)\n            .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 6_usize, None))\n            .collect();\n        let expected: Vec<f64> = Vec::from(&[0., 0., 0., 0., 0., 0., 0., 0.]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn bspldnev_single_m() {\n        let x: f64 = 4.0_f64;\n        let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n        let k: usize = 4;\n        let result: Vec<f64> = (0..8)\n            .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 2_usize, None))\n            .collect();\n        let expected: Vec<f64> = Vec::from(&[0., 0., 0., 0., 0., 3., -9., 6.]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn bspldnev_single_m_zero() {\n        let x: f64 = 1.5_f64;\n        let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n        let k: usize = 4;\n        let result: Vec<f64> = (0..8)\n            .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 0_usize, None))\n            .collect();\n        let expected: Vec<f64> = Vec::from(&[0.125, 0.375, 0.375, 0.125, 0., 0., 0., 0.]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn ppspline_new() {\n        let _pps: PPSpline<f64> = PPSpline::new(\n            4,\n            vec![1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.],\n            None,\n        );\n    }\n\n    #[test]\n    fn ppspline_bsplmatrix() {\n        let pps: PPSpline<f64> = PPSpline::new(4, vec![1., 1., 1., 1., 2., 3., 3., 3., 3.], None);\n        let result = pps.bsplmatrix(&vec![1., 1., 2., 3., 3.], 2_usize, 2_usize);\n        let expected: Array2<f64> = arr2(&[\n            [6., -9., 3., 0., 0.],\n            [1., 0., 0., 0., 0.],\n            [0., 0.25, 0.5, 0.25, 0.],\n            [0., 0., 0., 0., 1.],\n            [0., 0., 3., -9., 6.],\n        ]);\n        assert_eq!(result, expected)\n    }\n\n    #[test]\n    fn csolve_() {\n        let t = vec![0., 0., 0., 0., 4., 4., 4., 4.];\n        let tau = vec![0., 1., 3., 4.];\n        let val = vec![0., 0., 2., 2.];\n        let mut pps: PPSpline<f64> = PPSpline::new(4, t, None);\n        let _ = pps.csolve(&tau, &val, 0, 0, false);\n        let expected = vec![0., -1.11111111, 3.111111111111, 2.0];\n        let v: Vec<bool> = pps\n            .c\n            .expect(\"csolve\")\n            .into_raw_vec_and_offset()\n            .0\n            .iter()\n            .zip(expected.iter())\n            .map(|(x, y)| is_close(&x, &y, None))\n            .collect();\n\n        assert!(v.iter().all(|x| *x));\n    }\n\n    #[test]\n    fn csolve_dual() {\n        let t = vec![0., 0., 0., 0., 4., 4., 4., 4.];\n        let tau = vec![0., 1., 3., 4.];\n        let d1 = Dual::one();\n        let val = vec![0. * &d1, 0. * &d1, 2. * &d1, 2. * &d1];\n        let mut pps = PPSpline::new(4, t, None);\n        let _ = pps.csolve(&tau, &val, 0, 0, false);\n        let expected = vec![0. * &d1, -1.11111111 * &d1, 3.111111111111 * &d1, 2.0 * &d1];\n        let v: Vec<bool> = pps\n            .c\n            .expect(\"csolve\")\n            .into_raw_vec_and_offset()\n            .0\n            .iter()\n            .zip(expected.iter())\n            .map(|(x, y)| is_close(&x.real(), &y.real(), None))\n            .collect();\n\n        assert!(v.iter().all(|x| *x));\n    }\n\n    #[test]\n    fn ppev_single_() {\n        let t = vec![1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.];\n        let mut pps = PPSpline::new(4, t, None);\n        pps.c = Some(arr1(&[1., 2., -1., 2., 1., 1., 2., 2.]));\n        let r1 = pps.ppdnev_single(&1.1, 0).unwrap();\n        assert!(is_close(&r1, &1.19, None));\n        let r2 = pps.ppdnev_single(&1.8, 0).unwrap();\n        assert!(is_close(&r2, &0.84, None));\n        let r3 = pps.ppdnev_single(&2.8, 0).unwrap();\n        assert!(is_close(&r3, &1.136, None));\n    }\n\n    #[test]\n    fn partialeq_() {\n        let pp1 = PPSpline::<f64>::new(2, vec![1., 1., 2., 2.], None);\n        let pp2 = PPSpline::<f64>::new(2, vec![1., 1., 2., 2.], None);\n        assert!(pp1 == pp2);\n        let pp3 = PPSpline::new(2, vec![1., 1., 2., 2.], Some(vec![1.5, 0.2]));\n        let pp4 = PPSpline::new(2, vec![1., 1., 2., 2.], Some(vec![1.5, 0.2]));\n        assert!(pp3 == pp4);\n        assert!(pp3 != pp2);\n        assert!(pp1 != pp4);\n    }\n\n    #[test]\n    #[should_panic]\n    fn backwards_definition() {\n        let _pp1 = PPSpline::<f64>::new(4, vec![3., 3., 3., 3., 2., 1., 1., 1., 1.], None);\n    }\n}\n"
  },
  {
    "path": "rust/splines/spline_py.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n//! Wrapper to export spline functionality to Python\n\nuse crate::dual::{Dual, Dual2, Number};\nuse crate::json::json_py::DeserializedObj;\nuse crate::json::JSON;\nuse crate::splines::spline::{\n    bspldnev_single_f64, bsplev_single_f64, PPSpline, PPSplineDual, PPSplineDual2, PPSplineF64,\n};\nuse std::cmp::PartialEq;\n\nuse numpy::{PyArray2, ToPyArray};\nuse pyo3::exceptions::{PyTypeError, PyValueError};\nuse pyo3::prelude::*;\n\nmacro_rules! create_interface {\n    ($name: ident, $type: ident) => {\n        #[pymethods]\n        impl $name {\n            #[new]\n            #[pyo3(signature = (k, t, c=None))]\n            fn new(k: usize, t: Vec<f64>, c: Option<Vec<$type>>) -> Self {\n                Self {\n                    inner: PPSpline::new(k, t, c),\n                }\n            }\n\n            fn __getnewargs__(&self, _py: Python) -> PyResult<(usize, Vec<f64>, Option<Vec<$type>>)> {\n                Ok((self.k()?, self.t()?, self.c()?))\n            }\n\n            /// The dimension of the pp spline.\n            #[getter]\n            fn n(&self) -> PyResult<usize> {\n                Ok(*self.inner.n())\n            }\n\n            /// The order of the pp spline.\n            #[getter]\n            fn k(&self) -> PyResult<usize> {\n                Ok(*self.inner.k())\n            }\n\n            /// The knot sequence of the pp spline, of length ``n+k``.\n            #[getter]\n            fn t(&self) -> PyResult<Vec<f64>> {\n                Ok(self.inner.t().clone())\n            }\n\n            /// The spline coefficients of length ``n``.\n            #[getter]\n            fn c(&self) -> PyResult<Option<Vec<$type>>> {\n                match self.inner.c() {\n                    Some(val) => Ok(Some(val.clone().into_raw_vec_and_offset().0)),\n                    None => Ok(None)\n                }\n            }\n\n            /// Solve the spline coefficients given the data sites.\n            ///\n            /// Parameters\n            /// ----------\n            /// tau: list[f64]\n            ///     The data site `x`-coordinates.\n            /// y: list[type]\n            ///     The data site `y`-coordinates in appropriate type (float, *Dual* or *Dual2*)\n            ///     for *self*.\n            /// left_n: int\n            ///     The number of derivatives to evaluate at the left side of the data sites,\n            ///     i.e. defining an endpoint constraint.\n            /// right_n: int\n            ///     The number of derivatives to evaluate at the right side of the datasites,\n            ///     i.e. defining an endpoint constraint.\n            /// allow_lsq: bool\n            ///     Whether to permit least squares solving using non-square matrices.\n            ///\n            /// Returns\n            /// -------\n            /// None\n            fn csolve(\n                &mut self,\n                tau: Vec<f64>,\n                y: Vec<$type>,\n                left_n: usize,\n                right_n: usize,\n                allow_lsq: bool\n            ) -> PyResult<()> {\n                self.inner.csolve(&tau, &y, left_n, right_n, allow_lsq)\n            }\n\n            /// Evaluate a single *x* coordinate value on the pp spline.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: float\n            ///     The x-axis value at which to evaluate value.\n            ///\n            /// Returns\n            /// -------\n            /// float, Dual or Dual2 based on self\n            ///\n            /// Notes\n            /// -----\n            /// The value of the spline at *x* is the sum of the value of each b-spline\n            /// evaluated at *x* multiplied by the spline coefficients, *c*.\n            ///\n            /// .. math::\n            ///\n            ///    \\$(x) = \\sum_{i=1}^n c_i B_{(i,k,\\mathbf{t})}(x)\n            ///\n            fn ppev_single(&self, x: Number) -> PyResult<$type> {\n                match x {\n                    Number::F64(f) => self.inner.ppdnev_single(&f, 0),\n                    Number::Dual(_) => Err(PyTypeError::new_err(\n                        \"Cannot index PPSpline with `Dual`, use either `ppev_single(float(x))` or `ppev_single_dual(x)`.\"\n                        )),\n                    Number::Dual2(_) => Err(PyTypeError::new_err(\n                        \"Cannot index PPSpline with `Dual2`, use either `ppev_single(float(x))` or `ppev_single_dual2(x)`.\")),\n                }\n            }\n\n            /// Evaluate a single *x* coordinate value on the pp spline.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: Dual\n            ///     The x-axis value at which to evaluate value.\n            ///\n            /// Returns\n            /// -------\n            /// Dual\n            ///\n            /// Notes\n            /// -----\n            /// This function guarantees preservation of accurate AD :class:`~rateslib.dual.Dual`\n            /// sensitivities. It also prohibits type mixing and will raise if *Dual2* data types\n            /// are encountered.\n            fn ppev_single_dual(&self, x: Number) -> PyResult<Dual> {\n                match x {\n                    Number::F64(f) => self.inner.ppdnev_single_dual(&Dual::new(f, vec![]), 0),\n                    Number::Dual(d) => self.inner.ppdnev_single_dual(&d, 0),\n                    Number::Dual2(_) => Err(PyTypeError::new_err(\"Cannot mix `Dual2` and `Dual` types, use `ppev_single_dual2(x)`.\")),\n                }\n            }\n\n            /// Evaluate a single *x* coordinate value on the pp spline.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: Dual2\n            ///     The x-axis value at which to evaluate value.\n            ///\n            /// Returns\n            /// -------\n            /// Dual2\n            ///\n            /// Notes\n            /// -----\n            /// This function guarantees preservation of accurate AD :class:`~rateslib.dual.Dual2`\n            /// sensitivities. It also prohibits type mixing and will raise if *Dual* data types\n            /// are encountered.\n            fn ppev_single_dual2(&self, x: Number) -> PyResult<Dual2> {\n                match x {\n                    Number::F64(f) => self.inner.ppdnev_single_dual2(&Dual2::new(f, vec![]), 0),\n                    Number::Dual(_) => Err(PyTypeError::new_err(\"Cannot mix `Dual2` and `Dual` types, use `ppev_single_dual(x)`.\")),\n                    Number::Dual2(d) => self.inner.ppdnev_single_dual2(&d, 0),\n                }\n            }\n\n            /// Evaluate an array of *x* coordinates derivatives on the pp spline.\n            ///\n            /// Repeatedly applies :meth:`~rateslib.splines.PPSplineF64.ppev_single`, and\n            /// is typically used for minor performance gains in chart plotting.\n            ///\n            /// .. warning::\n            ///\n            ///    The *x* coordinates supplied to this function are treated as *float*, or are\n            ///    **converted** to *float*. Therefore it does not guarantee the preservation of AD\n            ///    sensitivities. If you need to index by *x* values which are\n            ///    :class:`~rateslib.dual.Dual` or :class:`~rateslib.dual.Dual2`, then\n            ///    you should choose to iteratively map the\n            ///    provided methods :meth:`~rateslib.splines.PPSplineF64.ppev_single_dual` or\n            ///    :meth:`~rateslib.splines.PPSplineF64.ppev_single_dual2` respectively.\n            ///\n            /// Returns\n            /// -------\n            /// 1-d array of float\n            fn ppev(&self, x: Vec<f64>) -> PyResult<Vec<$type>> {\n                let out: Vec<$type> = x.iter().map(|v| self.inner.ppdnev_single(&v, 0)).collect::<Result<Vec<$type>, _>>()?;\n                Ok(out)\n            }\n\n            /// Evaluate a single *x* coordinate derivative from the right on the pp spline.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: float\n            ///     The x-axis value at which to evaluate value.\n            /// m: int\n            ///     The order of derivative to calculate value for (0 is function value).\n            ///\n            /// Returns\n            /// -------\n            /// float, Dual, or Dual2, based on self\n            ///\n            /// Notes\n            /// -----\n            /// The value of derivatives of the spline at *x* is the sum of the value of each\n            /// b-spline derivatives evaluated at *x* multiplied by the spline\n            /// coefficients, *c*.\n            ///\n            /// Due to the definition of the splines this derivative will return the value\n            /// from the right at points where derivatives are discontinuous.\n            ///\n            /// .. math::\n            ///\n            ///    \\frac{d^m\\$(x)}{d x^m} = \\sum_{i=1}^n c_i \\frac{d^m B_{(i,k,\\mathbf{t})}(x)}{d x^m}\n            fn ppdnev_single(&self, x: Number, m: usize) -> PyResult<$type> {\n                match x {\n                    Number::Dual(_) => Err(PyTypeError::new_err(\"Splines cannot be indexed with Duals use `float(x)`.\")),\n                    Number::F64(f) => self.inner.ppdnev_single(&f, m),\n                    Number::Dual2(_) => Err(PyTypeError::new_err(\"Splines cannot be indexed with Duals use `float(x)`.\")),\n                }\n            }\n\n            /// Evaluate a single *x* coordinate derivative from the right on the pp spline.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: Dual\n            ///     The x-axis value at which to evaluate value.\n            /// m: int\n            ///     The order of derivative to calculate value for (0 is function value).\n            ///\n            /// Returns\n            /// -------\n            /// Dual\n            ///\n            /// Notes\n            /// -----\n            /// This function guarantees preservation of accurate AD :class:`~rateslib.dual.Dual`\n            /// sensitivities. It also prohibits type mixing and will raise if any *Dual2*\n            /// data types are encountered.\n            fn ppdnev_single_dual(&self, x: Number, m: usize) -> PyResult<Dual> {\n                match x {\n                    Number::F64(f) => self.inner.ppdnev_single_dual(&Dual::new(f, vec![]), m),\n                    Number::Dual(d) => self.inner.ppdnev_single_dual(&d, m),\n                    Number::Dual2(_) => Err(PyTypeError::new_err(\"Cannot mix `Dual2` and `Dual` types, use `ppdnev_single_dual2(x)`.\")),\n                }\n            }\n\n            /// Evaluate a single *x* coordinate derivative from the right on the pp spline.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: Dual2\n            ///     The x-axis value at which to evaluate value.\n            /// m: int\n            ///     The order of derivative to calculate value for (0 is function value).\n            ///\n            /// Returns\n            /// -------\n            /// Dual2\n            ///\n            /// Notes\n            /// -----\n            /// This function guarantees preservation of accurate AD :class:`~rateslib.dual.Dual2`\n            /// sensitivities. It also prohibits type mixing and will raise if any *Dual*\n            /// data types are encountered.\n            fn ppdnev_single_dual2(&self, x: Number, m: usize) -> PyResult<Dual2> {\n                match x {\n                    Number::F64(f) => self.inner.ppdnev_single_dual2(&Dual2::new(f, vec![]), m),\n                    Number::Dual(_) => Err(PyTypeError::new_err(\"Cannot mix `Dual2` and `Dual` types, use `ppdnev_single_dual(x)`.\")),\n                    Number::Dual2(d) => self.inner.ppdnev_single_dual2(&d, m),\n                }\n            }\n\n            /// Evaluate an array of x coordinates derivatives on the pp spline.\n            ///\n            /// Repeatedly applies :meth:`~rateslib.splines.PPSplineF64.ppdnev_single`.\n            ///\n            /// .. warning::\n            ///\n            ///    The *x* coordinates supplied to this function are treated as *float*, or are\n            ///    **converted** to *float*. Therefore it does not guarantee the preservation of AD\n            ///    sensitivities.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: 1-d array of float\n            ///     x-axis coordinates.\n            /// m: int\n            ///     The order of derivative to calculate value for.\n            ///\n            /// Returns\n            /// -------\n            /// 1-d array of float\n            fn ppdnev(&self, x: Vec<f64>, m: usize) -> PyResult<Vec<$type>> {\n                let out: Vec<$type> = x.iter().map(|v| self.inner.ppdnev_single(&v, m)).collect::<Result<Vec<$type>, _>>()?;\n                Ok(out)\n            }\n\n            /// Evaluate value of the *i* th b-spline at x coordinates.\n            ///\n            /// Repeatedly applies :meth:`~rateslib.splines.bsplev_single`.\n            ///\n            /// .. warning::\n            ///\n            ///    The *x* coordinates supplied to this function are treated as *float*, or are\n            ///    **converted** to *float*. Therefore it does not guarantee the preservation of AD\n            ///    sensitivities.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: 1-d array of float\n            ///     x-axis coordinates\n            /// i: int\n            ///     Index of the B-spline to evaluate.\n            ///\n            /// Returns\n            /// -------\n            /// 1-d array of float\n            fn bsplev(&self, x: Vec<f64>, i: usize) -> PyResult<Vec<f64>> {\n                Ok(self.inner.bspldnev(&x, &i, &0))\n            }\n\n            /// Evaluate *m* order derivative on the *i* th b-spline at *x* coordinates.\n            ///\n            /// Repeatedly applies :meth:`~rateslib.splines.bspldnev_single`.\n            ///\n            /// .. warning::\n            ///\n            ///    The *x* coordinates supplied to this function are treated as *float*, or are\n            ///    **converted** to *float*. Therefore it does not guarantee the preservation of AD\n            ///    sensitivities.\n            ///\n            /// Parameters\n            /// ----------\n            /// x: 1-d array of float\n            ///     x-axis coordinates.\n            /// i: int\n            ///     The index of the B-spline to evaluate.\n            /// m: int\n            ///     The order of derivative to calculate value for.\n            ///\n            /// Returns\n            /// -------\n            /// 1-d array\n            fn bspldnev(&self, x: Vec<f64>, i: usize, m: usize) -> PyResult<Vec<f64>> {\n                Ok(self.inner.bspldnev(&x, &i, &m))\n            }\n\n            /// Evaluate the 2d spline collocation matrix at each data site.\n            ///\n            /// Parameters\n            /// ----------\n            /// tau: 1-d array of float\n            ///     The data sites `x`-axis values which will instruct the pp spline.\n            /// left_n: int\n            ///     The order of derivative to use for the left most data site and top row\n            ///     of the spline collocation matrix.\n            /// right_n: int\n            ///     The order of derivative to use for the right most data site and bottom row\n            ///     of the spline collocation matrix.\n            ///\n            /// Returns\n            /// -------\n            /// 2-d array of float\n            ///\n            /// Notes\n            /// -----\n            /// The spline collocation matrix is defined as,\n            ///\n            /// .. math::\n            ///\n            ///    [\\mathbf{B}_{k, \\mathbf{t}}(\\mathbf{\\tau})]_{j,i} = B_{i,k,\\mathbf{t}}(\\tau_j)\n            ///\n            /// where each row is a call to :meth:`~rateslib.splines.PPSplineF64.bsplev`, except the top and bottom rows\n            /// which can be specifically adjusted to account for\n            /// ``left_n`` and ``right_n`` such that, for example, the first row might be,\n            ///\n            /// .. math::\n            ///\n            ///    [\\mathbf{B}_{k, \\mathbf{t}}(\\mathbf{\\tau})]_{1,i} = \\frac{d^n}{dx}B_{i,k,\\mathbf{t}}(\\tau_1)\n            fn bsplmatrix<'py>(\n                &'py self,\n                py: Python<'py>,\n                tau: Vec<f64>,\n                left_n: usize,\n                right_n: usize\n            ) -> PyResult<Bound<'py, PyArray2<f64>>> {\n                Ok(self.inner.bsplmatrix(&tau, left_n, right_n).to_pyarray(py))\n            }\n\n            fn __eq__(&self, other: &Self) -> PyResult<bool> {\n                Ok(self.inner.eq(&other.inner))\n            }\n\n            fn __copy__(&self) -> Self {\n                $name { inner: self.inner.clone() }\n            }\n\n            fn __repr__(&self) -> String {\n                format!(\"<rl.{} at {:p}>\", stringify!($name) ,self)\n            }\n\n             // JSON\n            /// Return a JSON representation of the object.\n            ///\n            /// Returns\n            /// -------\n            /// str\n            #[pyo3(name = \"to_json\")]\n            fn to_json_py(&self) -> PyResult<String> {\n                match DeserializedObj::$name(self.clone()).to_json() {\n                    Ok(v) => Ok(v),\n                    Err(_) => Err(PyValueError::new_err(\"Failed to serialize `PPSpline` to JSON.\")),\n                }\n            }\n        }\n    };\n}\n\ncreate_interface!(PPSplineF64, f64);\ncreate_interface!(PPSplineDual, Dual);\ncreate_interface!(PPSplineDual2, Dual2);\n\n/// Calculate the value of an indexed b-spline at *x*.\n///\n/// Parameters\n/// ----------\n/// x: float\n///     The *x* value at which to evaluate the b-spline.\n/// i: int\n///     The index of the b-spline to evaluate.\n/// k: int\n///     The order of the b-spline (note that k=4 is a cubic spline).\n/// t: sequence of float\n///     The knot sequence of the pp spline.\n/// org_k: int, optional\n///     The original k input. Used only internally when recursively calculating\n///     successive b-splines. Users will not typically use this parameters.\n///\n/// Notes\n/// -----\n/// B-splines can be recursively defined as:\n///\n/// .. math::\n///\n///    B_{i,k,\\mathbf{t}}(x) = \\frac{x-t_i}{t_{i+k-1}-t_i}B_{i,k-1,\\mathbf{t}}(x) + \\frac{t_{i+k}-x}{t_{i+k}-t_{i+1}}B_{i+1,k-1,\\mathbf{t}}(x)\n///\n/// and such that the basic, stepwise, b-spline or order 1 are:\n///\n/// .. math::\n///\n///    B_{i,1,\\mathbf{t}}(x) = \\left \\{ \\begin{matrix} 1, & t_i \\leq x < t_{i+1} \\\\ 0, & \\text{otherwise} \\end{matrix} \\right .\n///\n/// For continuity on the right boundary the rightmost basic b-spline is also set equal\n/// to 1 there: :math:`B_{n,1,\\mathbf{t}}(t_{n+k})=1`.\n#[pyfunction]\n#[pyo3(signature = (x, i, k, t, org_k=None))]\npub(crate) fn bsplev_single(\n    x: f64,\n    i: usize,\n    k: usize,\n    t: Vec<f64>,\n    org_k: Option<usize>,\n) -> PyResult<f64> {\n    Ok(bsplev_single_f64(&x, i, &k, &t, org_k))\n}\n\n/// Calculate the *m* th order derivative (from the right) of an indexed b-spline at *x*.\n///\n/// Parameters\n/// ----------\n/// x: float\n///     The *x* value at which to evaluate the b-spline.\n/// i: int\n///     The index of the b-spline to evaluate.\n/// k: int\n///     The order of the b-spline (note that k=4 is a cubic spline).\n/// t: sequence of float\n///     The knot sequence of the pp spline.\n/// m: int\n///     The order of the derivative of the b-spline to evaluate.\n/// org_k: int, optional\n///     The original k input. Used only internally when recursively calculating\n///     successive b-splines. Users will not typically use this parameter.\n///\n/// Notes\n/// -----\n/// B-splines derivatives can be recursively defined as:\n///\n/// .. math::\n///\n///    \\frac{d}{dx}B_{i,k,\\mathbf{t}}(x) = (k-1) \\left ( \\frac{B_{i,k-1,\\mathbf{t}}(x)}{t_{i+k-1}-t_i} - \\frac{B_{i+1,k-1,\\mathbf{t}}(x)}{t_{i+k}-t_{i+1}} \\right )\n///\n/// and such that the basic, stepwise, b-spline derivative is:\n///\n/// .. math::\n///\n///    \\frac{d}{dx}B_{i,1,\\mathbf{t}}(x) = 0\n///\n/// During this recursion the original order of the spline is registered so that under\n/// the given knot sequence, :math:`\\mathbf{t}`, lower order b-splines which are not\n/// the rightmost will register a unit value. For example, the 4'th order knot sequence\n/// [1,1,1,1,2,2,2,3,4,4,4,4] defines 8 b-splines. The rightmost is measured\n/// across the knots [3,4,4,4,4]. When the knot sequence remains constant and the\n/// order is lowered to 3 the rightmost, 9'th, b-spline is measured across [4,4,4,4],\n/// which is effectively redundant since its domain has zero width. The 8'th b-spline\n/// which is measured across the knots [3,4,4,4] is that which will impact calculations\n/// and is therefore given the value 1 at the right boundary. This is controlled by\n/// the information provided by ``org_k``.\n///\n/// Examples\n/// --------\n/// The derivative of the 4th b-spline of the following knot sequence\n/// is discontinuous at `x` = 2.0.\n///\n/// .. ipython:: python\n///    :suppress:\n///\n///    from rateslib import bspldnev_single\n///\n/// .. ipython:: python\n///\n///    t = [1,1,1,1,2,2,2,3,4,4,4,4]\n///    bspldnev_single(x=2.0, i=3, k=4, t=t, m=1)\n///    bspldnev_single(x=1.99999999, i=3, k=4, t=t, m=1)\n///\n/// .. plot::\n///\n///    from rateslib.splines import *\n///    import matplotlib.pyplot as plt\n///    from datetime import datetime as dt\n///    import numpy as np\n///    t = [1,1,1,1,2,2,2,3,4,4,4,4]\n///    spline = PPSplineF64(k=4, t=t)\n///    x = np.linspace(1, 4, 76)\n///    fix, ax = plt.subplots(1,1)\n///    ax.plot(x, spline.bspldnev(x, 3, 0))\n///    plt.show()\n#[pyfunction]\n#[pyo3(signature = (x, i, k, t, m, org_k=None))]\npub(crate) fn bspldnev_single(\n    x: f64,\n    i: usize,\n    k: usize,\n    t: Vec<f64>,\n    m: usize,\n    org_k: Option<usize>,\n) -> PyResult<f64> {\n    Ok(bspldnev_single_f64(&x, i, &k, &t, m, org_k))\n}\n"
  },
  {
    "path": "rust/tests/dual1.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::dual::Dual;\nuse std::sync::Arc;\n\n#[test]\nfn clone_arc() {\n    let d1 = Dual::new(20.0, vec![\"a\".to_string()]);\n    let d2 = d1.clone();\n    assert!(Arc::ptr_eq(&d1.vars, &d2.vars))\n}\n"
  },
  {
    "path": "rust/tests/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n// mod dual1;\n// mod splines;\n"
  },
  {
    "path": "rust/tests/splines/mod.rs",
    "content": "// SPDX-License-Identifier: LicenseRef-Rateslib-Dual\n//\n// Copyright (c) 2026 Siffrorna Technology Limited\n// This code cannot be used or copied externally\n//\n// Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)\n// Source-available, not open source.\n//\n// See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,\n// and/or contact info (at) rateslib (dot) com\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\nuse crate::splines::{PPSpline, bspldnev_single_f64, bsplev_single_dual, bsplev_single_f64};\nuse crate::dual::{Dual, Gradient1};\nuse ndarray::{arr1, arr2, Array2};\nuse num_traits::One;\n\nfn is_close(a: &f64, b: &f64, abs_tol: Option<f64>) -> bool {\n    // used rather than equality for float numbers\n    (a - b).abs() < abs_tol.unwrap_or(1e-8)\n}\n\n#[test]\nfn bsplev_single_f64_() {\n    let x: f64 = 1.5_f64;\n    let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n    let k: usize = 4;\n    let result: Vec<f64> = (0..8)\n        .map(|i| bsplev_single_f64(&x, i as usize, &k, &t, None))\n        .collect();\n    let expected: Vec<f64> = Vec::from(&[0.125, 0.375, 0.375, 0.125, 0., 0., 0., 0.]);\n    assert_eq!(result, expected)\n}\n\n#[test]\nfn bsplev_single_dual_() {\n    let x: Dual = Dual::new(1.5, vec![\"x\".to_string()]);\n    let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n    let k: usize = 4;\n    let result: Vec<Dual> = (0..8)\n        .map(|i| bsplev_single_dual(&x, i as usize, &k, &t, None))\n        .collect();\n    let expected: Vec<f64> = Vec::from(&[0.125, 0.375, 0.375, 0.125, 0., 0., 0., 0.]);\n    for i in 0..8 {\n        assert_eq!(result[i].real(), expected[i])\n    }\n    // These are values from the bspldnev_single evaluation test\n    let dual_expected: Vec<f64> = Vec::from(&[-0.75, -0.75, 0.75, 0.75, 0., 0., 0., 0.]);\n    for i in 0..8 {\n        assert_eq!(result[i].dual()[0], dual_expected[i])\n    }\n}\n\n#[test]\nfn bsplev_single_f64_right() {\n    let x: f64 = 4.0_f64;\n    let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n    let k: usize = 4;\n    let result: Vec<f64> = (0..8)\n        .map(|i| bsplev_single_f64(&x, i as usize, &k, &t, None))\n        .collect();\n    let expected: Vec<f64> = Vec::from(&[0., 0., 0., 0., 0., 0., 0., 1.0]);\n    assert_eq!(result, expected)\n}\n\n#[test]\nfn bspldnev_single_f64_() {\n    let x: f64 = 1.5_f64;\n    let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n    let k: usize = 4;\n    let result: Vec<f64> = (0..8)\n        .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 1_usize, None))\n        .collect();\n    let expected: Vec<f64> = Vec::from(&[-0.75, -0.75, 0.75, 0.75, 0., 0., 0., 0.]);\n    assert_eq!(result, expected)\n}\n\n#[test]\nfn bspldnev_single_f64_right() {\n    let x: f64 = 4.0_f64;\n    let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n    let k: usize = 4;\n    let result: Vec<f64> = (0..8)\n        .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 1_usize, None))\n        .collect();\n    let expected: Vec<f64> = Vec::from(&[0., 0., 0., 0., 0., 0., -3., 3.]);\n    assert_eq!(result, expected)\n}\n\n#[test]\nfn bspldnev_single_shortcut() {\n    let x: f64 = 1.5_f64;\n    let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n    let k: usize = 4;\n    let result: Vec<f64> = (0..8)\n        .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 6_usize, None))\n        .collect();\n    let expected: Vec<f64> = Vec::from(&[0., 0., 0., 0., 0., 0., 0., 0.]);\n    assert_eq!(result, expected)\n}\n\n#[test]\nfn bspldnev_single_m() {\n    let x: f64 = 4.0_f64;\n    let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n    let k: usize = 4;\n    let result: Vec<f64> = (0..8)\n        .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 2_usize, None))\n        .collect();\n    let expected: Vec<f64> = Vec::from(&[0., 0., 0., 0., 0., 3., -9., 6.]);\n    assert_eq!(result, expected)\n}\n\n#[test]\nfn bspldnev_single_m_zero() {\n    let x: f64 = 1.5_f64;\n    let t: Vec<f64> = Vec::from(&[1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.]);\n    let k: usize = 4;\n    let result: Vec<f64> = (0..8)\n        .map(|i| bspldnev_single_f64(&x, i as usize, &k, &t, 0_usize, None))\n        .collect();\n    let expected: Vec<f64> = Vec::from(&[0.125, 0.375, 0.375, 0.125, 0., 0., 0., 0.]);\n    assert_eq!(result, expected)\n}\n\n#[test]\nfn ppspline_new() {\n    let _pps: PPSpline<f64> = PPSpline::new(\n        4,\n        vec![1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.],\n        None,\n    );\n}\n\n#[test]\nfn ppspline_bsplmatrix() {\n    let pps: PPSpline<f64> = PPSpline::new(4, vec![1., 1., 1., 1., 2., 3., 3., 3., 3.], None);\n    let result = pps.bsplmatrix(&vec![1., 1., 2., 3., 3.], 2_usize, 2_usize);\n    let expected: Array2<f64> = arr2(&[\n        [6., -9., 3., 0., 0.],\n        [1., 0., 0., 0., 0.],\n        [0., 0.25, 0.5, 0.25, 0.],\n        [0., 0., 0., 0., 1.],\n        [0., 0., 3., -9., 6.],\n    ]);\n    assert_eq!(result, expected)\n}\n\n#[test]\nfn csolve_() {\n    let t = vec![0., 0., 0., 0., 4., 4., 4., 4.];\n    let tau = vec![0., 1., 3., 4.];\n    let val = vec![0., 0., 2., 2.];\n    let mut pps: PPSpline<f64> = PPSpline::new(4, t, None);\n    let _ = pps.csolve(&tau, &val, 0, 0, false);\n    let expected = vec![0., -1.11111111, 3.111111111111, 2.0];\n    let v: Vec<bool> = pps\n        .c\n        .expect(\"csolve\")\n        .into_raw_vec_and_offset()\n        .0\n        .iter()\n        .zip(expected.iter())\n        .map(|(x, y)| is_close(&x, &y, None))\n        .collect();\n\n    assert!(v.iter().all(|x| *x));\n}\n\n#[test]\nfn csolve_dual() {\n    let t = vec![0., 0., 0., 0., 4., 4., 4., 4.];\n    let tau = vec![0., 1., 3., 4.];\n    let d1 = Dual::one();\n    let val = vec![0. * &d1, 0. * &d1, 2. * &d1, 2. * &d1];\n    let mut pps = PPSpline::new(4, t, None);\n    let _ = pps.csolve(&tau, &val, 0, 0, false);\n    let expected = vec![0. * &d1, -1.11111111 * &d1, 3.111111111111 * &d1, 2.0 * &d1];\n    let v: Vec<bool> = pps\n        .c\n        .expect(\"csolve\")\n        .into_raw_vec_and_offset()\n        .0\n        .iter()\n        .zip(expected.iter())\n        .map(|(x, y)| is_close(&x.real(), &y.real(), None))\n        .collect();\n\n    assert!(v.iter().all(|x| *x));\n}\n\n#[test]\nfn ppev_single_() {\n    let t = vec![1., 1., 1., 1., 2., 2., 2., 3., 4., 4., 4., 4.];\n    let mut pps = PPSpline::new(4, t, None);\n    pps.c = Some(arr1(&[1., 2., -1., 2., 1., 1., 2., 2.]));\n    let r1 = pps.ppdnev_single(&1.1, 0).unwrap();\n    assert!(is_close(&r1, &1.19, None));\n    let r2 = pps.ppdnev_single(&1.8, 0).unwrap();\n    assert!(is_close(&r2, &0.84, None));\n    let r3 = pps.ppdnev_single(&2.8, 0).unwrap();\n    assert!(is_close(&r3, &1.136, None));\n}\n\n#[test]\nfn partialeq_() {\n    let pp1 = PPSpline::<f64>::new(2, vec![1., 1., 2., 2.], None);\n    let pp2 = PPSpline::<f64>::new(2, vec![1., 1., 2., 2.], None);\n    assert!(pp1 == pp2);\n    let pp3 = PPSpline::new(2, vec![1., 1., 2., 2.], Some(vec![1.5, 0.2]));\n    let pp4 = PPSpline::new(2, vec![1., 1., 2., 2.], Some(vec![1.5, 0.2]));\n    assert!(pp3 == pp4);\n    assert!(pp3 != pp2);\n    assert!(pp1 != pp4);\n}\n\n#[test]\n#[should_panic]\nfn backwards_definition() {\n    let _pp1 = PPSpline::<f64>::new(4, vec![3., 3., 3., 3., 2., 1., 1., 1., 1.], None);\n}\n"
  }
]